Перейти к содержанию
  • записей
    98
  • комментариев
    109
  • просмотр
    6311

О блоге

 

 

Записи в этом блоге

 

[Left 4 Dead 2] Опыт в подсветке мобов

Для примера возьмем 2 игры: CSGO и Left 4 Dead 2, в них используется Source Engine, и как можно подумать подсветка должна реализовываться одинаково, но не тут-то было.   Давайте взглянем на процесс инициализации предметов (включая игроков) в CSGO: Как можем здесь увидеть для каждого объекта при инициализации резервируется место в структуре подсветки и возвращается ID в этой структуре, который отличный от ID игрока.     Внутри GetGlowIndex:     Прелести наследования, т.е. есть класс, который наследуется для любых предметов и инициализирует структуру подсветки, даже для обычных занавесок или телевизора.
 
Вот только в L4D2 ничего такого нет, тут все куда более просто или оптимизировано, структура подсветки наследуется только для боссов (танк, курильщик и т.д.) и игроков, а подсветка ведется постоянно нужно лишь изменить уровень прозрачности. А вот структура определяющая подсветку для мобов (обычных зараженных) является динамической из-за чего заполняется только при определенных условиях, это создает некоторые сложности с подсветкой т.к. придется вручную выделять память под каждого моба и заполнять структуру определяющую подсветку, но просто так записав значение по определенным адресам не выйдет из-за подсчета объектов в структуре. Но мы ведь не пальцем деланные, немного пореверсив находим функцию инициализации, правда перед вызовом необходимо получить указатель на моба, это делается с помощью вызова виртуальной функции, которая принимает id и возвращает указатель на структуру. На выходе получаем это:    

partoftheworlD

partoftheworlD

 

[IDA/GHIDRA/radare2] Немного о сигнатурных модулях и FLIRT

Начнем с FLIRT(Fast Library Identification and Recognition Technology).
Суть технологии проста - экономия времени, вместо ручного восстановления библиотечных функций, используются сигнатурные файлы сгенерированные из статических библиотек с помощью утилит, которые идут в комплексе с IDA SDK, которые по ходу анализа переименовывают функции найденный с помощью сигнатур.
Сигнатурный файлы выглядит так:

  Например, мы знаем, что игра X использует Lua для выполнения скриптов, но сама длл луа вшита в файл, а не идет в комплексе рядом с ним, из-за чего мы не можем увидеть импортируемые функции, а значит придется потратить кучу времени, чтобы вручную по исходникам восстановить их. И тут на помощь приходит FLIRT, подключаем сигнатурный модуль в процесс анализа


и буквально через пару секунд функции начинаются помечаться как библиотечные и подписываться. Пока IDA работает, реверсер отдыхает.
  У GHIDRA есть подобный функционал, но называется Function ID. И чтобы базы для гидры не генерировать руками, есть готовый скрипт, но для начала совету прочитать статью автора, чтобы не было вопросов по использованию скрипта:
https://blog.threatrack.de/2019/09/20/ghidra-fid-generator/

partoftheworlD

partoftheworlD

 

[Raft] Генерация кодов локаций

С недавним обновлением Raft в игре изменили радар, теперь необходимо ввести код из 4-х цифр, который является частотой, чтобы радар показал координаты, найти массив с координатами и кодами слишком просто, и поэтому мы будем генерировать коды без какого-либо воздействия на память игры. Открыв dnSpy необходимо найти участок кода, отвечающий за генерацию частот.

  Восстановив код и запустив для проверки, обнаружились некоторые проблемы: 1.       Cид используется 1 раз при создании нового мира. 2.       Частота состоит из 4 цифр, из-за чего очень много сидов будет подходить под значение первой частоты, которая дается в начале игры, поэтому придется брутить и генерировать все возможные частоты для 2-4 локаций.     На генерацию сидов ушло порядка 30 минут, 90000+ возможных сидов и лишь 1 правильный набор кодов.  Занятие было бессмысленным, но это был отличный способ занять вечер.   Seed: 1276855 ------------------------------ 9839 1106 5814 8084 ------------------------------

