ida2asm

секреты ассемблирования дизассемблерных листингов

крис касперски, ака мыщъх, no-email

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

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

Значительно реже дизассемблированную программу требуется оттранслировать заново. Например, хочется исправить множественные ошибки разработчиков, нарастить функционал или внести другие изменения… Конечно, все это можно сделать непосредственно в двоичном коде, наложив на программу «заплатку», присобаченную с помощью jump'ов. В большинстве случаев это самый короткий и самый _надежный_ пусть. Нет никаких гарантий, что программа дизассемблирована правильно. Существует по меньшей мере три фундаментальные проблемы дизассемблирования: а) синтаксическая неразличимость смещений от констант; б) неоднозначность соответствия ассемблерных мнемоник машинным командам; в) код ошибочно принятый за данные и данные, ошибочно принятые за код.

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

Давайте создадим _простейшую_ консольную программку типа «hello, world!», откомпилируем ее, а затем дизассемблируем с помощью IDA Pro и попытаемся ассемблировать полученный листинг.

Исходный текст в нашем случае выглядит так:

#include <stdio.h>

main()

{

printf(«hello,world!\n»);

}

Листинг 1 исходный текст программы demo_comsole.c

Компилируем ее компилятором MicrosoftVisualC++ 6.0 с настойками по умолчанию («cl.exe demo_console.c») и загружаем полученный exe-файл в IDA Pro 4.7. Естественно, можно использовать и другие версии продуктов, но тогда результат будет несколько отличаться, что, впрочем, на ход повествования практически никак не повлияет.

Рисунок 1 успешно дизассемблированный файл

Дождавшись завершения дизассемблирования файла (когда экран IDA Pro будет выглядеть приблизительно как показано на рис. 1), попросим ее сгенерировать ассемблированный листинг. Порядочные дизассемблеры поддерживают несколько популярных синтаксисов: TASM, MASM и, учитывая, что IDA Pro недавно была перенесена на Linux, неплохо бы добавить к этому списку еще и AT&T, но… увы! В меню «Options»  «Targetassembler» значиться только какой-то загадочный «GenericforIntel 80×86», не совместимый ни MASM'ом, ни с TASM'ом (во всяком случае не с их последними версиями). В IDA Pro 5.0 в этом отношении сделан огромный шаг вперед и теперь нам предлагают выбор между «GenericforIntel 80×86» и «BorlandTASMinIdealmode» (см. рис. 2).

Рисунок 2 ассемблеры, поддерживаемые IDA Pro 4.7 (слева), и IDA Pro 5.0 (справа)

Очень своевременное решение, особенно в свете того, что TASM давно мертв — не «переваривает» новых инструкций, не обновляется, не поддерживается и официально не распространяется. Borland уже давно забила на этот проект. И хотя есть несколько некоммерческих TASM-совместимых ассемблеров (см. статью «обзор ассемблерных трансляторов») всех проблем они не решают и дизассемблерные листинги транслируются только после существенной переделки, а раз так — лучше остановить свой выбор на пакете MASM, входящим в состав NTDDK.

Решено! Выбираем «GenericforIntel 80×86» и говорим «File»  «Produceoutputfile»  «ProduceASMfile» или просто нажимаем горячую клавишу <Alt-F10>. Даем файлу имя (например, «demo_1.asm») и через несколько минут шуршания диском у нас образуется… нечто по имени ничто.

Скармливаем эту штуку ассемблеру «ml.exe /c demo_1.asm» (версия 6.13.8204) для справки. Транслятор выдает свыше сотни ошибок, после чего прекращает свою работу, не видя никакого смысла ее продолжать (см. рис. 3).

Рисунок 3 результат непосредственной трансляции дизассемблерного листинга

Анализ показывает, что 90% ошибок связаны с неверным определением типа процессора «instructionorregisternotacceptedincurrentCPUmode». Ах, да! По умолчанию IDA Pro выбирает «MetaPC (disassembleall 32-bitopcodes)», но забывает поместить соответствующую директиву в дизассемблерный листинг, а транслятор по умолчанию устанавливает 8086 ЦП, совершенно не совместимый с 32-разрядным режимом.

