Введение в крэкинг с нуля, используя OllyDbg - Глава 47

  • На форуме работает ручное одобрение пользователей. Это значит, что, если Ваша причина регистрации не соответствует тематике форума, а также Вы используете временную почту, Ваша учётная запись будет отклонена без возможности повторной регистрации. В дальнейшем - пожизненная блокировка обоих аккаунтов за создание мультиаккаунта.
  • Мы обновили Tor зеркало до v3!
    Для входа используйте следующий url: darkv3nw2...bzad.onion/
  • Мы вновь вернули telegram чат форуму, вступайте, общайтесь, задавайте любые вопросы как администрации, так и пользователям!
    Ссылка: https://t.me/chat_dark_time

AnGel

Администратор
Команда форума

AnGel

Администратор
Команда форума
27 Авг 2015
3,411
2,025
Продолжим с Patrick’ом. Когда я дошел до места, на котором мы остановились в предыдущей главе, то заметил, что после восстановления оригинальных байтов команды, где был бесконечный цикл для присоединения, было бы хорошо вместо обычного BP установить на эту инструкцию аппаратный брейк, поскольку обычный обнаруживается и DLL’ка не запускается. Установка HE обеспечивает остановки не хуже, а после появления в списке загруженных модулей AntiDebugDll.dll можно установить в ее секции кода BPM ON ACCESS и оказаться на ее Entry Point:

5ef31948886c43c7dc3fd5d0a857ce4e.png

271a98fb5955418f7c9a77cac09a6a45.png

Это Entry Point DLL’ки. В данный момент все модули загружены. Теперь можно приступать, но продвигаться следует постепенно, шаг за шагом.

Наша задача — оказаться в OEP крэкми с помощью OllyDbg, а это сделать, как уже упоминалось, сложнее, чем с помощью способа, не использующего Олли. Но в данном случае мы пользуемся именно Олли, поэтому оставим тот способ на потом и продолжим наш сложный путь сквозь патрицианскую защиту, как сказал Jack.

Давайте снова попадем в то место, где создается второй процесс: вначале изменим PID Олли на PID Проводника, затем, дойдя до CreateProcessA, изменим параметр dwCreationFlags для заморозки процесса и выполним эту функцию до ее команды RET. Далее установим бесконечный цикл в ntdll.dll, как это было описано в предыдущей главе. Присоединимся ко второму процессу. Затем, после снятия бесконечного цикла, установим на той же строке аппаратный брейк. Произведя нескольких остановок, дождемся загрузки AntiDebugDll.dll, после чего установим BPM ON ACCESS в секцию ее кода — и окажемся в ее EP. Всё это было проделано в предыдущей главе. Теперь открыты два OllyDbg: в одном отлаживается первый процесс, который мы назовем РОДИТЕЛЕМ (он находится в точке возврата из API-функции CreateProcessA), а в другом — второй процесс, остановленный на EP AntiDebugDll.dll, пусть он будет ПОТОМКОМ.

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

0b849930d57472fe07a5b0f192173c1e.png

Таким образом, в родителе мы остановились на возврате из API-функции CreateProcessA.

2668df3f961cc6149a2c7581850942bb.png

А в потомке — на EP AntiDebugDll.dll. Чтобы видеть, что это именно потомок, в этом отладчике желательно установить другую цветовую схему. Так я делаю не только для того, чтобы читателям было понятнее, какой из двух процессов я имею в виду — это и мне самому помогает не запутаться при взломе.

Теперь, по идее, нужно эмулировать одновременное выполнение двух процессов, но это невозможно. Однако известно, что процесс выполняется до места, где он ожидает действий другого, поэтому можно довести один процесс до состояния ожидания, а далее продолжать работать с другим.

Мы начнем с потомка. Как известно, если при его запуске он замечает что-либо подозрительное, то через несколько секунд закрывается. Следовательно, перед этим должна осуществляться проверка, какой это процесс: первый или второй, родительский или дочерний. В результате процесс станет выполнять соответствующую роль родителя или потомка. Важно понять, что, хотя эти процессы похожи (так как начинают выполняться с одной и той же строки), но, тем не менее, первый обнаруживает, что это первый процесс, а второй — что второй, поэтому они решают различные и даже противоположные задачи.

Как мы уже знаем, с помощью API-функции CreateMutexA проверяется, первый ли это запуск процесса, поэтому установим на нее BP в потомке.

44ca144642be4899b47bc76641c5b130.png

Остановка произошла здесь. Родитель уже выполнял эти действия и сейчас мьютекс уже существует, поэтому API-функция теперь должна возвратить 0B7 (ERROR_ALREADY_EXISTS), а не 00 (ERROR_SUCESS), как это было при создании мьютекса родителем. Дойдем до команды RET API-функции:

95cbce64d54f7af1ddd85fa73905b515.png

Код возвращенной ошибки говорит, что это была попытка создания уже созданного родителем мьютекса. Важно понимать, что когда программа обнаружит код спровоцированной ошибки B7, возвращенной API-функцией, она начнет выполнять совсем другую роль, нежели родитель, поэтому данный процесс надо будет рассматривать как отдельный процесс-потомок.

034a40cb585a72956670e9443cb3284f.png