partoftheworlD

partoftheworlD

 

Наработки по сканеру указателей основанному на отладке

Разработка этого плагина началась с того, что Dark Souls 3 начал выеживаться, а именно не работал сканер указателей, в целом этот "плагин" бесполезная, но точная(в теории) вещь для большинства игр.
Может быть кто-то более опытный в Lua захочет его доделать. Осталось сделать 3 пункта: Нормальный парсинг инструкций   Парсинг...ну что тут сказать, в Lua паттерны это какой-то regex после авиакатастрофы попавший в аварию на машине скорой.      

partoftheworlD

partoftheworlD

 

CppHackGame CTF Challenge Решение задания

Рассмотрим решение задания из   Начало стандартное, ищем строку с сообщением об ошибке, переходим по перекрестным ссылкам и находим инструкцию проверки. Теперь зайдем в эту функцию и начнем разбирать.
  .text:00002173 cmp username_lenth, 10h .text:0000217A mov esi, offset username .text:0000217F movsd xmm0, ds:dbl_7D48 ; // xmm0 = 2.0 .text:00002187 cmovnb esi, username .text:0000218E movd xmm1, ebx .text:00002192 cvtdq2pd xmm1, xmm1 .text:00002196 call _libm_sse2_pow_precise .text:0000219B movsx eax, byte ptr [esi+ebx] ; // v0 = esi[ebx] получаем символ из массива .text:0000219F imul eax, ebx ; // v1 = v0 * idx .text:000021A2 inc ebx ; // idx++ .text:000021A3 cvttsd2si ecx, xmm0 .text:000021A7 cdq .text:000021A8 idiv ecx ; // v2 = v1 % pow(2, idx) .text:000021AA add edi, edx ; // checksum += v2 Немного восстановили функцию, это что-то похожее на генерацию чексуммы из строки. Приведем это в человеческий вид   username = 'UserName' checksum = 0 for idx in range(len(username)): v1 = ord(username[idx]) * idx v2 = v1 % pow(2, idx) checksum += v2 print(checksum) Спускаемся немного ниже и видим инструкции, которые проверяют чексуммы, введенного пароля и имени пользователя. while (tmp_checksum != input_checksum): checksum -= char_value_ char_value_ = *tmp_checksum++; Зная  алгоритм проверки, можем написать кейген. Для этого необходимо генерировать новые символы и отнимать от чек суммы значение, пока чексумма не станет равна 0   def generate_password(checksum): g_check = 0 password = '' tmp = 0 while True: char = random.randint(0x21, 0x2E) # генерируем символы, можно выбрать любые tmp = checksum - g_check # вычисление временной чексуммы if tmp // 0x56 == 0: # небольшая проверка последнего символа, чтобы избежать символов, которые будут нечитаемыми # после некоторых тестов 0x56 было более подходящим значением с минимальным выводом мусора. password += chr(tmp) break g_check += char # вычисление сгенерированной чек суммы password += chr(char) # добавление символа в строку с сгенерированным паролем print(password)      

partoftheworlD

partoftheworlD

 

[Dark Souls 3] Автоматический поиск указателей не находит выхода в указанную структуру

После двух дней тестов Cheat Engine 6.8.3/7, оказалось что автоматический поиск указателей не работает в некоторых играх, каких-то определенных причин для этого нет. В пути к скану указателей, а так же в название скана нет кириллицы, пробелов и всякого такого. В других играх или в том же CE туториале сканер указателей работает как и должен, в общем тут точно не обошлось без магии. Выглядит все это так:     Пока у меня только 2 варианта решения этой проблемы: 1. Использовать ручной поиск указателей. 2. Писать свой сканер.

partoftheworlD

partoftheworlD

 