Материмся, лезем в начало листинга, вставляем директиву «.386», после чего повторяет сеанс трансляции заново. И опять куча ошибок (правда, на этот раз чуть меньше ста, что не может не радовать). Смотрим, что не понравилось транслятору: «demo_1.asm(34):errorA2008:syntaxerror:flat». Хм?! Открываем demo_1.asm, переходим к строке 34 и видим: «model flat». А точка где?! Кто ее будет ставить? Абель что ли? Возвращаем точку на место, заодно добавляя квалификатор языка Си: «.model flat,C» и вновь прогоняем программу через транслятор. На этот раз MASM едет крышей настолько, что выпадает в soft-ice (если тот был предварительно запущен) или выбрасывает знаменитое сообщение о критической ошибке.

Рисунок 4 критическая ошибка при попытке ассемблирования листинга, сгенерированного IDA Pro

Ладно, положим, это ошибка самого транслятора, легко обходимая добавлением волшебного ключика «/coff» к командной строке и следующая попытка трансляции проходит уже без ошибок: «ml.exe /c /coff demo_1.asm». В смысле без _критических_ ошибок самого транслятора, а ошибок в листинге по прежнему предостаточно.

Большинство из них относится к невозможности определения имен библиотечных функций, имен и меток:

demo_1.asm(53) : error A2006: undefined symbol : _printf

demo_1.asm(64) : error A2006: undefined symbol : exit demo_1.asm(285) : error A2006: undefined symbol : _fclose demo_1.asm(297) : error A2006: undefined symbol : _free demo_1.asm(453) : error A2006: undefined symbol : off_403450 demo_1.asm(490) : error A2006: undefined symbol : off_403450 Листинг 2 транслятор не может найти имена библиотечных функций в листинге Черт! Как же мы могли забыть, что хитрая IDA Pro коллапсирует библиотечные функции, стремясь расчистить листинг от бесполезного мусора, не несущего никакой полезной нагрузки. Вернемся к рисунку 1 и сравним его со следующим фрагментом сгенерированного ассемблерного листинга: ; [00000031 BYTES: COLLAPSED FUNCTION _printf. PRESS KEYPAD «+«TO EXPAND] ; [000000D4 BYTES: COLLAPSED FUNCTION start. PRESS KEYPAD »+«TO EXPAND] Листинг 3 сколлапсированные функции остаются сколлапсированными и в ассемблерном листинге! Это же какую ума палату нужно уметь, чтобы допустить такое?! Интересно, тестировался ли ассемблерный генератор вообще или был написан в расчете на авось?! Матерясь, возвращаемся в IDA Pro, в меню «View» выбираем пункт «Unhideall», наблюдая за тем как «раскрываются» библиотечные функции. Генерируем новый ассемблерный файл, на этот раз «demo_2.asm», не забыв вставить в его начало директивы ».386» и «.model flat,C». Повторяем трансляцию. Просматривая протокол ошибок (ну куда же IDA Pro без ошибок) с удивлением обнаруживаем множественные ругательства на неопределенные символы StartupInfo и CPInfo, представляющие собой легко узнаваемые структуры: demo_2.asm(2533) : error A2006: undefined symbol : _STARTUPINFOA demo_2.asm(4276) : error A2006: undefined symbol : _cpinfo Листинг 4 реакция транслятора на отсутствие объявления структур Куда же они могли подеваться?! Открываем ассемблерный листинг в текстовом редакторе и… нет, в русском языке просто не существует подходящих слов, чтобы адекватно выразить наше состояние: ; [00000012 BYTES. COLLAPSED STRUCT _cpinfo. PRESS KEYPAD «+» TO EXPAND] ; [00000044 BYTES. COLLAPSED STRUCT _STARTUPINFOA. PRESS KEYPAD«+» TO EXPAND] Листинг 5 сколлапсированные структуры в ассемблерном файле Ассемблерный генератор IDA Pro поместил структуры в целевой файл, даже не удосужившись их автоматически развернуть! Что же, придется это сделать самостоятельно. Возвращаемся в IDA Pro, в меню «View» находим пункт «Open Subview», а там — «Structures» или просто жмем горячую клавишу <Shift-F9>. Перед нами появляется окно с перечнем всех структур и для их разворота достаточно дать команду «View»  «Unhide all», после чего можно повторить генерацию ассемблерного файла, назвав его «demo_3.asm» (про директивы .386/.model flat мы не забываем, да?). Поразительно, но количество ошибок трансляции совсем не уменьшается, а даже возрастает. И ассемблер по прежнему не может найти «развернутые» структуры. Что же ему мешает? Присмотревшись к логу ошибок повнимательнее, мы видим, что ругательству на неопределенный символ предшествует ошибка типа «operandmustbeamemoryexpression» (операнд должен быть выражением, адресующим память): demo_3.asm(2561) : error A2027: operand must be a memory expression demo_3.asm(2596) : error A2006: undefined symbol : StartupInfo demo_3.asm(2599) : error A2006: undefined symbol : StartupInfo demo_3.asm(2601) : error A2006: undefined symbol : StartupInfo Листинг 6 транслятор по прежнему не может определить развернутые структуры Открываем ассемблерный файл в редакторе, переходим к строке 2561 и видим следующую картину маслом: ioinitproc near; CODE XREF: start+6Fp