Теперь вызывается API-функция, возвращающая последнюю произошедшую ошибку, и программа получает значение B7, которое отладчик уже показал, т.е. ERROR_ALREADY_EXISTS.

05bf46ad85e0325ed0814e0260d992f6.png

76a7ddc11d514cb9bd6556f8ac532ef1.png

Затем это значение сохраняется. Если установить BPM ON ACCESS на ту ячейку памяти, где оно сохраняется и нажать RUN, то остановка произойдет в момент его считывания перед сравнением.

5a767f297fe3e8e9afb650bdf7bd261d.png

Здесь сохраненный код сравнивается с 0B7.

70cbf977d1614fa830185ef0c5486e06.png

При истинном результате сравнения команда SETE поместит в EAX единицу. Далее идет решающий переход: если регистр EAX окажется ненулевым, то процесс станет потомком, иначе — родителем. В данном случае переход выполняется, поскольку это дочерний процесс. Если бы это был родитель, то переход бы не состоялся. Таким образом, здесь ход выполнения процессов разветвляется: до сих пор они делали одно и то же, а теперь родителю надо будет подтвердить, что он был создан Проводником и будет вызвана CreateProcessA для создания потомка. А то, что будет делать потомок, мы рассмотрим далее.

Для отслеживания API-функций существуют весьма полезные утилиты, подобные Kam и APISpy. Они логируют используемые в программе API-функции и помогают избежать лишней трассировки. Давайте попытаемся сделать собственный Kam с помощью Olly, что позволит нам переходить от API-функции к API-функции, сэкономит время и поможет в изучении таких насыщенных программ, как наш крэкми.

Прежде всего, надо в потомке найти IAT DLL’ки. Не трассируя, посмотрим в листинге выше команду CALL, вызывающую какую-либо API-функцию:

4f26c1fea4226163418105c516b25162.png

Вот подходящий вызов: он берет значение из адреса 1000C034, который должен соответствовать элементу IAT’а. Посмотрим его в DUMP’е:

29a635657ee0b6bcf0fe635ecff8ebd0.png

Это IAT; после нахождения ее начала и конца установим BPM ON ACCESS на всю таблицу:

09b3c23c1d813f0ec304bbb815b706cd.png

Сделав то же самое в родителе, получим второй собственный Kam. Конечно, IAT там находится по тем же адресам:

5fc984e7c0e0d64e3ccba899a4d1f1a8.png

Таперь у нас есть два собственных Kam’а. Следует заметить, что они позволяют останавливаться на API-функциях из IAT’а DLL’ки. В случае с API-функциями не из IAT’а, полученными с помощью GetProcAddress, надо будет установить HARDWARE BPX ON ACCESS куда потребуется. Важно, что это работает с API-функциями из IAT’а, где наш Kam будут функционировать без нужды в чем-либо еще, хе-хе.

Вернемся к потомку, нажмем F9 и посмотрим, в какой API-функции он остановится. Кроме того, при каждой остановке будем смотреть параметры, передаваемые функции.

2b20d124f5febb35d251ce88cbba2d52.png

Однако, это избавило нас от часов трассировок: первая же остановка в потомке произошла снова в CreateMutexA при создании еще не существующего мьютекса:

1663a146c3f51c15215eee57218a1c24.png

Если вспомнить, то название рассмотренного нами ранее мьютекса, созданного родителем, было MYFIRSTINSTANCE, что означает: «мой первый экземпляр». А новый мьютекс называется MYMAININSTANCE, что значит: «мой главный экземпляр»; он создан потомком при выполнении программы, поэтому главный.

Нажмем F8, чтобы пройти API-функцию:

3c910321f7bd8da1167dd71dac4fc00a.png

Не думаю, что результат этой второй проверки в процессе потомка на что-то влияет. Так как больше процессов не создается, мы не будем смотреть сравнение значений — в данном случае это не столь важно. Лучше нажмем F9 и перейдем с помощью нашего OllyKam’а к следующей API-функции, хе-хе.

fd54c90526e9887a7d5748ac40abf397.png

Эта функция возвращает последнюю ошибку, произошедшую в результате вызова CreateMutexA. Нажмем F9 и остановимся на очередной API-функции:

dda8b01321a196fa8f9b50ed2ea49259.png

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

6044787b19f65dc3fe93a03400f9ce90.png

А теперь — F9:

c298a62b65651ae96df50683d7817c48.png

Для тех, кто не знаком с данными API-функциями, это будет хорошим упражнением. Если бы мы были на экзамене, а я был учителем, то я бы обязательно спросил: «Что возвращает эта API-функция?» Нам необходимо знать это до такой степени, чтобы и без помощи Win32 API, а только увидев это, мы и без нажатия на F8 могли понять, что здесь происходит, хе-хе.

858c742d50a986f298babe62b361090f.png

Поскольку параметр функции — ntdll.dll, то после нажатия на F8 получим основание этой DLL’ки, которое затем станет параметром GetProcessAddress при поиске адреса новой API-функции.

dc5203de9effdcf61ea31cc56d59086a.png

Надеюсь, я был прав. Нажмем F9:

2fd9911981240b6d66fcf70e33f7a0dd.png

Ну, посмотреть чуть ниже было не так трудно, хе-хе.

Вот параметры функции:

148d8a6b0bd00107db3d2c26d97352ea.png

