作者:A1Pass
时间:2010-05-20
来源:黑客反病毒 (http://bbs.hackav.com)
出处:看雪论坛 (http://bbs.pediy.com)
注意:转载请务必附带本组信息,否则侵权必究!
最近一直被一些初学者问及有关于汇编指令的长度问题,因此为此专门撰写本文,以求为不知OpCode为何物,或者正为汇编长短不一的指令而烦恼的朋友一个最为快速的指引。
其实,OpCode并不复杂,在本文中我不打算细致入微的告诉大家OpCode的原理,不会为大家带来一大堆有关于什么是定长指令、什么是变长指令的理论知识,更不会带着各位读者玩OpCode Hacking,我只会告诉你“怎么了”、“为什么”以及“如何解决”。
1、我的汇编指令怎么了?
;哦,天啊!怎么我今天突然发现汇编指令竟然是长短不一的!你还没发现吗?那么请过目:
代码:
E8 31880000 CALL 00430B86 E9 17FEFFFF JMP 00428171 8B4424 04 MOV EAX, DWORD PTR SS:[ESP+4] 85C0 TEST EAX, EAX 56 PUSH ESI 8BF1 MOV ESI, ECX
很明显我们的汇编指令继承了Intel工程师的狡猾本质,为了尽可能的减少体积,所以它们的体积被设计的不尽相同。
哇哦!很多读者此时似乎已经想明白是怎么回事了,肯定是不同的指令对应的字节数不一样,恩……这样只要我们搞到一张表就可以了!不是吗?一张可以描述每个指令所用二进制码的表格,然后我们就万事大吉了。
但是很不幸,我在初次接触OpCode时也想出了这个“超级点子”,但是很可惜我的“超级点子”与各位读者的一样,并没有为我解决任何问题,请过目:
代码:
B8 01000000 MOV EAX, 1 8BC3 MOV EAX, EBX 8BC7 MOV EAX, EDI
2、这是为什么?
嗯,我想这个问题是很明显的,源操作数如果是一个寄存器的话,那么能有几种可能呢?按照规则来讲貌似只有不超过50种可能,那么如果被操作数是一个数值呢?你想想,32位能表示多少数,将其乘以2就是最终的可能性了,这么多的可能性一定不是区区两个16位数就能表示过来的。
所以说我们的OpCode的长度不是一成不变是有道理的,那么既然如此,那么既然CPU可以正确时识别它,这里面肯定有什么方法是可以计算这些的,没错!这些确实是可以计算的,而且正像我们上面所设想的那样,Intel也确实为我们准备了表格,只不过不是一张,只不过有些复杂……
首先,我们要现拥有这些,以下是我提供的一些连接,因为我们需要这些,请你下载他们:
zip附件上传限制1000KB,而且不能上传分卷,所以大家还是到以下下载方便些:
http://bbs.hackav.com/thread-1641-1-1.html
拥有了这些文档后,我们就可以开始“破译”它了,现在加入我们要“破译”的是“ADD EAX,1”这条指令,请各位读者跟我一起做:
我们先打开《处理器指令参考》手册(x86eas.hlp),找到汇编指令ADD,我满看到了如下解释:
啊哈,那么问题到这就解决了,我们上面的“ADD EAX,1”符合第8行的“ADD r/m32,imm8”,那么它的OpCode就应该是“83 01”了吧……
结果估计大家已经猜到了“事情没那么简单”,实际上我们的汇编指令“ADD EAX,1”所对应的OpCode是如下玩意:
代码:
83C0 01 ADD EAX, 1
3、我们如何解决这个问题?
到这里,我们就要步入正轨了,通过这一节我们要搞明白那个“C0”究竟是怎么出来的。
既然要步入正轨,我们就要了解一下Intel的指令结构(在24319102.PDF的第31页),具体情况如下:
Prefixes:前缀(最多4个前缀,每个1字节,并不是必需的)
code:主操作码(1-3字节不等)
ModR/M:固定1字节大小,并不是必需的
SIB:固定1字节大小,并不是必需的
Displacement:偏移量(1、2、4字节,并不是必需的)
Immediate:立即数(1、2、4字节,并不是必需的)
由上可见,其实Intel指令格式中只有一个是必须存在的,就是“主操作码”,也就是我们在上一节查到的那堆东西。不过其他结构索然是可有可无,但是往往在某些时候它们当中的某些结构是必须添加上去的,例如上个例子中的“ADD EAX,1”就是如此。
在我们讲解Prefixes之前,首先请大家务必牢记一件事,就是OpCode的结构是绝对不能被打乱的,例如Prefixes肯定是要在code前面,而Immediate肯定是在最后面。
好了,记住上面的基本原则后,我就为大家简单讲解一下这个前缀(Prefixes)究竟做了些什么,非要把指令结构搞得这么复杂,我在24319102.PDF的第31页下面找到了这些信息:
F0HLOCK prefix.
F2HREPNE/REPNZ prefix (used only with string instructions)
F3HREP prefix (used only with string instructions).
F3HREPE/REPZ prefix (used only with string instructions).
F3HStreaming SIMD Extensions prefix.
这都是什么意思呢?我们拿第一个来说,Intel对它的解释是锁定前缀,首先各位的汇编语言要过关,所谓的锁定就是将我们的指令变为原子指令,具体例子如下:
代码:
F0:8300 01 LOCK ADD DWORD PTR DS:[EAX], 1 ; 锁定前缀 F0:0FB10A LOCK CMPXCHG DWORD PTR DS:[EDX], ECX ; 锁定前缀
Intel手册给了我们很多其他的前缀,功能也各不相同,本文中作者不可能对其一一进行解释,因此深入的学习就要靠各位自己的努力了。
而关于操作码,我们在上一节中已经讲了,这里不再多说,因此直接入“ModR/M”与“SIB”中。
关于“ModR/M”,我认为它在汇编指令中应该是最难的了(虽然只是简单的查表,不过我说的是编程实现),有关于ModR/M的表格在Intel指令手册24319102.PDF的第36页,读到这里的朋友不妨先去看看。
看完后千万不要头大,我们那一条指令解释一下,就什么都清楚了,其实很简单的,我们仍然拿“add EAX,1”为例吧。
我在倒数第8行找到了目的操作数,Intel在表中描述如下:
83 /0 ib ADD r/m32,imm8 Add sign-extended imm8 to r/m32
在上一节我仅告诉各位“/0”是代表此OpCode里存在ModR/M结构,但并没有多说什么,其实这里的“/0”就是代表此表中竖排(列)中第一排,其内容如下:
“ModR/M”解决了,还剩最后的“SIB”了,要想学习“SIB”,我们先要搞明白他什么时候会出现,因此我找到了第36页下面的注释:
代码:
01048E ADD DWORD PTR DS:[ESI+ECX*4], EAX
这里我们要着重分解目的操作数“[ESI+ECX*4]”里的内容,我们可以将其分为两部分,既索引与倍率因子(或叫做比率因子)。
索引指的是基址,本例中就是ESI了,而倍率因子在本例中则是“ECX*4”,我们先从横排取得倍率因子信息如下:
到了这里本文也该结束了,但是各位读者需要注意的是,本文的责任就像是标题所体现的一样,只是带领大家快速入门,因此有关于很多OpCode的细节本文并没有体现出来,如果你需要深入了解的话,建议各位还是以Intel手册为蓝本手动试验,慢慢摸索。