StartupInfo= _STARTUPINFOAptr -44h

cmp[esp+54h+StartupInfo.cbReserved2], 0

jzloc_4022E6

moveax, [esp+54h+StartupInfo.lpReserved2]

Листинг 7 камень преткновения всех структур

Мыщъх не уверен на счет «GenericforIntel 80×86», но транслятор MASM, начиная с версии >5.1, такого способа объявлений структур уже не поддерживает, и чтобы откомпилировать программу у нас есть по меньшей мере два пути: разрушить все структуры на хрен (все равно в ассемблерном листинге они нам несильно понадобятся), либо же использовать ключ командной строки /Zm, обеспечивающим обратную совместимость с MASM 5.1. Вот так, наверное, мы и поступим: «ml.exe /c /coff /Zmdemo_3.asm».

Количество ошибок сразу же уменьшается чуть ли не в три раза и они свободно помешаются на экран, что не может не радовать!

Рисунок 5 трансляция ассемблерного листинга в режиме совместимости с MASM 5.1

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

Переходим к строке 141 и видим:

moveax, large fs:0

pusheax

movlarge fs:0, esp

Листинг 8 здесь возникает ошибка типа «отсутствующий оператор»

Ну и зачем ассемблерному генератору было вставлять «large»? Все равно MASM его не понимает и отродясь не понимал. Находясь во интегрированном редакторе FAR'а, нажимаем <Ctrl-F7> (replace) и заменяем все «largefs» на просто «fs» (см. рис. 6)

Рисунок 6 автоматическая замена всех «largefs» на «fs» в FAR'e

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

demo_3.asm(70) : error A2015: segment attributes cannot change : Alignment

demo_3.asm(8063) : error A2189: invalid combination with segment alignment : 2048

demo_3.asm(12004) : error A2015: segment attributes cannot change : Alignment

demo_3.asm(13742) : error A2005: symbol redefinition : cchMultiByte

demo_3.asm(14176) : error A2005: symbol redefinition : Filename

demo_3.asm(14200) : error A2005: symbol redefinition : Locale

demo_3.asm(14215) : error A2005: symbol redefinition : CodePage

demo_3.asm(142) : error A2206: missing operator in expression

demo_3.asm(2860) : error A2206: missing operator in expression

demo_3.asm(2888) : error A2206: missing operator in expression

demo_3.asm(2924) : error A2006: undefined symbol : loc_402480

demo_3.asm(3639) : error A2001: immediate operand not allowed

demo_3.asm(4158) : error A2006: undefined symbol : loc_402D11

demo_3.asm(1257) : error A2006: undefined symbol : $NORMAL_STATE$1535

demo_3.asm(1258) : error A2006: undefined symbol : loc_4012AA

demo_3.asm(1259) : error A2006: undefined symbol : loc_4012C5

demo_3.asm(1260) : error A2006: undefined symbol : loc_401311

demo_3.asm(1261) : error A2006: undefined symbol : loc_401348

demo_3.asm(1262) : error A2006: undefined symbol : loc_401350

demo_3.asm(1263) : error A2006: undefined symbol : loc_401385

demo_3.asm(1264) : error A2006: undefined symbol : loc_401418