Хмм, этот вызов даст программе адрес указанной API-функции. Снова F9:

d60ace719841cefdbd4396614313a8ed.png

3bd280ef75382ebeb5a2b155596f78c7.png

Итак, поскольку здесь пропатчивается API-функция того же самого процесса, то это реализация части системы антиотладки. Для тех, кто желает изучить данный вопрос глубже, можно сказать так: здесь изменяются настройки доступа упомянутой API-функции, чтобы в ней можно было писать.

41788dd48ae915ff751f98f5af47d309.png

Изменения вносятся API-функцией WriteProcessMemory. Посмотрим ее параметры:

c8b9bb3a8b89b7357a7a2544f5531174.png

Итак, что за байт будет записан в этом процессе с помощью псевдохэндла FFFFFFFF по адресу исследуемой API-функции?

d1b42f7cdbd21671ae6eec764e3d89ec.png

В этой процедуре есть вызов DbgBreakPoint. Если вспомнить, то после присоединения остановка произошла именно на DbgBreakPoint, так что данное место имеет к этому отношение. Выполнив API-функцию WriteProcessMemory, посмотрим, что изменится в DbgUiRemoteBreakin:

482942d75b70f21c370ed467ae056ed7.png

Эта функция аннулируется установкой команды RET в ее начале.

Теперь опять воспользуемся нашим OllyKam’ом, который достаточно много рассказывает нам о происходящем в программе и о вызываемых в ней API-функциях:

f5ae1b6b762a6a0bed9132a6d9ce144e.png

Снова извлекается основание ntdll.dll. Далее идет вызов GetProcAddress для поиска адреса процедуры DbgBreakPoint, чтобы пропатчить и ее.

d11dc909617733c207d8780bb43eab5d.png

0d99510986e61272dff00d3dabce7f89.png

Устанавливаются настройки доступа для чтения/записи. Далее:

6b17c4777a77c07c47132274ce2a2724.png

Теперь будут внесены изменения. Посмотрим параметры этой API-функции:

da484f59e87be0b110be336363924f38.png

Она изменит один байт в процедуре DbgBreakPoint. Взглянем на нее до внесения изменений:

c0244892ce61898ff6a05d12f5113482.png

После нажатия на F8 начало функции станет таким:

ccfbfd90ccacc2fdddd04d5bc1de3903.png

То есть удалена команда INT3, которая вызывает исключение, дающее нам возможность приаттачиться, а вместо нее установлена инструкция RET, не позволяющая нам это сделать. Хе-хе, OllyKam действительно помогает тем, кто желает изучить систему антиотладки! Нажмем F9:

939452fd44f70e11334858f5e3f08816.png

Остановились на API-функции OpenMutexA, которая используется при работе с мьютексами. Один из них уже создан родителем, и это можно проверить в окне хэндлов родительского процесса: там есть мьютекс под названием WAIT:

f2d3ff4265e03e63b10d594447d742c0.png

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

e4065dbf87e3d24e13cf14d4760a7de4.png

Как это обычно бывает при успешном открытии объектов, хэндл возвращается в регистре EAX.

2892474c68970c9df8e90653538cd15f.png

Теперь он загружен и появился в списке хэндлов потомка.

ebd3fbd3925cf3d30c4d92573f2f3d1e.png

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

e28055bd45d2f75466143b4d316909a3.png

Вызов WaitForSingleObject — еще одно критическое место потомка, где придется бороться с Patrick’ом. Как известно, если родитель не выполнит определенные действия за несколько секунд или войдет в цикл и потратит время, потомок закроется. Данная функция как раз в этом и виновна. Посмотрим ее параметры:

6b30a7f1fc77f991483c830d46ea70be.png

Первый — это хэндл мьютекса, названного WAIT. Значит, потомок будет ожидать от родителя определенных действий, а после освобождения этого мьютекса выполнение потомка продолжится. Проблема заключается во втором параметре, где указан период в 5000 мс: если за этот промежуток времени потомок не дождется от родителя освобождения мьютекса, он будет закрыт, и, как это ни прискорбно, закроется также и потомок. Посмотрим, не трассируя, что идет далее после вызова этой функции:

f37839deb86ff3f021b0edd4872a8bff.png

38d30787de99a0fb1f0427d06897207a.png

Здесь происходит сравнение с нулем, и, в зависимости от результата, выполняется или не выполняется обход JMP’а, ведущего к вызову ExitProcess.

67ed1ecff2c208c63ff65eb41737ede3.png

edbb20f5561cfadd58991da4d6f0dc8d.png

То есть, во избежание закрытия потомку следует не выходить из этой API-функции до тех пор, пока родитель не освободит мьютекс WAIT. Мы просто изменим параметр времени dwMilliseconds (Timeout в Olly) на INFINITE (бесконечность), и потомок не сможет выйти из функции WaitForSingleObject:

efcf7076790b2e8cc1f4afdd49c9a706.png

После замены значения параметра 00001388 на FFFFFFFF (–1), установим BP сразу за API-функцией, чтобы после освобождения мьютекса родителем произошла остановка:

5a9ec5f19ba3beba271d977458ce591b.png

Теперь нажмем F9:

fb5c8f53b428e713af283c38cefe2417.png

