# О ДИСАССЕМБЛИРОВАНИИ И БИТОВЫХ МАСКАХ

## ПОСТАНОВКА ЗАДАЧИ

Каждому нормальному человеку в жизни на каждом шагу встречается дисассемблирование, а тот, кто никогда не видел битовых масок - вообще не жил. ;-)
Исходя из этого, рассмотрим вопрос подробнее.

Пусть есть код x86 процессора. И пусть нам необходимо написать процедуру, дисассемблирующую (разбирающую) команды процессора (инструкции).

Здесь дисассемблировать значит решить (не все но некоторые) такие задачи, как:

- определение длины инструкции
- определение регистров, используемых в инструкции
- определение, например арифметической операции, которую выполняет инструкция и т.п.

## МЕТОДЫ ДИСАССЕМБЛИРОВАНИЯ

Поскольку (для одной инструкции) значения битов, установленных в предыдущих байтах определяют дальнейший ход разбора последующих байтов, и все инструкции переменной длины, то разбирать их придется байт за байтом.

Возможно несколько способов побайтового разбора инструкций.

### 1. Дисассемблирование с использованием данных

### 1.1. Организация массива переходов, по одному на каждый байт.

    disasm:         ...
                    lodsb                   ; взять байт - код инструкции
                    movzx   ebx, al
                    jmp     jmptable[ebx*4] ; вызвать соответствующую процедуру
                    ...
    jmptable        dd      opc_00, opc_01, ...

Такой способ достаточно быстрый, занимает много памяти, весьма удобен для различных эмуляторов, и имеет потенциальную возможность для дисассемблирования всех инструкций. (так как 256 указателей)

### 1.2. Организация массива данных

Этод способ не сильно отличается от предыдущего, за исключением того, что вместо указателей на процедуры в таблице хранятся флаги, в соответствии с которыми выполняются те или иные действия.

    disasm:         ...
                    lodsb                   ; взять байт - код инструкции
                    movzx   ebx, al
                    mov     ecx, flagtable[ebx*4] ; получить флаги
                    ...
                    test    ecx, flag_prefix  ; выполнять действия в соответствии
                    jnz     __prefix          ; с флагами
                    ...
                    test    ecx, flag_modrm
                    jnz     __modrm
                    ...
    flagtable       dd      flag_modrm+flag_s+flag_w, ...

### Примечание

Надо сказать, что возможны модификации этих способов, когда вместо таблиц для байтов будут хранится таблицы для слов.

    xxxtable        dd      65536 dup (?)   ; 256k

Лет десять назад за такие вещи сожгли бы на костре ;-), а сегодня уже не каждый скажет, что это заняло бы много памяти. Хотя для некоторых случаев можно было бы увеличить скорость дисассемблирования.

### Недостатки

1. Одной килобайтной таблицей указателей/флагов дисассемблирование отнюдь не исчерпывается.
2. Бывают такие случаи, когда нужно дисассемблировать не все инструкции процессора, а только некоторый известный их набор, и тогда создавать таблицы данных не совсем удобно.
3. Не всегда удобно хранить в программе данные. (например в случае пермутирующих вирусов)

### 2. Дисассемблирование без использования данных

Здесь обсуждается по-сути, преобразование вышеперечисленных таблиц в код.

Но вы ошибаетесь, если думаете, что я предложу сделать так:

    disasm:         ...
                    lodsb
                    ...
                    cmp     al, 00h
                    je      opc_00
                    ...
                    cmp     al, 0FFh
                    je      opc_FF
                    ...

### 2.1. Преобразование таблицы переходов в код с использованием "дерева"

Здесь идея заключается в том, чтобы таблицу переходов преобразовать в код процессора следующим образом:

    disasm:         ...
                    lodsb
                    shl     al, 1
                    jnc     opc_0xxxxxxx
                    jc      opc_1xxxxxxx
                    ...
    opc_0xxxxxxx:   shl     al, 1
                    jnc     opc_00xxxxxx
                    jc      opc_01xxxxxx
                    ...

Преимущество метода в том, что дерево вовсе не является строго "двоичным", и для битовых масок, которые содержат "любые" значения цепочка команд будет несколько короче.

Например для сегментных префиксов 26,2E,36,3E и соответствующей им битовой маски 001xx110 будут выполнены примерно следующие инструкции:

                    ...
                    test    al, 10000000b
                    jz      opc_0xxxxxxx    ; jmp
    opc_0xxxxxxx:   test    al, 01000000b
                    jz      opc_00xxxxxx    ; jmp
    opc_00xxxxxx:   test    al, 00100000b
                    jz      opc_000xxxxx    ; no jmp
                    jnz     opc_001xxxxx    ; jmp
    opc_001xxxxx:   ; здесь два элемента дерева пропущены - и в этом плюс
                    test    al, 00000100b
                    jz      opc_001xx0xx    ; no jmp
                    jz      opc_001xx1xx    ; jmp
                    ...

### 2.2. Сравнение с использованием битовых масок

В этом методе последовательно выполняются проверки битовых масок.

Итак, пусть нам нужно выявить все сегментные префиксы, как то - ES,CS,SS,DS,FS и GS.

Очевидно, что опкоды их кодируются так:

    ES: 26 00100110
    CS: 2E 00110110
    SS: 36 00101110
    DS: 3E 00111110
    FS: 64 01100100
    GS: 65 01100101

и тогда мы имеем две битовых маски: 001xx110 и 0110010x

Возникает вопрос, как вместо 6-ти сравнений и переходов сделать что-то более правильное.

