第三章

OllyMachine汇编语言

终于来到最有意思的部分了!OllyMachine Script是我定义的一套汇编语言,它是用户与OllyMachine虚拟机打交道的最重要的接口(当然,如果你不嫌麻烦,也可以用16进制编辑器直接编写字节码,不过我想应该没有什么人会这样做吧。^_^)。实际上,编写一个Assembler比编写一个Virtual Machine要难得多,我花费在这个汇编器上的时间足足有半个多月。

3.1  基本元素

每一个OllyMachine汇编语言源程序都可以被分解成一系列的语句,而每条语句将在汇编语言源文件里面占用一个独立的文本行,即一行只能写一条语句,语句行的长度没有最大限制。语句又可以分成两种不同的形式:

请注意:OllyMachine汇编语言中所有的指令都是不区分大小写的。

3.1.1  指令

指令是用来给汇编器转换为字节码的语句。一条指令由一个操作码(opcode)和零个或多个操作数(operand)组成:

指令 = 操作码 [ 操作数 [ 操作数 ... ] ]

3.1.1.1  操作码

操作码是一个符号,它定义了指令将要采取的操作动作和整条指令的格式。我为OllyMachine汇编语言所定义的操作码能够完全决定其操作数的个数和这些操作数的类型。

请看下面这两条指令:

add reg00, 0x1234
add reg01, reg00

操作码add要求自己的后面跟有一个通用寄存器,一个逗号,以及一个立即数或者一个通用寄存器,它同时还规定了要把0x1234或者reg00里面的值放入寄存器reg00或者reg01。也就是说,操作码add既给出了这条指令将要采取的操作动作,又给出了这条指令的完整格式。

3.1.1.2  操作数

操作数给出的是指令将要对其进行操作的数据。OllyMachine汇编语言能够识别3种不同类型的指令操作数。这些操作数的类型如下图所示:

操作数类型 示例
寄存器 reg00、eax
标识符 loop1、_continue
整数常数 -100、0x100
图 5:  OllyMachine汇编语言的指令操作数

寄存器

OllyMachine虚拟机一共有83个通用寄存器,它们全都是32位的,寻址范围为:无符号类型:0~4GB,有符号类型:-2GB~+2GB。其中:

reg00、reg01、reg02 ... reg64、FreeBufferReg、FreeBufferSizeReg

这67个寄存器可以供用户自由使用。FreeBufferReg和FreeBufferSizeReg有其特殊用途。

FreeBufferReg指向一个提供给脚本解码、临时存放数据使用的长度为4K的缓冲区;FreeBufferSizeReg表示FreeBuffer的当前长度。例如,如果需要获得一段字符串,这两个寄存器就可以派上用场了。

注意!程序员在编程时,应尽量避免对FreeBufferReg和FreeBufferSizeReg直接进行写操作,从语法上来说对它们进行的操作是跟别的寄存器一样的(即允许读、写操作),但由于这两个寄存器有其特殊含义,它们一般是由相关的API在内部进行维护的,所以如果由程序员显式地改写了它们的值,可能会导致无法预期的程序运行结果。

以及9个寄存器为:

eax、ecx、edx、ebx、esp、ebp、esi、edi、eip

对这9个寄存器的使用必须小心谨慎,因为对它们的操作将直接反映到OllyDbg的当前被调试进程上。例如:

mov reg00, eax

将会把当前调试进程的eax寄存器的值赋值给reg00。

mov eax, 0x12345678

将会把0x12345678赋值给当前调试进程的eax寄存器。

还有7个标志位寄存器:

CF、PF、AF、ZF、SF、DF、OF

这7个标志位寄存器对应着OllyDbg里面的相应的标志位,举例如下:

not cf
mov zf, 0
mov pf, 1

标识符

标识符是由一组字符构成的符号。标识符的作用是命名标号(label)。标识符的第一个字符必须是一个字母(即a-z和A-Z之中的一个字符),或者一个下划线(“_”)。跟在第一个字符后面的其余字符可以是字母、下划线或者数字,即:

下面是一些合法的标识符:

_1continue
exit0
loop1

下面是一些非法的标识符:

1continue
.exit0
my_#_loop2

整数常数

整数常数可以分为两种类型:10进制和16进制。

10进制数不需要任何前缀,例如:

100、-1234

16进制数要加上“0x”或者“0X”作为前缀,例如:

0x100、-0x1234

10进制数和16进制数都可以由正数和负数表示,正数无需加“+”号(加了反而会编译不通过),负数需要加上“-”号。注意,整数常数最大不能超过0xFFFFFFFF。

