воскресенье, 30 октября 2011 г.

Пример некачественной эмуляции инструкций в Dr.Web

Виртуальные машины есть везде, в протекторах исполняемых файлов(VMprotect, Themida etc), в антивирусах(Dr.Web, KAV etc), в специальных средах эмулирующих ОС(VmWare, VirtualPc etc), и даже в самих ОС(эмуляция инструкций V8086 режима в винде).

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

Dr.Web эмулирует сравнительно чесно, есть гигантская ф-ция в которой разбираются префиксы инструкции, обрабатываются опкоды, путем сопоставления их и обработчиков в таблицах. Имеется внутренняя память, виртуальные базовые и сегментные регистры и прочее, все это добро лежит в одной структуре, довольно большой, её я приводить не буду.

Почему я говорю про сравнительно чесную эмуляцию? Потому, что не абсолютно все инструкции эмулируются, а также потому, что есть пропуски групп инструкций(для увеличения быстродействия):

nop_handler:

    cmp     dword ptr [ebx+4890h], 0
    jz      short nop_skip_cycle
    mov     ecx, [esp+138h+var_120]
    inc     ecx
    inc     ebp
    mov     [esp+138h+var_120], ecx
    jmp     next

nop_skip_cycle:                        

    inc     [esp+138h+var_120]
    mov     cl, [ebp+1]
    inc     ebp
    cmp     cl, 90h
    jz      short nop_skip_cycle ; в цикле скипаются nop'ы идущие подрят
    jmp     next

Рассмотрим теперь обработчик инструкции bswap, которая и содержит баг в эмуляции:

    xor     eax, eax       
    mov     al, [ebp+1]
    mov     edx, [esi+eax*4-320h] ; esi = VM context, edx = VM_reg
    mov     [esp+138h+saved_reg], edx
    mov     ecx, [esp+138h+saved_reg]
    shr     edx, 8       
    shr     ecx, 18h  
    and     edx, 0FF00h
    or      ecx, edx   
    mov     edx, [esp+138h+saved_reg]
    shl     edx, 8        
    and     edx, 0FF0000h 
    or      ecx, edx      
    mov     edx, [esp+138h+saved_reg]
    shl     edx, 18h      
    and     edx, 0FF000000h
    or      ecx, edx      
    mov     [esi+eax*4-320h], ecx
    mov     edx, [esp+138h+var_120]
    add     edx, 2
    mov     [esp+138h+saved_reg], ecx
    mov     [esp+138h+var_120], edx
    jmp     next

Или, для удобочитаемости, псевдокод:

    edx = VM_reg;
    saved_reg = edx;
    ecx = saved_reg;
    edx >>= 8;
    ecx >>= 0x18;
    edx &= 0xFF00;
    ecx |= edx;
    edx = saved_reg;
    edx <<= 8;
    edx &= 0xFF0000;
    ecx |= edx;
    edx = saved_reg;
    edx <<= 0x18;
    edx &= 0xFF000000;
    ecx |= edx;
    VM_reg = ecx;
    instr_length += 2;
    goto next_opcode

То есть, казалось бы, все эмулируется как надо, например, было в регистре значение 12345678h, после эмуляции станет 78563412h, казалось бы все хорошо, эмуляция прямо по интеловским манам, никаких багов тут не видно.

Однако, если взять инструкцию, полудокументированную, например:

66 0fc8: bswap ax - которая обнулит младшее слово, то станет ясно, что эмуляция такого не учитывает и проэмулирует это криво.

Пример на масме(так как он тоже не знает про такую инструкцию - конструируем её сами):

start:
    mov eax, 11112222h
    mov byte ptr [lbl], 66h
lbl:
    nop
    bswap eax
    ret

После эмуляции bswap, eax будет равен 22221111h
На реальной машине, eax будет равен 11110000h

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

2 комментария:

  1. Цитата:
    "To swap bytes in a word value (16-bit register), use the XCHG instruction. When the BSWAP instruction references a 16-bit register, the result is undefined."

    ОтветитьУдалить
  2. Документация это здорово, до тех пор, пока она не противоречит реальности.

    ОтветитьУдалить