CppHackGame CTF Challenge

CppHackGame - это очень маленькая текстовая RPG игра.
Тем не менее, эта игра предназначена для челленджа по взлому. Сможешь ли, ты убить гоблина и забрать сундук?     Правила: Разрешено использовать любые инструменты, разрешено патчить только код относящийся к игроку/гоблину.   Скачать cpphackgame.exe

partoftheworlD

partoftheworlD

 

DS3

PhyreEngine

// velocity. used for backstab detection
// speed the opponent is approaching at. Player doesnt need to know their own. Idealy would like just if sprinting or not, actual velocity isnt important
// -0.04 slow walk
// -0.13 walk
// -0.16 - 18 sprint


wireshark filter

((udp.port >= 27000 && udp.port <= 27030) or udp.port == 3478 or udp.port == 4379 or udp.port == 4380 or (udp.port >= 50000 && udp.port <= 50003)) or ((tcp.port >= 27014 && tcp.port >= 27050) or tcp.port == 50050)


NS_SPRJ::WorldChrManImp - (48 8B 1D ? ? ? ? 48 8B F9 48 85 DB 74 40) 48 8B 0D ? ? ? ? 48 85 C9 74 09 48 8B 89 ? ? ? ? EB 03 49 8B CD 4D 85 FF 74 60 48 85 C9 74 0E 48 8B 01 FF 90 ? ? ? ? 4C 8B E0 EB 03
NS_SPRJ::WorldChrManImp + 80] = NS_SPRJ::PlayerIns
NS_SPRJ::PlayerIns + 1f90] = ptr_table
ptr_table + 18] = NS_SPRJ::SprjChrDataModule

NS_SPRJ::WorldChrManImp + 1d0/1ce8] = EntityList

EntityMax = 100

EntityList + idx * 0x38 - список игроков/npc       ds3.rcnet

partoftheworlD

partoftheworlD

 

[Skyrim] Как генерировать уникальную отмычку, чтобы стражники Вайтрана не прострелили колено

Статья должна была выйти вместе с видео, но мне лень было её писать.   В прошлом видео я показывал, как это работает и настало время для того, чтобы рассказать, как это делалось. Да можно было писать свое значение на прямую в значение разблокировки замка, но это скучно и вообще какой-то дет.сад.     На деле все оказалось довольно странно, вместо обычной генерации рандомного значения, в скайриме используется двухуровневая т.е. сначала одна функция генерирует одно значение, результат которой влияет на результат второй функции.   Рассмотрим код, хотя в нем особо комментировать нечего, я разделил его на блоки, чтобы проще воспринимались стадии работы алгоритма.     Дальше можно в ручную уже посчитать текущее значение для открытия замка:
  v5 = 9.615000725f * 0.5; // 4.8075003625 v6 = v5 - 90.f; // -85.1924996375 v7 = 90.f - v5; // 85.1924996375 link = &qword_7FF6A0CEED70 LockPick->unlock_Value = ( v7 - v6 ) * (GenerateRandomValue(link, 0xFFFFFFFF) * 2.328306437E-10) + v6; LockPick->unlock_Value = 170.38 * (0xAE504DB6 * 2.328306437E-10) + -85.1924996375 LockPick->unlock_Value = 170.38 * (2924498432.00 * 2.328306437E-10) + -85.1924996375 LockPick->unlock_Value = 170.38 * (0.68) + -85.1924996375 LockPick->unlock_Value = 30.82 И проверяем в отладчике:     Генератор отмычек готов.      

partoftheworlD

partoftheworlD

 

Книги

Немного обновил и упорядочил книги из личного хранилища, может кому пригодятся. (Возможно это считается за пиратством, так что ссылку буду выдавать в личных сообщениях, на форуме или в дискорде) В архив входят:  

partoftheworlD

partoftheworlD

 

История о том, как я для юнити писал автосплитер

