Несколько слов о дизассемблерах |
Feb. 9th, 2013|12:01 am |
Re:)
Изучая сорцы разных дизасмов, пришел к выводу, что как такового алгоритма дизассемблирования нет (хм, где-то я это уже видел). Возможно, по этой причине хотящие написать свой дизасм и спрашивают "с чего начать?", "как это сделать?" и т.д. И вот, как-то ночью увидев топик с подобным вопросом, я и решил написать кое-что по этому поводу.
Это своего рода справочная портянка для создания (простого) дизасма.
Предполагается, что тот, кто собирается прочитать сей текст, знает формат команд IA-32 (так как здесь он описан коротко). Если это не так, то сначала желательно просмотри ссылки в конце данного текста.
Итак, поехали.
Для начала определение:
Машинные Команды - это сформированные по определенным правилам последовательности байтов. Так вот, правила эти называют форматом команд, и формат этот в свою очередь состоит из нескольких полей, ОБЯЗАТЕЛЬНЫМ из которых является код операции (опкод). Остальные поля могут отсутствовать (тогда команда содержит только 1 байт - опкод). Формат команд IA-32:
ПРЕФИКС ОПКОД МОДРМ СИБ СМЕЩЕНИЕ НЕПОСРЕДСТВЕННЫЙ ОПЕРАНД
А вот, собственно, вариант дизассемблирования:
- Определение префиксов команды
- Опкод (разбор)
- Парсинг ModR/M
- Парсинг SIB
- Смещение и непосредственный операнд
Теперь по порядку.
1. ОПРЕДЕЛЕНИЕ ПРЕФИКСОВ КОМАНДЫ.
Префиксы - необязательные однобайтные элементы машинной команды, и в своем дизасме их также следует определять как однобайтные конструкции. В памяти - предшевствуют команде. Порядок следования префиксов может быть любым, а также может играть или не играть вообще никакой роли.
Префиксы делятся на 4 группы:
- блокировки шины (LOCK - 0F0h) & повторения(REP или REPE/REPZ - 0F3h, REPNE/REPNZ - 0F2h)
- замены сегмента (CS - 2Eh, SS - 36h, DS - 3Eh, ES - 26h, FS - 64h, GS - 65h)
- размера оперенда (66h)
- размера адреса (67h)
Максимальная длина команды равна 15 байтам. Все 15 байт префиксами быть не могут (почему? - вспоминаем про опкод:) - по ходу возникнет #UD (недействительный код операции). LOCK можно использовать только один раз и только с определенными командами. REP/E/NE - могут использоваться с командами обработки срок, могут быть частью команд из расширенного набора или вообще "быть не при делах". Разрешено многократное использование данных префиксов, однако это будет равносильно однократному + использоваться будет последний встреченный. 2 группа префиксов - по идее также используется последний встреченный. Здесь же: 2Eh и 3Eh (появились в Pentium 4 & Xeon) - еще и определяют (только с инструкциями Jcc):
- 2Eh - переход выполняется;
- 3Eh - переход не выполяется.
Префиксы 66h/67h изменяют разрядность операнда/адреса: 16 или 32 бита, а также могут быть частью команд из расширенного набора. При создании дизасма - быть внимательным к этим префиксам(блин, и к другим тоже). В общем, здесь все. Остальное - внимательное изучение доков от Intel.
2. ОПКОД (РАЗБОР).
Код оперции (опкод) - обязательный элемент, описывающий операцию, выполняемую командой. Может занимать 1,2,3 байта, а также и часть битов для некоторых машинных команд в байте ModR/M (о нем чуть ниже).
Опкод не имеет однозначной структуры. В зависимости от определенных команд может иметь от 1 до 3 элементов, уточняющих детали операции:
- d - направление передачи данных
- s - расширение непосредственного операнда
- w - размер данных
- reg - регистр
Бывают ситуации, что после разбора опкода надо вернуться к пункту 1 (посмотреть на выделенные префиксы - для некоторых команд из расширенного набора - но и это решается).
Теперь, собственно, о декодировании команд. Так вот, лучше всего декодировать по характеристикам опкода. Как это делается? Тут есть несколько вариков: можно искать опкод
- кучей if{} else{}, и если нашли - установить соответствующие флаги (характеристики) - это имеющиеся в данном опкоде поля, набор параметров и другие.
- switch (аналог if/else) - тогда их будет 2: 1-ый - для однобайтных опкодов, а 2-ой - для 0x0F, и также, при нахождении данного опкода выставлять соответствующие флаги. Но, по-моему, лучше вариант #3, а именно:
- соcтавить таблицы, содержащие Характеристики уже всех опкодов. Некоторые спрашивают, как же создаются эти самые таблицы и как узнать, какие из характеристик соответствуют какому опкоду? Так вот, таблицы (массивы) строятся так, чтобы опкод команды был ИНДЕКСОМ (индексом в массиве) элемента, описывающего данный опкод. Дизасм выбирает характеристики опкода из таблицы, после чего декодирует опкод уже по набору этих характеристик. Насчет характеристик: все находится в мануалах Intel (IA-32 Intel Architecture Software Developer's Manual Volume 2B Instruction Set Reference N-Z - где-то в конце книги Appendix A Opcode Map). Короче, приведу несколько примеров:
Opcode Instruction Description 88 /r MOV r/m8,r8 Move r8 to r/m8 C6 /0 MOV r/m8,imm8 Move imm8 to r/m8
88 /r MOV r/m8,r8 Move r8 to r/m8 C6 /0 MOV r/m8,imm8 Move imm8 to r/m8
Вспоминаем, что означают условные знаки "/r", "rb","/0", "r8", "imm32", etc.
Обозначим, например, наличие ModR/M в команде как B_MODRM equ 01h, а наличие непосредственного однобайтного значения(imm8) как B_DATA8.
Так, для опкода 0x88 выставлено "/r" - это означает, что ModR/M задаёт оба операнда (регистр и r/m) - значит выставляем флаг B_MODRM. Улавливаешь мыслю? Еще пример: опкод 0xC6. Напомню: "/0" говорит о том, что поле Reg в ModR/M всегда равно 0. Видно, что здесь также присутствует ModR/M. Но, еще здесь есть "imm8" - значит опкод 0xC6 будет содержать следующие характеристики: B_MODRM+B_DATA8. И так далее.
3. РАЗБОР ModR/M.
Байт ModR/M несет информацию об операндах и режиме адресации и состоит из следующих полей:
- mod
- Reg/Opcode (обычно называют Reg)
- R/M
Поле mod - определяет режим адресации и количество байтов поля СМЕЩЕНИЯ в команде. Поля Reg и R/M обычно служат для указания операндов. Следует уточнить, что в Reg для некоторых команд может кодироваться расширение опкода.
mod address
00b [reg] - если reg==5, то сразу за modrm идет 32-битное смещение - если reg==4, то сразу за modrm идет байт SIB
01b [reg+8-битное смещение] - если reg==4, то сразу за modrm следует байт SIB
10b [reg+32-битное смещение] - если reg==4, то сразу за modrm следует байт SIB
11b reg
С префиксом 0x67 32-битное смещение превратится в 16-битное. Также при наличии 0x67, если mod=0, rm=6 (110b), то за modrm идет 16-битное смещение.
4. РАЗБОР SIB (Scale-Index-Base).
Этот байт используется для расширения возможности адресации операндов. Его присутствие можно определить из байта ModR/M (смотри выше, как). SIB состоит из 3-х полей:
С помощью байта SIB можно задавать выражения вида [reg1+reg2*scale+СМЕЩЕНИЕ]. Структура SIB:
Биты 6-7: scale
- 00b - 1
- 01b - 2
- 10b - 4
- 11b - 8
Биты 3-5: reg2 - если reg2 == 4, то reg2 не используется
Биты 0-2: reg1 - если reg1==5 и если mod в ModR/M равен 00, то за сразу SIB идет 32-битное смещение, иначе ebp
Важно: в 16-разрядном режиме (0x67) байта SIB нет!
5. СМЕЩЕНИЕ и НЕПОСРЕДСТВЕННЫЙ ОПЕРАНД.
Эти поля - 8, 16 или 32-разрядные числа. Разрядность определяется по характеристикам опкода + префиксам (0x67/0x66).
Также, хотелось бы черкануть пару слов о дизасмах в вирусах. Такую силу должен иметь каждый нормальный вирь:) Пока еще большинство прог (но не все!) юзают стандартные & fpu команды. Поэтому задумайся - так ли тебе нужны хери типа mmx, sse1/2/3? Дизассемблер должен быть быстр и предельно компактен - короче оптимизация во все щели. Но, как это часто случается, страдает либо морда, либо жопа. Компактен - не означает, что после написания движка к нему хер прикрутишь новые команды. Это важно. И естесственно, код должен быть базонезависимым.
В общем, вот такие помидоры. В данном тексте я написал только некоторую часть тех вещей, с которыми может столкнуться писатель дизасма. Так как существуют еще и такие проблемы, как разделение кода и данных, реакция на инвалидность команд, и. т.д.
Добавлю, что для написания своего дизасма в первую очередь нужно решить - для чего он тебе нужен? Ну а потом уже штудировать маны от Intel + сорцы других движков. Удачи!
P.S. Начать изучение сорсов можешь с моего простенького дизассемблера.
Используемые ссылки:
//pr0mix@mail.ru
|
|