从二进制代码解析操作码

逆向工程 拆卸
2021-06-17 08:41:55

我一直在使用Distorm库,使用distorm_decode()x64 中函数从二进制代码中检索操作码和操作数。例如,44 0F45 C8被解码为cmovne r9d,eax.

我想有一种方法来解析十六进制二进制指令以提取它的操作码部分,在示例中44 *0F45* C8,想提取*0F45*哪个是cmovne操作码部分。

我正在看这里,我想也许会在给定的十六进制操作码中查找,给定一个助记符字符串。

是否有任何其他内置库(例如 Distorm)可以实现此目的?据我了解,Distorm具有distorm_decompose的功能和接口,但我不能达到的cmovne二进制表示*0F45*struct _DInst从调用返回。

2个回答

基本上,您常用的 x86_64 Intel CISC 指令有 2 种不同的操作码格式 legacy 和 VEX。


常用操作码

包括最常用的操作码的遗留操作码包括:

  • 1 - 4 字节的旧前缀,可选
  • 有时需要的 REX 前缀的 1 个字节
  • 1 - 3 个字节的操作码
  • 1 - 18 字节的附加操作数/模式数据

如果我理解正确,您只对 1 - 3 个字节的序列感兴趣为了从单个指令中提取它,您需要确定前缀是否“结束”以及操作码的长度。

第二点很简单,操作码模式是这样的:让 # 表示单个字节一个字节序列是遗留的 x86 操作码只能是以下模式之一:

  • #
  • 0x0F #← 这是您示例中的模式
  • 0x0F 0x3A #

第二部分看起来更棘手,因为理论上这 5 个前缀字节(传统 + 前缀)有很多组合。

某些操作使用旧前缀字节来标记另一种含义,例如对其他内存段进行操作,这是一个有限的集合,包括(根据):0xF0、0xF2、0xF3、0x2E、0x36、0x3E, 0x26、0x64、0x65、0x66 和 0x67

因此,应忽略这些字节开头的 1-4 字节序列。

REX 前缀用于在长模式下运行,通常在存在 64 位寄存器操作数或使用扩展寄存器 R8 - R15 时使用。由于这也是一个前缀,因此也应该被忽略 REX 前缀总是以0100作为字节最高有效半字节 (0x4X)的位序列开始

尽管它并未涵盖所有指令,但对于您遇到的大多数日常 x86 指令,这应该足够了。


例子

让我们应用上面的方法来分析指令: 0x44 0F 45 C8

首先是前缀,0x44 不在遗留集合中,但从最重要的(左)半字节 4 位可以明显看出这是一个 REX 前缀。(此处为 0100 0100)。这意味着我们已经完成了前缀,接下来是操作码,0x0F 和一个字节,完成匹配 2 字节操作码,并且确实0F45如您可以验证的那样剩余的尾随操作码数据将被忽略。完毕。

另一种类型的运算是 VEX / XOP,主要用于向量 / 浮点运算,但它们的检测更为复杂。您可以在此处了解有关它们的更多信息

埃利安发布了一个很好的答案,解释了这个理论

这是一个使用 capstone 和 python 的实现

import sys
import capstone

print ( "dissecting a stram of hex pairs to its components in x86")

if(len(sys.argv) < 2):
    sys.exit("usage python %s quoted_hex_pairs like %s" % ( sys.argv[0] ,
    "\"F0 0D 15 F0 0D BA D0 12 50 0D\""))

CODE = []
a = sys.argv[1].split(' ')
for i in range(0,len(a),1):
    CODE.append( chr(int(a[i],16)))
CODESTR = ''.join(CODE)

md = capstone.Cs(capstone.CS_ARCH_X86, capstone.CS_MODE_64)
md.detail = True

for i in md.disasm(CODESTR, 0x1000):
    print "TLDR:"
    for j in range( 0,len(i.opcode),1):
        if(i.opcode[j] !=0):
            print "%02x" % i.opcode[j],
    print "\n"            
    print ( "FULL DETAILS"              )
    print ( "i.address",    i.address   );    print ( "i.mnemonic",   i.mnemonic    ) 
    print ( "i.op_str" ,    i.op_str    );    print (   "i.id" ,        i.id        ) 
    print ( "i.size" ,      i.size      );    print (   "i.bytes" ,     i.bytes     ) 
    print ( "i.prefix" ,    i.prefix    );    print (   "i.opcode" ,    i.opcode    )
    print ( "i.rex" ,       i.rex       );    print (   "i.addr_size" , i.addr_size ) 
    print ( "i.modrm" ,     i.modrm     );    print (   "i.sib" ,       i.sib       ) 
    print ( "i.disp" ,      i.disp      );    print (   "i.sib_index" , i.sib_index ) 
    print ( "i.sib_scale" , i.sib_scale );    print (   "i.sib_base" ,  i.sib_base  )
    print ( "i.sse_cc" ,    i.sse_cc    );    print (   "i.avx_cc" ,    i.avx_cc    ) 
    print ( "i.avx_sae" ,   i.avx_sae   );    print (   "i.avx_rm" ,    i.avx_rm    ) 

输出

python capstest.py "44 0f 45 c8"
dissecting a stram of hex pairs to its components in x86
TLDR:
0f 45

FULL DETAILS
('i.address', 4096L)
('i.mnemonic', u'cmovne')
('i.op_str', u'r9d, eax')
('i.id', 83L)
('i.size', 4)
('i.bytes', bytearray(b'D\x0fE\xc8'))
('i.prefix', [0, 0, 0, 0])
('i.opcode', [15, 69, 0, 0])
('i.rex', 68)
('i.addr_size', 8)
('i.modrm', 200)
('i.sib', 0)
('i.disp', 0)
('i.sib_index', 0L)
('i.sib_scale', 0)
('i.sib_base', 0L)
('i.sse_cc', 0L)
('i.avx_cc', 0L)
('i.avx_sae', False)
('i.avx_rm', 0L)