汇编指令之OpCode快速入门

作者: admin 分类: 学习文档 发布时间: 2011-11-01 17:37

 最近一直被一些初学者问及有关于汇编指令的长度问题,因此为此专门撰写本文,以求为不知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

我们可以看见“CALL 00430B86”这条汇编指令竟然占用了5个字节,而“PUSH ESI”则只占用了1个字节,汇编指令的脾气犹如一只滑头的猴子一样让你摸不到头脑,它很明显的告诉了你“嘿!兄弟,你别想搞懂我!”你也许会感到很郁闷,但是我并不这么想,因为如果我要想自己搞一个反汇编引擎,或者是我要在我的壳里加上代码混淆功能……嗯,算了,就算是我想娱乐一下搞搞免杀吧,那么我终归是要搞懂它的,为什么?因为如果搞懂它的话,那么我就没办法做到这些!
很明显我们的汇编指令继承了Intel工程师的狡猾本质,为了尽可能的减少体积,所以它们的体积被设计的不尽相同。
哇哦!很多读者此时似乎已经想明白是怎么回事了,肯定是不同的指令对应的字节数不一样,恩……这样只要我们搞到一张表就可以了!不是吗?一张可以描述每个指令所用二进制码的表格,然后我们就万事大吉了。
但是很不幸,我在初次接触OpCode时也想出了这个“超级点子”,但是很可惜我的“超级点子”与各位读者的一样,并没有为我解决任何问题,请过目:

代码:

B8 01000000 MOV EAX, 1
8BC3 MOV EAX, EBX
8BC7 MOV EAX, EDI

看到了吗,一样的指令,一样的目的操作数,得到的确是完全不同的机器码……

