Как IDA дизассемблирует программы
Чтобы понять, для чего именно предназначена моя библиотека, Вы должны очень хорошо представлять себе, как же IDA работает. Так что запаситесь терпением, пивом, поставьте что-нть приятное слуху (от себя могу порекомендовать Marylin Manson, Nine Inch Nails, Pearl Jam или Alice in Chains) - глава будет долгой...
Я встречал много людей, которые на полном серьёзе утверждали, что создание дизассемблера - тривиальное занятие. Хм, однако почему в таком случае так мало хороших дизассемблеров ? Дело в том, что под понятием "хороший дизассемблер" мы подразумеваем не только программу, генерирующую на выходе текстовый файл с ассемблерными инструкциями. Хороший ассемблер должен заниматься так же и анализом кода - чтобы отличить код от данных, распознать использование локальных переменных, начало и конец функций и ещё множество вещей, за которые мы любим IDA Pro. А как же IDA Pro может делать всё вышеперечисленное ? Весь секрет заключается в том, что в ней используется не просто дизассемблер - а ещё и эмулирующий анализатор.
Чтобы заставить IDA Pro понимать ещё один процессор (скажем, HP-PA), Вы должны написать dissassembler module - для краткости в дальнейшем будем называть его просто module. В IDA SDK есть пример такого модуля (для процессора 8051). Рассмотрим, из каких функций состоит module. Module представляет собой .DLL (для Win32), экспортирующую под именем LPH всего одну структуру processor_t. Структура эта достаточно велика - ведь она должна полностью описывать ассемблер некоторого процессора - но нас в этой структуре нас интересует всего несколько членов:
- int (*u_ana) (void)
Указатель на функцию, анализирующую одну инструкцию, в результате анализа заполняется глобальная переменная cmd - структура insn_t. Адрес инструкции задаётся в поле cmd.ea. Функция возвращает длину декодированной инструкции, или 0, если инструкция не распознана.
На самом деле мы не можем вызвать эту функцию непосредственно - мы можем вызвать для анализа только следующие функции:
- ida_export int ua_code(ea_t ea)
Высокоуровневая функция, анализирует байты по адресу ea, и преобразует их в код. - ida_export int ua_ana(ea_t ea)
Анализирует байты байты по адресу ea, преобразует их в код, а также производит некоторые сопутствующие действия (например, применение fixups, увеличение сегментов и т.д.) - ida_export int ua_ana0(ea_t ea)
Наша рабочая лошадка - просто анализирует байты по адресу ea, заполняя структуру cmd, при этом загруженная база данных не изменяется. - int (*u_emu) (void)
Указатель на функцию, эмулирующую выполнение инструкции. Несмотря на отсутствие аргументов, эта функция имеет доступ к ранее заполненной в результате анализа структуре cmd для эмулируемой инструкции. Именно наличием эмулятора (а также наличием встроенной в IDA виртуальной регистровой машины) и объясняются её выдающиеся способности - инструкции не просто дизассемблируются, но и частично эмулируются, что позволяет производить более глубокий анализ кода. Эта функция отвечает за создание кросс-ссылок, за включение в зону анализа ветвей исполнения (для инструкций переходов и вызова функций) и множество других вещей... - Функции генерации текстового представления (то, что мы видим на экране):
Все эти три функции возвращают длину декодированной инструкции, или 0, если инструкция не опознана. Аргумент ea - адрес (ea_t - просто ulong).
- void (*u_out) (void)
Генерирует текстовое представление инструкции по ранее заполненной структуре cmd. - int (*u_outop) (op_t &op)
Генерирует текстовое представление операнда op_t инструкции. Возвращает 1 в случае успеха, и 0, если операнд скрыт. - instruc_t *instruc
Массив описаний инструкций (см. подробности ниже). - const char near *name
Строка - имя инструкции - ushort feature
Характеристики инструкции. Битовая маска, могущая состоять из следующих значений: - CF_STOP
Инструкция не передаёт исполнение следующей инструкции (например, hlt) - CF_CALL
Вызов процедуры. - CF_CHG1
Инструкция модифицирует свой первый операнд. - CF_CHG2
Инструкция модифицирует свой второй операнд. - CF_CHG3
Инструкция модифицирует свой третий операнд. - CF_USE1
Инструкция использует значение своего первого операнда. - CF_USE2
Инструкция использует значение своего второго операнда. - CF_USE3
Инструкция использует значение своего третьего операнда. - CF_JUMP
Инструкция передаёт управление. - CF_SHFT
Инструкция производит побитовый сдвиг. - CF_HLL
Инструкция может быть описана на языке высокого уровня (я не знаю, что конкретно имелось в виду за столь витиеватой формулировкой) - ushort itype
Внутренний код инструкции. Значения кодов определены в каждом процессорном модуле по-разному для каждого процессора (я также сильно подозреваю, что их значения изменяются от версии к версии). Также является индексом в ранее описанном массиве инструкций instruc_t *instruc. - ea_t ea
Линейный адрес инструкции. - ushort size
Размер инструкции в байтах. - op_t Operands[3]
Операнды инструкции. Почему операндов именно три ? См., например, x86 инструкцию shld или shrd - char n
Номер операнда - 0,1 или 2. - optype_t type
Тип операнда. Этот член определяет, какие прочие члены структуры имеют значение, и как оно интерпретируется. Насколько я понял, процессорозависим. Более детальное описание см. ниже. - char offb
Смещение значения операнда от начала инструкции. Имеет смысл не для всех типов операндов. - char offo
Такой же, как и предыдущий член, для операндов из из двух численных значений указывает на смещение второго из них. - uchar flags
Некоторые характеристики операндов. Объяснение см. ниже. - char dtyp
Тип значения операнда. Наиболее распространённые типы значений:
dt_byte 0 // 8 бит dt_word 1 // 16 бит dt_dword 2 // 32 бит dt_float 3 // 4 байта dt_double 4 // 8 байт dt_qword 7 // 64 бит
Все нижеописанные члены структуры op_t хранят информацию о значении операнда (и их использование зависит от значений type & flags):
union { uchar reg; uchar phrase; };
Номер регистра. Для ассемблера x86 номера регистров определены в файле заголовков x86.h моей библиотеки - см. макросы r_XX. - lock auxpref_chars.low & 0x1 == 0x1
- rep/repe auxpref_chars.low & 0xE == 0xA
- repne auxpref_chars.low & 0xE == 0xC
- o_void
Операнд не имеет значение. Признак отсутствия операнда и всех последующих операндов. - o_reg
Операнд является регистром, поле reg содержит номер регистра. Значения регистров см. в моём файле заголовков x86.h. - o_mem
Операнд является прямой ссылкой на данные в памяти. Сегментный регистр содержится в поле specval_shorts.high, поле addr содержит адрес. - o_phrase
Операнд представляет собой ссылку типа [базовый регистр + индексный регистр]. Сегментный регистр содержится в поле specval_shorts.high. Все значения операнда хранятся в SIB байте - поле specflag2.
Значение базового регистра определяется как specflag2 & 0x7
Значение индексного регистра определяется как (specflag2 >> 3) & 0x7
Масштабный индекс определяется как (specflag2 & 0xC0) >> 6:
0 eq 0
1 eq 2
2 eq 4
3 eq 8
По этой схеме specflag2, равный, скажем, 64, представляет операнд [eax+eax*2] - o_displ
Операнд представляет собой ссылку типа [базовый регистр + индексный регистр + смещение]. Сегментный регистр содержится в поле specval_shorts.high. Поле phrase содержит значение базового регистра. Также используется SIB байт (см. описание выше). Поле value содержит значение, если flags & OF_OUTER_DISP.
Поле addr содержит адрес, если !(flags & OF_NO_BASE_DISP). - o_imm
Операнд содержит непосредственное значение в поле value. - o_far & o_near
Операнд содержит дальнюю и ближнюю ссылку на адрес в поле addr. Сегментный регистр содержится в поле specval_shorts.high. - 11
Для инструкций с плавающей точкой - поле reg содержит номер регистра FPU (0 - ST(0), 1 - ST(1) и т.д.) - 12
Для инструкций с использованием MMX - поле reg содержит номер регистра MMX (0 - MM0, 1 - MM1 и т.д.) - 13
Для инструкций с использованием SSE - поле reg содержит номер регистра SSE (0 - XMM0, 1 - XMM1 и т.д.)
Все эти функции не изменяют загруженную базу данных, генерируемый ими текст доступен через глобальную переменную u_line.
Структура instruc_t используется для внутреннего представления характеристик инструкции, и имеет всего два члена:
Структура insn_t используется для внутреннего представления инструкции, нас интересуют следующие члены:
union { ushort auxpref; struct { uchar low; uchar high; } auxpref_chars; };
Процессорно-зависимое поле. Используется для x86 процессора (см. детальное описание ниже).
Структура op_t заслуживает более пристального рассмотрения. Она состоит из следующих членов:
union { ulong value; struct { ushort low; ushort high; } value_shorts; };
Значение непосредственного операнда.
union { ulong addr; struct { ushort low; ushort high; } addr_shorts; };
Значение виртуального адреса, используемого операндом.
union { ulong specval; struct { ushort low; ushort high; } specval_shorts; };
Содержит значение операнда, не могущее быть представленным остальными членами структуры op_t. Специфично для каждого процессорного модуля, для ассемблера x86 не используется (IMHO ?).
char specflag1; char specflag2;
Содержат дополнительную информацию об операнде.
Как же именно вышеописанные структуры могут быть использованы для анализа кода ? Я выяснил этот вопрос, не дождавшись ответа от Гильфанова. Хочу ещё раз предупредить, что всё нижеописанное касается только IDA Pro версии 3.85b (и может измениться без предупреждения в любой из следующих версий) и только процессора x86.
Итак, инструкция может иметь один из префиксов. Наличие или отсутствие префиксов определяет поле auxpref_chars.low структуры insn_t:
Количество операндов можно определить так: последний из них должен иметь type
o_void (но не больше трёх).
Значение операнда определяется его полем type: