Дизассемблер является примером того,что сразу написать непросто .В процессе
работы с ним появляются новые потребности в чем-либо,которые не были
актуальными вчера, и приходится модифицировать дизассемблер для своих нужд
снова.Завтра ,может,понадобится еще более гибкая система - и история
повторится вновь.
Как можно понять LDIZX - дизассемблирующий движок и является продуктом такой
вот невостребованности.
Основные характеристики :
* Достаточно полная информация ,получаемая на выходе для анализа/сборки
команды.
* Использование в качестве только лишь дизассемблера длин
* Не содержит данных/абсолютных смещений
* Использован универсальный C call т.е. может быть включен в какой-нибудь
HLL
* Обрабатывает int 20h (0CD20h) как 6-ти байтную команду, т.е. как VxDcall
(win32-ориентация).Если необходимо перенести его куда-нибудь еще ,то это
достигается изменением 1-5 строк в исходнике.
Т.к. использован C call,то тут дано описание ф-ций на C , хотя это просто
способ передачи параметров процедуре.
Для работы движка необходимы таблицы. Процедура их распаковки:
ldizx_init ( VOID* TablePtr // Килобайтный буфер для таблиц
);
После выполнения этой процедуры в память по адресу TablePtr распаковываются
таблицы движка,которые используются при дизассемлировании.
Главная функция (собственно дизассемблирующая код):
ldizx ( VOID* InPtr, // Поинтер на входной код
CMD* OutPtr, // Поинтер на структуру CMD
VOID* TablePtr // Поинтер на распакованые таблицы
)
После выполнения данной процедуры структура ,заданная вторым параметром
заполняется исходя из команды ,которая дизассемблируется.На выходе LDIZX
передает длину команды в EAX либо FFFFFFFFh в случае ошибки.
Если при дизассемблировании не требуется информация о команде ,кроме ее
длины, то в качестве второго параметра передается 0 и LDIZX возвращает лишь
длину команды в EAX.
Процедура ассемблирования структуры cmd в команду не включена, т.к. почти
всегда требуется специфичное конструирование команды или, наоборот, очень
простое и лучше писать ассемблирование отдельно.
Вторым параметром в движок передается поинтер на структуру CMD ,в которую
после выполнения главной процедуры, записывается информация о команде.Ее
формат представлен ниже:
typedef struct {
BYTE lc_size; // длина команды
BYTE lc_psize; // длина префиксов
DWORD lc_flags; // флаги
BYTE lc_tttn; // tttn
BYTE lc_sib; // sib
BYTE lc_modrm; // modrm
BYTE lc_reg; // reg
BYTE lc_mod; // mod
BYTE lc_ro; // r/o
BYTE lc_rm; // r/m
BYTE lc_base; // base
BYTE lc_index; // index
BYTE lc_scale; // scale
DWORD lc_offset; // смещение
BYTE lc_operand[6]; // операнд
BYTE lc_soffset; // длина смещения
BYTE lc_soperand; // длина операнда
BYTE lc_mask1; // маска
BYTE lc_mask2; //
} cmd;
Содержание полей lc_sib,lc_modrm,lc_rm,lc_ro,lc_base,lc_index,lc_scale думаю
понятно из их названия , замечу ,что поле mod дано сдвинутым на 6 бит влево
т.к. его часто с целью восстановления команды так сдвигают и было бы разумно
его таким оставить,каким оно есть в команде (т.е. занимающем 6-й и 7-й биты)
Операнд является 6-ти байтовым с учетом таких команд, как JMP FAR/CALL FAR,
где есть и селектор и смещение,поэтому он пишется в порядке следования его
байтов в команде.Т.е. необходимо при обращении к полю lc_operand учитывать
размер операнда ,содержащийся в поле lc_soperand.
tttn имеет смысл в командах условного выполнения (JCC/SETCC и т.д.), для них
это поле содержит условие выполнения команды.
На lc_reg стоит смотреть только тогда,когда команда не имеет modr/m и
работает с регистром.Содержание этого поля и определяет использованный
регистр.
Длина префиксов дана с целью выявления нестандартных команд,в которых по
нескольку одинаковых префиксов.
В полях lc_mask1 и lc_mask2 содержится маска команды.Причем она дается в
полном виде включая 0F,если этот префикс у команды есть.Это поле полезно,
когда надо найти определенную команду по ее маске.Т.е. движок сам определит
маску команды и занесет ее в эти поля.
Наиболее важную информацию о команде содержат флаги (lc_flags),в зависимости
от их содержания трактуются большинство полей в структуре CMD, поэтому
следует прежде всего смотреть на флаги,а уже только потом на содержание
полей.
LF_PCS
0x00000001
Присутствует префикс CS
LF_PDS
0x00000002
Присутствует префикс DS
LF_PES
0x00000004
Присутствует префикс ES
LF_PSS
0x00000008
Присутствует префикс SS
LF_PFS
0x00000010
Присутствует префикс FS
LF_PGS
0x00000020
Присутствует префикс GS
LF_POP
0x00000040
Присутствует префикс замены разрядности операнда
LF_POF
0x00000080
Присутствует префикс замены разрядности адреса
LF_PLOCK
0x00000100
Присутствует префикс LOCK
LF_PREPZ
0x00000200
Присутствует префикс REPZ
LF_PREPNZ
0x00000400
Присутствует префикс REPNZ
LF_MODRM
0x80000000
Присутствует modr/m
LF_SIB
0x40000000
Присутствует sib
LF_OFFSET
0x20000000
Присутствует смещение
LF_OPERAND
0x10000000
Присутствует операнд
LF_REG
0x08000000
Присутствует reg (команда без modr/m)
LF_REG1
0x04000000
R/m является регистром и имеет смысл
LF_REG2
0x02000000
R/o является регистром и имеет смысл
LF_BASE
0x01000000
База в sib присутствует и имеет значение
LF_BASE
0x00800000
Индекс в sib присутствует и имеет значение
LF_MEM
0x00400000
Команда работает с памятью (т.е. mod <> 11b )
LF_TTTN
0x00200000
Присутствует tttn
LF_RAW
0x00100000
cmd не содержит полной информации
LF_D
0x00008000
В опкоде присутствует d
LF_S
0x00004000
В опкоде присутствует s
LF_SDV
0x00002000
s либо d равен 1 (в зависимосити от флагов LF_S и LF_S)
LF_W
0x00001000
В опкоде присутствует w
LF_WV
0x00000800
w равен 1
Флаги LF_REG1,LF_REG2,LF_BASE,LF_INDEX показывают ,что r/m,r/o,base и index
соответственно являются регистрами.Т.е. это значит ,что их необходимо
трактовать как регистры .Это сделано для того , чтобы отделить команды ,не
использующие эти поля как регистры.При прямой адресации команды типа :
mov [12345678h],edx
Имеют в r/m 101b т.е.регистр ebp и по идее должна быть использована
косвенная адресация, но по логике работы команды нет никакого регистра т.к.
это особенность строения команд. Тоже самое и с базой, индексом.В r/o может
быть и не регистр ,а уточняющий операцию код.Поэтому и введены эти флаги.