Техника снятия дампа с защищенных приложений

         

Дамп изнутри


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

Классический способ внедрения кода реализуется так: открываем процесс функцией OpenProcess, выделяем блок памяти вызовом VirtualAllocEx, копируем код дампера через WriteProcessMemory, а затем либо создаем удаленный поток функцией CreateRemoteThread (только на NT-подобных системах), либо изменяем регистр EIP в контексте чужого потока, обращаясь к SetThreadContext (действует на всех системах). Естественно, предыдущей EIP должен быть сохранен, а сам поток — остановлен.

Постойте! Но ведь это мало чем отличается от обычного межпроцессорного взаимодействия! Функции NtAllocateVirtualMemory/NtSetContextThread/NtCreateThread любая защита перехватит со смаком! (никакой ошибки тут нет, API-функция CreateRemoteThread в действительности представляет собой "обертку" вокруг ядерной функции NtCreateThread).

Хорошо, вот другой классический путь. Помещаем дампер в DLL и прописываем ее в HKLM\Software\Microsoft\Windows NT\CurrentVersion\Windows\AppInit_DLLs, в результате чего она будет отображаться на все процессы, какие только есть в системе и перед передачей управления на очередной запускаемый процесс первой получит управление наша DLL! К сожалению, об этой ветке знают не только протекторы, но и другие программы (антивирусы, персональные брандмауэры) и следят за ней. Наша запись может быть удалена еще до того, как дампер приступит к работе! Если же ему все-таки удастся получить управление, первое, что он должен сделать — выделить себе блок памяти внутри процесса, скопировать туда весь необходимый код и вернуть ветку AppInit_DLLs в исходное состояние. Поскольку, дамер получает управление еще до того, как защита начнет работать, она никак не сможет обнаружить, что здесь кто-то уже побывал.


Исключение составляют активные защиты резидентного типа постоянно присутствующие в системе даже если защищаемый файл не был запущен. Но в этом случае они сталкиваются со следующей проблемой — как отличить "свой" процесс от всех остальных? По имени файла? Это не слишком надежно… Лучше использовать "метку" — уникальное сочетание байт по определенному адресу. С такими защита справиться очень сложно, но все-таки возможно. Перепробовав несколько вариантов, автор остановился на следующем алгоритме, который обходит все существующие на сегодняшний день активные и пассивные защиты:

q       копируем оригинальный файл (с защитой) в tmp.tmp;

q       открываем оригинальный файл в hiew'е, переходим в точку входа (EP) и ставим jmp на свободное место, где и размещаем код дампера, который при получении управления осуществляет следующие действия:

o        выделяет блок памяти и копирует туда свое тело, обычно подгружаемое с диска (динамическую библиотеку лучше не загружать, поскольку некоторые защиты контролируют список DLL и если вызов идет из неизвестной динамической библиотеки, расценивают это как вторжение);

o        устанавливает таймер через API-функцию SetTimer с таким расчетом, чтобы процедура дампера получила управление когда весь код будет полностью распакован или, в случае, с CopyMem, когда защита успеет установить отладочный процесс (конечно, снять дамп в OEP в этом случае уже не получится, но… даже такой дамп лучше, чем совсем ничего);

o        переименовывает оригинальный файл (тот, что исполняется в данный момент!) в tmp.xxx, а файлу tmp.tmp возвращает оригинальное имя;

o        вычищает себя из памяти, восстанавливает EP и передает управление защищенной программе;

o        если активная защита охраняет свой файл, опознавая его по сигнатуре, используем какой-нибудь безобидный упаковщик с "нулевым побочным эффектом" типа UPX, при этом все вышеуказанные действия следует выполняет на отдельной заведомо "стерильной" машине;



q       запускаем модифицированный файл на выполнение;

Таким образом, защита не сможет обнаружить изменений ни в файле, ни в памяти (при попытке определения имени текущего файла операционная система будет возвращать то имя файла, какое он имел на момент запуска, игнорируя факт его "онлайнового" переименования). Но это слишком громоздкий и навороченный алгоритм, к тому же активной защите ничего не стоит перехватить SetTimer и запретить установку таймера внутри "своего" процесса до завершения распаковки/передачи управления на OEP.

Забавно, но многие защиты забывают о функции SetWindowsHookEx, позволяющей внедрять свою DLL в адресное пространство чужого процесса. Впрочем, даже если бы они помнили о ней, осуществить корректный перехват весьма непросто. Многие легальные приложения (например, мультимедийные клавиатуры или мыши с дополнительными кнопками по бокам) используют SetWindowsHookEx для расширения функциональности системы. Не существует никакого способа отличить честное приложения от дампера. Защита может распознать факт внедрения чужой DLL в адресное пространство охраняемого ее процесса, но откуда ей знать, что эта DLL делает?! Можно, конечно, просто выгрузить ее из памяти (или воспрепятствовать загрузке), но какому пользователю понравится, что легальное приобретенная программа конфликтует с его крутой клавиатурой, мышью или другим устройством? Так что, SetWindowsHookEx при всей своей незатейливости — довольно неплохой выбор для хакера!

Самый радикальный способ внедрения в чужое адресное пространство — это правка системных библиотек, таких как KERNEL32.DLL или USER32.DLL. Править можно как на диске, так и в памяти, однако, в последнем случае защита может легко разоблачить факт вторжения простым сравнением системных библиотек с их образом. Внедрившись в системную библиотеку, не забудьте скорректировать контрольную сумму в PE-заголовке, иначе NT откажется ее загружать. Сделать это можно как с помощью PE-TOOLS, так и утилитой rebuild.exe, входящей в состав SDK.Внедряться лучше всего в API-функции, вызываемые стартовым кодом оригинального приложения (GetVersion, GetModuleHandleA

и т. д.), определяя "свой" процесс функцией GetCurrentProcessId или по содержимому файла (последнее — надежнее, т. к. GetCurrentProcessId может быть перехвачена защитой, которая очень сильно "удивиться", если API-функция GetVersion неожиданно заинтересуется идентификатором текущего процесса). Во избежании побочных эффектов, запускать такой дампер следует на "выделенной" операционной системе, специально предназначенной для варварский экспериментов и обычно работающей под виртуальной машиной типа BOCHS или VM Ware.


Содержание раздела