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

         

исходный текст программы 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 объявила их в "удобочитаемом" виде, который совсем не совпадет с тем, как они объявлены в библиотеках. Но линковка (и последующая доводка программы до ума) — это уже тема совсем другого разговора, а, может быть, и целой статьи.