3.1.2  注释

注释的作用就不必多说了。注释的重要特点是汇编器将完全忽略它们,不对它们做任何处理。通常来说,注释是夹杂在源代码中的说明文档。

OllyMachine汇编语言中的注释分为两种:行注释和块注释。

行注释:以“//”或“;”开头,一直到行尾,例如:

// line comment 1.
; line comment 2.

块注释:以“/*”开头,直到遇到“*/”为止,可以占据多行,例如:

/*
   this is a block comment.
*/

3.2  标号

从程序设计的布局来说,一个汇编语言程序无非是一个由多个过程组成的集合体。那么我们该如何控制过程呢?我定义了标号(label)这种结构。

标号的格式是在标识符的后面加上一个冒号,例如:

Error0:

注意,OllyMachine汇编语言中的标号必须是独一无二的,它们不得与任何其他的名字重复。

定义好标号之后,就可以通过跳转指令(后面再详细介绍)来控制过程了,例如:

jmp Error0
// instructions ...
Error0:
// other instructions ...

3.3  数据传送指令

数据传送指令负责把数据、地址或立即数传送到寄存器中。它可以分成:

3.3.1  MOV

格式为:MOV DST, SRC
执行操作:(DST) <- (SRC)

它有两种格式:

例如:

MOV reg00, 0x100
MOV reg00, reg01

3.3.2  XCHG

格式为:XCHG OPR1, OPR2
执行操作:(OPR1) <-> (OPR2)

它只有一种格式:

例如:

mov reg01, 1
mov reg02, 2
XCHG reg01, reg02   // now reg01 == 2, reg02 == 1

XCHG指令不会影响虚拟机的标志位。

3.3.3  LDS

格式为:LDS 寄存器, "字符串"

例如:

LDS reg00, "Hello World!"

可以把LDS记忆成:LoaD String,即载入字符串到寄存器的意思,这样比较容易记住。^_^

3.3.4  PUSH

格式为:PUSH SRC
执行操作:(ESP) <- (ESP) - 4
     ((ESP) + 4, (ESP)) <- (SRC)

它有两种格式:

例如:

PUSH 0x100
PUSH reg00

3.3.5  POP

格式为:POP DST
执行操作:(DST) <- ((ESP) + 4, (ESP))
     (ESP) <- (ESP) + 4

它只有一种格式:

例如:

POP reg00

3.4  算术指令

算术运算指令用来执行算术运算,它们当中有双操作数指令,也有单操作数指令。

3.4.1  加法指令

3.4.1.1  ADD

格式:ADD DST, SRC
执行操作:(DST) <- (SRC) + (DST)

它有两种格式:

例如:

ADD reg00, 0x100
ADD reg00, reg01

注意:ADD指令会影响虚拟机的CF标志位。

3.4.1.2  INC

格式:INC DST
执行操作:(DST) <- (DST) + 1

它只有一种格式:

例如:

INC reg00

INC指令不影响虚拟机的标志位。

3.4.2  减法指令

3.4.2.1  SUB

格式:SUB DST, SRC
执行操作:(DST) <- (DST) - (SRC)

它有两种格式:

例如:

SUB reg00, 0x100
SUB reg00, reg01

注意:SUB指令会影响虚拟机的CF和ZF标志位。

3.4.2.2  DEC

格式:DEC DST
执行操作:(DST) <- (DST) - 1

它只有一种格式:

例如:

DEC reg00

DEC指令不影响虚拟机的CF标志位,但会影响ZF标志位。

3.4.2.3  CMP

格式:CMP OPR1, OPR2
执行操作:(OPR1) - (OPR2)

它有两种格式:

例如:

CMP reg00, 0x100
CMP reg00, reg01

注意:该指令与SUB指令一样执行减法操作,但它并不保存结果,只是根据结果设置条件标志位CF和ZF,CMP指令后往往跟着一条条件转移指令,根据比较结果产生不同的程序分支。

3.4.3  乘法指令

3.4.3.1  MUL

格式:MUL DST, SRC
执行操作:(DST) <- (DST) * (SRC)

它有两种格式:

例如:

MUL reg00, 0x100
MUL reg00, reg01

MUL指令不会影响虚拟机的标志位。

3.4.4  除法指令

3.4.4.1  MUL

格式:DIV DST, SRC
执行操作:(DST) <- (DST) / (SRC)

它有两种格式:

例如:

DIV reg00, 0x100
DIV reg00, reg01

DIV指令不会影响虚拟机的标志位。

注意:不能把0当作除数,否则虚拟机在运行时会检测出来并报错。

