genetic-18

генетический распаковщик своими руками

крискасперскиаргентинскийболотныйбобер nezumi el raton аканутрякибнмыщъх, no-email.

прежде чем ломать, хакер берет программу и смотрит упакована она или нет и тут же бежит искать адекватный распаковщик (поскольку большинство защищенных программ упаковано), к сожалению, далеко не для всех упаковщиков/проекторов существуют готовые распаковщики… обобщив свой опыт борьбы с защитами, мыщъх предлагает алгоритм универсального распаковщика, пробивающего 99% защит

Как хитро я вас обманул! Ни о генах, ни о хромосомах здесь речь не идет. Искусственный интеллект отдыхает на задворках истории! «genetic» в переводе с английского означает «общий». Генетический распаковщик — универсальный распаковщик, общий для всех упаковщиков/протекторов. Кстати говоря, «GeneralMotors» это не «двигатели для генералов», а двигатели вообще. Почувствуйте разницу!

Создание универсального распаковщика начинается с алгоритма определения OEP (OriginalEntryPoint — Исходная Точка Входа), отслеживающего момент завершения распаковки с последующей передачей управления «программе-носителю». Это самая сложная часть генетических распаковщиков, поскольку определить исходную точку входа в общем случае невозможно, вот и приходится прибегать к различным ухищрениям. Чаще всего для этого используется пошаговая трассировка, которой упаковщик/протектор может легко противостоять (и ведь противостоит!).

Немногим лучше с задачей справляются трассеры нулевого кольца. Справиться с ними с прикладного уровня (а большинство упаковщиков/протекторов) работают именно на нем, практически невозможно, однако, разработка подобного трассера зачастую оказывается непосильной задачей для начинающих и хотя в распоряжении автора имеются исходные тексты великолепного трассера, разработанного группой Володи с WASM'а, мыщъх решил сходить другим путем, ограничившись только аппаратными точками останова, для установки которых прибегать к написанию драйвера совершенно необязательно. В «записках мыщъха» (электронную копию которой можно свободно утянуть с ftp://nezumi.org,.ru) показано как это сделать и с прикладного уровня, даже без прав администратора!

На первом этапе в качестве основного экспериментального средства мы будем использовать «Блокнот», пожатый различными упаковщиками и знаменитый отладчик soft-ice. Кодирование последует потом. Чтобы писать красиво и по сто раз не переписывать уже написанное и отлаженное, необходимо иметь нехилый боевой опыт, для которого нам и понадобиться soft-ice.

дамп живой программы

Самый простой (и самый популярный) способ борьбы с упаковщиками — снятие дампа заведомо после завершения распаковки. Дождавшись появления главного окна программы, хакер сбрасывает ее дамп, преобразуя его в исполняемый файл. Иногда он работает, иногда нет. Попробуем разобраться почему. Возьмем классическое приложение «Блокнот» из поставки NT (которое в защищенности не обвинишь) и, не упаковывая его никакими упаковщиками, попробуем снять дамп с помощью одного из двух лучших дамперов Proc Dump или Lord PE Deluxe (см. рис. 1).

Рисунок 1 снятие дампа с работающего «Блокнота»

Процесс, проходит успешно, и образовавшийся файл как бы даже запускается (см. рис 2), но… оказывается не вполне работоспособен! Исчез заголовок окна и все текстовые сообщения в диалогах! Если мы не сумели снять сдампить Блокнот, то с настоящими защитами нам и вовсе не справится. Давайте попробуем разобраться почему!

Рисунок 2 нормально работающий «Блокнот» (сверху) и тот же самый «Блокнот» после вснятия дампа — все текстовые строки исчезли

Расследование показывает, что исчезнувшие текстовые строки хранятся в секции ресурсов и, стало быть, обрабатываются функцией LoadString. Загружаем оригинальный notepad.exe в IDA Pro (или любой другой дизассемблер по вкусу) и находим цикл, считывающий строки посредством функции LoadStringW (суффикс 'W' означает, что мы имеем дело с уникодовыми строками).

