Издательский дом ООО "Гейм Лэнд"ЖУРНАЛ ХАКЕР 121, ЯНВАРЬ 2009 г.

Энциклопедия антиотладочных приемов. Сокрытие кода в хвостах секций

Крис Касперски




Вовлеченный в проект по созданию интерактивного распаковщика PE-файлов, я исследую особенности реализации системного загрузчика Win32 на предмет следования своим же спецификациям. Обилие багов, часть которых носит довольно коварный характер, просто поражает. Итак, сегодня мы продемонстрируем прием, позволяющий прятать код/данные от популярных дизассемблеров типа IDA-Pro.

Исполняемые файлы, динамические библиотеки и драйвера — все они «крышуются» одним и тем же форматом, проходящим под кодовым названием Potable Executable (или, сокращенно, PE). Спецификации на него доступны всем желающим. Для разработчиков линкеров, отладчиков, дизассемблеров и прочего «фирменного» инструментария спецификации — это Коран (Библия, Тора — нужное подчеркнуть). Вот только живая операционная система спецификаций не придерживается и местами ведет себя совсем не так, как это следует из документации.

Создатели вирусов ведут масштабные раскопки ntoskrnl.exe, отыскивая различия реализаций системного загрузчика и дизассемблеров/отладчиков/антивирусов. Конечно, никаких гарантий, что найденная багофича сохранится в последующих версиях Windows (не говоря уже за эмуляторы типа wine) у нас нет, но если действовать с умом, то можно писать не только надежные вирусы и коммерческие приложения.

А сейчас на мгновение (грозящее растянуться в минуты или даже часы) оторвемся от статьи и попробуем взломать относительно несложный crackme (kpnc.org/ftp/KedaH3.zip), используя любой инструмент по вкусу (IDA-Pro, OllyDbg, HIEW, etc). После чего можно продолжить чтение, ведь когда решение известно — ломать становится неинтересно.

Ликбез

PE-файлы грузятся довольно хитрым образом, проецируя «сырые» (raw) дисковые данные в виртуальную память. В результате каждая секция имеет два набора атрибутов: один описывает образ секции на диске, другой — ее проекцию в памяти. Физический (physical) размер секции на диске может отличаться от виртуального (virtual). Если виртуальный размер больше физического, система автоматически инициализирует «хвост» секции нулями, о чем осведомлены линкеры и компиляторы, генерирующие более компактные PE-файлы, – что является общепринятой нормой и хорошим тоном. Обратная ситуация (когда виртуальный размер меньше физического) встречается намного реже, но все-таки встречается, когда приложение хочет прицепить оверлей или другие данные, с которыми предполагается работать посредством прямого дискового ввода/вывода без загрузки в память.

На самом деле, спецификация не гарантирует, что остаток (Virtual Size - Physical Size) останется на диске. Никто ведь не запрещает грузить «хвост» секции в память, особенно если он укладывается в гранулярность выравнивания, определенную в PE-заголовке. Допустим, физический размер секции составляет 10h байт, виртуальный — 100h, а выравнивание на диске/в памяти — 1000h. Система, очевидно, не может прочитать 10h байт с диска. Вместо этого она читает всю страницу целиком (именно страницу, а не сектор, поскольку проецирование PE-файлов осуществляется на уровне страниц). Теоретически, система могла бы очистить хвост секции, забив его нулями и сохранив только первые 10h байт физического размера, но... к чему все эти телодвижения? Спецификация ведь не обязывает, а потому хвост секции исправно грузится в память.

А вот дизассемблеры ведут себя иначе. Слепо следуя спецификации, они грузят только 10h байт. Остальные байты либо вообще не загружаются, либо превращаются в нули. Это открывает огромные перспективы для сокрытия кода, который не предполагается видеть реверсерам и антивирусам. Во всяком случае, на этот трюк ловится IDA-Pro (вплоть до версии 5.3 – самой последней на момент написания этих строк), HIEW, DUMPBIN и куча других.

В поисках лома

