七 Bit filed(一)
在我们继续学习OpCode块中的域之前,记住一个非常重要且常被使用的域--"reg field"--是非常必要的.
reg field在域mod r/m,sib,以及某些单字节OpCode内部被使用.Reg bit field有3位,所以可以包含2^3 = 8个值,对应8个通用寄存器.
000 = EAX
001 = ECX
010 = EDX
011 = EBX
100 = ESP
101 = EBP
110 = ESI
111 = EDI
Reg field也可以对应一种不同集合的寄存器--"局部"寄存器(partial registers),我们将在后面碰到的时候再详细讨论这个。现在我们只需要记住:它的前四个值表示全寄存器(full registers)的低局部寄存器,
000 = AL
001 = CL
010 = DL
011 = BL
后四个值则全寄存器的高局部寄存器,
100 = AH
101 = CH
110 = DH
111 = BH
我们先看看只有全寄存器被使用的情况(在带reg field的单字节OpCode中).在这种指令中,高5位是CODE bitfield,低3位是REG field.
我写了一个简单的用来解码带reg field单字节指令的程序,稍后我将给出有关它的详细文档.希望你们可以充分使用它,并能通过简单的浏览这些文档记住这所有的bit fields.
在之前的教程里,我们用OllyDbg 就足以实践和讨论OpCode.
现在我们来看关于指令的至关重要的一部分地址.但这个问题关系到bit field.
两个最重要的域mod r/m,sib都有bit field,你可以用大脑联想一下如何用一个或多个十六进制数字表示bit field.
bit field的格式是: 2 : 3 : 3.这意味着高2位代表一个东西,低3位代表一个东西,中间的3位代表另一个东西.但我们将它用16进制表示,它又是2个十六进制数.因为每个十六进制位由四位组成,所以对于位格式2 : 3 : 3,高十六进制位由第一个域的两位和第二个域的前两位组成,剩余的位则组成低十六进制位.
例如,在mod r/m中,如果要表示两个操作数都是寄存器,且寄存器分别是edx和edi,那么mod r/m的值是F9.如
mov edi,ecx的OpCode是8B F9,
sub edi,ecx是2B F9,
可以看到两个OpCode的modr/m相同都是F9.F9的二进制位格式是11 111 001,所以
11 -- mod(11表示操作数都是寄存器)
111 -- reg(111代表edi)
001 -- 寄存器或者内存数(001代表ecx)
关于mod r/m的另外一点是(两个操作数都是寄存器,且这寄存器是edx或者edi):也可以这样编码11 001 111,也就是 1100 1111即CF(可以看到只是111 和001的位置互换),希望这没有使你感到困惑.
如果想通过mod r/m和sib创建任意地址,我们只要知道
1.reg field 8个可能的值
2.modr/m 4个可能的值
3.sib 4个可能的值
我们还需要一些简单通用的规律,和少数的几个Exeption.
当我们学完这个,在输入指令的时候你就能够很容易的确定任意OpCode的大小(尺寸).OpCode中的地址部分是非常耗空间的.我们经常使用操作数,这意味着我们总是指定操作数或者说是它们的地址(包括寄存器).而Exeption是操作数预先定义的助记符,例如串操作指令的操作码都非常简短,这是因为它们没有地址部分.
假如我们不了解OpCode的地址部分的话,我们无法断定指令长短或者尺寸.
举个例子,下面两条指令实现同样的功能:
1. mov edx,[ecx*4]
2. xor eax,eax
Mov edx,[ecx*4][eax]
从表面看似乎第二个比第一个多一行,它的第二条指令甚至要多一个域,还多一些字符等等.好像似乎一定是第二种情况要长一些^_^.
然而,这只是我们的主观臆断而已!
第二个版本比第一个要少两个字节:
8B 14 8D 00 00 00 00 MOV EDX,[DWORD DS:ECX*4]
33 C0 XOR EAX,EAX
8B 14 88 MOV EDX,[DWORD DS:EAX+ECX*4]
通过一些不是很艰苦的工作,我们也可以在很短的时间内学会这个.
关于bit field的编码:虽然使用十六进制对位格式5 : 3或者2 : 3 : 3进行编码不是那么简单,但是在汇编源码中使用二进制是非常容易的.例如指令"inc reg"的CODE域是01000,剩下的3位是寄存器.因为
eax = 000,所以
inc eax就是
01000 000即40h.
这里唯一的问题是,在位之间加入空格是不会被汇编器接受的,所以我们应该写作01000000b.但这种方式导致我们不易区分不同的bit fields.我们可以编写一个简单的宏(macro),用它来帮助我们轻易的分开这些bit fields.我们给这个宏取名bcr(a Byte OpCode with Code and Reg fields)
Bcr macro _code,_reg
db _code & _reg & b
endm
现在我们可以用下面的写法来代替db 01000000b
Bcr 01000,000 ;inc eax
Bcr 01000,001 ;inc ecx等等
Bcr 01010,000 ;xchg eax,eax 或者 nop
Bcr 01010,001 ;xchg eax,ecx
......
Bcr 01010,111 ;xchg eax,edi等等.
同样的,我们可以编写一个宏帮助我们编码mod r/m以及sib,它们的格式是2 : 3 : 3.
Modrm macro _mod, _regcode, _rm
Db _mod & _regcode & _rm & b
Endm
由于sib有同样的格式所以我们可以定义
bsib EQU modrm.
现在我们可以很容易的看出各个field:
Modrm 11,000,101
比如
mov reg1,reg2 的CODE是8Bh,跟在它后面的是mod r/m字节,其中mod field是11.
例如:
Db 8bh
Modrm 11,000,001 ;mov eax,ecx(eax=000, ecx=001)
Db 8bh
Modrm 11,111,001 ;mov edi, ecx(edi=111)
Db 8bh
Modrm 11,001,111 ;mov ecx,edi
这里我举的例子只是说明使用二进制编码bit fields是没有问题的.