Ага, вот он! Рассмотрим его повнимательнее (мыщъх, уверяет, тут есть чему поучиться):

01004825hmovebp, ds:LoadStringW; ebp - указатель на LoadStringW

0100482Bhmovedi, offstoff_10080C0;указатель на таблицу ресурсов

01004830h

01004830h loc_1004830:; CODE XREF: sub_10047EE+65↓j

01004830hmoveax, [edi]; грузим очередной указатель на uID в eax

01004832hpushebx; nBufferMax (максимальная длина буфера)

01004833hpushesi; lpBuffer (указатель на буфер)

01004834hpushdwordptr [eax]; передаем извлеченный uID функции

01004836hpush[esp+0Ch+hInstance]; hInstance

0100483Ahcallebp ; LoadStringW; считываем очередную строку из ресурса

0100483Chmovecx, [edi]; грузим тот же самый uID в ecx

0100483Ehinceax; увеличиваем длину считанной строки на 1

0100483Fhcmpeax, ebx; ?строка влезает в буфер?

01004841hmov[ecx], esi; сохраняем указатель на буфер поверх

01004841h; старого uID(он больше не понадобится)

01004841h

01004843hleaesi, [esi+eax*2]; позиция для следующей строки в буфере

01004846hjgshortloc_100488B; если буфер кончился, то это облом

01004848haddedi, 4; переходим к следующему uID

0100484Bhsubebx, eax; уменьшаем свободное место в буфере

0100484Dhcmpedi, offstoff_1008150; ?конец таблицы ресурсов?

01004853hjlshortloc_1004830; мотаем цикл пока не конец ресурсов

Листинг 1 хитро оптимизированный цикл чтения строковых ресурсов

В переводе на русский, это звучит так: Блокнот берет очередной идентификатор строки из таблицы ресурсов, загружает строку, размещая ее в локальном буфере, и сохраняет полученный указатель на строку поверх… самого идентификатора, который уже не нужен! Классический трюк с повторным использованием освободившихся переменных, известных еще со времен первых PDP, если не раньше. А вы все Microsoft ругаете! Блокнот писал не глупый хакер, бережно относящийся в системным ресурсам и, в частности, к памяти. Для нас же это в первую очередь означает, что снятый с «живой» программы дамп будет неполноценным. Вместо реальных идентификаторов строк, в секции ресурсов содержатся указатели на память, указывающие в «космос»! Ведь при повторном запуске Блокнота листинг 1 уже не срабатывает.

Во многих программах встречается конструкт вида:

void *p=0; глобальная переменная if (!p) p = malloc(BUFF_SIZE); Листинг 2 «защита» от дампинга живых программ Очевидно, если сдампить программу после завершения строки с «if», то глобальная переменная p будет содержать указатель доставшийся ей в «наследство» от предыдущего запуска, однако, соответствующий регион памяти выделен не будет и программа либо рухнет, либо залезет в чужие данные, устроив там настоящий переполох! Сформулируем главное правило: дампить программу можно только в точке входа! Остается разобраться: как эту точку входа отловить. универсальный примем поиска OEP, основанный на балансе стека Вот мы и подобрались к самому интересному и универсальному способу определения OEP, который к тому же легко автоматизировать. Упаковщик (даже если это не совсем корректный упаковщик) просто обязан после распаковки восстановить стек, в смысле вернуть регистр ESP на место под которым будет первичный фильтр структурных исключений, установленный системой по умолчанию. Некоторые упаковщики еще восстанавливают и регистры, но делать это в общем-то и не обязательно. Возьмем, к примеру, тот же ASPack и посмотрим в его начало: :u eip 01010001PUSHAD 01010002CALL0101000A 01010007JMP465E04F7 0101000CPUSHEBP 0101000DRET Листинг 3 точка входа в распаковщик ASPack Замечательно! Первая же команда сохраняет все регистры в стеке. Очевидно, что непосредственно перед передачей управления на OEP они будут восстановлены командой POPA, выполнение которой очень легко отследить, установив точку останова на двойное слово, лежащее выше верхушки стека: «bpm esp - 4». Результат превосходит все ожидания: 010103AFPOPAD;  на этой команде отладчик всплывает 010103B0JNZ010103BA(JUMP↓) 010103B2MOVEAX,00000001 010103B7RET000C 010103BAPUSH1006420; передача управления на OEP 010103BFRET Листинг 4 передача управления на OEP Распаковав программу, ASPack заботливо выталкивает сохраненные регистры из стека, вызывая всплытие отладчика и мы видим тривиальный код передающий управление на OEP «классическим» способом через PUSH offset OEP/RET. Поиск исходной точки входа не затратил и десятка секунд! Ну разве не красота? А теперь возьмем UPX и проверим, удастся ли нам провернуть этот трюк и над ним? Ведь мы же претендуем на универсальный примем! 01011710PUSHAD; ← упаковщик сохраняем регистры 01011711MOVESI,0100D000 01011716LEAEDI,[ESI+FFFF4000] 0101171CPUSHEDI Листинг 5 так начинается UPX Вот он, уже знакомый нам PUSHAD, сохраняющий все регистры в стеке и восстанавливающий их непосредственно перед передачей управления на OEP. Даем команду «bpm esp-4» и выходим из отладчика, пока он не всплывет 0101185EPOPAD 0101185FJMP01006420(JUMP ↑) Листинг 6 так UPX передает управление на OEP А вот и отличия! Передача управления осуществляется командой JMP 1006420h, где 1006420h — исходная точка входа. Похоже, что все упаковщики работают по одному и тому же алгоритму и ломаются как в ночь перед исходом. Но не будем смешить. Возьмем PE-compact и проверим свою догадку на нем. 01001000MOVEAX,01011974 01001005PUSHEAX 01001006PUSHDWORD PTR FS:[00000000] 0100100DMOVFS:[00000000],ESP Листинг 7 точка входа в файл, упакованный PE-compact Плохо дело! PE-compact никаких регистров вообще не сохраняет, а PUSH EAX используется только затем, чтобы установить свой обработчик структурных исключений. Тем не менее, на момент завершения распаковки указатель стека должен быть восстановлен, следовательно, точка останова на «bpm esp-4» все-таки может сработать…. 77F8AF78PUSHDWORD PTR [EBX+04] 77F8AF7BLEAEAX,[EBP-10] 77F8AF7EPUSHEAX Листинг 8 первое срабатывание точки останова на esp-4 Так, ну это срабатывание явно левое (судя по EIP 77F8AF78h мы находится где-то внутри KERNEL32.DLL, использующим стек для нужд производственной необходимости), давим <Ctrl-D> не желая здесь больше задерживаться. 010119A6PUSHEBP 010119A7PUSHEBX 010119A8PUSHECX 010119A9PUSHEDI Листинг 9 Кузьмич?! Где-то это я? Следующее всплытие отладчика, так же несет в себе немного смысла. Места как-то непотные и совершенно незнаемые. Ясно только одно, в стек сохраняется регистр EBP вместе с другими регистрами. Давим <Ctrl-D> и ждем дальше. :ueip-1 01011A35POPEBP 01011A36JMPEAX (01006420h) Листинг 10 переход на OEP А вот на этот раз нам повезло! Регистр EBP выталкивается из стека и вслед за этим осуществляется переход на OEP посредством команды JMP EAX. Все идет хорошо, вот только ложные срабатывания напрягают. Это мы, опытные хакеры, на глаз может отличить где происходит передача на OEP, а где нет. С автоматизацией в этом плане значительно сложнее, у компьютера с интуицией сплошной напряг. А ведь пока мы всего лишь развлекаемся… Про борьбу с протекторами речь еще не идет. Возьмем более серьезный упаковщик FSG 2.0 bybart/xt (http://xtreeme.prv.pl/, http://www.wasm.ru/baixado.php?mode=tool&id=345) и начнем его пытать. 01000154XCHGESP,[010185B4] 0100015APOPAD 0100015BXCHGEAX,ESP 0100015CPUSHEBP 0100015DMOVSB Листинг 11 многообещающая точка входа в упаковщик FSG Разочарование начинается в первых же команд. FSG переназначает регистр FSP и хотя через некоторое время восстанавливает его вновь — особой радости нам это не добавляет. Упаковщик очень интенсивно использует стек, поэтому точка останова на «bpm esp-4» выдает миллион ложных срабатываний, причем большинство из них находится в цикле. 010001C1POPESI 010001C2LODSD 010001C3XCHGEAX,EDI 010001C4LODSD 010001C5PUSHEAX 010001C6CALL[EBX+10] Листинг 12 фрагмент кода, генерирующий ложные срабатывания точки останова Необходимо ввести какое-то дополнительное условие (к счастью, soft-ice поддерживает условные точки останова!), автоматически отсеивающее ложные срабатывания или хотя бы их часть. Давайте подумаем! Если стартовый код упакованной программы начинается со стандартного пролога типа PUSH EBP/MOV EBP,ESP, то точка останова «bpm esp‑4 if *(esp)= = EBP», отсеет кучу мусора, но… при этом, она будет срабатывать на любом стандартном прологе нулевого уровня вложенности, а во-вторых, упакованная программа может иметь оптимизированный пролог, в котором регистр EBP не используется. А вот другая идея. Допустим, управление на OEP передается через PUSH offset OEP/RETN, тогда на вершите стека окажется адрес возврата, что опять-таки легко запрограммировать в условной точке останова. Еще управление может передаваться через MOV EAX,offset OEP/JMP EAX. Это только легко проконтролировать и отследить, но вот против «прямых» команд JMP offset OEP или JMP [OEP] мы бессильны. К тому же слишком много вариантов получается. Запаришься пока всех их переберешь. Ложные срабатывания неизбежны! Попробуйте повоюйте с FSG… В какой-то момент кажется, что решения нет, и наше дело труба, но это не так! Все известные мыщъху упаковщики (и значительная часть протекторов) не желая перемешивать себя с кодом упаковываемой программы, размещается в отдельной секции (или не секции), размещенной либо перед упаковываемой программой, либо после нее! Код упаковщика сосредоточен в одном определенном месте (нескольких местах) и никогда не пересекается с кодом распаковываемой программы! Вроде бы очевидный факт. Сколько раз мы проходили мимо него, даже не задумываясь, что он полностью позволяет автоматизировать процесс поиска OEP! Взглянем на карту памяти еще раз: MAP32 NOTEPAD-fsg0001 001B:01001000 00010000 CODE RW NOTEPAD-fsg0002 001B:01011000 00008000 CODE RW Листинг 13 две секции упакованной программы Мы видим две секции, принадлежащие упакованной программе. Сам черт не поймем которая из них секция кода, а какая данных, тем более что должна быть еще одна секция — секция ресурсов, но коварный упаковщик каким-то образом скомбинировал их друг с другом, один черт знает каким, впрочем (код самого упаковщика, как мы уже видели, сосредоточен в пространстве 10001xxh и отдельной секции для себя создавать не стал). Чтобы отсеять лишние всплытия отладчика, мы сосредоточимся на диапазоне адресов, принадлежащих упакованной программе, то есть от начала первой секции до конца последней, автоматически контролируя значение регистра EIP на каждом срабатывании точки останова. В данном случае это выглядит так: bpm esp-4 if eip >= 0x1001000 && eip < 0x1011000 Листинг 14 «магическая» последовательность, приводящаяся нас к OEP Невероятно, но после продолжительного молчания (а он и будет молчать, ведь стек распаковщиком используется очень интенсивно), отладчик неожиданно всплывает непосредственно в OEP! 01006420PUSHEBP 01006421MOVEBP,ESP 01006423PUSHFF 01006425PUSH1001888 0100642APUSH10065D0 0100642FMOVEAX,FS:[00000000] 01006435PUSHEAX 01006436MOVFS:[00000000],ESP Листинг 15 отсюда начинается распакованный код исходной программы Фантастика!!! А ведь FSG далеко не самый слабый упаковщик, фактически граничащий с протекторами. Однако, методика поиска OEP во всех случаях та же самая. Выделяем секции, принадлежащие упакованной программе, и устанавливаем точку останова на esp-4 в их границах. Даже если стартовый код использует оптимизированный пролог, первый же регистр (локальная переменная) заталкиваемая в стек, вызовет срабатывание отладчика. Если мы попадем не в саму OEP, то будет где-то очень-очень близко от нее и нам. А найти начало оптимизированного пролога можно и автоматом! Таким образом, мы получаем в свои руки мощное оружие многоцелевого действия, которого легко реализовать в виде плагина к LordPE, IDA Pro или самостоятельной утилиты. ===== »> врезка что делать если отладчик проскакивает точку входа в распаковщик ===== Берем исполняемый файл, загружаем его в NuMegaSoftICESymbolLoader, внимательно убедившись, что горячая лампочка опция «StartatWinMain, Main, DllMain» активирована (см. рис. 3), но… При попытке загрузки программы soft-ice коварно проскакивает точку входа, полностью утрачивая управление и контроль. Рисунок 3 символьный загрузчик. хорошая штука, но не всегда работающая Это известные глюк soft-ice, с которым борются по всем направлениям. Вот только один способ: Загружаем (неправленую) программу в hiew, переходим в hex-режим, жмем <F8> и вычисляем адрес точки входа путем сложения EntrypointRVA (в нашем случае — 10001) с ImageBase (в нашем случае 1000000). Получаемся 1010001. Если считать лень, можно просто нажать <F5>, чтобы hiew перенес нас в точку входа, сообщив ее адрес (однако, это не срабатывает на некоторых защищенных файлах с искаженной структурой заголовка). ОК, адрес EP получен. Вызываем soft-ice путем нажатия на <Ctrl-D> и устанавливаем точку останова на любую API-функцию, которую в какой-то момент вызывает наша программа. Это может быть и GetModuleHandleA («bpx GetModuleHandleA») и CreateFileA — неважно! Выходим из soft-ice и запускам нашу программу. Отладчик всплывает. Убедившись, что правый нижний угол отражает имя нашего процесса (если нет, выходим из soft-ice и ждем следующего всплытия), устанавливаем аппаратную точку останова на EP, отдавая команду «bpx 010001 X», где — 1010001 адрес точки входа в PE-файл. Выходим из soft-ice и перезапускаем программу. hint: soft-ice запоминает установленные точки в контексте данной программы и не удаляет их даже после ее завершения. При повторном (и всех последующих) перезапусках, soft-ice будет послушно останавливаться на EP в точке останова. Ну разве это не здорово?! ===== заключение ===== Вот мы и научились находить OEP. Остается самая малость — сбросить дамп программы на диск. Но здесь не все так просто, как может показаться вначале и многие упаковщики/проекторы этому всячески сопротивляются. В следующей статье этого цикла мы покажем как реализовать универсальный дампер, распаковывающий в том числе и DLL и обходящий продвинутый механизм динамической шифровки, известный под именем CopyMemII — это когда вся программа зашифрована и отдельны страницы памяти расшифровываются непосредственно перед их употреблением, а потом зашифровываются вновь. Так же мы коснемся вопросов восстановления таблицы импорта. В конечном счете, получится нехилый генетический распаковщик, обходящий своих конкурентов.