«Против лома — нет приема», говорят одни, а другие ехидно добавляют – «если нет другого лома». Но в нашем случае все еще более позитивно. У нас есть прием (против дизассемблеров), но нет подходящего лома. Какой инструмент ни возьми — сплошной облом. Во всяком случае, в автоматическом режиме с которого, собственно говоря, мы и начнем.

А начнем мы с проверки работоспособности программы, запущенной в живой системе без всяких левых отладчиков. Как и следовало ожидать, файл запускается нормально (тестировался под W2K, S2K3 и XP), выводя на экран мессадж-бокс с заголовком «.no pain -- no gain.» и лозунгом: «condom-principle: it'd rather have one and not need it that need it and not have one».

Ну, condom нам в ближайшие несколько часов не понадобится, а вот дизассемблер – очень даже. Никакого риска тут нет, поскольку crack-me представляет собой тривиальный вызов MessageBoxA.

Берем HIEW — простой как топор; грузим файл, привычным движением руки переключаемся в HEX-mode (<ENTER>) и переходим в точку входа <F8> (Header), <F5> (Entry). Опс... Не переходится! То есть, вообще никуда не переходится. Там, где стояли — там и остались. Причем, точка входа смотрит по вполне легальному адресу 401010h, расположенному в 10h байтах от начала секции .text, что видно из заголовка, вызываемого нажатием <F8>.

Ладно! Не хочет работать HIEW и не надо! Вызываем «тяжелую артиллерию» – ИДУ. И со всего маху фэйсом оп тэйбл: «The input file contains non-empty TLS (Thread Local Storage) callback table. However, IDA Pro couldn't find the TLS callback procedures in the loaded code» — «Анализируемый файл содержит не пустую таблицу TLS-callback'ов, однако IDA Pro обломалась с поиском TLS-callback'ов в загруженном файле».

Покорно жмем «ОК», чтобы закрыть противное диалоговое окно, и получаем пустой дизассемблерный листинг.

Так выглядит KedaH3.exe после загрузки в IDA-Pro – из всех инструкций одна RETN, текстовых строк — нет

.text:00401000 _text segment para public 'CODE' use32
.text:00401000 assume cs:_text
.text:00401000 ;org 401000h
.text:00401000 assume es:nothing, ss:nothing, ds:_data, fs:nothing, gs:nothing
.text:00401000 retn
.text:00401000
.text:00401001 dd 3 dup(?)
.text:0040100D db 3 dup(?)
.text:00401010 public start
.text:00401010 start dd 8 dup(?)
.text:00401010 _text ends

Листинг – не то, чтобы совсем пустой, но одинокий RET, к тому же расположенный перед точкой входа, нас как-то не возбуждает. Где MessageBoxA? Где наша текстовая строка? Нету! Даже TLS Callback'ов и тех не хватает, о чем, впрочем, нас уже предупреждали.

Обреченно загружаем KedaH3.exe в Ольгу, и... отладчик тупо виснет. Оправившись после шока, замечаем, что виснет не сам отладчик, а отлаживаемый процесс, вызывая 100% загрузку ЦП. В переводе на русский — один хрен разница. Практически все пункты меню выделены серым (то есть, недоступны), окно дизассемблера и дампа выглядит абсолютно не кошерно, и вообще, непонятно, как с этим жить и что теперь делать.

Выходит, что KedaH3.exe (состоящий всего из двух дюжин ассемблерных инструкций) активно противостоит всем основным хакерским орудиям — отладчикам, дизассемблерам и дамперам, но работает под любой версией Windows.

Новые загадки

Возвращаемся к HIEW'у. Загружаем файл, просматривая его содержимое в HEX-виде безо всякой автоматизации. Даже если системные структуры искажены и/или разрушены строго дозированным образом, актуальный код/данные никаким образом не смогут спрятаться от HEX-редактора. И действительно. Перед нами пробегают машинные инструкции (мы же ведь умеем дизассемблировать код в уме, правда?) и даже текстовые строки, хранящиеся открытым текстом без всякой шифровки.

