如何编写反汇编器?

72

我有兴趣编写一个x86反汇编器作为教育项目。

我找到的唯一真正的资源是Spiral Space的"如何编写反汇编器"。虽然它提供了关于反汇编器各个组件的良好高层级描述,但我对更详细的资源感兴趣。我还快速查看了NASM的源代码,但这有点难以学习。

我意识到这个项目的主要挑战之一是我将要处理相当大的x86指令集。我也对基本结构、基本反汇编器链接等感兴趣。

有人能指向任何关于编写x86反汇编器的详细资源吗?


不是答案,但是 http://stackoverflow.com/questions/82432/is-learning-assembly-language-worth-the-effort 上的答案对于那些刚开始学习的人来说也是一个不错的阅读材料。 - claws
5个回答

69

请参阅80386程序员参考手册17.2章节。反汇编器实际上只是一种被美化的有限状态机。反汇编的步骤如下:

  1. 检查当前字节是否为指令前缀字节(F3F2F0);如果是,则有一个REP/REPE/REPNE/LOCK前缀。继续到下一个字节。
  2. 检查当前字节是否为地址大小字节(67)。如果是,则在16位模式下解码指令的其余部分,如果当前处于32位模式,则在32位模式下解码地址。
  3. 检查当前字节是否为操作数大小字节(66)。如果是,则在16位模式下解码立即数操作数,如果当前处于32位模式,则在32位模式下解码立即数操作数。
  4. 检查当前字节是否为段覆盖字节(2E363E266465)。如果是,则使用相应的段寄存器来解码地址,而不是默认的段寄存器。
  5. 下一个字节是操作码。如果操作码是0F,则它是扩展操作码,并读取下一个字节作为扩展操作码。
  6. 根据特定的操作码,读入并解码Mod R/M字节、比例索引基址(SIB)字节、位移(0、1、2或4字节)和/或立即值(0、1、2或4字节)。这些字段的大小取决于先前解码的操作码、地址大小覆盖和操作数大小覆盖。

操作码告诉您正在执行的操作。操作码的参数可以从Mod R/M、SIB、位移和立即值的值中解码。由于x86的复杂性,存在许多可能性和特殊情况。有关更详细的说明,请参见上面的链接。


2
英特尔手册表示“组1到4可以相对于彼此以任何顺序放置”,因此步骤1-4可能不按照那个顺序。 - szx
2
这对于x86是正确的,但对于x86_64或现代指令集(如AVX / AVX2)则不是。 - phuclv

24
我建议查看一些开源的反汇编工具,最好是distorm和特别是"disOps(指令集数据库)"(在页面上按ctrl +查找)。文档本身充满了关于操作码和指令的有趣信息。
引用自https://code.google.com/p/distorm/wiki/x86_x64_Machine_Code

80x86 Instruction:

A 80x86 instruction is divided to a number of elements:

  1. Instruction prefixes, affects the behaviour of the instruction's operation.
  2. Mandatory prefix used as an opcode byte for SSE instructions.
  3. Opcode bytes, could be one or more bytes (up to 3 whole bytes).
  4. ModR/M byte is optional and sometimes could contain a part of the opcode itself.
  5. SIB byte is optional and represents complex memory indirection forms.
  6. Displacement is optional and it is a value of a varying size of bytes(byte, word, long) and used as an offset.
  7. Immediate is optional and it is used as a general number value built from a varying size of bytes(byte, word, long).

The format looks as follows:

/-------------------------------------------------------------------------------------------------------------------------------------------\
|*Prefixes | *Mandatory Prefix | *REX Prefix | Opcode Bytes | *ModR/M | *SIB | *Displacement (1,2 or 4 bytes) | *Immediate (1,2 or 4 bytes) |
\-------------------------------------------------------------------------------------------------------------------------------------------/
* means the element is optional.
数据结构和解码阶段在https://code.google.com/p/distorm/wiki/diStorm_Internals中有详细说明。
引用:
解码阶段:
  1. [前缀]
  2. [提取操作码]
  3. [过滤操作码]
  4. [提取操作数]
  5. [文本格式化]
  6. [十六进制转储]
  7. [已解码指令]
每个步骤也都有详细的说明。

出于历史原因,原始链接被保留:

http://code.google.com/p/distorm/wiki/x86_x64_Machine_Codehttp://code.google.com/p/distorm/wiki/diStorm_Internals


1
+1 这些是一些非常好的指针。 - claws
3
"Optional mandatory prefix" 看起来很有趣。 - Calmarius
1
看起来链接已经失效了。 - szx

6
从一个已经编写好的小程序开始,获取生成的代码和指令。查看指令结构的参考资料,并手动使用这些参考资料分析部分生成的代码。你会发现指令具有一种非常典型的inst op op op结构,其中操作数数量不同。你需要做的就是翻译代码的十六进制或八进制表示以匹配指令;稍微尝试一下就可以找到正确的方式。
这个过程自动化后即为反汇编器的核心。理想情况下,您可能需要在内部(或外部,如果程序很大)构建一组指令结构的数组。然后,您可以将该数组转换为汇编格式的指令。

4
你需要加载的是一张操作码表。
最基本的查找数据结构是Trie树,但是如果你不太在意速度,用表格也可以。
要获取基本的操作码类型,在表格上执行以其开头的匹配。
有几种常见的解码寄存器参数的方法;但是,由于有足够多的特殊情况需要单独实现,因此大部分都需要进行个别实现。
由于这是教育性的内容,请看一下ndisasm。

2

查看objdump源代码——这是一个很棒的工具,它包含了许多操作码表,而且它的源代码可以为制作自己的反汇编器提供良好的基础。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接