你如何构建一个虚拟机来解释机器码?

逆向工程 部件 x86 机器码 虚拟机
2021-06-11 14:08:41

解析器如何解析机器码?它是一个字节一个字节的吗?解析指令时它如何知道要读取多少字节?它是否有某种字节表可以通过表直接将机器代码转换为像 AST 这样的程序集?

我开始了解如何从程序集生成机器代码,但是您如何从机器代码到程序集,从机器代码到 VM 使用的 AST?一般原则是什么?

是否有任何开源项目可以为 x86 演示这一点?我在 GitHub 上似乎有很多“x86 vms”解释汇编指令,但没有一个直接解释机器代码。我想这将是某种逆向工程项目(也许是一个?),但不确定在哪里看。即使是采用机器代码并将其转换为汇编字符串的东西也很有价值,类似于objdump 的东西,但最好是在 JavaScript/Node.js 中:)

看起来是一个好的开始,这是标准吗?

void
xed_instruction_length_decode(xed_decoded_inst_t* ild)
{
    prefix_scanner(ild);
#if defined(XED_AVX) 
    if (xed3_operand_get_out_of_bytes(ild)) 
        return;
    vex_scanner(ild);
#endif
#if defined(XED_SUPPORTS_AVX512) || defined(XED_SUPPORTS_KNC)

    // evex scanner assumes it can read bytes so we must check for limit first.
    if (xed3_operand_get_out_of_bytes(ild))
        return;

    // if we got a vex prefix (which also sucks down the opcode),
    // then we do not need to scan for evex prefixes.
    if (!xed3_operand_get_vexvalid(ild) && chip_supports_avx512(ild)) 
        evex_scanner(ild);
#endif

    if (xed3_operand_get_out_of_bytes(ild))
        return;
#if defined(XED_AVX)
    // vex/xop prefixes also eat the vex/xop opcode
    if (!xed3_operand_get_vexvalid(ild) &&
        !xed3_operand_get_error(ild)     )
        opcode_scanner(ild);
#else
    opcode_scanner(ild);
#endif
    modrm_scanner(ild);
    sib_scanner(ild);
    disp_scanner(ild);
    imm_scanner(ild);
}

看起来需要很多处理才能弄清楚指令。

但遗憾的是,缺少某些功能源代码,例如xed3_operand_get_out_of_bytes...

1个回答

是的,解析器可以逐字节解析
x86 指令最大 15 字节

因此,如果它看到 15 个字节的 0x66 (PREFIX BYTE) 一个后跟另一个
字节,它将丢弃 13 个字节,将第 14 个字节视为 VALID PREFIX 并反汇编第 14 个和第 15 个字节
(对于所有 LEGACY_PREFIX(2e,...67) ,REX_FAMILY 都相同x64 (0x40,0x4f)

查看 python poc

from capstone import *
CODE = [
b"\x66\x66\x66\x66\x66\x66\x66\x66\x66\x66\x66\x66\x66\x66\x90\x90",
b"\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x90\x90",
b"\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x90\x90"
]
print("\nCODE[0] parsed notice the Address of Successive instructions")
for i in (Cs(CS_ARCH_X86,CS_MODE_64).disasm(CODE[0],0x10000000)):
    print("0x%x:\t%s\t%s" %(i.address, i.mnemonic, i.op_str))
print("\nCODE[1] parsed notice the Address of Successive instructions")
for i in (Cs(CS_ARCH_X86,CS_MODE_64).disasm(CODE[1],0x10000000)):
    print("0x%x:\t%s\t%s" %(i.address, i.mnemonic, i.op_str))
print("\nCODE[2] parsed notice the Address of Successive instructions")
for i in (Cs(CS_ARCH_X86,CS_MODE_64).disasm(CODE[2],0x10000000)):
    print("0x%x:\t%s\t%s" %(i.address, i.mnemonic, i.op_str)) 


:\>python dis64.py

CODE[0] parsed notice the Address of Successive instructions
0x10000000:     nop
0x1000000f:     nop

CODE[1] parsed notice the Address of Successive instructions
0x10000000:     xchg    eax, r8d
0x1000000f:     nop

CODE[2] parsed notice the Address of Successive instructions
0x10000000:     nop
0x1000000f:     nop

解析操作码是一个复杂的过程(x86/x86_64 的 cisc 指令)

手动解码随机字节流

一个简单的 1 字节操作码,立即数为 4 字节

>>> "{0:040b}".format(0x3dffffffff)
'0011110111111111111111111111111111111111'
========================================================
  0x3d       0xff       0xff       0xff       0xff
76543210 | 76543210 | 76543210 | 76543210 | 76543210
00111101 | 11111111 | 11111111 | 11111111 | 11111111
------ds | modregrm sib immediate etc follows

简单天真的解析器动作就像

first 6 bits 001111  using a look up table this is a CMP mnemonic 

(0x3c,al,imm8 or 0x3d eax,imm32)

7th bit dbit = 0 a register
8th bit sbit = 1 so 32 bit register so takes a  32 bit wide immediate 

so this will be 
CMP EAX,0xffffffff

检查一些已知的实现

windbg
0:000> eb . 3d ff ff ff ff;u . l 1
ntdll!LdrpDoDebuggerBreak+0x2c:
778b05a6 3dffffffff      cmp     eax,0FFFFFFFFh
0:000>

objdump
:\>echo "\x3dffffffff" | xxd -r -p > foo.bin
:\>xxd -g 1 foo.bin
00000000: 3d ff ff ff ff                                   =....
:\>objdump.exe -b binary -mi386 -D foo.bin

foo.bin:     file format binary
Disassembly of section .data:
00000000 <.data>:
   0:   3d ff ff ff ff          cmp    $0xffffffff,%eax

你也可以使用 capstone,gdb,llvm,distorm,xed,.......如上所述