Начнем с того, что теперь я ненавижу юнити, так, что моей ненавистью можно питать пару крупных городов еще и останется.
Проблема возникла с тем, что у игры есть свои значения проверяющие загружена игра или нет, но получить к ним доступ через указатель оказалось невозможным. Перепробовав около 50 разных способов и потратив 1.5 недели, решил залезть в управление памятью юнити, возможно что-то интересно окажется там. Доков и исходников нет и поэтому - Реверсим, реврсим, вертим крутим и делаемся всякие непотребства с памятью и...   Бам, оказалось( возможно это чистое совпадение), но UnityPlayer использует статические адреса для "работы" с CFG.
CFG/CF это Control Flow Guard -  механизм защиты Windows, нацеленный на усложнения процесса эксплуатации бинарных уязвимостей в пользовательских и ядреных приложения.   Сам механизма проводит валидацию неявных вызовов, предотвращающая перехват потока исполнения, например переписывая таблицы виртуальных функций. Именно этим мы и воспользуемся, но для начала найдем максимум информации о CFG и его внутреннем устройстве.   Итак, когда игра не загружена то, адреса должны указывать на статические данные внутри UnityPlayer.dll, но после загрузки игры выделяться, например в куче. Зная это, можем приступать к поиску. В итоге нашелся статический адрес указывающий на старшую часть указателя, который работает с CFG, когда игра находится в меню или загружается, то старшая часть равна 7FFX( X - значение от 0 до F), но когда игра загружена, то значение меняется на адрес кучи. Что-то типа: Static - 7FFFDEADBEEF Heap   - 01EFDEADBEEF   Значит берем старшую часть, уменьшаем её для более простого сравнения используя:
  value & 0xFFF В итоге, получаем значение которое можем засунуть для проверки в автосплитере, если игра не загружена, то значение равно от 4080 до 4095, но если игра загружена, то значение меньше 600, но на всякий случай, я указал:   value < 4080   И на этом все, на все про все у меня ушло где-то 2.5-3 недели, просто чтобы узнать состояние игры. В общем, было интересно и стало более понятно как юнити работает с памятью. 
Желаю всем подобных заданий, это ведь интересней, чем патрончики накручивать.

partoftheworlD

partoftheworlD

 

[Ghidra] Правка вывода декомпилятора

Небольшая заметка.
В общем давняя проблема была со значениями при декомпиляции, а именно они просто не отображались и вывод декомпилятора выглядел вот так: fVar12 = (float)uVar3 * (float)0x2f800000 * FLOAT_142ab7340; а нам нужно: fVar12 = (float)uVar3 * (float)0x2f800000 * 6.28318548; Это возникает только при анализе дампов, когда гидра считает, что адрес по которому лежит значения является динамическим, поэтому брать на себя ответственность за неправильный вывод гидра не хочет и предоставляет нам адрес. Чтобы поправить вывод, необходимо указать, что этот адрес и значение в нем являются константой. Для этого надо перейти по адресу со значением, ПКМ по значению, Data -> Settings -> Mutability = constant.
    Теперь мне кажется, что гидра еще более сложный инструмент, чем ида.?

partoftheworlD

partoftheworlD

 

[Writeup] TraineMe by Xipho

(С сегодняшнего дня все статьи будут идти в 2-х экземплярах, постом в блоге и в pdf версии, чтобы скрины в случае чего не полетели) Прошла уже неделя с момента релиза TraineMe, думаю все кто хотел уже поковыряли программу и уже пора выпустить решебник. Итак начнем. Используемые программы: Ghidra, CE. x64dbg, CherryTree.(Гидра была выбрана по 2-м причинам, первая она мне больше нравится, а вторая декомпилятор с этим файлом у гидры генерировал более понятный код, чем hex-rays.) Задание 1 m_fnEncodeValue(this ,param_2,m_fHealth ,AllocatedMemory,0); m_fnEncodeValue(this ,param_2,m_fAttack ,AllocatedMemory,1); m_fnEncodeValue(this ,param_2,m_fHealth ,AllocatedMemory,2);   Здоровье передается в функцию m_fnEncodeValue в hex 1000.f = 0x447A0000 линия 1 и 3