Как видим, потомок выполняется, и это будет продолжаться до тех пор, пока родитель не окажется в области, где освобождается мьютекс. Поэтому теперь мы ничего не сможем сделать в потомке. В момент освобождения мьютекса родителем произойдет остановка на установленном нами брейке, а сейчас, когда потомок вечно выполняется, есть время для продолжения работы с родителем.

Не стоит забывать, что если бы мы не изменили заданный в миллисекундах параметр времени API-функции WaitForSingleObject, это встревожило бы потомка и он перестал бы ждать родителя и отправился бы к ExitProcess, хе-хе. Бедный сын — воспитав его, мы заставили его послушно ожидать, хе-хе.

Теперь настала очередь трудиться родителю, поэтому начнем направлять его с помощью его OllyKam’а. Нажмем F9, чтобы узнать, что он будет делать.

Остановка произошла на API-функции Sleep, которая заставляет потерять его немного времени. Продолжая нажимать F9, пройдем не очень важную API-функцию и дойдем досюда:

790c58afe802c1aa2b00cd4eb415c65c.png

Параметры этой функции стоит рассмотреть, поскольку здесь организован гнусный трюк и нужно быть настороже.

Но перед тем как продолжать объяснение трюка, следует заметить, что в данном случае API-функция CreateFileA используется для обнаружения изменения кода. Установленные брейки изменяют код, а это после сравнения дает различия, которые потом приводят к плохой расшифровке программы. Поэтому все BP следует удалить. Можно оставить BPM ON ACCESS, который используется в OllyKam’е, но придется удалить все BP как в родителе, так и в потомке, а BP, который мы поместили для остановки потомка после прекращения вечного выполнения, нужно заменить на HPB ON EXECUTION, иначе возникнут проблемы при расшифровке.

17d20c3a26297c9b07a0bb731f522646.png

Эти BP в родителе нужно удалить все, даже One-shot, останавливающий OllyDbg на EP, так как он модифицирует код. А также следует удалить все BP в потомке.

После оставления насиженного добра можно продолжать изучение трюка, реализованного с помощью API-функции CreateFileA. Но теперь мы не будем устанавливать ни одного обычного брейкпоинта — все брейки должны быть либо BPM, либо HE.

Вот параметры API-функции CreateFileA:

d9199a0635639f7fb5fa07d2b0327e18.png

Здесь происходит что-то странное, что я считаю багом OllyDbg — другого объяснения я не нахожу. Если кто-то, прочитав дальнейшее, узнает, почему так происходит, скажите мне. Я никогда не видел такого противооллиного трюка и у меня нет никакой информации о том, что в API-функции CreateFileA может использоваться баг Olly. Поиски в форумах ничего не дали. Возможно, друг Fatmike обнаружил какой-то новоявленный баг. И только из-за того, что я довольно давно в крэкинге, после усиленных поисков удалось вычислить, что здесь происходит. Я постараюсь объяснить эти довольно необычные вещи как можно более ясно.

Если попытаться изменить в DLL’ке хотя бы один байт и сохранить изменения, то при следующем запуске программа узнает, что произошло изменение, дойдет до OEP, но окажется плохо распакованной, т.е. будет состоять из мусора, вызывающего ошибки.

Дело в том, что программа с помощью CreateFileA проверяет AntiDebugDll.dll, а затем с помощью ReadFile считывает и сравнивает байты файла с теми, которые выполняются, проверяя таким образом их равенство. При обнаружении несоответствия даже одного байта происходит плохая распаковка. Так должно быть в случае, если изменен один или более байтов, но мы в файле ничего не меняли, а делали всё в памяти, поэтому после считывания файла байты должны совпасть (если, конечно, не установлены BP). Однако, при использовании OllyDbg произойдет то же самое: программа плохо расшифруется и в OEP окажется мусор. Похоже, в Olly происходит нечто, из-за чего при прочтении файла программа ведет себя так, как если бы ее байты были изменены.

Вернемся к API-функции CreateFileA и ее параметрам:

0becedbe8177cc6c36da8817add97b79.png

Большинство из них довольно стандартны, но параметр dwShareMode требует особого внимания. Давайте выясним его возможные значения по Win32 API:

0d0eea1fa794a7530e4a9771cae0a854.png

Здесь сказано, что если этот параметр равен FILE_SHARE_READ, то следующий вызов файла будет успешен только при установке параметра dwDesiredAccess в GENERIC_READ. Установка нуля вместо FILE_SHARE_READ запрещает разделение доступа к файлу и следующая попытка его открытия будет тщетна, поскольку файл уже открыт.

В нашем случае в следующий раз файл будет открыт только при установке параметра GENERIC_READ. Нажмем F8, чтобы посмотреть, будет ли возвращен хэндл:

1861b633b7892957320a61b0673773a2.png

Хэндл получен. Теперь с помощью OllyKam’а перейдем к очередной API-функции:

32ed66663549fa35bf06e336e734652a.png

Здесь вызывается CreateFileMappingA, создающая проекцию файла в памяти, о чем можно прочитать в новых туториалах этого курса. Она не сильно влияет на функционирование программы. С ее помощью последняя получает хэндл проецированного файла.

52919c619b2706bac1cb40a5e160bdc0.png

