Виртуальные машины есть везде, в протекторах исполняемых файлов(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
Конечно, винить программистов Др.Веб наверное не стоит, ибо в мануале про это упомянуто вскользь, однако мне почему-то кажется, что если покопать поглубже можно найти еще много всего интересного.
Способы эмуляции также отличаются у перечисленных програмных продуктов, ктото пытается чесно эмулировать, ктото перекомпилирует на лету и сканирует внутренние буфера на предмет интересующих инструкций, а остальное пускает на исполнение, ктото трейсит. Качество эмуляции зависит от многих факторов, в данной заметке я поверхностно рассмотрю эмулятор антивируса 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
Конечно, винить программистов Др.Веб наверное не стоит, ибо в мануале про это упомянуто вскользь, однако мне почему-то кажется, что если покопать поглубже можно найти еще много всего интересного.
Цитата:
ОтветитьУдалить"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."
Документация это здорово, до тех пор, пока она не противоречит реальности.
ОтветитьУдалить