Реверсим...Ставим бряк по адресу TraineMe.0+12E6  в x64dbg: bp TraineMe.0+12e6 Чтобы узнать, что лежит в регистрах и сделать код в функции m_fnEncodeValue более читаемым. После восстановления видим, что существует 2 массива для работы со здоровьем с общей структурой т.е.: PlayersArray[idx * 4] = KeysArray[idx * 4] ^ m_fLoadedHealth
Сам ключ вычисляется довольно просто: srand(time64(0)) //Задаем seed для последующей генерации "рандомных" чисел с помощью rand() m_wPseudorandom = rand() KeysArray[idx * 4] = (m_wPseudorandom + (m_wPseudorandom * 0x93275ab3) >> 0x3f) * 0xdeadbeef + 1   Обратите внимание на код, который был показан выше, а точнее на последний аргумент: m_fnEncodeValue(this ,param_2,m_fHealth ,AllocatedMemory,0); m_fnEncodeValue(this ,param_2,m_fAttack ,AllocatedMemory,1); m_fnEncodeValue(this ,param_2,m_fHealth ,AllocatedMemory,2);
0, 1, 2 указывает на используемый индекс в структуре, которая была выделена во время нажатия кнопки “Start Game” 0 - текущее значение 1 - урон (???) 2 - статичное значение, скорее всего максимальное значение здоровья
Эти значения могут пригодится для одного из способов создания бессмертия. Немного о выделении памяти, в этом traineme существует 2 основных выделения памяти под структуры на 48 и 24 байт. Все основные вычисления проводятся в той что по-больше.
Задание 2

Выполнив первое задание, мы без проблем можем обнаружить единственную нужную инструкцию.  
После быстрого анализа кода с помощью трассировок, найти участок кода, где здоровье вычитается(а на деле прибавляется.), не составит труда. А теперь вспомним о значение 50, которое мы находили в первом задании. Весь алгоритм вычисления урона:   srand(time64(0)) damage_multiplier = rand() / 32767.00000000 //приводим множитель к 1 damage = 50.f * damage_multiplier ^ 0x80000000 // 50.0 * 0.67 = 33.5 ^ 0x80000000 = -33.5 // // Загружается и расшифровывается значение здоровья (health) // health += damage //1000.0 += -33.5 damage = 0 damage_multiplier = 0 Задание 3
Довольно сложное задание, для тех, кто никогда/мало работал с GDI, поэтому ответ есть в этом видео:
01. Основные графики Windows. Изучаем GDI. Pen, Brush, Rectangle.   Ну и табличка прилагается. TraineMe.CT RE--TraineMe.pdf

partoftheworlD

partoftheworlD

 

Субтитры в UE3/4

FText является классом, для каждого субтитра устанавливается свое время отображения.      

partoftheworlD

partoftheworlD

 

Экономия времени при реверсе за счет вычислений энтропии

В зависимости от энтропии, мы можем с ходу определить какие утилиты нам понадобятся, для примера возьмем игровую дллку. Возьмем файл из каталога и проверим энтропию:
      Обычно энтропия выше 2-3 указывает на то, что файл накрыт чем-то и не подходит для статического анализа, из-за того, что будет попадаться мусор, куски нерабочего кода и отсутствие перекрестных ссылок, проделаем тоже самое, но уже с дампом этой же дллки.

    Как видите, энтропия стала меньше, а линия стала более ровной, это означает, что файл готов к использованию и во время реверса не возникнет никаких проблем.

partoftheworlD

partoftheworlD

 

Radare2 Эмуляция кода