Затем вызывается API-функция MapViewOfFile, которая мэппирует файл в память с помощью полученного хэндла. После нажатия на F8 станет известен адрес, по которому загружена проекция:

e18ce84f701fc067b0fb4388c1e893d3.png

AntiDebugDll.dll загружена в память побайтно, начиная с адреса 9F0000. Казалось бы, теперь эти байты должны быть сравнены с теми, которые выполняются, но этого не происходит — сравнивается несколько байтов заголовка, а вовсе не код программы. Посмотрим в дампе загруженные байты DLL’ки:

d6d619162660e00003a9db197f574a17.png

Они должны совпадать с байтами выполняющейся в данный момент DLL’ки, загруженной по адресу 10000000. И действительно, это те же самые байты:

dffb3b84c6f7785bdafe34aff09a8020.png

Программисту было бы легко сделать сравнение прямо здесь, поскольку байты уже загружены, и в секции, созданной проецированием файла в память, нельзя устанавливать ни BPM ни HE. Можно было замести следы с помощью переходов и начать сравнение здесь, проверяя наличие измененных байтов, но, как бы это удивительно ни было, сравниваются только 4 или 5 значений в заголовке. Я трассировал дальше с помощью F7, так как в секции 9F0000 нельзя устанавливать ни BPM, ни HE. После долгой бессмысленной трассировки я заметил, что байты почему-то не сравниваются. Было бы проще установить брейки, но пришлось всё делать вручную. Это была одна из причин подозревать, что автор что-то замышляет. Затея, подобно первым шагам ребенка, завершилась, чтобы продолжиться позже. Какая-то причина однозначно существует!

Что ж, далее воспользуемся OllyKam’ом:

d98278f4aeaa35a6393a239970631155.png

Здесь считывается размер файла, чтобы узнать, был ли он изменен, а затем идут довольно нелепые сравнения расширения DLL’ки: dll или DLL, exe или EXE… Ну и пусть теряет свое время, если ему так хочется, хе-хе.

a6aeccad7e42891ec0fc71a4e9b2ca21.png

Продолжим:

30bbfca8fd6bff6bbc1ee628ee187c3d.png

Здесь удаляется проекция файла, загруженного в 9F0000, то есть эти байты больше не потребуются.

2f5d773be4ea7da736de999ed3af1379.png

Затем закрывается хэндл только что удаленной проекции файла, и это должно поставить точку в данном вопросе.

77d2d813e089e01578e9cbd372841b1b.png

Тем не менее, снова вызывается CreateFileA, но уже с такими параметрами:

ad390a603c8f932c3b18aa48d4dad686.png

Параметр dwDesiredAccess по-прежнему установлен в GENERIC_READ, и, согласно Win32 API, при существующем файле его хэндл можно получить снова. Нулевой dwShareMode приводит к тому, что хэндл не возвращается только при последующих вызовах CreateFileA, а здесь такого ограничения нет, и он должен возвратиться, поскольку файл существует. Посмотрим, что произойдет.

ff10ab59e6d4e301dbe369fc34b9b591.png

Вместо хэндла файла OllyDbg возвратил код FFFFFFFF, а также показал сообщение об ошибке разделения доступа ERROR_SHARING_VIOLATION:

06454069c7c603136f77b6eda66668e8.png

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

Тот факт, что далее в программе хэндл, который должен был получен здесь, используется для чтения DLL’ки с помощью ReadFile и выполняется сравнение, когда это можно было сделать ранее, доказывает, что так было сделано намеренно. Кто-то нашел в OllyDbg баг и воспользовался им, чтобы в этом отладчике было невозможно считать байты файла и сравнить их: это приводит к тому же, что и изменение байтов — файл плохо расшифровывается. Все эти догадки были подтверждены экспериментально.

4de6032f5441710f52f5614b5f953f17.png

После неудачной попытки получения хэндла вызывается ReadFile и производится чтение файла с параметром FFFFFFFF. В результате этого в регистре EAX оказывается ноль и происходит переход. Если бы был указан хэндл, то чтение выполнилось бы и переход бы не состоялся. Это означает, что пока программа выполняется в OllyDbg, адрес в образе 10003901 никогда не будет достигнут, поскольку выше происходит переход из-за неудачного выполнения ReadFile.

А что, если попытаться обеспечить нормальную работу ReadFile вне OllyDbg, чтобы эта функция возвратила единицу и переход, обходящий указанную область, не состоялся?

Я воспользовался не очень информативным способом: в файерволе Kerio есть возможность установить в настройках галку, чтобы программы спрашивали разрешения на запуск. Я запустил Patrick’а вне OllyDbg, и окошко с вопросом выскочило. Потом в Pupe установил EB FE по адресу ntdll.dll, из которого происходит переход на EP DLL’ки (на моем компьютере это 7C9111A4) и ответил в диалоговом окне. Затем установил другой цикл несколькими строками выше в 7C911199 и снял первый, и так поочередно устанавливал один и снимал другой, пока DLL’ка не загрузилась. Потом поместил бесконечный цикл в 10003901 и удалил циклы из ntdll, чтобы программа запустилась и осталась там зацикленной. Это позволило приаттачиться к процессу и увидеть, что он был остановлен в месте, в которое из OllyDbg никогда не попасть и которое достижимо только в случае возвращения функцией CreateFileA валидного хэндла. Таким образом было установлено, что программа выполняется нормально и в OllyDbg действительно есть баг.