Перемещаемся курсом туда, где по нашему мнению должен быть код (по адресу 401000h) и нажимаем <ENTER> для перехода в дизассемблерный режим. Видим что-то странное, причем после первой же команды (RETN) трансляция виртуальных адресов «слетает», и HIEW отображает «сырые» смещения внутри файла.

Но это мелочи. Главное, что HIEW заработал, а остальное, как говорится, дело техники. Вот только техника эта на уровне начала девяностых и дизассемблировать запутанный crack-me в HIEW совершенно не кошерно. Сколько хакера HIEW'ом не корми, он все равно ИДУ хочет.

ОК, ИДА, так ИДА. Загружаем файл, как и прежде. Теперь, прежде чем со всей дури долбануть по ENTER'у, взведем «магическую» галочку «Manual Load», расположенную в диалоговом окне «Load File of New Format», утвердительно отвечая на все последующие запросы. А запросы будут, только успевай!

Вплоть до версии IDA-Pro 5.2 включительно этот трюк мало чем помогал, но начиная с 5.3, Ильфак исправил ошибку, и мы видим вполне вменяемый код, выводящий на экран диалоговое окно с обозначенными текстовыми строками.

Дизассемблерный листинг KedaH3.exe, сгенерированный IDA-Pro 5.3 при загрузке файла в ручном режиме

00401010 public start
00401010 start proc near
00401010 dec eax
00401011 retn
00401011 start endp
00401011
00401012 loc_401012: ; CODE XREF: .text:00401049vj
00401012 push offset a_noPainNoGain_ ; " .no pain -- no gain. "
00401017 push offset unk_403018
0040101C push 0
0040101E call ds:MessageBoxA
00401024 xor eax, eax
00401026 mov eax, [eax]
00401028 db 65h
00401028 jp short near ptr dword_4010A0
0040102B insd
00401030
00401030 public TlsCallback_0
00401030 TlsCallback_0: ; CODE XREF: .text:0040103Bvj
00401030 mov esi, esp
00401032 lodsd
00401033 lodsd
00401034 mov al, 10h
00401036 mov ah, al
00401038 xchg eax, esi
00401039 lodsb
0040103A dec eax
0040103B jnp short TlsCallback_0
0040103D xor eax, eax
0040103F push eax
00401040 mov al, 30h
00401042 mov esi, fs:[eax]
00401045 inc esi
00401046 dec byte ptr [esi+1]
00401049 jnp short loc_401012
0040104B xor eax, eax
0040104D mov eax, [eax]
0040104D ; ------------------------------------------------------------------
00403000 a_noPainNoGain_ db ' .no pain -- no gain. ',0 ; DATA XREF: loc_401012^o
00403018 aCondomPrincipl db 0Ah ; DATA XREF: .text:00401017^o
00403018 db 'condom-principle',0Ah, 0Ah
00403018 db 9,'it',27h,'d rather have one and not need it... '
00403071 TlsIndx db 'http://kpnc.org',0 ; DATA XREF: .data:TlsIndex_ptrvo
00403090 TlsDirectory dd offset TlsDirectory ; DATA XREF: .data:TlsDirectoryvo
00403094 TlsEnd_ptr dd offset TlsDirectory
00403098 TlsIndex_ptr dd offset TlsIndex ; "http://kpnc.org"
0040309C TlsCallbacks_ptr dd offset TlsSizeOfZeroFill
004030A0 TlsSizeOfZeroFill dd offset TlsCallback_0
004030A4 TlsCharacteristics dd 0

Но вот через какую ж... гм… он их выводит — загадка. Точка входа смотрит в DEC EAX/RET, что приводит к немедленному завершению программы. Впрочем, мы хакеры опытные, нас на мякине не проведешь. Если ты читал предыдущие выпуски «антиотладки» (ведь ты же их читал, верно?), то TLS-callback для нас не ругательство, а способ перехвата управления до исполнения точки входа, и такой TLS-callback в нашем crack-me действительно есть!