Глава 0. Пролог   В общем в прошлой статье слетели картинки, да и статья не особо понравилась, вот её переписанная и обновленная версия. Место действия – терминал, в главных ролях – radare2, отлаживаемая программа.   Глава 1. Настройка окружения ESIL e asm.emu=true - Run ESIL emulation analysis on disasm e io.cache=true Глава 2. Настройка эмулятора Тут достаточно простой список команд: aei – инициализирует состояние виртуальной машины aeim – инициализирует стек aeip – устанавливает EIP по текущему положению в коде после всех этих команд в регистрах выставились адреса ebp, esp, eip.   Остальные регистры устанавливаем с помощью команды aer, значения обычно берутся из отладчика, но можно выставить любые. aer eax = 00000004;aer ebx = 0x17426e8;aer edx = 00000004;aer edi = 0x05e4830;aer tf  = 1;aer if  = 1;   Для пошагового выполнения кода используем aes, я обычно использую такую связку, чтобы получать необходимый минимум информации: pd -1 @ eip; aer=; aes но если кому так не удобно, есть возможность использовать визуальную отладку используя команду V!   Глава 3. Тест   Попробуем отладить код из дампа и посмотреть, что там вообще происходит.     Глава 4. Эпилог   Как видите, все достаточно просто, плюс поддерживается для эмуляции туева хуча архитектур. Так же это быстрее, чем поднять qemu или использовать реальное железо, плюс поддержка скриптов радара делает из эмуляции достаточно мощный инструмент для статической отладки и анализа кода.  

partoftheworlD

partoftheworlD

 

Немного о том, почему я люблю radare2

Покажу на примере кода по расшифровке строк, ничего особенного, просто xor шифрование, вроде бы все и понятно, но так не хочется вручную восстанавливать. А декомпилятор в x64dbg ужасен.
  И что же в таком случае делать? Воспользоваться radare2. Просто выделяем функцию, записываем её в файл и отрываем этот файл в r2.
Теперь определяемся что нам нужно из этого кода? Например, регистр cl. Пишем такую команду: pdc~cl Читается как:  pdc - pseudo disassembler output in C-like syntax ~ - grep cl   Хоба, и видим алгоритм расшифровки: [0x00aa93b0]> pdc~cl cl = bl cl ^= dl byte [rdi + rax + 1] = cl cl = bl cl ^= byte [rsi + 0x12c66dc] byte [rsp + rax + 0x1d] = cl cl = bl cl ^= byte [rsi + 0x12c66e0] byte [rax + rbp + 3] = cl cl = bl cl ^= dl byte [rsp + rax + 0x1f] = cl     Это не совсем декомпилятор, просто вывод дизассемблерного кода в удобно читаемом виде.  Код целиком.    

partoftheworlD

partoftheworlD

 

Приключение Wireshark в мире USB

Решил тут сделать кастомные пресеты для подсветки, но в софте производителя такой возможности не было, были стандартные, ограниченные. Как же я не люблю ограничения, ну что ж...приступим к исследованию.   Для начала необходимо понять как софт общается с клавиатурой, для этого запускаем Wireshark. При изменении яркости, происходит отправка пакета по USB.     Отлично, это уже что-то. Выполним еще пару действий, чтобы сделать шаблон для данных. //Стандартные настройки : 11 ff 0c 3a 00 01 ff 00 00 02 00 00 00 00 00 00 00 00 00 00 //Изменение яркости : 11 ff 0c 3a 00 01 f5 00 00 02 00 00 00 00 00 00 00 00 00 00 //Включение пресета : 11 ff 0c 3a 00 02 ff 00 00 10 40 00 64 00 00 00 00 00 00 00 //Изменение скорости пресета: 11 ff 0c 3a 00 02 ff 00 00 05 c7 00 64 00 00 00 00 00 00 00 Открываем 010 Editor и делаем шаблон.     Шаблон:   Шаблон можно будет использовать для программирования клавиатуры. Для программирования будем обращаться по имени HID устройства, чтобы с помощью CreateFile получить хендл и общаться с клавиатурой. Не самый лучший способ, но быстрый, хотя я бы выбрал хуки. Все есть файл. (ну почти) Посмотрим под отладчиком, что говорит софт логитека клавиатуре. Ставим бряк на WriteFile и бдыщ.     Дальше остается использовать CreateFile, Read/WriteFile, чтобы сделать основу для собственных пресетов. Один из плюсов всего этого, это уменьшение размера программы с 200 мб до 10кб.   Как раз вот-вот уже выходные, в планах привязать изменение подсветки клавиатуры в зависимости от готовности кофе в кофеварке. А до следующей статьи, можно будет разобрать как расширить функционал встроенных скриптов в клавиатуре и можно ли, туда прописать что-то действительно полезное, и что может пойти не так. И разобрать, за что отвечают последние 4 байта.

