反汇编引擎在稍微系统一点的软件上都有应用。要写反汇编引擎首先要知道每个平台的编码规则。当然我们要了解的就是INTEL的IA-32编码规则了。这里发下牢骚,INTEL的编码说真的一点也不清爽。要做反汇编引擎RISC(精简指令集)指令集要简单的多,像IA-32这种CISC(复杂指令集)写起来要稍微比前者麻烦点。
 
<LOADing 正文, Please Wait... ^_^>
目录:
1 --- 什么是指令
2 --- 什么是prefix(前缀)
3 --- 什么是opcode
4 --- 什么是ModRM
5 --- 什么是SIB
6 --- 总结一下
 
正文:
 
1) 什么是指令
我们通常编写应用程序的指令通常就是两种,一种就是X86指令,一种就是X87指令(浮点指令)。统一都被称为x86指令,大家用汇编写应用程序的就是了。
类似 mov ecx, dword ptr [4*eax+12345678h]这种就是了。其实这是给人看的,给计算机看那实际上是8B0C85 78563412。前者只不过是否则的一个记号而已。我们写反汇编引擎的目的就是分析后面的数字将其翻译成前者。这后面的数字串是有一定格式的。这个格式是:
[前缀][opcode][ModRM][SIB][偏移][立即数]这6个部分,它们之中除了opcode是必须的,这个区域也可以当做是指令,其余的几个区域可以看做是修饰和补充它的。但是他们的顺序是不变的。
这6个区域的顺序不能颠倒。而且大家还要记住的是只要有SIB前面肯定是ModRM,但是有ModRM不一定有SIB.SIB可以说是对ModRM的一个补充.而后面的偏移域也是在ModRM出现才有可能出现,否则也不出现,但是有ModRM也不一定有偏移。后面文章中有详细解释。最后的立即数是跟的opcode走的。有些opcode要度 有些不需要读。现在这个规则记住,后面就好理解了。
例如我们上面的 8B0C85 78563412 这条指令, 这条指令是没有前缀的 8B 是它的opcode
0C是它的ModRM,85是它的SIB, 78563412 是偏移。
8B这个代表的就是mov, 0C代表使用 ecx, dword ptr [XXXX], 而85就是XXXX = 4*eax+12345678h。 这里我们稍微了解一下。 后面慢慢的讲解。 
 
2 --- 什么是prefix(前缀)
这区域可以看做是一个副词来形容opcode(动词). 这样比喻感觉挺合适的。
这个区域就以下几个,所以可以很轻松的从指令中分辨出他们。
LOCK(F0h): 这个前缀在常用中出现几率几乎为0,他是为了在多处理器的条件下确保唯一指令访问
共享内存用的。
REPNZ(F2h):这个和F3h只在串指令中出现。用于做循环使用。
REPZ(F3h)
 
CS(2Eh):这到GS都是用来进行换段来用的。我们写壳用到最多的应该是FS了从FS寄存器获取PEB
等。反汇编看一下一般指令的第一个字节就都是64h了。
SS(36h)
DS(3Eh)
ES(26h)
FS(64h)
GS(65h)
 
66:这个前缀是用做指令长度切换的。如果你当前是在32位下那么它将你切换到16位下,如果是16位反之,记住他是切换不是改变。。。 因为我们通常是在32下工作所以可以直接默认它是改变到16位下的。这样写反汇编引擎的时候会方便一些。例如:mov eax, 1 他的编码是 B8 01000000, 如果是mov ax, 1那么的编码为 66 B8 0100 可以看到前面多了一个66的作用。
但是如果要操作8位数 例如 mov al, 1就是B0 01了。 INTEL的设计师们将操作1个字节数的指令都统一给它们设计一套opcode.而16位和32位的就相同了。其中用66开始切换
67:这个前缀也很强大,它也是用作切换的,不过是地址的切换,道理和66一样。这个前缀是影响SIB位的。下文SIB一节会有详细的解释。
 
这些前缀就介绍完了,还有一个要说明的是,前缀不一定是一个的。有可能是多个例如 LOCK REP STOSB这样。一般最多也就两个。很少有比两个再多的。 大多数情况下至少99%都是一个前缀。 
 
3 --- 什么是opcode
在上一节我把opcode形容为动词,这个一点也不过分,它其实就是指令。翻看INTEL的官方文档可以看到N条指令。当然有些指令可能是我们这辈子都用不上的。这节需要了解的是opcode不一定为1,有可能一条指令有2到3个opcode.例如浮点指令FRNDINT就为D9 FC两个opcode。ModRM有
时候也为opcode进行扩充,例如80h这个opcode 它在ModRM的RO位不同时也表示不同的指令。
这个规则在ModRM节将讲到。记住以上两条,这节的任务也就算达成了。 
 