Эта проблема решается достаточно просто. Например вот так:

    disasm:         ...
                    lodsb
                    ...
                    push    eax
                    and     al, 11111110    ; 64/65
                    cmp     al, 01100100
                    pop     eax
                    je      __prefix_seg
                    ...
                    push    eax
                    and     al, 11100111b   ; 26/2E/36/3E
                    cmp     al, 00100110b
                    pop     eax
                    je      __prefix_seg
                    ...

## БИТОВЫЕ МАСКИ

Но вся проблема в том, что редкий маньяк будет в ручную переделывать битовые маски из таблицы опкодов в битовые значения для команд and и cmp, ибо опкодов тьма, а набивать еще в два раза больше - совсем тяжело.

В общем, к чему я это все. Гвоздем этого текста призван стать нижеследующий макрос.

    ; --- begin CMPJ.MAC -----------------------------------------------------
    ; programmed/debugged under TASM32 5.0

    ; by default assigned BX = not AX

    cmpj_al                 equ     al      ; регистры по умолчанию
    cmpj_bl                 equ     bl
    cmpj_ax                 equ     ax
    cmpj_bx                 equ     bx

    ; примеры использования макроса:

    ; cmpj E9,label   -->   cmp al, E9 / je label
    ; cmpj 6x,label   -->   test bl,60 / jnz skip
    ;                         / test al,90 / jz label / skip:
    ; cmpj 0x,label   -->   test al,F0 / jz label
    ; cmpj xF,label   -->   test bl,0F / jz label
    ; cmpj xx,label   -->   jmp label
    ; cmpj 8x,label   -->   push eax / and al, F0
    ;                         / cmp al, 80 / pop eax / je label

    ; cmpj 100010xx,label
    ; cmpj 1xx4,label
    ; cmpj xx000xxx11111111,label

    ; виды параметров, передаваемых макросу:

    ; cmpj HH,label                  hex form, 2 digits (0..9,A..F or "x")
    ; cmpj HHHH,label                hex form, 4 digits (0..9,A..F or "x")
    ; cmpj BBBBBBBB,label            binary form, 8 digits (0..1 or "x")
    ; cmpj BBBBBBBBBBBBBBBB,label    binary form, 16 digits (0..1 or "x")

    ; lower-case hex-digits "a".."f" are available

    cmpj                    macro   mask, label
                            local   count,base,reg0,reg1,max,andmask,cmpmask,i
                            local   skip,mask0,mask1
                            count   = 0
                            irpc    c,<mask>
                              count   = count + 1
                            endm ;irpc
                            if      count eq 2
                              base    = 16
                              max     = 255
                              reg1    = cmpj_al
                              reg0    = cmpj_bl
                            elseif  count eq 4
                              base    = 16
                              max     = 65535
                              reg1    = cmpj_ax
                              reg0    = cmpj_bx
                            elseif  count eq 8
                              base    = 2
                              max     = 255
                              reg1    = cmpj_al
                              reg0    = cmpj_bl
                            elseif  count eq 16
                              base    = 2
                              max     = 65535
                              reg1    = cmpj_ax
                              reg0    = cmpj_bx
                            else
                              %out cmpj: invalid bitmask(mask) length
                              .err
                            exitm
                            endif
                            andmask = 0
                            cmpmask = 0
                            irpc    c,<mask>
                              andmask = andmask * base
                              cmpmask = cmpmask * base
                              if      ("&c" ge "0") and ("&c" le "9")
                                i       = "&c"-"0"
                              elseif  ("&c" ge "a") and ("&c" le "f")
                                i       = "&c"-"a"+10
                              elseif  ("&c" ge "A") and ("&c" le "F")
                                i       = "&c"-"A"+10
                              elseif  ("&c" eq "x") or ("&c" eq "X")
                                i       = -1
                              else
                                %out cmpj: invalid digit in bitmask(mask) -- c
                                .err
                                exitm
                              endif
                              if      i ge base
                                %out cmpj: too big digit in bitmask(mask) -- c
                                .err
                                exitm
                              endif
                              if      i ne -1
                                andmask = andmask + base-1
                                cmpmask = cmpmask + i
                              endif
                            endm;irpc
                            mask0   = cmpmask
                            mask1   = andmask xor cmpmask
                            if      andmask eq max
                              cmp     reg1, cmpmask
                              je      label
                            else
                              if      cmpmask eq 0
                                if      andmask eq 0
                                  jmp     label
                                else
                                  test    reg1, andmask
                                  jz      label
                                endif
                              else
    ;                           push    eax
    ;                           and     reg, andmask
    ;                           cmp     reg, cmpmask
    ;                           pop     eax
    ;                           je      label
                                if      mask1 eq 0
                                  test    reg0, mask0
                                  jz      label
                                else
                                  test    reg0, mask0
                                  jnz     skip
                                  test    reg1, mask1
                                  jz      label
    skip:                       endif
                              endif
                            endif
                            endm;cmpj

    ; --- end CMPJ.MAC -------------------------------------------------------

Используется макрос так: в AX загружается значение опкода и следующего за ним байта (на случай двухбайтовых проверок), в BX загружается not AX.
Первым параметром макросу подается битовая маска, вторым - имя метки, на которую передавать управление.

    disasm:         ...
                    mov     eax, [esi]
                    mov     ebx, eax
                    not     ebx
                    ...
                    cmpj    001xx000,__prefix_seg
                    cmpj    0110010x,__prefix_seg
                    ...

Вот собственно и все. Возможности макроса понятны из комментариев к нему.