partoftheworlD

partoftheworlD

 

DDG яндекс фикс

В любой блокировщик рекламы как кастомный фильтр. Удаляет мусор от яндекса в уточке. duckduckgo.com##.result.results_links_deep.highlight_d:-abp-contains(/(data-domain\=)|(.*yandex.*|.*ya\.ru)/)  

partoftheworlD

partoftheworlD

 

[Burp Suite] Написание кейгена для твика iOS

Похожая статья уже была, но тут "новый" инструмент и не просто подмена рандомного значения. Что ж, для начала надо запихнуть исполняемый файл под дизассемблер, ищем что-нибудь связанное с лицензией, реверсим.     Привели к читаемому виду, а теперь разбираемся что же тут происходит.   Часть 1, проверка, чтобы имя пользователя было не длиннее, чем 20 символов. IVar1 = __picsymbolstub4::_objc_retain(param_3,param_2,(_NSZone *)param_3); lenth = __picsymbolstub4::_objc_msgSend(IVar1,"length"); if (lenth < 20) { bResult._0_1_ = 0; } Часть 2. Считываем введенное имя пользователя с 12 по 16 символ. uname = __picsymbolstub4::_objc_msgSend(inpurt_username,"substringWithRange:",12,16); user_name = __picsymbolstub4::AutoreleasedReturnValue(uname); Часть 3. Получение серийного номера устройства. SerialNumber = __picsymbolstub4::_objc_msgSend(&ATTweakClient,"getSerialNumber"); serial_number = __picsymbolstub4::AutoreleasedReturnValue(SerialNumber); Часть 4. Составление строки из соли, серийного номера и имени пользователя. _complite_string = __picsymbolstub4::_objc_msgSend (&_OBJC_CLASS_$_NSString,"stringWithFormat:",&cf_%@%@%@, & cf_kyf6jFJYT8aawe[we]D+F-s4JYI3jsaDp69jsdq123sDdflkDuF3sgvlkd3asfKLFDGhjalsddgasd ,serial_number,user_name); complite_string = __picsymbolstub4::AutoreleasedReturnValue(_complite_string); Часть 5. Хеширование полученной строки. hash = __picsymbolstub4::_objc_msgSend(&Utils,"sha1:",complite_string); IVar1 = __picsymbolstub4::AutoreleasedReturnValue(hash); Часть 6. Узнаем, что приходит от сервера. (Формат ключа)   Начинаем писать кейген. #Импортируем модуль для работы с хеш-функциями from hashlib import sha1 #Задаем серийный номер аппарата serial = b"QTW6NR7XXP7M" #Задаем имя пользователя == 16 знакам name = b"partoftheworlD16" #Задаем соль salt = b"kyf6jFJYT8aawe[we]D+F-s4JYI3jsaDp69jsdq123sDdflkDuF3sgvlkd3asfKLFDGhjalsddgasd" def generate_license_file(): #Хешируем и переводим хеш в hex license_hash = sha1(salt + serial + name).hexdigest() #Формируем ключ key = license_hash[:12]+name.decode("utf-8")+license_hash[12:] #Формируем лицензионный файл print("{\"validated\":true,\"key\":\""+key+"\",\"create_time\":\"00:00:00 Apr 10, 2019 MSK\"}") generate_license_file() На выходе получаем. {"validated":true,"key":"2ebb4e00950epartoftheworlD16cff6343256a724d56eb6506173ec","create_time":"00:00:00 Apr 10, 2019 MSK"}   Теперь необходимо этот ключ как-то скормить программе. 1. Способ, поместить ключ прямо в папку с программой 2. Скормить, через ответ с сервера.   Я выбрал второй, просто потому, что мне лень по папкам лазить, чтобы создать файл. Пусть за меня это делает программа, я что её зря взламывал? Заходим в Burp Suite, вкладка Proxy, Options.     Переходим в программу и запрашиваем лицензию.     Ах да, я забыл снова включить прокси. Все, после запроса, срабатывает подмена ответа с сервера и в Burp Suite видим.     А на экране телефона, успешно загруженную лицензию.  
   

