《天书夜读:从汇编语言到windows内核编程》十三 机器码与反汇编引擎

时间:2021-05-19 01:12:24

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位的指令。关于这个引擎的分析,作者讲得很含糊,只不过提了下大概框架,以后有时间再仔细阅读与注释下。先跳过。