Дизассемблеры в вирусах ----------------------- (x) 2001 Z0MBiE Вы, конечно, знаете, что чем больше в вирусе фичей (возможностей), и чем больше связей между ними и окружением вируса (реакций) -- тем вирус живее; а чем больше у вируса сложность, тем лучше для нас. Например модульность и/или написание портируемого вируса на скрипте -- весьма перспективные течения; кроме того, совсем уже близко технология недетектируемости и объединения вирусов в сеть, со всеми последствиями. И это только начало. В связи со вторжением всех этих кульных фичезов в массовое вирмэйкерское сознание, возникает вопрос об удобных инструментах, т.е. о тулзах, инклюдниках, статьях и кусках кода, которые помогут начинающему вирмэйкеру поскипать тяжелый путь от арвихакера до наших дней, и практически сразу же взять и воплотить в жизнь свои самые черные мечты. Так вот я здесь для того, чтобы помочь вам в этом нелегком, но правильном деле. Одним из таких супер-пупер инструментов является... дизассемблер. Но не тот, который софт-айс. А тот, который встроен в вирус. Применить его можно практически везде; и везде это дает нехилый эффект. Например, анализ кода и разбор его на инструкции производится последовательным вызовом дизассемблера инструкций. Дизассемблер применяется также при пермутации и интеграции кода; возможно применение в туннелинге (tunneling), т.е. в эмуляции, трассировке без выполнения. Самое же тривиальное использование дизассемблера -- это поскипать после точки входа несколько инструкций и вставить переход на вирус уже туда. Код, который (и только который) я рассматриваю -- это 32-битный x86-код. Однако, приведенный в этой статье дизассемблер может быть элементарно преобразован для работы с 16-битным кодом. Теперь возникает вопрос о том, что мы будем дизассемблировать. Другими словами, какую информацию мы желаем извлечь из потока инструкций переменных длин. Достаточно ли нам только длин инструкций; хотим ли мы кроме этого знать что-либо о префиксах, коде операции, аргументах инструкции. Когда-то давным давно (в 1997 году) я еще не знал, до какой степени универсальный дизассемблер -- полезная штука; поэтому в движке ZCME было написано так: ... inc cx ; 5 cmp al, 0EAh je @@exit cmp ax, 3E80h ; cmp [xxxx], yy je @@exit inc cx ; 6 ... Как видно, это дизассемблер, заточенный под конкретный набор инструкций; каковые и были использованы в ZCME-based вирусе. Позже, в следующем таком вирусе, дизассемблер пришлось дополнить. И в следующем через один вирусе, его тоже пришлось изменить. И так прошло три года... ;-) Короче, в конце концов я написал универсальный дизассемблер длин LDE, а позже появились несколько его модификаций, под разные задачи. Как видно из примера выше, мне были нужны одни только длины инструкций; все остальное же заключалось в проверке опкода на EB, E8, E9, 7x, 0F 8x, и т.п. Но есть случаи, когда кроме длины хочется знать о инструкции еще что-то; и тогда дизассемблер становится чуть сложнее. Например, зная, какие регистры используются в некой инструкции программы, можно вставить перед ней свои собственные: было: стало: mov eax, [ebx+4] mov eax, vir_1 add vir_2, eax mov eax, [ebx+4] А при пермутации собственного кода, знание о регистрах и использовании стэка необходимо для перемешивания вирусных инструкций между собой. Другими словами, чем больше мы знаем о инструкциях с помощью встроенного в вирус дизассемблера, тем меньше ограничено наше воображение, и тем в перспективе больше всяких интересных вещей мы можем сотворить. В конце текста приведен фрагмент кода на С++, позволяющий разобрать заданную инструкцию на составляющие. Вызывается оно так: int disasm_ok = disasm( &buf[ip] ); В результате чего из процедуры возвращается 1 если все ок, и 0, если наступил локальный пиздец -- попалась неизвестная инструкция. В случае, когда все окей, вызванная процедура разложит заданную инструкцию на составляющие, т.е. заполнит следующие переменные: DWORD disasm_len; -- длина инструкции в байтах, 0 если была ошибка DWORD disasm_flag; -- битовая маска, флаги, C_xxx C_66 -- есть префикс 66 C_67 -- есть префикс 67 C_LOCK -- есть префикс lock (F0) C_REP -- есть префикс repz/repnz, значение в disasm_rep C_SEG -- есть сегментный префикс, значение в disasm_seg C_OPCODE2 -- есть второй опкод (первый был равен 0x0F), значение в disasm_opcode2 C_MODRM -- есть байт modrm, значение в disasm_modrm C_SIB -- есть байт sib, значение в disasm_sib DWORD disasm_memsize; -- длина адреса памяти, используемого в инструкции, значение в disasm_mem BYTE disasm_mem[8]; -- адрес (длина в disasm_memsize) DWORD disasm_datasize; -- длина числового аргумента-данных (в байтах), значение в disasm_data BYTE disasm_data[8]; -- данные (длина в disasm_datasize) BYTE disasm_seg; -- C_SEG: сегментный префикс (CS DS ES SS FS GS) BYTE disasm_rep; -- C_REP: префикс REPZ/REPNZ BYTE disasm_opcode; -- собственно опкод, присутствует всегда BYTE disasm_opcode2; -- C_OPCODE2: второй опкод (если первый был 0x0F) BYTE disasm_modrm; -- C_MODRM: байт modxxxrm BYTE disasm_sib; -- C_SIB: байт sib Сборка инструкции из всего вышеприведенного отстоя, выглядит так: if (disasm_flag & C_66) *outptr++ = 0x66; if (disasm_flag & C_67) *outptr++ = 0x67; if (disasm_flag & C_LOCK) *outptr++ = 0xF0; if (disasm_flag & C_REP) *outptr++ = disasm_rep; if (disasm_flag & C_SEG) *outptr++ = disasm_seg; *outptr++ = disasm_opcode; if (disasm_flag & C_OPCODE2) *outptr++ = disasm_opcode2; if (disasm_flag & C_MODRM) *outptr++ = disasm_modrm; if (disasm_flag & C_SIB) *outptr++ = disasm_sib; for (DWORD i=0; i case 0x04: case 0x05: case 0x0C: case 0x0D: case 0x14: case 0x15: case 0x1C: case 0x1D: case 0x24: case 0x25: case 0x2C: case 0x2D: case 0x34: case 0x35: case 0x3C: case 0x3D: if (disasm_opcode & 1) disasm_datasize += disasm_defdata; else disasm_datasize++; break; case 0x6A: case 0xA8: case 0xB0: case 0xB1: case 0xB2: case 0xB3: case 0xB4: case 0xB5: case 0xB6: case 0xB7: case 0xD4: case 0xD5: case 0xE4: case 0xE5: case 0xE6: case 0xE7: case 0x70: case 0x71: case 0x72: case 0x73: case 0x74: case 0x75: case 0x76: case 0x77: case 0x78: case 0x79: case 0x7A: case 0x7B: case 0x7C: case 0x7D: case 0x7E: case 0x7F: case 0xEB: case 0xE0: case 0xE1: case 0xE2: case 0xE3: disasm_datasize++; break; case 0x26: case 0x2E: case 0x36: case 0x3E: case 0x64: case 0x65: if (disasm_flag & C_SEG) return 0; disasm_flag |= C_SEG; disasm_seg = disasm_opcode; goto retry; case 0xF0: if (disasm_flag & C_LOCK) return 0; disasm_flag |= C_LOCK; goto retry; case 0xF2: case 0xF3: if (disasm_flag & C_REP) return 0; disasm_flag |= C_REP; disasm_rep = disasm_opcode; goto retry; case 0x66: if (disasm_flag & C_66) return 0; disasm_flag |= C_66; disasm_defdata = 2; goto retry; case 0x67: if (disasm_flag & C_67) return 0; disasm_flag |= C_67; disasm_defmem = 2; goto retry; case 0x6B: case 0x80: case 0x82: case 0x83: case 0xC0: case 0xC1: case 0xC6: disasm_datasize++; disasm_flag |= C_MODRM; break; case 0x69: case 0x81: case 0xC7: disasm_datasize += disasm_defdata; disasm_flag |= C_MODRM; break; case 0x9A: case 0xEA: disasm_datasize += 2 + disasm_defdata; break; case 0xA0: case 0xA1: case 0xA2: case 0xA3: disasm_memsize += disasm_defmem; break; case 0x68: case 0xA9: case 0xB8: case 0xB9: case 0xBA: case 0xBB: case 0xBC: case 0xBD: case 0xBE: case 0xBF: case 0xE8: case 0xE9: disasm_datasize += disasm_defdata; break; case 0xC2: case 0xCA: disasm_datasize += 2; break; case 0xC8: disasm_datasize += 3; break; case 0xF1: return 0; case 0x0F: disasm_flag |= C_OPCODE2; disasm_opcode2 = *opcode++; switch (disasm_opcode2) { case 0x00: case 0x01: case 0x02: case 0x03: case 0x90: case 0x91: case 0x92: case 0x93: case 0x94: case 0x95: case 0x96: case 0x97: case 0x98: case 0x99: case 0x9A: case 0x9B: case 0x9C: case 0x9D: case 0x9E: case 0x9F: case 0xA3: case 0xA5: case 0xAB: case 0xAD: case 0xAF: case 0xB0: case 0xB1: case 0xB2: case 0xB3: case 0xB4: case 0xB5: case 0xB6: case 0xB7: case 0xBB: case 0xBC: case 0xBD: case 0xBE: case 0xBF: case 0xC0: case 0xC1: disasm_flag |= C_MODRM; break; case 0x06: case 0x08: case 0x09: case 0x0A: case 0x0B: case 0xA0: case 0xA1: case 0xA2: case 0xA8: case 0xA9: case 0xAA: case 0xC8: case 0xC9: case 0xCA: case 0xCB: case 0xCC: case 0xCD: case 0xCE: case 0xCF: break; case 0x80: case 0x81: case 0x82: case 0x83: case 0x84: case 0x85: case 0x86: case 0x87: case 0x88: case 0x89: case 0x8A: case 0x8B: case 0x8C: case 0x8D: case 0x8E: case 0x8F: disasm_datasize += disasm_defdata; break; case 0xA4: case 0xAC: case 0xBA: disasm_datasize++; disasm_flag |= C_MODRM; break; default: return 0; } // 0F-switch break; } //switch if (disasm_flag & C_MODRM) { disasm_modrm = *opcode++; BYTE mod = disasm_modrm & 0xC0; BYTE rm = disasm_modrm & 0x07; if (mod != 0xC0) { if (mod == 0x40) disasm_memsize++; if (mod == 0x80) disasm_memsize += disasm_defmem; if (disasm_defmem == 2) // modrm16 { if ((mod == 0x00)&&(rm == 0x06)) disasm_memsize+=2; } else // modrm32 { if (rm==0x04) { disasm_flag |= C_SIB; disasm_sib = *opcode++; rm = disasm_sib & 0x07; } if ((rm==0x05)&&(mod==0x00)) disasm_memsize+=4; } } } // C_MODRM for(DWORD i=0; i