исходный текст программы demo_comsole.c
Компилируем ее компилятором Microsoft Visual C++ 6.0 с настойками по умолчанию ("cl.exe demo_console.c") и загружаем полученный exe-файл в IDA Pro 4.7. Естественно, можно использовать и другие версии продуктов, но тогда результат будет несколько отличаться, что, впрочем, на ход повествования практически никак не повлияет.
транслятор не может найти имена библиотечных функций в листинге
Черт! Как же мы могли забыть, что хитрая IDA Pro коллапсирует библиотечные функции, стремясь расчистить листинг от бесполезного мусора, не несущего никакой полезной нагрузки. Вернемся к рисунку 1 и сравним его со следующим фрагментом сгенерированного ассемблерного листинга:
; [00000031 BYTES: COLLAPSED FUNCTION _printf. PRESS KEYPAD "+" TO EXPAND]
; [000000D4 BYTES: COLLAPSED FUNCTION start. PRESS KEYPAD "+" TO EXPAND]
сколлапсированные функции остаются сколлапсированными и в ассемблерном листинге!
Это же какую ума палату нужно уметь, чтобы допустить такое?! Интересно, тестировался ли ассемблерный генератор вообще или был написан в расчете на авось?! Матерясь, возвращаемся в IDA Pro, в меню "View" выбираем пункт "Unhide all", наблюдая за тем как "раскрываются" библиотечные функции.
Генерируем новый ассемблерный файл, на этот раз "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
реакция транслятора на отсутствие объявления структур
Куда же они могли подеваться?! Открываем ассемблерный листинг в текстовом редакторе и… нет, в русском языке просто не существует подходящих слов, чтобы адекватно выразить наше состояние:
; [00000012 BYTES. COLLAPSED STRUCT _cpinfo. PRESS KEYPAD "+" TO EXPAND]
; [00000044 BYTES. COLLAPSED STRUCT _STARTUPINFOA. PRESS KEYPAD "+" TO EXPAND]
сколлапсированные структуры в ассемблерном файле
Ассемблерный генератор IDA Pro поместил структуры в целевой файл, даже не удосужившись их автоматически развернуть! Что же, придется это сделать самостоятельно. Возвращаемся в IDA Pro, в меню "View" находим пункт "Open Subview", а там — "Structures" или просто жмем горячую клавишу <Shift-F9>
. Перед нами появляется окно с перечнем всех структур и для их разворота достаточно дать команду "View" à "Unhide all", после чего можно повторить генерацию ассемблерного файла, назвав его "demo_3.asm" (про директивы .386/.model flat мы не забываем, да?).
Поразительно, но количество ошибок трансляции совсем не уменьшается, а даже возрастает. И ассемблер по прежнему не может найти "развернутые" структуры. Что же ему мешает? Присмотревшись к логу ошибок повнимательнее, мы видим, что ругательству на неопределенный символ предшествует ошибка типа "operand must be a memory expression" (операнд должен быть выражением, адресующим память):
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
транслятор по прежнему не может определить развернутые структуры
Открываем ассемблерный файл в редакторе, переходим к строке 2561 и видим следующую картину маслом:
__ioinit proc near ; CODE XREF: start+6Fp
StartupInfo = _STARTUPINFOA ptr -44h
…
cmp [esp+54h+StartupInfo.cbReserved2], 0
jz loc_4022E6
mov eax, [esp+54h+StartupInfo.lpReserved2]
камень преткновения всех структур
Мыщъх не уверен на счет "Generic for Intel 80x86", но транслятор MASM, начиная с версии >
5.1, такого способа объявлений структур уже не поддерживает, и чтобы откомпилировать программу у нас есть по меньшей мере два пути: разрушить все структуры на хрен (все равно в ассемблерном листинге они нам несильно понадобятся), либо же использовать ключ командной строки /Zm, обеспечивающим обратную совместимость с MASM 5.1. Вот так, наверное, мы и поступим: "ml.exe /c /coff /Zm demo_3.asm".
Количество ошибок сразу же уменьшается чуть ли не в три раза и они свободно помешаются на экран, что не может не радовать!
здесь возникает ошибка типа "отсутствующий оператор"
Ну и зачем ассемблерному генератору было вставлять "large"? Все равно MASM его не понимает и отродясь не понимал. Находясь во интегрированном редакторе FAR'а, нажимаем <Ctrl-F7>
(replace) и заменяем все "large fs" на просто "fs" (см. рис. 6)
перечень ошибок, выявленных ассемблером при очередном сеансе трансляции
Беглый взгляд на листинг обнаруживает целый каскад ошибок типа "undefined symbol" (неопределенный символ). Так, посмотрим, что же у нас не определено на этот раз. Переходим к строке 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
очередная обитель зла на подступах к успешной трансляции
Хм, выглядит вполне обычно и _все_ метки без исключения обнаруживаются простым контекстным поиском:
$NORMAL_STATE$1535: mov ecx, dword_406428
…
loc_4012AA: or [ebp+var_10], 0FFFFFFFFh
…
loc_4012C5: movsx eax, bl
потерянные метки" легко обнаруживаются контекстным поиском
Почему же тогда ассемблерный транслятор их ни хрена не видит?! Все дело в том, что IDA Pro неверно определила границы функции, поместив обращения к меткам _за_ границы функции в которой они упоминаются!!! А метки вообще-то локальны. Вот потому-то транслятор их и не находит!
$NORMAL_STATE$1535:
…
loc_4012AA:
…
loc_4012C5:
…
loc_401311:
…
__output endp ; ß конец функции
; ---------------------------------------------------------------------------
off_401956 dd offset $NORMAL_STATE$1535
dd offset loc_4012AA
dd offset loc_4012C5
dd offset loc_401311
IDA Pro поместила обращения
Чтобы исправить ситуацию, необходимо переместить директиву "__output endp" _за_ конец обращений к меткам. Так, чтобы они стали частью функции __output. После чего ассемблерный код будет выглядеть так:
off_401956 dd offset $NORMAL_STATE$1535
dd offset loc_4012AA
dd offset loc_4012C5
__output endp
исправленный вариант, позволяющий транслятору обнаружить "недостающие метки"
После ассемблирования количество ошибок тает буквально на глазах и мы даже в порыве вдохновения едва удерживаемся от того, чтобы не закурить новый косяк:
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
список ошибок, обнаруженных
В глаза бросается пара уже известных нам ошибок типа "undefined symbol", первую из которых исправить достаточно легко:
__NLG_Notify1:
push ebx
push ecx
mov ebx, offset unk_406364
jmp short loc_402480
; --------------- S U B R O U T I N E ---------------------------------------
__NLG_Notify proc near
push ebx
push ecx
mov ebx, offset unk_406364
mov ecx, [ebp+8]
loc_402480:
mov [ebx+8], ecx
mov [ebx+4], eax
mov [ebx+0Ch], ebp
__NLG_Dispatch:
pop ecx
pop ebx
retn 4
__NLG_Notify endp
оригинальный код, сгенерированный IDA Pro, который не хочет транслироваться
Достаточно "завести" подпрограмму __NLG_Notify1 под "retn 4" процедуры __NLG_Notyfy, но перед директивой __NLG_Notify endp, тогда она метка будет распознаваться как надо!
__NLG_Notify proc near
push ebx
push ecx
mov ebx, offset unk_406364
mov ecx, [ebp+8]
loc_402480:
mov [ebx+8], ecx
mov [ebx+4], eax
mov [ebx+0Ch], ebp
__NLG_Dispatch:
pop ecx
pop ebx
retn 4
__NLG_Notify1:
push ebx
push ecx
mov ebx, offset unk_406364
jmp short loc_402480
__NLG_Notify endp
исправленный вариант
А вот со следующей ошибкой справиться уже сложение, поскольку функция strcpy
совершает прыжок в середину функции strcat:
_strcpy proc near
arg_0 = dword ptr 8
push edi
mov edi, [esp+arg_0]
jmp loc_402D11
_strcpy endp
_strcat proc near
arg_0 = dword ptr 4
arg_4 = dword ptr 8
mov ecx, [esp+arg_0]
…
loc_402D11:
mov ecx, [esp+4+arg_4]
test ecx, 3
jz loc_402D36
…
retn
_strcat endp
IDA Pro сгенерировала неработоспособный листинг для парной функции strcpy/strcat
Никаким ухищрениями у нас не получится перетасовать код так, чтобы метка loc_402D11
оказалась в границах видимости, но… ведь как то же это было запрограммировано?! Обратившись к исходным текстам библиотеки LIBC.LIB (они поставляются вместе с компилятором) мы обнаружим волшебный ключик. Чтобы метка была видна отовсюду, после нее должен стоят не один знак ":", а целых два — "::".
Самая трудная задача осталась позади и теперь нам предстоит разобраться с уже встречавшимися ошибками типа "missing operator in expression". На этот раз транслятору не понравились конструкции "push large dword ptr fs:0" и "pop large dword ptr fs:0". Убираем все лишнее, превращая их в "push fs:0" и "pop fs:0" и движемся дальше, где нас ждет ошибка "immediate operand not allowed" (непосредственный операнд недозволен), затаившаяся в 3640 строке: "cmp Locale,0". Естественно, транслятор решил трактовать Locale как смещение, а не как содержимое ячейки, поэтому без явной расстановки квадратных скобок здесь не обойтись: "cmp dword ptr ds:[Locale],0".
Теперь на линии фронта остается лишь большой конгломерат ошибок типа "symbol redefinition" (символ переопределен), против которых не пропрешь, ведь он действительно переопределен, вот например, взять тот же cchMultiByte:
___crtLCMapStringA proc near ; CODE XREF: _setSBUpLow+BEp
; _setSBUpLow+E6p
Locale = dword ptr 8
lpMultiByteStr = dword ptr 10h
cchMultiByte = dword ptr 14h
…
___crtLCMapStringA endp
…
cchMultiByte dd 1 ; DATA XREF: _wctomb+31r
дважды определенный символ Locale
Ничего не остается как "расщеплять" переменные вручную, давая им различные имена. Главное — не перепутать переменные местами. Впрочем, перепутать будет довольно трудно, поскольку, одна копия переменной — локальная и адресуется через стек, а другая — глобальная и обращение с ней происходит через непосредственную адресацию.
Разобравшись с астральными переменными, нам остается только побороть три ошибки, связанные с выравниванием. Ну, ошибку в строке 8064 мы ликвидируем путем удаления директивы "align 800h" (800h в десятичном представлении как раз и будет 2048). Две остальные ошибки требуют переименования сегментов _text и _data
во что-нибудь другое, например, _text1 и _data1, только это переименование должно иди по всему тексту.
Все! Теперь ассемблерный листинг, сгенерированный дизассемблером, и "слегка" исправленный напильником, транслируется без ошибок! Добавим к командой строке MASM'а ключ "/Cp", чтобы он соблюдал регистр публичных имен и….
Первое боевое крещение
Давайте создадим _простейшую_ консольную программку типа "hello, world!", откомпилируем ее, а затем дизассемблируем с помощью IDAPro и попытаемся ассемблировать полученный листинг.
Исходный текст в нашем случае выглядит так:
#include <stdio.h>
main()
{
printf("hello,world!\n");
}
успешно дизассемблированный файл
Дождавшись завершения дизассемблирования файла (когда экран IDA Pro будет выглядеть приблизительно как показано на рис. 1), попросим ее сгенерировать ассемблированный листинг. Порядочные дизассемблеры поддерживают несколько популярных синтаксисов: TASM, MASM и, учитывая, что IDA Pro недавно была перенесена на Linux, неплохо бы добавить к этому списку еще и AT&T, но… увы! В меню "Options" à "Target assembler" значиться только какой-то загадочный "Generic for Intel 80x86", не совместимый ни MASM'ом, ни с TASM'ом (во всяком случае не с их последними версиями). В IDA Pro 5.0 в этом отношении сделан огромный шаг вперед и теперь нам предлагают выбор между "Generic for Intel 80x86" и "Borland TASM in Ideal mode" (см. рис. 2).
ассемблеры, поддерживаемые IDA Pro 4.7 (слева), и IDA Pro 5.0 (справа)
Очень своевременное решение, особенно в свете того, что TASM давно мертв — не "переваривает" новых инструкций, не обновляется, не поддерживается и официально не распространяется. Borland уже давно забила на этот проект. И хотя есть несколько некоммерческих TASM-совместимых ассемблеров (см. статью "обзор ассемблерных трансляторов") всех проблем они не решают и дизассемблерные листинги транслируются только после существенной переделки, а раз так — лучше остановить свой выбор на пакете MASM, входящим в состав NTDDK.
Решено! Выбираем "Generic for Intel 80x86" и говорим "File" à "Produce output file" à "Produce ASM file" или просто нажимаем горячую клавишу <Alt-F10>. Даем файлу имя (например, "demo_1.asm") и через несколько минут шуршания диском у нас образуется… нечто по имени ничто.
Скармливаем эту штуку ассемблеру "ml.exe /c demo_1.asm" (версия 6.13.8204) для справки. Транслятор выдает свыше сотни ошибок, после чего прекращает свою работу, не видя никакого смысла ее продолжать (см. рис. 3).
результат непосредственной трансляции дизассемблерного листинга
Анализ показывает, что 90% ошибок связаны с неверным определением типа процессора "instruction or register not accepted in current CPU mode". Ах, да! По умолчанию IDA Pro выбирает "MetaPC (disassemble all 32-bit opcodes)", но забывает поместить соответствующую директиву в дизассемблерный листинг, а транслятор по умолчанию устанавливает 8086 ЦП, совершенно не совместимый с 32-разрядным режимом.
Материмся, лезем в начало листинга, вставляем директиву ".386", после чего повторяет сеанс трансляции заново. И опять куча ошибок (правда, на этот раз чуть меньше ста, что не может не радовать). Смотрим, что не понравилось транслятору: "demo_1.asm(34):error A2008:syntax error:flat". Хм?! Открываем demo_1.asm, переходим к строке 34 и видим: "model flat". А точка где?! Кто ее будет ставить? Абель что ли? Возвращаем точку на место, заодно добавляя квалификатор языка Си: ".model flat,C" и вновь прогоняем программу через транслятор. На этот раз MASM едет крышей настолько, что выпадает в soft-ice (если тот был предварительно запущен) или выбрасывает знаменитое сообщение о критической ошибке.
критическая ошибка при попытке ассемблирования листинга, сгенерированного 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
Подавляющее большинство ошибок имеют тип
Подавляющее большинство ошибок имеют тип "missing operator in expression" (в выражении отсутствует оператор) и чем скорее мы с ними разберемся, тем будет лучше как для нас самих, так и для транслируемой программы.
Переходим к строке 141 и видим:
mov eax, large fs:0
push eax
mov large fs:0, esp
автоматическая замена всех "large fs" на "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
Секреты ассемблирования дизассемблерных листингов
крис касперски, ака мыщъх, no-email
дизассемблер IDA Pro (как и любой другой) умеет генерировать ассемблерные листинги, однако, их непосредственная трансляция невозможна и прежде чем ассемблер проглотит наживку, приходится совершить немало телодвижений, о самых значимых из которых мыщъх и рассказывает в этой статье
Обычно дизассемблер используется для реконструкции
Обычно дизассемблер используется для реконструкции алгоритма подопытной программы, который после этого переписывается на Си/Си++ или в двоичном файле правится тот нехороший jx, который не дает приложению работать, если не найден ключевой файл или демонстрационный период давно истек.
Значительно реже дизассемблированную программу требуется оттранслировать заново. Например, хочется исправить множественные ошибки разработчиков, нарастить функционал или внести другие изменения… Конечно, все это можно сделать непосредственно в двоичном коде, наложив на программу "заплатку", присобаченную с помощью jump'ов. В большинстве случаев это самый короткий и самый _надежный_ пусть. Нет никаких гарантий, что программа дизассемблирована правильно. Существует по меньшей мере три фундаментальные проблемы дизассемблирования: а) синтаксическая неразличимость смещений от констант; б) неоднозначность соответствия ассемблерных мнемоник машинным командам; в) код ошибочно принятый за данные и данные, ошибочно принятые за код.
Как следствие, откомпилированный дизассемблерный листинг в лучшем случае вообще не работает, зависая при запуске, в худшем же — периодически падать в разных местах. Но до этих проблем нам — как до Луны, а, может, еще и дальше. Для начала необходимо протащить дизассемблерный листинг сквозь ассемблер, устранив явные ошибки трансляции, а со всем остальным мы разберемся как ни будь потом (быть может, даже в следующей статье).
то выясняется, что полученный obj
…и вот тут- то выясняется, что полученный obj наотрез отказывается линковаться, потому что линкер не может найти API-функции! Это не покажется удивительным, если вспоминать, что IDA Pro объявила их в "удобочитаемом" виде, который совсем не совпадет с тем, как они объявлены в библиотеках. Но линковка (и последующая доводка программы до ума) — это уже тема совсем другого разговора, а, может быть, и целой статьи.