Хочу написать дизассемблер длин от простого к сложному. В идеале вижу дизассемблер таким, что можно просто заменить какие-нибудь таблицы и можно будет дизасмить любой набор инструкций, например, инструкции для своего виртуального процессора. У меня есть такой шеллкод: Code (Text): format binary use32 start: pushad call get_delta_offset get_delta_offset: pop ebp sub ebp, get_delta_offset lea eax, [ebp + dllPath] push eax mov eax, 0EEEEEEEEh call eax popad push 0EEEEEEEEh retn dllPath: db "C:\windows\system32\pe.dll", 0 И он же, пропущенный через bin2h: Code (Text): #pragma once const char shellcode[] = { 0x60,0xe8,0x00,0x00,0x00,0x00,0x5d,0x83, 0xed,0x06,0x8d,0x45,0x1c,0x50,0xb8,0xee, 0xee,0xee,0xee,0xff,0xd0,0x61,0x68,0xee, 0xee,0xee,0xee,0xc3,0x43,0x3a,0x5c,0x77, 0x69,0x6e,0x64,0x6f,0x77,0x73,0x5c,0x73, 0x79,0x73,0x74,0x65,0x6d,0x33,0x32,0x5c, 0x70,0x65,0x2e,0x64,0x6c,0x6c,0x00 }; Хочу этот шеллкод дизассемблировать, а потом обобщить алгоритм дизассемблирования. Но мне непонятно, как выделять инструкции по префиксам. Есть однобайтовые инструкции, двухбайтовые. Есть инструкции, составленные из кода операции (один байт) и значения (4 байта). В некоторых наборах инструкций значения можно закодировать в каждом полубайте. Как сгруппировать однобайтовые инструкции в одну таблицу, однобайтовые опкоды со значениями в другую и т.д.? А может, я неправильно понимаю принцип, и делать надо по-другому: Вот что я читаю: http://z0mbie.daemonlab.org/disasme.txt https://github.com/greenbender/lend/blob/master/ld32.h http://www.rohitab.com/discuss/topic/36031-defs-lde/ Что за сжатие опкодов по последней ссылке? Наверное, чтобы разобраться в последнем алгосе, надо понять, почему там используется выражение dict[packed[*src/4] + *src%4]; для индексации массивов.
Aoizora, https://software.intel.com/en-us/articles/intel-sdm#combined 2.1 -> A.1 -> таблицы опкодов, B.1 формат инструкции. Опкод определяется по табицам последовательно, тоесть 0F -> вторая таблица, 38 -> следующая и тд.
Почитаю про формат. Code (Text): seg000:00000000 60 pusha seg000:00000001 E8 00 00 00 00 call $+5 seg000:00000006 5D pop ebp seg000:00000007 83 ED 06 sub ebp, 6 seg000:0000000A 8D 45 1C lea eax, [ebp+1Ch] seg000:0000000D 50 push eax seg000:0000000E B8 EE EE EE EE mov eax, 0EEEEEEEEh seg000:00000013 FF D0 call eax seg000:00000015 61 popa seg000:00000016 68 EE EE EE EE push 0EEEEEEEEh seg000:0000001B C3 retn Кодирование E8, B8 и 68 для пятибайтовых инструкций не случайно? В них меняется только первый полубайт.
Aoizora, Смотрите в таблицах: E8 Jz(A.2) -> z: смещение 16/32 бит. B8 Iv -> W/D/Q соотвественно. 68 Iz 8D Gv, M -> MODR/M.reg: R(может быть опкодом в других случаях). MODR/M произвольной длины(те зависит от её формата) и так же имеет свою кодировку(зависит от режима): 2.1.3
Про любой набор инструкций ты загнул. У интела последовательно выбираются префиксы (если есть), опкод, modrm и sib если есть. Алгоритм, который ты привел, специфический для интела. ЗЫ: своему воображаемому ассемблеру ты можешь помочь кодируя длину инструкции 1-3 битами самого опкода, тогда весь дизассемблер длин будет проверкой этих бит.
Стало понятнее. Интел мануал читаю. Хочу дизассемблировать в уме инструкцию B8 EE EE EE EE, mov eax, 0EEEEEEh. Ищем префиксы. Это может быть lock-prefix F0, rep, repne префиксы F2 и F3, другие префиксы. Их нет. Каким образом определяются префиксы в коде https://github.com/greenbender/lend/blob/master/ld32.h ? Code (Text): /* CHECK_PREFIX */ #if defined(USE_T) && (USE_T & PREFIX_T) const static unsigned int prefix_t[] = { /* 0 1 2 3 4 5 6 7 8 9 a b c d e f */ BITMASK32(0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, /* 0 */ 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0), /* 1 */ BITMASK32(0,0,0,0,0,0,1,0, 0,0,0,0,0,0,1,0, /* 2 */ 0,0,0,0,0,0,1,0, 0,0,0,0,0,0,1,0), /* 3 */ BITMASK32(0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, /* 4 */ 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0), /* 5 */ BITMASK32(0,0,0,0,1,1,1,1, 0,0,0,0,0,0,0,0, /* 6 */ 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0), /* 7 */ BITMASK32(0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, /* 8 */ 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0), /* 9 */ BITMASK32(0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, /* a */ 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0), /* b */ BITMASK32(0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, /* c */ 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0), /* d */ BITMASK32(0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, /* e */ 1,0,1,1,0,0,0,0, 0,0,0,0,0,0,0,0) /* f */ }; #define CHECK_PREFIX(v) CHECK_TABLE(prefix_t, v) #else #define CHECK_PREFIX(v) \ (((v)&0xe7)==0x26||((v)&0xfc)==0x64||(v)==0xf0||(v)==0xf2||(v)==0xf3) #endif Из 32 битов формируется префикс и помещается в массив, по которому делается проверка? Далее в опкоде может быть что-то из ModR/M, SIB, Displacement, Immediate. В данной инструкции из перечисленного есть только immediate константа 0EEEEEEEEh. На этом разбор инструкции закончен, так? Самый простой случай это когда есть только опкод и константа. А как вычислить длину инструкции по таблицам, если есть префиксы и другие опциональные байты? --- Сообщение объединено, May 9, 2019 --- Для дизассемблирования моего шеллкода таблицу префиксов можно сделать проще и определить как Code (Text): const static unsigned int prefix_t[128] = { // Group 1 prefixes 0xF0, // Lock-prefix 0xF2, // repne prefix 0xF3 // repe }; Правильно понимаю принцип создания таких таблиц? --- Сообщение объединено, May 9, 2019 --- Как происходит проверка по таблицам? Что означают эти магические числа? Code (Text): #define CHECK_TABLE(t, v) ((t[(v)>>5]>>((v)&0x1f))&1)
После префикса идет опкод инструкции, в зависимости от опкода может быть modrm, в зависимости от modrm может быть sib, в зависимости от modrm и sib может быть immediate или displacement, их длина может зависеть от префиксов. --- Сообщение объединено, May 9, 2019 --- Макрос BITMASK32 выстраивает эти биты в линеечку от младшего к старшему (они разделены на байты, но в байтах биты наоборот), макрос CHECK_TABLE берет определенный бит из всей таблицы (>>5 это деление на 32, 32 бита в каждой записи BITMASK32). Это дико байтодрочерский алгоритм, его надо с микроскопом изучать. --- Сообщение объединено, May 9, 2019 --- 32*8=256 очевидно это таблица для 1-байтного опкода, где каждый бит означает префикс или не префикс этот опкод.
Сделал дизассемблер очень тупо, но на моем шеллкоде он работает: Code (Text): int main() { const unsigned char* src = shellcode; while (unsigned length = disasm(src)) { std::cout << "OP length: " << length << std::endl; src += length; } return 0; } Code (Text): unsigned int disasm(const unsigned char* opcode) { switch (*opcode) { case 0x60: case 0x5D: case 0x50: case 0x51: case 0xC3: return 1; case 0xFF: return 2; case 0x83: case 0x8D: return 3; case 0xE8: case 0xB8: case 0x68: return 5; default: return 0; } } Есть дизассемблеры без битового хардкора, по которым можно освоить базовые принципы разбора инструкций? В одном из выпусков Inception, вроде, был дизасм. Надо пересмотреть. Есть еще статьи?
Здесь в таблице написано, что есть только 1- и 2-байтовые опкоды, а в интел мануале, который кинул Инде, говорится о 3-байтовых опкодах. http://ref.x86asm.net/geek32.html Работая с ассемблером для современных интелов, можно не закладываться на то, что все опкоды будут короче 3 байт? --- Сообщение объединено, May 10, 2019 --- Сложнаа. По книге "Дизассемблирование в уме" написал код, который собирает инструкцию mov с двумя разными битами направления: Code (Text): #define BITMASK8(b7, b6, b5, b4, b3, b2, b1, b0) \ ( \ (b0 << 0x00) | (b1 << 0x01) | (b2 << 0x02) | (b3 << 0x03) | \ (b4 << 0x04) | (b5 << 0x05) | (b6 << 0x06) | (b7 << 0x07) \ ) int main() { const unsigned char* src = shellcode; while (unsigned length = disasm(src)) { std::cout << "OP length: " << length << std::endl; src += length; } std::ofstream out("dump.bin"); unsigned char mov_l[] = { 0x00, 0xDA }; mov_l[0] = BITMASK8(1, 0, 0, 0, 1, 0, 1, 1); unsigned char mov_r[] = { 0x00, 0xDA }; mov_r[0] = BITMASK8(1, 0, 0, 0, 1, 0, 0, 1); out.write((const char *)mov_l, 2); out.write((const char *)mov_r, 2); out.close(); return 0; } Посмотрел в иде - работает. --- Сообщение объединено, May 10, 2019 --- Дизассемблер из журнала Inception сделан слишком примитивно. Очень много проверок условий вместо того, чтобы написать макросы или constexpr-функции, которые позволят работать на максимальном уровне абстракции, учитывая требования и ограничения Code (Text): /***********************************************************************************************************************\ * * * * * xxxxxxxx xxxxxxxx xxxxxxxxx xxxxxxx xxxx xxxx xxxxxxxx xxxx xxxx * * xxxxxxxxxx xxxxxxxx xxxxxxxxx xxxxxxxxx xxxx xxxx xxxxxxxx xxxx xxxx * * xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxxxxxx * * xxxx xxxx xxxx xxxx xxxx xxxx xxxxxxxxxxx xxxxxxx xxxxxx * * xxxx xxxx xxxx xxxx xxxxxxxxxxx xxxxxxxxxxx xxxxxxx xxxxxx * * xxxx xxxx xxxx xxxx xxxxxxxxxxx xxxx xxxx xxxx xxxxxxxx * * xxxxxxxxxx xxxxxxxx xxxxxxxxx xxxx xxxx xxxx xxxx xxxxxxxx xxxx xxxx * * xxxxxxxx xxxxxxxx xxxxxxxxx xxxx xxxx xxxx xxxx xxxxxxxx xxxx xxxx * * * * * ************************************************************************************************************************* * * * xxx xxxx * * xxx xxxxx * * xx xx * * xx xx * * xxx xxxxx * * xxx xxxx * * * ************************************************************************************************************************* * * * DIZAHEX DISASSEMBLER ENGINE * * * * [+] Дизассемблер инструкций x86/x86-64 (+ 16 bits): * * general-purpose, system, fpu, mmx, sse, sse2, sse3, 3dnow!, undocumented; * * [+] пригоден для пермутации (тогда массив dizahex_table перенести в функу dizahex_disasm()); * * [+] лёгкость добавления новых инструкций; * * [+] дока по дизасму в файле man.txt; * * [+] пример использования смотри в man.txt и в test.c; * * [+] дизассемблирование инструкции и её сборка (dizahex_disasm(), dizahex_asm()); * * [+] etc; * * * \***********************************************************************************************************************/ //v1.0; //pr0mix //pr0mix@mail.ru //вирмэйкинг для себя - искусство вечно #include "dizahex.h" #include "dh_tbl.h" #define get_flags(table, code) (table[table[code / 4] + (code % 4)]) /**********************************************************************************\ * Функция dizahex_disasm * Дизассемблирование инструкции x86/x86-64 (+ 16 bits); * ВХОД: * pcode - адрес инструкции; * pdiza - указатель на объект структы DIZAHEX_STRUCT; * pdiza->mode - DISASM_MODE_32 или DISASM_MODE_64; * ВЫХОД: * (+) - в случае успеха: заполненная структура DIZAHEX_STRUCT, * а также функа вернёт длину разобранной инструкции. * Иначе вернёт 0; * ЗАМЕТКИ: * [+] данная функа вначале обнулит почти все поля структуры DIZAHEX_STRUCT (кроме * pdiza->mode), поэтому делать это самостоятельно не требуется; * [+] если pdiza->mode == DISASM_MODE_32, то будет разбор x86 инструкции. Иначе, * если указан режим DISASM_MODE_64, то разбор x86-64 инструкции; \**********************************************************************************/ int dizahex_disasm(uint8_t *pcode, DIZAHEX_STRUCT *pdiza) { uint32_t i, opcode; uint8_t mod, reg, rm, ch, disp_size, pfx_66, pfx_67, data_64; uint8_t flags, *pdt = dizahex_table, *pc = pcode; opcode = disp_size = pfx_66 = pfx_67 = data_64 = 0; //обнуление нужных переменных; for(i = sizeof(uint8_t); i < sizeof(DIZAHEX_STRUCT); i++) //обнуление структы (кроме первого элемента, pdiza->mode); *((char *)pdiza + i) = 0; while((get_flags(pdt, *pc) & C_PREFIX) && ((pc - pcode) < 15)) //обработка префиксов; { ch = *pc++; if(ch == 0x66) //0x66 { pfx_66 = 1; pdiza->pfx_66 = ch; pdiza->flags |= F_PFX_66; } if(ch == 0x67) //0x67 { pfx_67 = 1; pdiza->pfx_67 = ch; pdiza->flags |= F_PFX_67; } if(ch == 0xF0) //LOCK { pdiza->pfx_lock = ch; pdiza->flags |= F_PFX_LOCK; } if((ch == 0xF2) || (ch == 0xF3)) //REPE / REPNE { pdiza->pfx_rep = ch; pdiza->flags |= F_PFX_REP; } if((ch == 0x26) || (ch == 0x2E) || (ch == 0x36) || (ch == 0x3E) || (ch == 0x64) || (ch == 0x65)) //SEGMENTS { pdiza->pfx_seg = ch; pdiza->flags |= F_PFX_SEG; } } ch = *pc++; if(pdiza->mode & DISASM_MODE_64) //если включён режим x64, { if((ch & 0xF0) == 0x40) //то проверим наличие префикса REX; { pdiza->flags |= F_PFX_REX; pdiza->pfx_rex = ch; ch = *pc++; if((pdiza->pfx_rex & 0x08) && ((ch & 0xF8) == 0xB8)) //если REX есть, REX.W есть и опкод = [0xb8..0xbf] data_64 = 1; //то будет imm64; } } pdiza->opcode = ch; //обработка опкодов; if(ch == 0x0F) //2opcode; { pdiza->opcode_2 = ch = *pc++; opcode |= C_OP_EXTENDED; } else if((ch >= 0xA0) && (ch <= 0xA3)) { pfx_66 = pfx_67; if(pdiza->mode & DISASM_MODE_64) //если x64 и опкод = [0xa0..0a3]; { data_64 = !pfx_66; pfx_66 = 0; } } opcode |= ch; flags = get_flags(pdt, opcode); //получение характеристик опкода; if(flags & C_MODRM) //обработка modrm; { pdiza->flags |= F_MODRM; pdiza->modrm = ch = *pc++; mod = ch >> 6; reg = (ch & 0x38) >> 3; rm = ch & 7; if(reg <= 1) { if(opcode == 0xF6) flags |= C_DATA_8; if(opcode == 0xF7) flags |= C_DATA_PFX_66_67; } if(!mod) //mod = 0; { if(pfx_67) { if((rm == 6) && !(pdiza->mode & DISASM_MODE_64)) disp_size = 2; //disp = 16bits только в 16-битном режиме; в 32 и 64 disp = 32bits; } else { if(rm == 5) disp_size = 4; } } else if(mod == 1) //mod = 1; { disp_size = 1; } else if(mod == 2) //mod = 2; { if(pfx_67 && !(pdiza->mode & DISASM_MODE_64)) disp_size = 2; else disp_size = 4; } if((mod != 3) && (rm == 4) && (!pfx_67 || (pdiza->mode & DISASM_MODE_64))) //обработка SIB; SIB отсутствует только в 16-разрядном режиме; { pdiza->flags |= F_SIB; pdiza->sib = ch = *pc++; if(((ch & 7) == 5) && !(mod & 1)) { disp_size = 4; } } if(disp_size == 1) //обработка смещения (DISP); { pdiza->flags |= F_DISP_8; pdiza->disp.disp_8 = *pc; } else if(disp_size == 2) { pdiza->flags |= F_DISP_16; pdiza->disp.disp_16 = *(uint16_t *)pc; } else if(disp_size == 4) { pdiza->flags |= F_DISP_32; pdiza->disp.disp_32 = *(uint32_t *)pc; } pc += disp_size; } if(flags & C_DATA_PFX_66_67) //обработка операнда (IMM); { if(data_64) { pdiza->flags |= F_IMM_64; pdiza->imm.imm_64 = *(uint64_t *)pc; pc += 8; } else if(pfx_66) { pdiza->flags |= F_IMM_16; pdiza->imm.imm_16 = *(uint16_t *)pc; pc += 2; } else { pdiza->flags |= F_IMM_32; pdiza->imm.imm_32 = *(uint32_t *)pc; pc += 4; } } if(flags & C_DATA_16) { if(pdiza->flags & (F_IMM_32 | F_IMM_16)) { pdiza->flags |= (F_DISP_16 | F_COP_IMM_DISP); pdiza->disp.disp_16 = *(uint16_t *)pc; } else { pdiza->flags |= F_IMM_16; pdiza->imm.imm_16 = *(uint16_t *)pc; } pc += 2; } if(flags & C_DATA_8) { if(pdiza->flags & (F_IMM_32 | F_IMM_16)) { pdiza->flags |= (F_DISP_8 | F_COP_IMM_DISP); pdiza->disp.disp_8 = *pc++; } else { pdiza->flags |= F_IMM_8; pdiza->imm.imm_8 = *pc++; } } if(flags & (C_REL_32 | C_REL_8)) pdiza->flags |= F_REL; pdiza->len = pc - pcode; //вычисляем длину инструкции; return pdiza->len; } /**********************************************************************************\ * конец функи dizahex_disasm; \**********************************************************************************/ /**********************************************************************************\ * вспомогательная внутренняя функа disp_asm; * ассемблирование значений полей disp*; * ВХОД: * pcode - адрес инструкции; * pdiza - указатель на объект структы DIZAHEX_STRUCT; * ВЫХОД: * (+) - адрес для дальнейшего ассемблирования; \**********************************************************************************/ uint8_t *disp_asm(uint8_t *pcode, DIZAHEX_STRUCT *pdiza) { if(pdiza->flags & F_DISP_8) *pcode++ = pdiza->disp.disp_8; if(pdiza->flags & F_DISP_16) { *(uint16_t *)pcode = pdiza->disp.disp_16; pcode += 2; } if(pdiza->flags & F_DISP_32) { *(uint32_t *)pcode = pdiza->disp.disp_32; pcode += 4; } return pcode; } /**********************************************************************************\ * конец функи disp_asm; \**********************************************************************************/ /**********************************************************************************\ * вспомогательная внутренняя функа imm_asm; * ассемблирование значений полей imm*; * ВХОД: * pcode - адрес инструкции; * pdiza - указатель на объект структы DIZAHEX_STRUCT; * ВЫХОД: * (+) - адрес для дальнейшего ассемблирования; \**********************************************************************************/ uint8_t *imm_asm(uint8_t *pcode, DIZAHEX_STRUCT *pdiza) { if(pdiza->flags & F_IMM_8) *pcode++ = pdiza->imm.imm_8; if(pdiza->flags & F_IMM_16) { *(uint16_t *)pcode = pdiza->imm.imm_16; pcode += 2; } if(pdiza->flags & F_IMM_32) { *(uint32_t *)pcode = pdiza->imm.imm_32; pcode += 4; } if(pdiza->flags & F_IMM_64) { *(uint64_t *)pcode = pdiza->imm.imm_64; pcode += 8; } return pcode; } /**********************************************************************************\ * конец функи imm_asm; \**********************************************************************************/ /**********************************************************************************\ * функа dizahex_asm; * сборка инструкции из структуры DIZAHEX_STRUCT; * ВХОД: * pcode - адрес инструкции; * pdiza - указатель на объект структы DIZAHEX_STRUCT; * ВЫХОД: * (+) - длина собранной инструкции; \**********************************************************************************/ int dizahex_asm(uint8_t *pcode, DIZAHEX_STRUCT *pdiza) { uint8_t *pc = pcode; if(pdiza->flags & F_PFX_66) *pc++ = pdiza->pfx_66; if(pdiza->flags & F_PFX_67) *pc++ = pdiza->pfx_67; if(pdiza->flags & F_PFX_LOCK) *pc++ = pdiza->pfx_lock; if(pdiza->flags & F_PFX_REP) *pc++ = pdiza->pfx_rep; if(pdiza->flags & F_PFX_SEG) *pc++ = pdiza->pfx_seg; if(pdiza->flags & F_PFX_REX) *pc++ = pdiza->pfx_rex; *pc++ = pdiza->opcode; if(pdiza->opcode == 0x0F) *pc++ = pdiza->opcode_2; if(pdiza->flags & F_MODRM) *pc++ = pdiza->modrm; if(pdiza->flags & F_SIB) *pc++ = pdiza->sib; if(pdiza->flags & F_COP_IMM_DISP) //если данный флаг установлен, тогда меняем порядок обработки IMM и DISP: Change Order of Processing (COP); { pc = imm_asm(pc, pdiza); pc = disp_asm(pc, pdiza); } else { pc = disp_asm(pc, pdiza); pc = imm_asm(pc, pdiza); } return (pc - pcode); } /**********************************************************************************\ * конец функи dizahex_asm; \**********************************************************************************/
А ты ассемблер, дизассемблер или дизассемблер длин хочешь сделать?) Можешь попробовать дизасм гидры в свой адаптировать, там основной принцип - обработка одного токена (порции данных) за раз (разделены на фазы) и сохранение флагов в контексте. Примерно то же самое, что в первом приведенном дизассемблере длин.
Хочу пока что написать дизассемблер длин, но ассемблер и дизассемблер тоже будет нужен. Я хочу пермутировать свой шеллкод, но для начала просто его дизассемблировать без помощи библиотек.