0x00 IA32处理器体系结构
微机的基本结构
指令执行周期
当指令使用了内存操作数时还需要两个额外的步骤:取操作数和存储输出操作数。
机器指令的执行;
- 取指令
- 解码
- 执行
操作模式
保护模式:处理器的基本模式。
虚拟8086模式:多任务环境中执行是地址模式的软件。
实地址模式:用于运行那些需要直接访问系统内存和硬件设备的MS-DOS程序。
系统管理模式:实现电源管理和系统安全等功能的机制。
基本执行环境
基本寄存器
寄存器中数据在内存中存放数据遵循高高低低的原则
8个通用寄存器
EAX EBX ECX EDX
EBP ESP ESI EDI
6个段寄存器
一个处理器状态标志寄存器(EFLAGS)和一个指令指针(EIP)寄存器。
ESP:栈地址寄存器 任意时刻指向栈顶元素
EBP:扩展帧指针寄存器 指向堆栈上的函数参数和局部变量
EIP:指令指针寄存器
EIP寄存器不能作为MOV指令的⽬标操作数
EFLAGS:由控制CPU的操作或反映CPU某些运算的结果的独立二进制位构成
状态标志
Name | ||
---|---|---|
CF | 进位标志 | 进位或借位时CF=1 |
AC | 辅助进位标志 | 低4位进位或借位时A=1 |
PF | 奇偶标志 | 偶数P=1 |
ZF | 零标志 | 结果为0则Z=1 |
SF | 符号标志 | S = 符号位值(补码时0=正,1=负) |
OF | 溢出标志 | 运算结果超界时O=1 |
DF | Direction Flag | |
IF | Intertupt Flag | |
TF | Trace Flag |
内存管理
实地址模式
可以寻址1MB内存 0~FFFFF
20位线性地址
linear address or abssolute address is 20 bits,range from 0 to FFFFF
用段-偏移地址表示
- CS:16位代码段
- DS:16位数据段
- SS:16位堆栈段
- ES,FS,GS可指向其他数据段
保护模式
可以寻址4GB内存 0~FFFFFFFF
段寄存器指向段描述符表,操作系统使用段描述符表定位程序使用的段的位置。
0x01 汇编语言基础
补码的表示法
- 正数的补码:与源码相同
- 负数的补码:反码加1
寻址方式
基本元素
16进制数第一个是字母时要在前面加0
指令
一条汇编指令包括4个部分:
- 标号(可选)
- 助记符
- 操作数
- 注释(可选)
INVOKE
相当于call,调用函数或过程
伪指令
伪指令课用于定义变量、宏以及过程,可用于执行命名段以及执行其他与汇编器相关任务。
.data?
:指明未初始化的数据段
NOP指令
占用一个字节的存储,什么也不做。
程序模板
TITLE Program Template
; 程序描述:
; 作者:
; 创建日期:
; 修改:
; 日期: 修改者:
INCLUDE Irvine32.inc
.data
;(在此插入变量)
.code
main PROC
;(在此插入可执行代码)
exit
main ENDP
;(在此插入其他子程序)
END main
汇编-链接-执行
定义数据
字符常量/字符串常量
- 以单引号或双引号括起来的单个/一串字符
- 存储为对应字符的ASCII码
后缀 | 含义 |
---|---|
d | 十进制 |
b | 二进制 |
q | 八进制 |
h | 十六进制 |
数据定义语句
初始值可以用?
表示不确定,可以是表达式。
可以指定多个初始值,用逗号隔开,变量名代表第一个初始值的偏移。
DUP
可以为多个数据项分配存储空间。
V1 BYTE 10 dup (0)
V1占用10个字节空间,初值均为0
符号常量
等号伪指令:名字=表达式
计算数组和字符串大小:
list BYTE 10, 20, 30
ListSize = ($ - list)
list word 10,20,30,40
ListSize = ($-list)/2
myString_len = ($ - myString)
EQU和TEXTEQU伪指令:
将符号名和整数表达式,文本联系起来。
name EQU expression
name EQU symbol
name EQU <text>
rowSize = 5
count TEXTEQU %(rowSize * 5)
move TEXTEQU <mov>
setupAL TEXTEQU <move al, count>
setupAL将被汇编成mov al, 10
0x02 数据传送,寻址,算术运算
小尾(小端)顺序
intel处理器使用小端顺序存储,最低字节存储在最低地址单元
Val DWORD 12345678h
数据传送指令
操作数类型
- 立即操作数(immediate)
- 寄存器操作数(register)
- 内存操作数(memory)
MOV指令
MOV destination, source
- 两个操作数尺寸必须一致
- 不能同时为内存操作数
- 目的操作数不能是CS, EIP,IP
- 立即数不能直接送至段寄存器
MOVZX
复制较小值至较大值中。
低八位原样复制,高八位补0扩展,仅适用于无符号整数。
MOVSX
低八位原样复制,高八位补F扩展,仅适用于有符号整数。
LAHF/SAHF
LAHF
将标志局存起EFLAGS
的低8位复制到AH
寄存器,SAHF
是将AH
复制到EFLAGS
XCHG指令
交换两个操作数的内容。
XCHG reg, reg
XCHG reg, mem
XCHG mem, reg
算数指令
名称 | 作用 | 影响标志位 |
---|---|---|
INC | 加1 | AF OF PF SF ZF 不影响CF |
DEC | 减1 | AF OF PF SF ZF 不影响CF |
ADD | 相加 | CF ZF SF OF AF PF |
SUB | 相减 | CF ZF SF OF AF PF |
NEG | 取相反数 | CF ZF SF OF AF PF |
加减法影响标志位
INC和DEC不会影响CF
标志位
NEG影响的标志位和ADD SUB一样
名称 | 作用 |
---|---|
CF进位位 | 无符号数是无溢出 |
OF溢出位 | 有符号数有无溢出 |
ZF零标位 | 判断结果是否为0 |
SF符号位 | 结果正负 |
PF奇偶标志 | 最低有效字节内1的个数是否为偶数 |
AC辅助进位标志 | 最低有效字节的第三位向高位进位 |
加减法算术运算指令的操作数自身不区分有无符号数,程序通过判断不同的标志位来实现对有符号数和无符号数的处理。
和数据相关的操作符和伪指令
名称 | 作用 |
---|---|
OFFSET | 取偏移地址 |
ALIGN | 设置对齐值 |
PTR | 重载默认尺寸 |
TYPE | 返回单个元素大小 |
LENGTHOF | 计算数组中元素的数目 |
SIZEOF | 返回LENGTHOF*TYPE |
LABEL | 插入一个标号并赋予尺寸 |
加逗号可以多行定义
LABEL不会分配存储空间
JMP和LOOP
JMP
无条件转移与条件转移
JMP 目的地址
功能:接着从目的地址开始执行指令
- 目的地址一般为标号
- 通常在当前过程内跳转
LOOP
LOOP 目的地址
功能:将ecx
的值减1,接着与0比较,如果不等于0,就执行目的地址开始的指令,如果等于0 ,则不跳转,接着执行紧跟在LOOP指令后的指令
- 通常,
ecx
里的值就是循环次数。但如果初值为0,因是先减1再判断是否等于0,所以,实际实际循环次数就是1 00 00 00 00 H
。 LOOPD
也使用ecx控制循环,LOOPW
使用cx控制循环。- 实模式下,使用的是cx作为控制循环的寄存器
实例
数组求和:
INCLUDE irvine32.inc
.data
vb1 byte 1 , 2 , 3
.code
main proc
mov esi , offset vb1
mov ecx , lengthof vb1
mov al , 0
L1:
add al , [ esi ]
add esi , type vb1
loop L1
exit
main endp
end main
复制字符串:
INCLUDE irvine32.inc
.data
s1 byte "source string",0
s2 byte sizeof s1 dup(0)
.code
main proc
mov esi , 0
mov ecx , sizeof s1
L1:
mov al , s1[ esi ]
mov s2[esi] , al
inc esi
loop L1
exit
main endp
End main
寻址方式总结
操作数寻址方式
数据寻址的基本方式:
- 立即寻址
- 寄存器寻址
- 存储器寻址
存储器寻址有六种类型
用寄存器作为指针并操纵寄存器的值。操作数使用间接寻址则叫间接操作数。
0x03 过程
堆栈操作
运行时栈
运行时栈是CPU直接管理的内存数组,使用到两个寄存器:SS和ESP
- 保护模式下,SS是段选择子,应用程序不应该修改它
- ESP是指向栈的特定位置的一个32位偏移值
- 一般不会直接修改ESP,而是通过使用CALL,RET,PUSH,POP等指令,由这些指令间接操作ESP。
- ESP指向最后压入到栈的数据
- 实模式下,使用的SS和SP
PUSH
PUSH r/m16
PUSH r/m32
PUSH imm32
压栈,将操作数放入堆栈中:
- 将ESP减4
- 将要压入的32位值拷贝到ESP指向的内存。
对于32位操作数,ESP减4,存到栈中的内容为双字;对于16位操作数,ESP减2,存到栈中的内容为字
POP
POP r/m16
POP r/m32
出栈,从堆栈中取出操作数放到指令中的操作数中
- 将ESP所指向内存中的内容取出放到操作数中
- 将ESP加4
对于32位操作数,是先从栈中拷贝双字到操作数中,然后ESP加4;对于16位操作数,是先从栈中拷贝字到操作数中,然后ESP加2。
PUSHFD 把32位标志寄存器压入堆栈
POPFD 从堆栈中弹出32位值到标志寄存器中
两指令无操作数
实模式下标志寄存器是16位的,入栈出栈指令分别是PUSHF,POPF。
PUSHAD 把八个32位通用寄存器按序全部压入堆栈
POPAD是以上序反序从堆栈中依次弹出值到八个32位通用寄存器中
过程定义
PROC
main proc
...
main endp
一般过程需要返回指令ret,起始过程需要调ExitProcess
CALL和RET
call 过程名
将EIP压栈(即当前CALL指令的下一条指令的地址),然后将过程名所在地址赋给EIP(相当于跳转到过程名所在的代码处)
RET
RET指令是从栈中取出32位地址,赋给EIP。
使用寄存器传递过程参数
.data
dArray DD 1, 2 , 3
dSum DD ?
.code
Main proc
mov ebx , offset dArray
mov ecx , lengthof dArray
call SumOf
mov dSum, eax
exit
Main endp
SumOf proc
push ebx
push ecx
mov eax , 0
L2: add eax , [ebx]
add ebx , 4
loop L2
pop ecx
pop ebx
ret
SumOf endp
End main
0x04 条件处理
布尔和比较指令
名称 | 作用 |
---|---|
AND | 与 |
OR | 或 |
XOR | 异或 |
NOT | 非 |
TEST | 与,不改变目的操作数只改变标志位 |
BT,BTC,BTR,BTS | 求补/清零/置位 |
尺寸相同
AND
, OR
,XOR
总是清除溢出标志和进位标志(CF和OF)
NOT
不影响任何标志位
实例
小写转大写:
同一字母的大写字母和小写字母的ASCII码的区别只在第5位不同,其他各位相同,小写字母第5位为1,大写字母第5位为0
如要把小写转大写,则可将小写的ASCII码与11011111B
相与
.data
aName byte “Abraham” , 0
nameSize=($-aName)-1
.code
Main proc
mov ecx , nameSize
mov esi , 0
L1:AND aName[esi] , 11011111B
inc esi
loop L1
Main endp
End Main
将0-9之间的整数转换为对应数字符号的ASCII码
.data
aNum byte 1,3,2,0
numSize=($-aNum)-1
.code
Main proc
mov ecx , numSize
mov esi , 0
L1:OR aNum[esi] , 110000B
inc esi
loop L1
exit
Main endp
End Main
CMP
功能:对两个操作数作相减运算,不修改操作数,但会影响标志位。会修改OF、SF、ZF、CF、AF、PF。
设置和清除单个CPU状态标志
条件跳转
基于特定标志位
为真时跳转 | 为假时跳转 | 相关标志位 |
---|---|---|
JZ | JNZ | ZF |
JC | JNC | CF |
JO | JNO | OF |
JS | JNS | SF |
JP | JNP | PF |
基于相等比较
助记符 | 描述 |
---|---|
JE | 相等跳转 同JZ |
JNE | 不相等跳转 同JNZ |
JCXZ | CX=0跳转 |
JECXZ | ECX=0跳转 |
基于无符号数比较
助记符 | 描述 |
---|---|
JA | 大于跳转 |
JB | 小于跳转 |
JAE | 大于等于 |
JBE | 小于等于 |
JNA | 不大于 |
JNB | 不小于 |
JNBE | 同JA |
JNAE | 同JB |
基于有符号数比较
助记符 | 描述 |
---|---|
JG | 大于跳转 |
JL | 小于跳转 |
JGE | 大于等于 |
JLE | 小于等于 |
JNG | 不大于 |
JNL | 不小于 |
JNLE | 同JG |
JNGE | 同JL |
实例
将最小有符号数存到AX:
Mov ax,v1
Cmp ax,v2
JL L1
mov ax,v2
L1:cmp ax,v3
JL L2
mov ax, v3
L2:
数组的顺序查找:
查找第一个非0值
INCLUDE Irvine32.inc
.data
intArray SWORD 0,0,0,0,5,20,35,-12,66,4,0
noneMsg BYTE "A non-zero value was not found", 0
.code
main PROC
mov ebx, OFFSET intArray
mov ecx, LENGTHOF intArray
L1: cmp WORD PTR [ebx], 0
jne found
add ebx, 2
loop L1
jmp notFound
found:
movsx eax, WORD PTR[ebx]
call WriteInt
jmp quit
notFound:
mov edx, OFFSET noneMsg
call WriteString
quit:
call Crlf
exit
main ENDP
END main
条件循环指令
指令 | 循环条件 |
---|---|
LOOPZ | ECX>0 && ZF=1 |
LOOPE | ECX>0 && ZF=1 |
LOOPNZ | ECX>0 && ZF=0 |
LOOPNE | ECX>0 && ZF=0 |
LOOPE和LOOPZ不影响任何状态标志
.data
Array SWORD -3,-6,-1,-10,10,30,40,5
Sentinel SWORD 0
.code
; …
mov esi , offset array
mov ecx , lengthof array
L1:test word ptr [esi],8000h
pushfd ; pushfd不修改标志位
add esi , type array
popfd
loopnz L1 ; 注意:loopnz不修改标志位
jnz quit
sub esi , type array
Quit:
0x05 整数算术指令
移位和循环移位
指令 | 含义 |
---|---|
SHL | 逻辑左移 |
SHR | 逻辑右移 |
SAL | 算术左移 |
SAR | 算术右移 |
ROL | 循环左移 |
ROR | 循环右移 |
RCL | 带进位的循环左移 |
RCR | 带进位的循环右移 |
SHLD | 双精度左移 |
SHRD | 双精度右移 |
逻辑移位和算术移位
SHL/SAL
SHL 目的操作数, 移位位数
功能:对目的操作数执行左移操作,最低位补0,移出的最高位送入进位标志CF,原来的进位位将丢失。SHL和SAL功能完全一样。
左移的SHL和SAL是等价的。算术移位不改变符号位,逻辑移位可能改变符号位
SHR
SHR 目的操作数, 移位位数
功能:将目的操作数逻辑右移,左边空出的位添0,右边最低位被移出,复制到CF位中
SHR可以实现无符号数的快速除法
SAR
有符号数的快速除法,右移过程中最高位保持不变
ROL/ROR/RCL/RCR
移出的位又送回另一端
SHLD/SHRD
应用
BinToAsc PROC uses eax ebx ecx esi
;将EAX中的数转换成二进制ASCII码存到ESI指向的数组中
Add esi , 31
Mov ecx ,32
Nxt:
Mov bl, al
And bl , 1
Add bl , 30H
Mov [esi],bl
Shr eax,1
Dec esi
Loop nxt
Ret
BinToAsc ENDP
乘法和除法指令
助记符 | 描述 |
---|---|
MUL | 无符号乘法 |
IMUL | 有符号乘法 |
DIV | 无符号除法 |
IDIV | 有符号除法 |
应用
Mov al, 30h
Mov bl, 4h
Mul bl ;AX =0C0H,CF=0
Mov ax , 2000h
Mov bx ,100h
Mul bx ;DX:AX=0020 0000h,CF=1
Mov al, -4
Mov bl, 4
IMUL bl ;AX=0FFF0H,CF=0
Mov ax, 30h
Mov bx, 4h
IMul bx ;DX:AX =0C0H,CF=0
Mov al, 48
Mov bl, 4
IMUL bl ;AX=00C0H(即十进制的192),CF=1
任意进制的码制转换
.data
ASCIICHAR BYTE '0123456789ABCDEFGHIJKLMNOPQRSTUVWZYX'
.code
ToASC PROC uses eax ebx ecx esi
;将EAX中的数按BL中指定的进制数,转换成ASCII字符串放到ESI指向的数组中
mov ecx , 0 ;
mov cl , bl ; movzx ecx, bl
add esi , 31
nxt_ta:
mov edx , 0
div ecx
mov bl,ASCIICHAR[edx]
mov [esi],bl
dec esi
cmp eax , 0
jnz nxt_ta ; jne
ret
ToASC ENDP
0x06 高级过程
stack frame
给子过程传递参数的两种基本方式
- 通过寄存器传递
- 执行效率高
- 代码可能显得混乱
- 寄存器数量有限
mov esi , offset array
mov ecx,lengthof array
mov ebx , type array
call DumpMem
- 通过堆栈传递
- 方式灵活通用
- 效率偏低
push offset array
push lengthof array
push type array
call DumpMem2
使用堆栈传递参数时压入了两类参数:
- 值参数(变量或常量的值)
- 引用/指针参数(变量的地址)
实例
传递值
.data
val1 dword 5
val2 dword 6
.code
push val2
push val1
call AddTwo
AddTwo(val1,val2)
传递引用
.data
val1 dword 5
val2 dword 6
.code
push offset val2
push offset val1
call AddTwo
AddTwo(&val1,&val2)
重点:参数访问
.data
Val1 dword 5
Val2 dword 6
.code
Push val2
Push val1
Call AddTwo
…
AddTwo proc
push ebp
Mov ebp , esp
mov eax , [ebp + 12] ;取得val2
add eax , [ebp + 8] ;加上val1
pop ebp
ret
AddTwo endp
堆栈清理
因为在调用子过程前,给堆栈压入了一些内容,在子过程返回时,必须调整堆栈指针。
- 在调用完子过程后通过加法指令改变ESP值
- 通过 RET imm 指令的形式
add方法:
.data
Val1 dword 5
Val2 dword 6
.code
Push val2
Push val1
Call AddTwo
Add esp , 8
ret方法,在子过程中调用:
.data
Val1 dword 5
Val2 dword 6
.code
Push val2
Push val1
Call AddTwo
AddTwo proc
push ebp
mov ebp,esp
mov eax,[ebp+12]
add eax,[ebp+8]
pop ebp
ret 8
AddTwo endp
采用uses操作符保存寄存器,则要注意uses指令是将寄存器的压栈指令放在子过程的开始,即在堆栈帧里push ebp语句之前,这时,参数偏移地址计算将会受到影响
0x07 字符串和数组
CLD
清除方向标志
STD
设置方向标志
MOVSB,MOVSW,MOVSD
指令 | 功能 | ESI和EDI修改量 |
---|---|---|
MOVSB | 复制字节 | 1 |
MOVSW | 复制字 | 2 |
MOVSD | 复制双字 | 4 |
复制双字数组
.data
source dword 20 dup(0ffh)
target dword 20 dup(?)
.code
; …
cld
mov ecx , lengthof source
mov esi , offset source
mov edi , offset target
rep movsd ;将source开始的20个双字复制到target中
; …
CMPSB,CMPSW,CMPSD
指令 | 操作 |
---|---|
CMPSB | 比较字节 |
CMPSW | 比较字 |
CMPSD | 比较双字 |
单个比较
.data
source dword 1234h
target dword 5678h
.code
; …
mov esi , offset source
mov edi , offset target
cmpsd ;比较双字
ja L1 ;如果source>targe跳转至L1
jmp L2 ;如果source<=target跳转至L2,本例即是
; ….
字符串比较
.data
CmpsTestSource byte "ABCDE"
CmpsTestTarget byte "AB "
.code
CMPSTEST proc
cld
mov esi , offset CmpsTestSource
mov edi , offset CmpsTestTarget
mov ecx, lengthof CmpsTestSource ;最多比较次数,此例为5
repe cmpsb ; 比较到第三个字母时,因两者不等,重复不再继续,但当前串
; 操作执行完,esi和edi还会增加。所以,最后,esi和edi会指向
; 第四个字母的位置。
ret
CMPSTEST endp
SCASB,SCASW,SCASD
将AL的值与EDI指向的内存内容相比较(相当于cmp AL , [edi]),即相当于是做查找操作,通常会跟重复前缀
- 如果使用repe前缀,则将查找到EDI开始的内存中第一个不等于AL时中止重复;
- 如果使用repne前缀,则将查找到EDI开始的内存中第一个等于AL时中止重复;
- 当然,如果ecx减到0,也会结束查找
SCASW
是用AX作字查找,SCASD
是用EAX作双字查找
扫描一个匹配字符
.data
alpha byte “ABCDEFGH”,0
.code
mov edi , offset alpha
mov al , ‘F’
mov ecx , lengthof alpha
cld
repne scasb ;不相等则重复,即找到第一个相等的
jnz quit ; 如果这个条件满足,表示是找完整个ecx长度,也没有找到
dec edi ;回减一,让edi指向找到第一个相等的位置
…
Quit:
STOSB,STOSW,STOSD
把AL/AX/EAX的内容存储在EDI指向的内存单元中,同时EDI的值根据方向标志的值增加和减少。
Stosb是存储AL,stosw存储AX,stosd存储EAX 使用rep前缀可以对一段内存进行填充
LODSB,LODSW,LODSD
将从esi指向的内存内容取出存到累加器中,同时,修改esi的值。
lodsb
是取出一个字节存到AL中,lodsw
是取出一个字存到AX中,lodsd
是取出一个双字存到EAX中。
该指令一般不会跟重复前缀
串操作指令对标志位的影响
cmps
和scas
指令会对标志位有影响,影响效果如同CMP指令。
movs
,lods
,stos
不会影响标志位。