Различия

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

Ссылка на это сравнение

articles:genetic-18 [2017/09/05 02:55] (текущий)
Строка 1: Строка 1:
 +====== genetic-18 ======
 +<​sub>​{{genetic-18.odt|Original file}}</​sub>​
 +
 +====== генетический распаковщик своими руками ======
 +
 +крискасперскиаргентинскийболотныйбобер nezumi el raton аканутрякибнмыщъх,​ no-email.
 +
 +**прежде чем ломать,​ хакер берет программу и смотрит упакована она или нет и тут же бежит искать адекватный распаковщик (поскольку большинство защищенных программ упаковано),​ к сожалению,​ далеко не для всех упаковщиков/​проекторов существуют готовые распаковщики… обобщив свой опыт борьбы с защитами,​ мыщъх предлагает алгоритм универсального распаковщика,​ пробивающего 99% защит**
 +
 +===== введение =====
 +
 +Как хитро я вас обманул! Ни о генах, ни о хромосомах здесь речь не идет. Искусственный интеллект отдыхает на задворках истории! "​genetic"​ в переводе с английского означает "​общий"​. Генетический распаковщик — универсальный распаковщик,​ общий для всех упаковщиков/​протекторов. Кстати говоря,​ "​GeneralMotors"​ это не "​двигатели для генералов",​ а двигатели вообще. Почувствуйте разницу!
 +
 +===== в поисках OEP =====
 +
 +Создание универсального распаковщика начинается с алгоритма определения OEP (OriginalEntryPoint — Исходная Точка Входа),​ отслеживающего момент завершения распаковки с последующей передачей управления "​программе-носителю"​. Это самая сложная часть генетических распаковщиков,​ поскольку определить исходную точку входа в общем случае невозможно,​ вот и приходится прибегать к различным ухищрениям. Чаще всего для этого используется пошаговая трассировка,​ которой упаковщик/​протектор может легко противостоять (и ведь противостоит!).
 +
 +Немногим лучше с задачей справляются трассеры нулевого кольца. Справиться с ними с прикладного уровня (а большинство упаковщиков/​протекторов) работают именно на нем, практически невозможно,​ однако,​ разработка подобного трассера зачастую оказывается непосильной задачей для начинающих и хотя в распоряжении автора имеются исходные тексты великолепного трассера,​ разработанного группой Володи с WASM'​а,​ мыщъх решил сходить другим путем, ограничившись только аппаратными точками останова,​ для установки которых прибегать к написанию драйвера совершенно необязательно. В "​записках мыщъха"​ (электронную копию которой можно свободно утянуть с ftp://​nezumi.org,​.ru) показано как это сделать и с прикладного уровня,​ даже без прав администратора!
 +
 +На первом этапе в качестве основного экспериментального средства мы будем использовать "​Блокнот",​ пожатый различными упаковщиками и знаменитый отладчик soft-ice. Кодирование последует потом. Чтобы писать красиво и по сто раз не переписывать уже написанное и отлаженное,​ необходимо иметь нехилый боевой опыт, для которого нам и понадобиться soft-ice.
 +
 +**дамп живой программы**
 +
 +Самый простой (и самый популярный) способ борьбы с упаковщиками — снятие дампа заведомо после завершения распаковки. Дождавшись появления главного окна программы,​ хакер сбрасывает ее дамп, преобразуя его в исполняемый файл. Иногда он работает,​ иногда нет. Попробуем разобраться почему. Возьмем классическое приложение "​Блокнот"​ из поставки NT (которое в защищенности не обвинишь) и, //**не упаковывая его никакими упаковщиками**//,​ попробуем снять дамп с помощью одного из двух лучших дамперов Proc Dump или Lord PE Deluxe (см. рис. 1).
 +
 +{{genetic-18_Image_0.png?​553}}
 +
 +Рисунок 1 снятие дампа с работающего "​Блокнота"​
 +
 +Процесс,​ проходит успешно,​ и образовавшийся файл как бы даже запускается (см. рис 2),​ но… оказывается не вполне работоспособен! Исчез заголовок окна и все текстовые сообщения в диалогах! Если мы не сумели снять сдампить Блокнот,​ то с настоящими защитами нам и вовсе не справится. Давайте попробуем разобраться почему!
 +
 +{{genetic-18_Image_1.png?​553}}
 +
 +Рисунок 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
 +
 +**0100483****Ah********call********ebp**** ; ****LoadStringW****;​ считываем очередную строку из ресурса**
 +
 +0100483Chmovecx,​ [edi]; грузим тот же самый uID в ecx
 +
 +0100483Ehinceax;​ увеличиваем длину считанной строки на 1
 +
 +0100483Fhcmpeax,​ ebx; ?​строка влезает в буфер?
 +
 +**01004841****h********mov****[****ecx****],​ ****esi****;​ сохраняем указатель на буфер поверх**
 +
 +**01004841****h****;​ старого ****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
 +
 +**010103****BA********PUSH****1006420;​ ******** передача управления на ****OEP**
 +
 +**010103****BF********RET**
 +
 +Листинг 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 коварно проскакивает точку входа, полностью утрачивая управление и контроль.
 +
 +{{genetic-18_Image_2.png?​553}}
 +
 +Рисунок 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 — это когда вся программа зашифрована и отдельны страницы памяти расшифровываются непосредственно перед их употреблением,​ а потом зашифровываются вновь. Так же мы коснемся вопросов восстановления таблицы импорта. В конечном счете, получится нехилый генетический распаковщик,​ обходящий своих конкурентов.
 +
 +