1)汇编指令与机器码并不一定是11对应关系,IDA与windbg一类反汇编软件出来的指令,并不一定完全正确,如何准确解析机器指令可参阅Intel和ADM指令手册。
2)X86所有指令的机器码长度不定,而且连续排序,因此读机器码的唯一方法是从头开始逐条解析指令。NOP指令为单字节空指令,机器码为90H,可以使用它填充修改指令以后的多余区域(用短指令替换长指令时的情况)。对于长指令来替换短指令的情况,由于空间不够,可以将替换或者修改后的指令段写入其它区域,然后在原指令段处使用JMP语句跳转到新指令处执行,执行完毕以后再跳回(windows内核补丁,运用层APIHook、破解、病毒、安全软件均使用了这样的原理)。
3)单指令格式:
前缀 (可选) |
操作码 |
MOD-REG-R/M (可选) |
SIB (可选) |
地址偏移 (可选) |
立即数 (可选) |
1、前缀:可有0~4个不重复前缀。
a)普通前缀:重复前缀、锁前缀与超越前缀
锁前缀:F0H:LOOK(锁定)---用来锁定总线,然后读写操作,主要用于自旋锁(spin lock)和多CPU同步场合。
重复前缀:F2H:REPNE/REPNZ(当ZERO标记为1,且rcx > 0 时重复)与F3H:REP/REPE/RPZ(当ZERO标记为0,且rcx > 0时重复)---用于重复操作场合(如串操作,IO操作),默认使用(r)cx作为计数器。重复前缀可作为指示性前缀用于SSE指令。
超越前缀:寄存器超越前缀(66H)与地址超越前缀(67H)---32位系统下,32位模式使用16位地址或寄存器,或者16位模式使用32位地址或寄存器则需要使用到超越前缀。
b)指示性前缀:
c)64位扩展前缀(REX前缀):64位模式下使用,紧靠操作码。REX范围40H~4FH,与32位下的INC,DEC指令冲突,所以64位下这两个指令被修改。REX前缀字节内部区:
7~4位 |
3位 |
2位 |
1位 |
0位 |
REX,必须为0100B |
W |
R |
X |
B |
W位指定宽度(width),与66H前缀(寄存器超越前缀)组合为64位下指令操作数长度:
REX.W |
有66H前缀的情况 |
默认操作数长度 |
1 |
被忽略 |
64位 |
0 |
16位 |
32位 |
0 |
32位 |
16位 |
R代表寄存器(Register),用于扩展MOD-REG-R/M位。
X代表indeX,用于扩展SIB索引字节。
B代表Base,用于扩展MOD-REG-R/M位或者SIB基域。
2、操作码:1~2个字节,如下格式(并不是全部操作码都是如此)
7~2位,操作码 |
D |
W |
数据传输时(串操作或者IO操作),传输的长度对应寄存器使用长度,而寄存器使用长度是由CPU工作模式以及超越前缀决定的。在W = 0的情况下,无论寄存器使用长度为多少,每次传输一个字节的数据;D表示传输方向,D = 1时,数据从R/M(寄存器或者内存)传输到REG(寄存器),当D = 0 时,数据从REG(寄存器)传输到R/M(寄存器或者内存)。
3、MOD-REG-R/M组成:可选,须从前面的指令解析是否存在这个域,它确定指令后是否有SIB域,地址偏移和立即操作数,此外还定义了使用的寄存器。
7~6位MOD |
5~3位REG |
2~0位R/M |
模式 |
寄存器 |
寄存器或者内存地址 |
MOD有2个位共4种模式:
MOD的值 |
意义 |
00B |
没有使用地址偏移 |
01B |
使用了8位地址偏移 |
10B |
使用了16位或者32位地址偏移。由当前模式决定16位或者32位, 当然默认值也可被超越前缀所修改 |
11B |
仅仅使用通用寄存器,此时R/M一定是寄存器,此外的情况是,R/M 是内存地址 |
R/M表示寄存器时(MOD = 11B)同REG,用3个位表示8种寄存器:
REG或R/M值 |
字节 |
字 |
双字 |
SSE |
MMX |
000B |
al |
ax |
eax |
ss0 |
mm0 |
001B |
cl |
cx |
ecx |
ss1 |
mm1 |
010B |
dl |
dx |
edx |
ss2 |
mm2 |
011B |
bl |
bx |
ebx |
ss3 |
mm3 |
100B |
ah |
sp |
esp |
ss4 |
mm4 |
101B |
ch |
bp |
ebp |
ss5 |
mm5 |
110B |
dh |
si |
esi |
ss6 |
mm6 |
111B |
bh |
di |
edi |
ss7 |
mm7 |
当REX.R = 1时(64位),REG域扩展为4位,可索引16个寄存器。当REX.B = 1时,R/M域也扩展为4位。除了上面列出的8个寄存器,还可所有r8~r15这8个新增的寄存器。
当R/M表示内存地址时(指虚拟的线性地址),32位下表示如下:
R/M的值 |
表示的地址 |
000B |
ds:[eax] |
001B |
ds:[ecx] |
010B |
ds:[edx] |
011B |
ds:[ebx] |
100B |
使用SIB域 |
101B |
ss:[ebp]使用特殊寻址 |
110B |
ds:[esi] |
111B |
ds:[esi](同上?) |
第一:R/M = 100B时,还须SIB域。
第二:MOD = 00B(不使用地址偏移) 且 R/M = 101B时(表示ebp),表示后面还有地址偏移,所以用ebp间接寻址的指令要多一个字节,也可直接寻址:
Mov [0x12345678],ebx
否则单靠MOD-REG-R/M无法实现直接内存访问。
第三:最后倒数两个值竟然一样?可能是原书错误,这里先存一个问号
4、SIB:Scale-Index-Base,一个字节,用于MOD-REG-R/M使用比例变址寻址方式时(R/M = 100B)。计算方法:
地址 = Base(2~0位)+Index(5~3位)*2^Scale(7~6位)
Index和Base表示寄存器中的内容,表示的方式同REG。当REX.X = 1 及REX.B =1 时,Index和Base分别扩展到4位。可表示16个寄存器。
5、地址偏移:依赖MOD-REG-R/M,可能为1、2、4个字节
6、立即数:取决于寻址方式 (MOD及超越前缀),位有符号数,可能为1、2、4个字节
4)练习:写出32位工作模式下的CPU以下汇编指令的机器码,已知MOV机器指令为8bH.
Mov eax, dword ptr [ebx + 4*ecx]
解答:
a)超越前缀:32位模式,操作数和地址均为32位,无须超越前缀
b)操作码:MOV指令机器码8bH
c)MOD-REG-R/M:指令操作数位eax和dword ptr [ebx + 4*ecx],无地址偏移,所以MOD = 00B;两个操作数一个是寄存器,一个是内存地址,REG的域要表示目的操作数---eax寄存器,所以REG = 000B。R/M要表示源操作数,且使用了ebx + 4*ecx的形式,所以为比例变址寻址方式,那么R/M = 100B。从而MOD-REG-R/M = 00000100B = 04H
d)SIB:使用了比例变址寻址方式时要使用到SIB域,由公式,以及ebx + 4*ecx可确定,Base要表示ebx,应该为011B;Index要表示ecx,应该为001B;而Scale = 4 = 10B,那么SIB = 10001011B = 8BH
e)地址偏移:这里没使用到
f)立即数:这里也没使用到
综上:本指令长度为3个字节,机器码为:8b048bH
5)反汇编引擎作用:把机器码解析成可理解的指令。XDE32反汇编引擎包括3个文件:xdetbl.h、xde.h和xde.c,它只能反汇编32位以及16位的指令。关于这个引擎的分析,作者讲得很含糊,只不过提了下大概框架,以后有时间再仔细阅读与注释下。先跳过。