partoftheworlD

partoftheworlD

 

Fiddler vs Burp Suite

Ну что ж, случайно на последнем CTF узнал, что Burp умеет сертификаты генерировать для прослушки HTTPS. Раньше я писал скрип для подмены запроса для mouse sensitivity в Fiddler, а тут оказалось, ничего писать не надо, составил регулярку для поиска и написал в окошечко на что заменить. Да и инструмент более гибкий, имеющий кучу плюшек.     Это же прелесть. А баг так и не по фиксили.      

partoftheworlD

partoftheworlD

 

Игровой фаззер

В общем начал разрабатывать игровой фазер, предназначенный для восстановления классов и смещений для не виртуальных функций. На данный момент я представляю это как отдельный проект, возможно написанный с нуля, но думаю использовать готовые дизассемблерные библиотеки, чтобы сэкономить время, который будет представлять из себя универсальный дампер SDK для любых игр. Хотя использовать x64dbg для подобного будет очень даже уместно т.к. функциональное ядро будет готово.   На первом этапе вешаем бряк на инструкцию. Получаем стек вызовов и восстанавливаем путь от 1-й функции вызванной клиентом до последней в которой находимся мы. На втором этапе будет создаваться каркас из классов, функций и ответвлений, я думаю воспользоваться перекрестными ссылками. На третьем этапе начнется интерпретация и парсинг инструкций основного древа без ответвлений кода, чтобы восстановить используемые в коде смещения и типы данных, отслеживая поведение инструкций. На четвертом этапе будет проводиться тоже самое, но только для ответвлений имея на руках уже найденные адреса и значения, я думаю это эффективней работы с ответвлениями начиная с нуля. На пятом этапе представлять в графическом виде все древо. На шестом генерировать таблицу рекласса.   Красным веделен четвертый этап, черным третий.   Основная проблема в том, что мы не знаем точный размер структур. И пока я не представляю, как можно получить размер динамически выделяемой памяти. Как вариант, пятым этапом вешать бряки на функции освобождающие память, фильтруя по всем найденным начальным адресам структур и после убивать процесс, чтобы перехватить размер. А после обрезать структуры по размеру, но концепция "ищи, чтобы удалить" мне не очень нравится.   Плюсы данного проекта в том, что ничего не надо делать самому(пфф, что-то там руками искать в 2019), а так же можно прилепить на один из этапов "рекурсивное" восстановление, т.е начинаешь с инструкции патрон и чем больше смещений восстановится, тем к большему числу классов получаешь доступ для восстановления и так будет продолжаться, пока файл не восстановится полностью. (нет это не нейронная сеть, хотя кто знает)

partoftheworlD

partoftheworlD

×

Важная информация

Находясь на нашем сайте, Вы автоматически соглашаетесь соблюдать наши Условия использования.