2、这是为什么?
嗯,我想这个问题是很明显的,源操作数如果是一个寄存器的话,那么能有几种可能呢?按照规则来讲貌似只有不超过50种可能,那么如果被操作数是一个数值呢?你想想,32位能表示多少数,将其乘以2就是最终的可能性了,这么多的可能性一定不是区区两个16位数就能表示过来的。
所以说我们的OpCode的长度不是一成不变是有道理的,那么既然如此,那么既然CPU可以正确时识别它,这里面肯定有什么方法是可以计算这些的,没错!这些确实是可以计算的,而且正像我们上面所设想的那样,Intel也确实为我们准备了表格,只不过不是一张,只不过有些复杂……
首先,我们要现拥有这些,以下是我提供的一些连接,因为我们需要这些,请你下载他们:
相关文档(下载页面http://bbs.hackav.com/thread-1641-1-1.html
拥有了这些文档后,我们就可以开始“破译”它了,现在加入我们要“破译”的是“ADD EAX,1”这条指令,请各位读者跟我一起做……
我们先打开《处理器指令参考》手册(x86eas.hlp),找到汇编指令ADD,我满看到了如下解释:

引用:

注:前面的标号是笔者为了大家方便阅读而加上去的。
Opcode Instruction Description
01 04 ib ADD AL,imm8 Add imm8 to AL
02 05 iw ADD AX,imm16 Add imm16 to AX
03 05 id ADD EAX,imm32 Add imm32 to EAX
04 80 /0 ib ADD r/m8,imm8 Add imm8 to r/m8
05 81 /0 iw ADD r/m16,imm16 Add imm16 to r/m16
06 81 /0 id ADD r/m32,imm32 Add imm32 to r/m32
07 83 /0 ib ADD r/m16,imm8 Add sign-extended imm8 to r/m16
08 83 /0 ib ADD r/m32,imm8 Add sign-extended imm8 to r/m32
09 00 /r ADD r/m8,r8 Add r8 to r/m8
10 01 /r ADD r/m16,r16 Add r16 to r/m16
11 01 /r ADD r/m32,r32 Add r32 to r/m32
12 02 /r ADD r8,r/m8 Add r/m8 to r8
13 03 /r ADD r16,r/m16 Add r/m16 to r16
14 03 /r ADD r32,r/m32 Add r/m32 to r32
解释:
imm是立即数的意思,而imm8就是指8个比特大小的立即数,下面将一一对上面的简写作出解释
imm:立即数,例如01、123、0FAB等
r:寄存器,如r16就代表ax、cx等,r32就代表eax、ebx等
m:内存地址,如[01]、[123]、[0FFFF]等
r/m:寄存器或内存
ib:代表OpCode后面跟着一个byte型数值
iw:代表OpCode后面跟着一个word型数值
id:代表OpCode后面跟着一个dword型数值
/0:代表此OpCode存在ModR/M结构(后面有讲)
/r:代表此OpCode存在ModR/M结构(后面有讲)

这是什么意思呢?我们以第一条信息为例,它的意思是,如果OpCode的表现形式为04后面在跟一个字节,那么它的指令格式(Description)必然是“ADD AL,8位立即数”,例如“ADD AL,11”。
啊哈,那么问题到这就解决了,我们上面的“ADD EAX,1”符合第8行的“ADD r/m32,imm8”,那么它的OpCode就应该是“83 01”了吧……
结果估计大家已经猜到了“事情没那么简单”,实际上我们的汇编指令“ADD EAX,1”所对应的OpCode是如下玩意:

复制内容到剪贴板

代码:

83C0 01 ADD EAX, 1

我们可以看到它很神奇的多出来个“C0”不知道是干什么的,这让我们很郁闷!

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页下面找到了这些信息:

—F0H—LOCK prefix.
—F2H—REPNE/REPNZ prefix (used only with string instructions)
—F3H—REP prefix (used only with string instructions).
—F3H—REPE/REPZ prefix (used only with string instructions).
—F3H—Streaming SIMD Extensions prefix.

这都是什么意思呢?我们拿第一个来说,Intel对它的解释是锁定前缀,首先各位的汇编语言要过关,所谓的锁定就是将我们的指令变为原子指令,具体例子如下:

复制内容到剪贴板

代码:

F0:8300 01 LOCK ADD DWORD PTR DS:[EAX], 1 ; 锁定前缀
F0:0FB10A LOCK CMPXCHG DWORD PTR DS:[EDX], ECX ; 锁定前缀

这两条指令前都多了个“LOCK”,但是请注意,这只是一个特例,并不是所有的前缀都会导致汇编指令前非要加些什么,这点一定要注意。
Intel手册给了我们很多其他的前缀,功能也各不相同,本文中作者不可能对其一一进行解释,因此深入的学习就要靠各位自己的努力了。
而关于操作码,我们在上一节中已经讲了,这里不再多说,因此直接进入“ModR/M”与“SIB”中。
关于“ModR/M”,我认为它在汇编指令中应该是最难的了(虽然只是简单的查表,不过我说的是编程实现),有关于ModR/M的表格在Intel指令手册24319102.PDF的第36页,读到这里的朋友不妨先去看看。
看完后千万不要头大,我们那一条指令解释一下,就什么都清楚了,其实很简单的,我们仍然拿“add EAX,1”为例吧。
我在倒数第8行找到了目的操作数,Intel在表中描述如下:

Effective Address Mod R/M
EAX/AX/AL/MM0/XMM0 11 000

但是我们的源操作数要怎么找呢?上面一行似乎并没有符合的,这就要看我们此条汇编语句在定义时指定了那里,还记得我们在上一节中查到的信息吗:

83 /0 ib
ADD r/m32,imm8
Add sign-extended imm8 to r/m32

在上一节我仅告诉各位“/0”是代表此OpCode里存在ModR/M结构,但并没有多说什么,其实这里的“/0”就是代表此表中竖排(列)中第一排,其内容如下:

引用:

r8(/r) AL
r16(/r) AX
r32(/r) EAX
mm(/r) MM0
xmm(/r) XMM0
/digit (Opcode) 0
REG = 000

到这里,其实我们的“ModR/M”已经出来了,我们将其以“Mod”“R/M”“/digit”“REG”的方式组合到一起后,正好组合为如下数值:

/digit REG Mod R/M
0 000 11 000 = 000011000 = C0h

其实在他们的交汇处我们可以看到Intel已经帮我们算好了,真实一张贴心的表呀。
“ModR/M”解决了,还剩最后的“SIB”了,要想学习“SIB”,我们先要搞明白他什么时候会出现,因此我找到了第36页下面的注释:

NOTES:
1.The [–][–] nomenclature means a SIB follows the ModR/M byte.
2.……

注释一的大致意思是“[–][–]”表示ModR/M 后跟随有一个SIB字节,因此我们现在创造一个带有“SIB”结构的汇编指令:

复制内容到剪贴板

代码:

01048E ADD DWORD PTR DS:[ESI+ECX*4], EAX

我们重新回顾一下所学知识,首先我们分析它的指令格式如下:

01 /r
ADD r/m32,r32
Add r32 to r/m32

根据“/r”我们可以得知这是一个有“ModR/M”结构的OpCode,因此查表得出其“ModR/M”信息如下:

引用:

横排
Effective Address Mod R/M
[–][–] 00 100

竖列
r8(/r) AL
r16(/r) AX
r32(/r) EAX
mm(/r) MM0
xmm(/r) XMM0
/digit (Opcode) 0
REG = 000

结果
/digit REG Mod R/M
0 000 00 100 = 000000100 = 04h

根据“Effective Address”的“[–][–]”可知此OpCode还存在“SIB”结构,于是继续查位于Intel指令手册24319102.PDF第37页的表格。
这里我们要着重分解目的操作数“[ESI+ECX*4]”里的内容,我们可以将其分为两部分,既索引与倍率因子(或叫做比率因子)。
索引指的是基址,本例中就是ESI了,而倍率因子在本例中则是“ECX*4”,我们先从横排取得倍率因子信息如下:

引用:

Scaled Index SS Index
[ECX*4] 10 001

而后由竖排取得索引信息如下:

r32 ESI
Base= 6
Base= 110

将其组合起来就是:

SS Index Base
10 001 110 = 10001110 = 8Eh

由此,我们便成功的解析了汇编指令“ADD DWORD PTR DS:[ESI+ECX*4], EAX”。
到了这里本文也该结束了,但是各位读者需要注意的是,本文的责任就像是标题所体现的一样,只是带领大家快速入门,因此有关于很多OpCode的细节本文并没有体现出来,如果你需要深入了解的话,建议各位还是以Intel手册为蓝本手动试验,慢慢摸索。

如果觉得我的文章对您有用,请随意赞赏。您的支持将鼓励我继续创作!

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

Protected by WP Anti Spam