Листинг 9 перечень ошибок, выявленных ассемблером при очередном сеансе трансляции

Беглый взгляд на листинг обнаруживает целый каскад ошибок типа «undefinedsymbol» (неопределенный символ). Так, посмотрим, что же у нас не определено на этот раз. Переходим к строке 1257, за которой тянется целый хвост ошибок в строках 1258, 1259, 1260, 1261, 1262, 1263 и 1264. Это настоящее осиное гнездо! Обитель зла, которую мыщъх собирается разбить одним взмахом хвоста:

1257:off_401956 dd offset $NORMAL_STATE$1535

1258:dd offset loc_4012AA

1259:dd offset loc_4012C5

1260:dd offset loc_401311

Листинг 10 очередная обитель зла на подступах к успешной трансляции

Хм, выглядит вполне обычно и _все_ метки без исключения обнаруживаются простым контекстным поиском:

$NORMAL_STATE$1535:movecx, dword_406428

loc_4012AA:or[ebp+var_10], 0FFFFFFFFh

loc_4012C5:movsxeax, bl

Листинг 11 «потерянные метки» легко обнаруживаются контекстным поиском

Почему же тогда ассемблерный транслятор их ни хрена не видит?! Все дело в том, что IDA Pro неверно определила границы функции, поместив обращения к меткам _за_ границы функции в которой они упоминаются!!! А метки вообще-то локальны. Вот потому-то транслятор их и не находит!

$NORMAL_STATE$1535:

loc_4012AA:

loc_4012C5:

loc_401311:

outputendp ; конецфункции ; ─────────────────────────────────────────────────────────────────────────── off_401956dd offset $NORMAL_STATE$1535 dd offset loc_4012AA dd offset loc_4012C5 dd offset loc_401311 Листинг 12 IDA Pro поместила обращения к меткам после конца функции, удалив их из границ видимости транслятора Чтобы исправить ситуацию, необходимо переместить директиву «output endp» _за_ конец обращений к меткам. Так, чтобы они стали частью функции output. После чего ассемблерный код будет выглядеть так: off_401956dd offset $NORMAL_STATE$1535 dd offset loc_4012AA dd offset loc_4012C5 outputendp Листинг 13 исправленный вариант, позволяющий транслятору обнаружить «недостающие метки» После ассемблирования количество ошибок тает буквально на глазах и мы даже в порыве вдохновения едва удерживаемся от того, чтобы не закурить новый косяк: demo_3.asm(70) : error A2015: segment attributes cannot change : Alignment demo_3.asm(8064) : error A2189: invalid combination with segment alignment : 2048 demo_3.asm(12005) : error A2015: segment attributes cannot change : Alignment demo_3.asm(13743) : error A2005: symbol redefinition : cchMultiByte demo_3.asm(14177) : error A2005: symbol redefinition : Filename demo_3.asm(14201) : error A2005: symbol redefinition : Locale demo_3.asm(14216) : error A2005: symbol redefinition : CodePage demo_3.asm(2861) : error A2206: missing operator in expression demo_3.asm(2889) : error A2206: missing operator in expression demo_3.asm(2925) : error A2006: undefined symbol : loc_402480 demo_3.asm(3640) : error A2001: immediate operand not allowed demo_3.asm(4159) : error A2006: undefined symbol : loc_402D11 Листинг 14 список ошибок, обнаруженных ассемблером после очередного сеанса трансляции (все ближе, ближе долгожданный миг победы!) В глаза бросается пара уже известных нам ошибок типа «undefinedsymbol», первую из которых исправить достаточно легко: NLG_Notify1: pushebx pushecx movebx, offset unk_406364 jmpshort loc_402480 ; ███████████████ S U B R O U T I N E ███████████████████████████████████████ NLG_Notifyproc near pushebx pushecx movebx, offset unk_406364 movecx, [ebp+8] loc_402480: mov[ebx+8], ecx mov[ebx+4], eax mov[ebx+0Ch], ebp NLG_Dispatch: popecx popebx retn4 NLG_Notifyendp Листинг 15 оригинальный код, сгенерированный IDA Pro, который не хочет транслироваться Достаточно «завести» подпрограмму NLG_Notify1 под «retn 4» процедуры NLG_Notyfy, но перед директивой NLG_Notify endp, тогда она метка будет распознаваться как надо! NLG_Notifyproc near pushebx pushecx movebx, offset unk_406364 movecx, [ebp+8] loc_402480: mov[ebx+8], ecx mov[ebx+4], eax mov[ebx+0Ch], ebp NLG_Dispatch: popecx popebx retn4 NLG_Notify1:

pushebx

pushecx

movebx, offset unk_406364

jmpshort loc_402480

NLG_Notifyendp Листинг 16 исправленный вариант А вот со следующей ошибкой справиться уже сложение, поскольку функция strcpy совершает прыжок в середину функции strcat: _strcpyproc near arg_0= dword ptr 8 pushedi movedi, [esp+arg_0] jmploc_402D11 _strcpyendp _strcatproc near arg_0= dword ptr 4 arg_4= dword ptr 8 movecx, [esp+arg_0] … loc_402D11: movecx, [esp+4+arg_4] testecx, 3 jzloc_402D36 … retn _strcatendp Листинг 17 IDA Pro сгенерировала неработоспособный листинг для парной функции strcpy/strcat Никаким ухищрениями у нас не получится перетасовать код так, чтобы метка loc_402D11 оказалась в границах видимости, но… ведь как то же это было запрограммировано?! Обратившись к исходным текстам библиотеки LIBC.LIB (они поставляются вместе с компилятором) мы обнаружим волшебный ключик. Чтобы метка была видна отовсюду, после нее должен стоят не один знак «:», а целых два — «::». Самая трудная задача осталась позади и теперь нам предстоит разобраться с уже встречавшимися ошибками типа «missingoperatorinexpression». На этот раз транслятору не понравились конструкции «push large dword ptr fs:0» и «pop large dword ptr fs:0». Убираем все лишнее, превращая их в «push fs:0» и «pop fs:0» и движемся дальше, где нас ждет ошибка «immediateoperandnotallowed» (непосредственный операнд недозволен), затаившаяся в 3640 строке: «cmp Locale,0». Естественно, транслятор решил трактовать Locale как смещение, а не как содержимое ячейки, поэтому без явной расстановки квадратных скобок здесь не обойтись: «cmp dword ptr ds:[Locale],0». Теперь на линии фронта остается лишь большой конгломерат ошибок типа «symbolredefinition» (символ переопределен), против которых не пропрешь, ведь он действительно переопределен, вот например, взять тот же cchMultiByte: _crtLCMapStringA procnear; CODE XREF: _setSBUpLow+BEp

; _setSBUpLow+E6p

Locale= dwordptr 8

lpMultiByteStr= dwordptr 10h

cchMultiByte= dwordptr 14h

___crtLCMapStringA endp

cchMultiBytedd 1; DATA XREF: _wctomb+31r

Листинг 18 дважды определенный символ Locale

Ничего не остается как «расщеплять» переменные вручную, давая им различные имена. Главное — не перепутать переменные местами. Впрочем, перепутать будет довольно трудно, поскольку, одна копия переменной — локальная и адресуется через стек, а другая — глобальная и обращение с ней происходит через непосредственную адресацию.

Разобравшись с астральными переменными, нам остается только побороть три ошибки, связанные с выравниванием. Ну, ошибку в строке 8064 мы ликвидируем путем удаления директивы «align 800h» (800h в десятичном представлении как раз и будет 2048). Две остальные ошибки требуют переименования сегментов _text и _data во что-нибудь другое, например, _text1 и _data1, только это переименование должно иди по всему тексту.

Все! Теперь ассемблерный листинг, сгенерированный дизассемблером, и «слегка» исправленный напильником, транслируется без ошибок! Добавим к командой строке MASM'а ключ «/Cp», чтобы он соблюдал регистр публичных имен и….

…и вот тут-то выясняется, что полученный obj наотрез отказывается линковаться, потому что линкер не может найти API-функции! Это не покажется удивительным, если вспоминать, что IDA Pro объявила их в «удобочитаемом» виде, который совсем не совпадет с тем, как они объявлены в библиотеках. Но линковка (и последующая доводка программы до ума) — это уже тема совсем другого разговора, а, может быть, и целой статьи.