Сделаем так, чтобы OllyDbg оказался в месте возвращения хэндла: повторим всё и дойдем до CreateFileA:

480d43ee236fe213d485461a1b440095.png

Заставить OllyDbg возвратить валидный хэндл можно, заменив нулевое значение параметра dwShareMode единицей:

47a7402086cf61fab2d30b5ad12a4391.png

Нажмем F8:

d1c1c5d6dba5bc48b3ecb6132b3e8288.png

И вот наш злополучный хэндл! Этот трюк, кажущийся таким простым, когда он известен, заставил меня провести целые дни в изнурительных попытках выяснить, что же здесь происходит. Так что на самом деле он совсем не прост…

Ладно, теперь продолжим юзать наш OllyKam:

d81eeba89b79a264da089584a73172c9.png

Дойдя до ReadFile, нажмем F8:

3aec714b24e3e2a1cde50675ef6e50e1.png

Теперь EAX=1, как и должно быть, и обход адреса 10003901 не производится.

7da46934369ca99bd0bf47880ef0947e.png

Сразу после сравнения идет закрытие хэдла:

883d5715596673ff7928d2fbbdf8e77d.png

Нужно быть внимательным, поскольку этот трюк используется несколько раз.

OllyKam, вперед!

c2953884dd38e751cd05b1eda1462866.png

После нескольких остановок на не очень важных API-функциях снова вызывается CreateFileA, теперь уже для считывания файла Patrick.exe:

400eb6c7590609de2ca0b58ab7e75645.png

d2cb2f8a3f1ed66b004efd00d5ccc3a5.png

Так как это происходит впервые, то проблем не возникает и возвращается валидный хэндл, позволяющий работать с файлом.

547f73cdfeeb60ce0edf1d5a0376af46.png

Теперь с файлом Patrick.exe происходит то же самое, что было с AntiDebugDll.dll: создается проекция файла в памяти, т.е. возвращается ее хэндл.

c11dbd7421479f4cae3f5dd1c662afb3.png

Здесь копируются байты файла и API-функция возвращает адрес проекции, в данном случае на моем компьютере это 0AF0000:

ce2fec0c87cf8cee5a357dd89e3e55d8.png

Если посмотрим этот адрес в дампе, то увидим отображение файла, содержащее считанные из файла Patrick.exe байты:

638dddfc68deb0d23c6cbcb229757856.png

Конечно, они будут совпадать с байтами Patrick.exe, находящимися по адресу 400000:

ed361d614a67abdc7de039249f9dea26.png

Как видим, они абсолютно одинаковы. Далее, как это было с DLL’кой, сравнивается лишь несколько указателей заголовка, а не весь файл. Нажмем F9:

70c0ffee790ea56bace46072116fd2cc.png

Опять происходит проверка расширения, но на этот раз у экзешника Patrick.exe.

9ca61ce1fd7b989fe2d4663564ba60e2.png

Затем изображение файла удаляется, хотя сравнение было неполным.

a4c23cd7a78a1f24cb20d6a26afc2d05.png

После пары незначительных API-функций изменяются настройки доступа потомка, находящегося в 401000; это нужно для расшифровки.

b6edc781e27cf2698147c14cc64192bd.png

Через параметры функции передаются хэндл потомка и разрешение на чтение/запись в секции, начинающейся в 401000.

40c9c26a8e0e98468c9710b1a6f95941.png

Так вызывается ReadProcessMemory — командой CALL, которая находится чуть ниже. Войдем в нее:

f404f152048f6f5a6a155051330d7eed.png

Из параметров функции видно, что будет прочитано 16 байтов, начиная с адреса 401000. После их расшифровки они будут сохранены там же функцией WriteProcessMemory. В 401000 родителя окончательные байты не видны, но это не проблема, так как далее потомок произведет для него повторную расшифровку.

c252f8a4f4009eae047afaa2db4fd565.png

171e7dd76dfbbb2c375ee8d11236f1bd.png

c16c628c5f88ad844cbb274e75c8db32.png

Этот цикл будет повторяться вплоть до начала следующей секции в 406000, так что нам придется терпеливо удерживать клавишу F9 нажатой, увеличивая адрес, пока не окажемся рядом с 406000, где скорость надо будет замедлить, чтобы не проскочить.

3ad56d1feb85cd64a6158b1301c0ca2b.png

Скоро произойдет завершение цикла, поэтому следует отпустить клавишу и продолжать продвигаться уже отдельными нажатиями.

1641769fb7bd708e76141ed89e48140b.png

Затем вызывается RealeaseMutex для освобождения потомка, который продолжает выполняться. Вспомним, что потомок выйдет из функции WaitForSingleObject только при освобождении родителем мьютекса, названного WAIT.

Если посмотреть параметры, то там указан хэндл 50 (так на моем компьютере):

8ca9094c6ec7afb163fdbf12db33d21c.png

13eb461f7ebff780381728adf4bfe2ad.png

Он, конечно же, соответствует мьютексу (или «Mutant», как указано в Olly) с именем WAIT. Сейчас потомок запущен и ожидает освобождения. После нажатия на F8 он будет освобожден и продолжит дальнейшее выполнение, поэтому на строке, следующей за вызовом WaitForSingleObject, был установлен брейк.

