汇编基础教程
16位和32位的80x86汇编语言的区别
需要注意的是汇编不是一种语言,不同平台有不同的汇编语言对应,因为汇编和操作系统平台相关,所以汇编语言没有移植性。对于IA-32架构平台而言,选用的是32位80386汇编语言,也就是说本教程讨论的操作系统平台是32位的,可执行文件的格式也是32位而不是64位或16位的。
实际分析中读者要知道研究的程序是运行在什么平台上,以选择相应的汇编语言,对应IA-32架构而言,IA-16架构的汇编语言原理其实和IA-32的汇编语言一样,学习过16位的80x386汇编语言的读者只需花一点时间就可以转到32位80386汇编语言上。
什么是IA-32?
IA-32(Intel Architecture),英特尔体系架构,英特尔从486开始采用,也就叫X86-32架构,
16位操作系统与32位操作系统的80x86汇编语言主要区别如下:
16位操作系统中的中断调用相当于32位操作系统中的API调用。16位操作系统中的段地址和偏移地址在32位中消失了,在32位操作系统中统一采用平坦的内存地址模式寻址。
16位操作系统中的程序运行在RING0级,也就是说普通程序和操作系统程序运行在同一个级别并且拥有最高权限,而32位操作系统中的程序一般只拥有RING3级运行权限,程序的所有操作都受到操作系统控制,若程序要获得RING0操作特权只能通过驱动程序实现。
16位操作系统的可执行文件格式和32位操作系统的可执行文件格式不同,在32位的Windows操作系统中,可执行文件的格式叫PE格式,32位的Windows操作系统运行在CPU的保护模式之上,而16位的系统则运行在CPU的实模式上。
了解EAX、EBX、ECX和EDX寄存器以及对应的16位寄存器
IA-32架构中一共有4个32位寄存器,用于保存临时数据,它们分别是EAX、EBX、ECX和EDX。
这4个32位寄存器的通用寄存器名字前面都有一个“E”字母,含义是“Expand”扩展,这是由于在16位的时代,这4个通用寄存器的名字是AX、BX、CX和DX,到了32位后就在它们的名字前面加个“E”来区别是32位还是16位。
这4个32位的通用寄存器可以当作16位使用,也可以当作8位使用。当作8位使用时,就将AX折开为AH和AL,AH中的“H”代表“high”,意思是高位的意思,AL中的“L”代表“low”,意思是地位的意思。同理,BX、CX和DX可折开为BH、BL、CH、CL、DH、DL来使用。
一些寄存器是别的寄存器的一部分:例如,如果EAX保存了值12782345,这里是其他寄存器的值。
|:--- |:------:|:------:|:------:|:------:|
| EAX | 12 | 78 | 23 | 45 |
| AX | 12 | 78 | 23 | 45 |
| AH | 12 | 78 | 23 | 45 |
学习EAX、EBX、ECX和EDX寄存器的用途
- EAX寄存器:EAX称为累加器,常用于算数运算、布尔操作、逻辑操作、返回函数结果等。
- EBX寄存器:EBX称为基址寄存器,常用于存档内存地址。
- ECX寄存器:ECX称为计数寄存器,常用于存放循环语句的循环次数,字符串操作中也常用。
- EDX寄存器:称为数据寄存器,常常和EAX一起使用。
注意:上面所述的4个通用寄存器的专门用途不是一成不变的,编译器在编译程序的时候会根据很多因素,例如编译器、编译条件、操作系统等做出相应的改变,读者要知道着手研究的程序是用什么编译器编译,然后针对具体的编译器参考该编译器的说明。
学习变址寄存器-ESI和EDI寄存器与其用途
变址寄存器
顾名思义,变址的含义是内存地址会变动的,也就是说变址寄存器中存放在变动的内存地址。80386架构中有两个变址寄存器,分别是ESI和EDI。
ESI和EDI寄存器:
- ESI:ESI称为源变址寄存器,通常存放要处理的数据的内存地址。
- EDI:EDI称为目的变址寄存器,通常存放处理后的数据的内存地址。
ESI和EDI寄存器的用途
ESI和EDI常用来配合使用完成数据的赋值操作,下面是一个ESI和EDI配合使用的例子。
Rep movs dword ptr [edi],dword ptr [esi]
上面的指令把ESI所指向的内存地址中的内容复制到EDI所指向的内存中,数据的长度在ECX寄存器中指定。
学习指针寄存器-EBP和ESP寄存器与其用途
指针寄存器
80386的指针寄存器有基址寄存器EBP,堆栈指针寄存器ESP和指令指针寄存器EIP。只需要了解基址寄存器EBP和堆栈指针寄存器ESP即可,指令指针寄存器EIP总是指向下一条要执行的指令的地址,一般情况下无需修改EIP。
EBP和ESP寄存器
(1)EBP:EBP称为基址寄存器,可作为通用寄存器用于存放操作数,常用来代替堆栈指针访问堆栈中的数据。
(2)ESP:ESP称为堆栈指针寄存器,不可作为通用寄存器使用,ESP存放当前堆栈栈顶的地址,一般情况下,ESP和EBP联合使用来访问函数中的参数和局部变量。
EBP和ESP寄存器的用途:
EBP和ESP常配合使用完成堆栈的访问,下面是一段常见的堆栈访问指令。
Push ebp
Mov ebp,esp
Sub esp,78
Push esi
Push edi
Cmp dword ptr [ebp+8],0
学习标志寄存器-EFLAGS寄存器与其用途
标志寄存器EFLAGS一共有32位,在这32位中大部分是保留和给编写操作系统的人用的,一般情况下只需知道32位的低16位中的8位即可.
下面的图列出了标志寄存器EFLAGS中需要了解的8个位的位置。
- OF(Overflow Flag):溢出标志,溢出时为1,否则置0。
- DF (Direction Flag):方向标志,在串处理指令中控制信息的方向。
- IF (Interrupt Flag) :中断标志
- AF (Auxiliary carry Flag) :辅助进位标志,有进位时置1,否则置0。
- ZF (Zero Flag) :零标志,运算结构为0时ZF位位置1,否则置0。
- SF (Sign Flag):符号标志,结果为负时置1,否则置0。
- CF (Carry Flag): 进位标志,进位时置1,否则置0。
- PF (Parity Flag): 奇偶标志。结果操作数中1的个数为偶数时置1,否则置0。
EFLAGS寄存器的用途
正如上面所说EFLAGS是实现条件判断和逻辑判断的一种机制,在汇编语言中一般不直接访问EFLAGS寄存器,而是通过指令的操作隐含访问EFLAGS寄存器,下面是一个利用EFLAGS寄存器的例子。
Cmp dword ptr [ebp+8],0 ;影响标志CF、ZF、SF、OF、AF和PF
Jz 00405898 ; 如果ZF等于1,则跳转到00405898
学习6种灵活的寻址方式与其作用
- 立即寻址
示例:
Mov eax,56H
作用:通常用于赋值。
- 直接寻址
示例:
Mov eax,[12558878H]
作用:通常用于处理变量。
- 寄存器寻址
示例
Mov eax,[edi]
作用:地址在寄存器中。
- 寄存器相对寻址
示例
Mov EAX,[EDI+32H]
作用:常用于访问数组和结构。
- 基址加变址寻址
示例
Mov EAX,[EBP+ESI]
作用:常用于访问数组
- 相对基址加变址寻址。
示例
MOV EAX,[EBX+EDI-10H]
作用:常用于访问结构。
高级语言中的数据结构和80386寻址方式的关系
一般高级语言中的数据结构和寻址方式有一定的关系,熟悉这些关系对逆向分析反汇编指令有很大的帮助。
表1-1所示为高级语言中的数据结构和80386寻址方式的关系。
全局类型
全部使用数据段,变量寻址使用直接的内存地址,数组寻址使用ESI(源变址寄存器)+内存地址。
局部类型
全部使用堆栈,变量寻址使用ESP(堆栈指针寄存器)+偏移,数组寻址使用ESP(堆栈指针寄存器)+ESI(源变址寄存器)+地址偏移
动态分配类型
全部使用数据段,变量寻址使用EBX(基址寄存器)或EAX(累加寄存器)+地址偏移,数组寻址使用EBX(基址寄存器)+ESI(源变址寄存器)+地址偏移
局部类型和动态分配类型都的数组寻址都是用了ESI(源变址寄存器)+地址偏移
学习80386指令-Intel格式和AT&T格式的指令格式
80386指令
观察一下Intel 的80x86 CPU文档手册会发现,Intel的80x86 CPU支持几百条指令,如此多的指令莫说深入研究,连入门恐怕也非易事。
幸运的是,在这几百条的指令中,常用的也不过几十条而已,Intel 的80x86 CPU之所以支持几百条指令,原因在于Intel 的80x86 CPU为了保持向下兼容的问题,所以从过去到现在的所有指令都包含在CPU里面,例如有8086/8088、80186、80286和80386等。
Intel格式和AT&T格式(&在英语里读and)
编写IA-32架构的汇编语言常见有两种格式,一种是Intel格式,另一种是AT&T格式。
- Intel 格式的指令格式。
指令名称 目标操作数DST,源操作数SRC
示例代码:
Mov eax,[edx] //将内存地址为EDX的数据放入EAX寄存器
Xchg eax,edi //交换EAX和EDI寄存器的值
Add eax,ebx // 将EAX和EBX相加,结构放回EAX中
Shl eax,4 //将EAX逻辑左移4位。
- AT&T格式的指令格式。
指令名称 源操作数SRC,目标操作数DST
示例代码:
Mov (%EDX),%EAX //将内存地址为EDX的数据放入EAX寄存器
XCHG %EDI,%EAX //交换EAX和EDI寄存器的值
ADD %EBX,%EAX //将EAX和EBX相加,结构放回EAX中
SHL $4,%EAX //将EAX逻辑左移4位。
本教程中讲解的汇编语言统一采用Intel 格式的汇编格式。
学习数据传送指令MOV、XCHG
80386的数据传送指令是为了实现CPU和内存、输入/输出端口之间的数据传送。
- MOV:称为数值传送指令,格式是“MOV DST,SRC”。
- MOV指令将源操作数SRC传送到目的操作数DST中,
- 传送的数据格式可以是8字节、16字节和32字节。
示例代码:
MOV EAX,56 //将56H立即数传送到EAX寄存器
MOV ESI,DWROD PTR [EAX*2+1] //将内存地址为EAX*2+1处的4字节数据传送到ESI寄存器。
MOV AH,BYTE PTR [ESI*2+EAX] //将内存地址为ESI*2+EAX处的8位数据传送到AH寄存器。
MOV DWORD PTR [ESP+36],EBX //将EBX寄存器的值以4字节传送到堆栈地址为ESP+36所指向的地方。
- XCHG:称为交换指令,XCHG实现寄存器间和内存间的数据交换。
格式是“XCHG DST,SRC”。XCHG指令交换SRC和DST之间的数据,
交换的数据可以是8字节、16字节和32字节,其中SRC和DST必须格式相同。
示例代码:
XCHG EAX,EDX //将EDX寄存器的值和EAX寄存器的值交换
XCHG [ESP-55],EDI //将EDI寄存器的值和堆栈地址为[esp-55]处的值交换。
XCHG BH,BL //将BL寄存器和BH寄存器的值交换。
学习数据传送指令PUSH、POP
PUSH和POP:称为压入堆栈指令和弹出堆栈指令,格式是“PUSH SRC(源操作数)”和“POP DST(目的操作数)”。
PUSH指令和POP指令是匹配出现的,上面的代码有多少个PUSH下面的代码就有多少个POP,否则堆栈就会不平衡。
PUSH指令将源操作数SRC压入堆栈,同时ESP-4,而POP恰恰相反,POP指令从堆栈的顶部弹出4字节的数值然后放入DST。在32位的Windows操作系统上,PUSH和POP指令的操作是以4字节为单位的。
PUSH和POP指令常用于向函数传递参数。
示例代码:
PUSH EAX //将EAX寄存器的值以4字节压入堆栈,同时ESP-4
PUSH DWORD PTR [12FF8589] //将内存地址为12FF8589所指向的值以4字节压入堆栈,同时ESP-4
POP DWORD PTR [12FF8589] //将堆栈顶部的4字节弹出到内存地址为12FF8589所指地方,同时ESP+4
POP EAX //将堆栈顶部的4字节弹出到EAX寄存器,同时ESP+4