3.5  逻辑指令

3.5.1  逻辑运算指令

3.5.1.1  AND

格式:AND DST, SRC
执行操作:(DST) <- (DST) & (SRC)

它有两种格式:

例如:

AND reg00, 0x100
AND reg00, reg01

注意:AND指令会影响虚拟机的ZF标志位,并把CF标志位置为0。

3.5.1.2  OR

格式:OR DST, SRC
执行操作:(DST) <- (DST) | (SRC)

它有两种格式:

例如:

OR reg00, 0x100
OR reg00, reg01

注意:OR指令会影响虚拟机的ZF标志位,并把CF标志位置为0。

3.5.1.3  NOT

格式:NOT DST
执行操作:(DST) <- !(DST)

它只有一种格式:

例如:

NOT reg00

NOT指令不会影响虚拟机的标志位。

3.5.1.4  XOR

格式:XOR DST, SRC
执行操作:(DST) <- (DST) ^ (SRC)

它有两种格式:

例如:

XOR reg00, 0x100
XOR reg00, reg01

注意:XOR指令会影响虚拟机的ZF标志位,并把CF标志位置为0。

3.5.2  移位指令

3.5.2.1  SHL

格式:SHL DST, SRC

它有两种格式:

例如:

MOV reg00, 0x10
SHL reg00, 8        // reg00 is now 0x1000

MOV reg00, 0x10
MOV reg01, 8
SHL reg00, reg01    // reg00 is now 0x1000

注意:SHL指令不会影响虚拟机的标志位。

3.5.2.2  SHR

格式:SHR DST, SRC

它有两种格式:

例如:

MOV reg00, 0x1000
SHR reg00, 8        // reg00 is now 0x10

MOV reg00, 0x1000
MOV reg01, 8
SHR reg00, reg01    // reg00 is now 0x10

注意:SHR指令不会影响虚拟机的标志位。

3.6  控制转移指令

一般情况下指令是顺序地逐条执行的,但实际上程序不可能全部顺序执行而经常需要改变其执行流程,在这里我们要介绍的控制转移指令就是用来控制程序的执行流程的。

3.6.1  无条件转移指令

3.6.1.1  JMP

格式:JMP label
执行操作:(EIP) <- (EIP) + 32位偏移量

无条件地转移到指令指定的地址去执行从该地址开始的指令,呵呵,好拗口啊。举个例子如下:

jmp Error0
mov reg01, 0x200
Error0:
mov reg00, 0x100

当程序运行到 jmp Error0 的时候,程序的执行流程就会跳转到 mov reg00, 0x100 处,因此,mov reg01, 0x200 将不会执行。

3.6.2  条件转移指令

3.6.2.1  JE

结果为零(或相等)则转移。

测试条件:ZF = 1

3.6.2.2  JNE

结果不为零(或不相等)则转移。

测试条件:ZF = 0

3.6.2.3  JB

低于,或者不高于或者等于,或进位为1则转移。

测试条件:CF = 1

3.6.2.4  JNAE

同JB。

3.6.2.5  JNB

不低于,或者高于或等于,或进位位为0则转移。

测试条件:CF = 0

3.6.2.6  JAE

同JNB。

3.6.2.7  JBE

低于或等于,或不高于则转移。

测试条件:CF = 1 or ZF = 1

3.6.2.8  JNA

同JBE。

3.6.2.9  JNBE

不低于或等于,或者高于则转移。

测试条件:CF = 0 and ZF = 0

3.6.2.10  JA

同JNBE。

3.7  其他指令

还有一些其他的指令是无法归类到上面的指令集中的,它们是:

3.7.1  INCLUDE

格式:INCLUDE "filename.oms"

INCLUDE指令可以包含另外一个汇编语言源文件一同进行汇编。

3.7.2  NOP

格式:NOP

NOP指令什么都不做,仅仅是空耗费一次虚拟机CPU运行周期,不会影响任何标志位,它所造成的影响是使虚拟机CPU的EIP加1。

3.7.3  PAUSE

格式:PAUSE

PAUSE指令会使虚拟机暂时停止运行,如要让虚拟机继续运行,请选择Plugins菜单中的“OllyMachine -> Resume”。

3.7.4  HALT

格式:HALT

HALT是停机指令,OllyMachine运行时只要一遇到HALT指令,就会立即停机,并且以正常状态退出。

3.7.5  INVOKE

格式 INVOKE Api_Name, parameter1, parameter2, ...

INVOKE宏用来调用API,类似MASM32中的INVOKE宏。