Нажмем F8, чтобы потомок освободился и остановился на HE.

Теперь свободны оба процесса, но мы пока продолжим работать с родителем: с помощью нашего OllyKam’а посмотрим, что будет далее:

24c90c709539c26051b68cf5c8853e9c.png

Этой API-функцией устанавливается другое ожидание, на этот раз в родителе:

f93cc754eeea4e912474eda5b1e9641b.png

Единственный параметр задает период времени, после окончания которого происходит выход из API-функции. Но в данном случае это не случится, так как задана бесконечность; выход произойдет только в момент запуска потомка при вводе с клавиатуры или подобном событии.

Как и прежде, установим HE на следующей за вызовом команде и нажмем RUN:

1e533eac5d844314534e9a12c978581a.png

198c378e0dcebdd9abe98d7329495c36.png

Теперь родитель будет находиться в состоянии Running до тех пор, пока отпрыск не вырастет, а мы тем временем поможем ему подрасти, хе-хе. Продолжим юзать его OllyKam:

58db24981239fccf187fe9b3dfd8c61c.png

На этот раз мьютекс WAIT будет освобожден в потомке, но на родителя это никак не повлияет, поскольку он находится в функции WaitForInputIdle, а не WaitForSingleObject и ожидает запуска потомка. Продолжим:

4c9884c3df1d947ce451c6fdf3732bb0.png

Здесь потомок впервые считывает DLL’ку, чтобы проверить ее содержимое. Так как это происходит в первый раз, то проблем не возникает и извлекается валидный хэндл (скриншоты далее привожу без комментариев, ибо этот трюк уже объяснялся в родителе):

27ece54cc776907847c10ef2a79764b0.png

ad472737dc54e9e9910fe74b54064eb1.png

0b11401a31435b95626f92e72e8f7a9b.png

b9024e24b84dc5a7a84f8ddb96fac34a.png

7630f060f2c448a8d9b7144e69c20288.png

Очередь доходит до ключевого вызова:

8efea09167a8b164517e9f9c910b1caf.png

03c75eb52c862f551c4463a69c484833.png

Как и прежде, обойдем баг Олли, изменив параметр dwShareMode на 1:

30f41f8a2da6501a454e82d85a5ed674.png

Возвращен валидный хэндл.

ed3630bb27388ab64981580150819b2d.png

После пары незначительных API-функций вызывается ReadFile с хэндлом файла в параметрах для считывания его байтов и их сравнения. Так проверяется оригинальность файла.

744b118f3d7e06261b6ad92eafe15c9a.png

После проведения проверки хэндл закрывается.

b7f0075ccb9201816b38d2a0ea2e5d42.png

Теперь опять CreateFileA, но уже для экзешника Patrick.exe:

7a3b6ace549bd1a2404d0612e82eb365.png

Здесь проблем не возникает и хэндл возвращается.

1ac6a176a8b547c86fc7bc1a683cdb0e.png

Происходит то же самое, что было в родителе, поэтому опустим подробности.

aee8853127ebdb22f474e2b20b775aad.png

0a30866d342ca2ac0e1ad48d1c99f0c1.png

Теперь тот же трюк применяется к файлу Patrick.exe:

322be39de25fdc9454a2c6ab46974bff.png

Но мы изменим dwShareMode на 1, и хэндл будет возвращен:

553da387ef281d2635af012d9c298740.png

Продолжим нажимать на F9, и после нескольких API-функций дойдем до ReadFile, выполняющей проверку содержимого файла:

0bd804cd4537e051ba2e5beff2000cb0.png

42704c4b4ab3f652b46fbd71517f38f9.png

Опять то же самое! Что ж, изменим dwShareMode на 1. Предполагается, что если ты смог дойти досюда, то трюк уже раскрыт.

823522f12c0086012f7cc2044d753f8a.png

Снова вызывается ReadFile для проверки.

a3efde4cc24625a5b32e7d213accff72.png

Дошли до GetCurrentProcess, которая возвращает псевдохэндл (хэндл выполняющегося процесса) FFFFFFFF. Наконец-то эти проверки пройдены и начинается что-то новое!

d89ff03f0d105e7ee4f00e315a486191.png

33728ba4033a273b2af0a2b8d0b976ae.png

Здесь, как и ранее в родителе, разрешается чтение и запись в секцию кода программы для обеспечения функционирования ReadProcessMemory и WriteProcessMemory.

8b6692a32f4478d26efe25d473d99cfd.png

Теперь вызывается WriteProcessMemory:

e50cd07e65f3492991f619808ecb3a8c.png

После нажатия на F8 расшифруются первые 16 байтов и их можно будет увидеть по адресу 401000:

a43a461af0b698013df5660c2f8f9e69.png

Как видим, они расшифрованы верно.

c266e7a3865e8177fba6bd1455b339ad.png

Остается добавить только штрих, хе-хе.

15f8306b7472e1bf7ca375627a91c6c9.png

Как и в прошлый раз, надо удерживать нажатой клавишу F9 до тех пор, пока не окажемся около 406000.

Область OEP расшифрована тоже правильно:

d69109f2417bd65c758bdde3ee5643eb.png