4 --- 什么是ModRM
如果把opcode看做是动词,ModRM可以看做是做宾语的名词。是谓语的作用对象。
ModRM只有一个1字,不可能有多了。1个字节可以分为三个部分2 : 3 : 3. 最高的两位为Mod.
中间的3位是R/O,最后的3位是R/M. 在附件的IA-32 Architecture Instruction Set Reference A-M.pdf的2.1.3节的表2-1,2-2就是ModRM的规则图了。2-1是16位下的, 2-2是32位下的。
这个切换通过67前缀了。16位的时候用2-1。这里需要说明的是16位情况下的ModRM是没有SIB对它进行补充的。我们通过32位的图来了解一下吧。
我们解释一条吧。其余的都和这个类似
就拿刚才的mov ecx, dword ptr [4*eax + 12345678h]吧. 它的ModRM是0C。换成2 : 3 : 3的
形式就为00 : 001 : 100
这里Mod和R/M可以看做是一组
R/O可以看做是一组
大家可以查表了R/O == 001 这里代表的是ECX寄存器。(在图的右上的那个框框中查R/O)
Mod == 00 R/M == 100可以看到是[--][--] 这个是什么东西? 需要解释一下。[--][--]这个代表
使用SIB,disp32代表32位的偏移,disp8代表8位的偏移,disp16表示16位的偏移。
如果是其他的情况直接使用对应的符号就好了。如果出现[--][--]了那么我们就该查SIB表了
现在我们就把它当做[XXXX],所以这时可以解释为 mov ecx, [XXXX]。 这里就差生一个小问题了,mov [XXXX], ecx 这样的指令是怎么产生的。 其实他俩个ModRM都一样,不同的是opcode
可以试的反汇编一下就可以知道。opcode还决定R/O和R/M的操作顺序,这里指的是操作顺序,在
编码方面R/O永远在R/M前面。
R/O还有一个作用是对指令的补充,例如add byte ptr [eax], 8 这种。他的编码为8000 01
80是它的opcode 00是它的ModRM. 我们分解后得知 Mod = 00 R\O = 000 R\M = 000
这个时候Mod 和 R\M起作用查表得知为[eax], 而R\O则是对80的补充 可以这样解释 当opcode = 80h, R\O = 00的时候这个指令表示为add 从eax指向的内存中取出一个字节与一个字节相加并存入eax指向的内存中。例如当opcode = 80h R\O = 010 的时候 他表示adc byte ptr [eax], 1了。而为什么是操作1个字节,这个都是opcode决定的 ModRM只是对它的补充。 
 
5 --- 什么是SIB
SIB是对ModRM的补充了。可以把它看做是形容宾语中名词的形容词.
当在32位ModRM表中查到"[--][--]"一样的符号时候就是要读取SIB的时候了。
当Mod == 00 01 10 RM == 100 时,那么它的下一个字节就是SIB了。
SIB和ModRM一样是2 : 3 : 3的形式 最高的两位是Scaled 中间的3位是Index 最后的3位是Base
我们继续刚才没有分析完的mov ecx [XXXX], 这个时候它的SIB是85 也就是10 : 000 : 101
Scaled = 10 Index = 000 Base = 101
查表可以得知[4*eax+*] 这个*就是Base = 101的时候进行选择的了
观察表中Base所在的表格。当Base = 101的时候是*符号 其余的都是寄存器的字样 唯独没有ebp. 这个*号其实是代表 ebp 和 偏移的。 这里有个规则 如果Mod == 00 时 它就表示读取一个4字节的偏移。 如果Mod == 01 和 10的时候 它就代表ebp. Mod == 11的时候是没有SIB的
SIB可以这样的解释 Scaled是比例00表示2的0次方 01表示2的1次方 10表示2的2次方 11表示2的3次方。Index可以想成是寄存器,观察表Index中没有esp寄存器取而代之的是none,用汇编可以写一下类似 mov eax, dword ptr [4*esp]之类的语句汇编器会通知你是无效的指令。
就是这里的原因。如果Index == 100(none)那么就忽略这个值,直接从Base中取出对应的寄存器或偏移。Base顾名思义是一个基值。之间的关系是相加。所以合成后就是[4*eax+12345678h]。看过SIB的表后也就可以理解为什么[X*reg] 中的X只能取2,4,8了。 
 
6 --- 总结一下
终于把该说的都说完了。这样可以想一下IA-32的编码规则就是一个动宾短语。
什么时候该使用ModRM 什么时候R/O是辅助编码 什么时候读立即数 读立即数读多少个字节。
这些都是opcode本身决定的。所以要做反汇编引擎重要的还是对opcode这个大集合一一进行解码。
这里只讨论了IA-32的编码形式,没讨论IA-32e(64位)的编码形式,目前来讲对于写一个32位反的汇编器这些足够了。
附件中是今天下午刚写好的反汇编引擎。支持X86 和 X87(浮点)。整体来说比较清爽。
头文件与源文件加起来也只有795行,使用也很简单。
它是一个静态库,包含到你应用程序后。使用InitDisassemblEngine后
然后使用DisassemblEngine 进行反汇编了 它的第一个参数是要反汇编的内存指针,第二个参数
是一个结构,用来将分析好的指令按格式填充到这个结构中。在头文件中有这个结构的定义。这个函数最后返回这条指令的长度。
如果有什么BUG希望各位能帮我调出来并且EMAIL给我。。。

上传的附件 LDisEngine_src.rar
LDisEngine_bin.rar