И перекрестная ссылка из процедуры, выводящей диалоговое окно, также имеется. Не нужно анализировать код, чтобы с вероятностью близкой к единице предположить, что инструкция 00401049 jnp short loc_401012 «перепрыгивает» оригинальную точку входа, передавая управление на процедуру вывода строки.
Правда, следом за CALL ds:MessageBoxA идет совершенно невменяемая последовательность XOR EAX, EAX/MOV EAX, [EAX], обращающаяся к памяти по нулевому указателю. Это должно приводить к выбросу исключения, а поскольку никаких SEH-обработчиков (за исключением системного) у нас нет, приложение обязано завершать свою работу в аварийном режиме с классическим ругательством в стиле «...совершила недопустимую операцию». В действительности ничего подобного не происходит! Почему?

Дизассемблер не дает нам ответа и приходится прибегать к помощи отладчика. Например, той же Ольги. А Ольга не работает, так?

Не совсем. Отлаживаемый процесс зациклен — это факт. Но кнопка «PAUSE» по-прежнему активна и мы можем остановить процесс. Естественно, после остановки отлаживать уже нечего, но если установить аппаратный бряк на TLS_callback и перезапустить crack-me, Ольга вернет нам бразды правления еще до того, как успеет отработать защита, распознающая присутствие отладчика (во всяком случае, мы на это очень сильно надеемся).

Сказано — сделано. Загружаем KedaH3.exe в Ольгу, нажимаем <F12> (Pause), переходим к точке входа в TLS-Callback – <Ctrl-G> (Goto), «401030» (адрес TLS-Callback'а нам подсказала IDA). Щелкаем правой клавишей мыши по строке «401030» и в появившемся контекстном меню выбираем «Breakpoint», а там — «Hardware, on execution». Лезем в Debug -> Hardware breakpoints, дабы убедиться, что новый бряк действительно установлен, после чего давим <CTRL-F2> (Restart), подтверждаем серьезность наших намерений кнопкой «Yes» – и в следующее мгновение Ольга послушно останавливается в самом начале TLS-Callback'а, передавая нам бразды правления!

Аналогичным образом укрощается и IDA-Pro Debugger (только начиная с версии 5.3). После ручной загрузки файла в память жмем <CTRL-E> и в списке точек входа выбираем TlsCallback_0, устанавливаем точку входа нажатием <F2> и запускаем процесс по <F9>. Все! С этого момента программу можно трассировать, как ни в чем не бывало. Как будто никаких антиотладочных трюков тут и нет. Диалоговое окно, впрочем, на экране так и не появится (облом, да?), так что чудеса только начинаются!

Домашнее задание

Подведем итоги. Мы получили дизассемблерный листинг защищенной программы и смогли загрузить ее в отладчик. Самая сложная (и непонятная) часть работы позади. Остается выяснить:

  1. Как именно защита распознает наличие отладчика
  2. Почему XOR EAX, EAX/MOV EAX, [EAX] работает как RET
  3. Какие именно структуры PE-файла ответственны за сокрытие кода/данных

А для самых наблюдательных бонус — в конце строки «condom-principle...» расположен адрес сервера «http://kpnc.org», не отображаемый на экране, но и не отделенный от строки завершающим нулем. Вопрос: почему функция MessageBoxA игнорирует URL? Это что, документированное поведение такое, еще один баг в Windows или...

Следующий выпуск «антиотладки» ответит на все эти вопросы, а пока пусть они будут домашней работой. Одно дело – читать статью, удобно устроившись на топчане, и совсем другое — работать мозгами, пытаясь взломать crack-me собственными руками.

WWW

Microsoft Portable Executable and Common Object File Format Specification: microsoft.com/whdc/system/platform/firmware/PECOFF.mspx.
Двоичный файл KedaH3 Crack Me: kpnc.org/ftp/KedaH3.zip.

Содержание
ttfb: 102.81181335449 ms