302460a23db1852b880d6a46ebc389c5.png

После окончания расшифровки создаются потоки защиты. Можно создать их замороженными, изменив параметр dwCreationFlags на 4.

0749d4ab76837bdc9d142715a70a0d15.png

Это начало первого треда.

cc27a01b73507160e27091d8dba1c00a.png

А это — второго. Здесь снова будет произведена проверка выполняющихся процессов, поскольку видны соответствующие API-функции.

Итак, изменим параметр dwCreationFlags на CREATE_SUSPENDED:

cb06e571df686c72b5a72e1919732ad5.png

После создания тредов на всякий случай снимем все аппаратные брейки, установим BPM ON ACCESS в секцию кода Патрика (начинающуюся в 401000) и BREAK ON ACCESS [Break-on-access?] в секцию кода AntiDebugDll.dll. После нажатия на F9 произошла незапланированная остановка в этой области:

3ef8caae51460cdb9a3212dd7c0386ad.png

Можно оказаться на OEP Патрика быстрее, но так как галки в настройках исключений Олли оказались сняты и BPM установлен на всю секцию [в секцию кода AntiDebugDll.dll?], нажмем F9. Одно исключение генерируется этой командой:

8361fd4d922ad94e72a4cc1f28b1b52b.png

Это попытка перейти на адрес 00000000. После нажатия на Shift+F9 окажемся в OEP из-за остановки на BREAK ON ACCESS [Рикардо что-то мудрит…]

f63864556aebd5e09c0ab9324683da57.png

Теперь можно было бы сразу приступить к сдампливанию, но мы поступим иначе: откроем еще один Patrick.exe в третьем OllyDbg и оставим его на системном брейке:

5c8f74826297d86332ee3e42b6c9dac3.png

Теперь скопируем в его первую секцию все байты первой секции потомка:

1078a4380c812de87fa47b9a0e25aa7f.png

Сохраним изменения: Copy to executable. Поскольку здесь правильная IAT, то проблем возникнуть не должно.

После открытия сохраненного Патрика в его первой секции содержится правильный код уже на System startup breakpoint. Но проблема в том, что далее запускается гнусная DLL’ка, поэтому перед ее удалением следует установить RET в ее EP и сохранить изменения:

194850825cd3576db544f1e8daaac55f.png

Потом откроем Patrick.exe в PE Editor’е и изменим настройки доступа всех секций на E0000020:

f54165c8fa4ad1662f6d1b78a1c8666e.png

А также есть CALL, который вызывает AntiDebugDll.dll — просто занопаем его:

d2fc34b3bb497e7fb32122a8cf525b23.png

82b72b944732e47c4f2ccbd5085d7514.png

Сохраненим изменения и перезагрузим крэкми:

536e508a4a35dd964fc2910af982a263.png

Теперь он запускается, но чтобы DLL’ка вообще не загружалась, следует сделать следующее:

27b720c272f4b6630c8b42028fa50955.png

Найдем в заголовке адрес начала IT — 406F3C.

f960fd9e460768c9939b2a7179cc6897.png

Это IT. Вспомним, что первые 5 DWORD’ов соответствуют 1-й DLL’ке. Узнаем ее имя по ее указателю — 4-му DWORD’у:

b815e49693756067e4f1c012b1af2d7e.png

Имя первой DLL’ки находится в 40712E, посмотрим его:

8edddfbe91779ec09b26ca1007b45e00.png

Это WINMM.DLL, а вторая — AntiDebugDll.dll. Таким образом, если скопировать первые 5 DWORD’ов таблицы импорта и вставить их в позицию следующих, то AntiDebugDll.dll аннулируется и второй DLL’кой будет опять WINMM.DLL:

731d74fd2b0873ed3688e54a1e6d19a9.png

Осталось только сохранить изменения и установить новое начало IT — 406F50:

2d2792dfd4be47ee891301595de030c8.png

Сохранившись и перезагрузив крэкми, изменим адрес начала IT:

6d3ce51e2750f6dbb5658834a6a6793d.png

Теперь будет так:

fdaea6eda1757f98cc387158b8df3e22.png

После сохранения изменений и перезапуска крэкми противная DLL’ка не загружается и крэкми выполняется верно:

1b1316cc0d5576b3a833213ef812f2e6.png

4a40bea18bf8e2352041e98c4253f10a.png

Хе-хе, вот и финал внушающего страх Патрика!

Простое же решение состоит в изменении настройки доступа 40000040 секции данных файла Patrick.exe, что лишает его всех привилегий и нескольких строк ниже OEP, вызывающих ошибку при открытии в JIT-отладчике OllyDbg. В таком виде можно дойти чуть ниже OEP, дождаться распаковки и сделать то же самое: скопировать и вставить первую секцию в третий OllyDbg, изменить IT — и он заработает, хе-хе.

Прощай, ПАТРИК! хе-хе
 

О нас

  • Наше сообщество существует уже много лет и гордится тем, что предлагает непредвзятое, критическое обсуждение различных тем среди людей разных слоев общества. Мы работаем каждый день, чтобы убедиться, что наше сообщество является одним из лучших.

    Dark-Time 2015 - 2022

    При поддержке: XenForo.Info

Быстрая навигация

Меню пользователя