x86 汇编基础篇
1. 进制
1.1 二进制与十六进制
十六进制 : 方便阅读二进制, 由十六个符号组成(可以是任意符号), 逢十六进一
0 1 2 3 4 5 6 7 8 9 A B C D E F 0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1110 1111
1.2 数据宽度
由于受硬件的制约, 数据都有长度限制, 超过容量最大宽度的数据高位会被丢弃 计算机仅存储数据, 数据是有符号或无符号取决于怎么解析
名字 说明 大小 BYTE 字节 8 bit WORD 字 16 bit DWORD 双字 32 bit QWORD 四字 64 bit
2. 寄存器
2.1 32 位通用寄存器
由于硬件限制, 大于寄存器宽度的数据, 高位会被丢弃
寄存器 说明 大小 编号 EAX (Extended Accumulator Register)累加器, 通常用于运算和返回值 0xFFFFFFFF 0 ECX (Extended Count Register)计数, 通常用于位移、循环计数 0xFFFFFFFF 1 EDX (Extended Data Register)I/O 指针 0xFFFFFFFF 2 EBX (Extended Base Register)DS 段的数据指针 0xFFFFFFFF 3 ESP (Extended Stack Pointer Register)堆栈指针 0xFFFFFFFF 4 EBP (Extended Base Pointer Register)SS 段的数据指针 0xFFFFFFFF 5 ESI (Extended Source Index Register)字符串操作的源指针; SS 段的数据指针 0xFFFFFFFF 6 EDI (Extended Destination Index Register)字符串操作的目标指针; ES 段的数据指针 0xFFFFFFFF 7
2.2 标志寄存器
Flag 说明 CF(Carry Flag) 进位标志(最高位进位), 用于无符号数运算 PF(Parity Flag) 奇偶标志, 操作结果的低 8 位中 1 的个数为偶数时, 值为 1 AF(Auxiliary Carry Flag) 辅助进位标志 ZF(Zero Flag) 零标志, 运算结果为 0 时, 值为 1 SF(Sign Flag) 符号标志, 即运算结果(二进制)的最高位的值 TF(Trap Flag) 陷阱标志, TF=1 时, CPU 处于单步执行方式(每执行一条指令就自动执行一次 1 号内部中断), 主要用于 Debug 中 DF(Interrupt Enable Flag) 中断允许标志 DF(Direction Flag) 方向标志, 字符串操作时, DF=0, 地址寄存器(SI,DI)的内容递增, DF=1 时, (SI,DI)的内容递减 OF(Overflow Flag) 溢出标志, 用于有符号数运算 IOPL(I/O Privilege Level) I/O 特权标志(2 bit), 如果当前特权级别小于等于 IOPL 的值, 则 I/O 指令可执行, 否则将发生一个保持异常 NT(Nested Task) 嵌套任务标志, 用于控制中断返回指令 IRET 的执行, NT=0, 执行常规的中断返回操作, 用堆栈中保存的值恢复 EFLAGS、CS 和 EIP; NT=1, 通过任务转换实现中断返回 RF(Restart Flag) 重启标志, 用于控制是否接受调试故障, RF=0 表示接受 VM(Virtual 8086 Mode) 虚拟 8086 方式标志, VM=1 表示处理器处于虚拟 8086 方式下的工作状态, 否则处理一般保护方式下的工作状态
# CF
mov eax, 0xFFFFFFFF # 负 + 负 = 正, 说明有溢出
add eax, 0x1 # 让无符号数最高位进位, 观察 CF 位的变化
# PF
mov eax, 0x0
add eax, 0x1 # eax 中的二进制值 1 的数量为奇数, PF=0
add eax, 0x2 # eax 中的二进制值 1 的数量为偶数, PF=1
# AF
mov eax, 0x0000FFFF
add eax, 0x1 # 让第四个 F 进位
mov ax, 0x00FF # 让第二个 F 进位
add ax, 0x1
mov al, 0x0F
add al, 0x1
# ZF
mov eax, 0x2
sub eax, 0x2 # 运算结果为 0, ZF=1
xor eax, eax # 将 eax 清零, 并且会修改 ZF=1
# SF
mov al, 0x7F # 7F = 01111111
add al, 0x2 # 结果为 10000001, SF=1(最高位的值)
# OF
mov al, 0x8 # 正 + 负, 有符号无符号都不溢出
add al, 0x8
mov al, 0x7F # 正 + 正 = 负, 说明有溢出
add al, 2 # 无符号不溢出 CF=0, 有符号溢出 OF=1
mov al, 0xFF
add al, 0x2 # 无符号溢出 CF=1, 有符号不溢出 OF=0
mov al, 0xFE
add al, 0x80 # 无符号有符号都溢出 CF=1 OF=1
# DF
mov esi, 0x0018FF90
mov edi, 0x0018FF98
movs dword ptr es:[edi], dword ptr ds:[esi] # DF=0, esi=esi+4, edi=edi+4
mov esi, 0x0018FF90
mov edi, 0x0018FF98
movs dword ptr es:[edi], dword ptr ds:[esi] # DF=1, esi=esi-4, edi=edi-4
2.3 段寄存器
3. 算术运算指令
r 表示通用寄存器, m 表示内存, imm 表示立即数
3.1 mov
指令格式 : mov target, source
, 两个操作数的宽度必须一致, target 和 source 不能同时为内存单元
mov r8, imm8
mov r16,imm16
mov r32,imm32
mov r/m8, r8
mov r/m16,r16
mov r/m32,r32
mov r8, r/m8
mov r16,r/m16
mov r32,r/m32
mov EAX, 12345678
mov AX, BBBB
mov AL, CC
mov AH, DD
3.2 add
add r/m8, imm8
add r/m16,imm16
add r/m32,imm32
add r/m32,imm8 # target 宽度大于 source 宽度即可
add r/m8, r8
add r/m16,r16
add r/m32,r32
add r8, m8
add r16,m16
add r32,m32
3.3 sub
sub r/m8, imm8
sub r/m16,imm16
sub r/m32,imm32
sub r/m32,imm8
sub r/m8, r8
sub r/m16,r16
sub r/m32,r32
sub r8, m8
sub r16,m16
sub r32,m32
3.4 adc
ADC : 带借位加法, target = target + source + CF
, 两边不能同时为内存, 数据宽度要一样
# 指令格式
adc r/m, r/m/imm
mov al, 0xFF
add al, 0x1 # 先让 CF=1
mov al, 0x1
mov cl, 0x2
adc al, cl # AL=4, CF=0
3.5 sbb
ADC : 带借位加法, target = target - source - CF
# 指令格式
sbb r/m, r/m/imm
mov al, 0xFF
add al, 0x1 # 先让 CF=1
mov al, 0x5
mov cl, 0x1
sbb al, cl # AL=3, CF=0
3.6 xchg
# 指令格式
xchg r/m, r/m
mov eax, 0x1
mov ebx, 0x2
xchg eax, ebx
xchg al, byte ptr ds:[0x18FF98]
xchg eax, dword ptr ds:[0x18FF98]
3.7 movs
movs byte ptr es:[edi], byte ptr ds:[esi]
movs word ptr es:[edi], word ptr ds:[esi]
movs dword ptr es:[edi], dword ptr ds:[esi]
# 简写形式
movsb
movsw
movsd
mov esi, 0x0018FF90
mov edi, 0x0018FF94
movsb # 交换一个字节内存的值, 并将 esi 和 edi 值自动加 1(DF=0)或减 1(DF=1)
movsw # 交换两个字节内存的值, esi 和 edi 值自动加 2(DF=0)或减 2(DF=1)
movsd # 将 esi 指向的内存数据移动到 edi 指向的内存, esi 和 edi 值自动加 4(DF=0)或减 4(DF=1)
3.8 stos
stos : 将 AL/EX/EAX 的值存储 到 [EDI] 指定的内存单元
stos byte ptr es:[edi]
stos word ptr es:[edi]
stos dword ptr es:[edi]
# 简写形式
stosb
stosw
stosd
mov eax, 0x12345678
mov edi, 0x0018FF94
stosd # [edi] = 0x12345678, edi = DF == 0 ? (edi = edi+4) : (edi = edi-4)
stosw # [edi] = 0x5678, edi = DF == 0 ? (edi = edi+2) : (edi = edi-2)
stosb # [edi] = 0x78, edi = DF == 0 ? (edi = edi+1) : (edi = edi-1)
3.9 rep
mov esi, 0x0018FF90
mov edi, 0x0018FF94
mov ecx, 0x10
rep movsd # 重复执行 movsd 10 次
mov ecx, 0x10
rep stosd # 重复执行 stosd 10 次
4. 条件判断指令
4.1 cmp
cmp : 用于比较两个值, 其结果只影响标志寄存器, 不会修改操作数; 运算指令会同时修改操作数和标志寄存器ZF=1 表示相等, SF=1 表示第一个操作类小于第二个操作数
# 指令格式
cmp r/m, r/m/imm
mov eax, 0x100
mov ecx, 0x100
cmp eax, ecx # ZF=1, SF=0
sub eax, ecx # ZF=1, SF=0, eax=0
cmp eax, ecx # ZF=0, SF=1
cmp al, byte ptr ds:[0x00401006]
cmp ax, word ptr ds:[0x00401006]
cmp eax, dwrod ptr ds:[0x00401006]
4.2 test
test : 对两个操作数进行或操作, 结果不保存, 仅改变相应的标志位
test r/m, r/m/imm
mov eax, 0x100
test eax, eax # ZF=0
sub eax, eax # eax=0
test eax, eax # ZF=1
5. 逻辑运算指令
5.1 and &
and r/m8, imm8
and r/m16,imm16
and r/m32,imm32
and r/m32,imm8
and r/m8, r8
and r/m16,r16
and r/m32,r32
and r8, m8
and r16,m16
and r32,m32
5.2 or |
or r/m8, imm8
or r/m16,imm16
or r/m32,imm32
or r/m32,imm8
or r/m8, r8
or r/m16,r16
or r/m32,r32
or r8, m8
or r16,m16
or r32,m32
5.3 xor ^
xor r/m8, imm8
xor r/m16,imm16
xor r/m32,imm32
xor r/m32,imm8
xor r/m8, r8
xor r/m16,r16
xor r/m32,r32
xor r8, m8
xor r16,m16
xor r32,m32
5.4 not !
not r/m8
not r/m16
not r/m32
6. 内存寻址
每个内存单元的宽度为一个字节(byte), 每个内存单元都有其编号, 即内存地址
6.1 立即数寻址
# 写内存
mov BYTE PTR DS:[0x18FF94], 0xA # [] 表示该值是一个内存编号, 而不是立即数
mov WORD PTR DS:[0x18FF98], 0xBB
mov DWORD PTR DS:[0x18FF9C], 0x12345678
# 读内存
mov EAX, BYTE PTR DS:[0x18FF94]
mov EAX, DWORD PRT DS:[0x18FF98]
# 获取内存编号
lea EAX, [0x18FF94] # 等同于 lea EAX, DWORD PTR DS:[0x18FF94]
lea EAX, [ESP+8] # 等同于 lea EAX, DWORD PTR DS:[ESP+8]
6.2 寄存器寻址
mov EBX, 0x18FF90
# 写内存
mov BYTE PTR DS:[EBX], 0xA
mov WORD PTR DS:[EBX], 0xBB
mov DWORD PTR DS:[EBX], 0x12345678
# 读内存
mov AL, BYTE PTR DS:[EBX]
mov AX, WORD PTR DS:[EBX]
mov EAX, DWORD PTR DS:[EBX]
# 获取内存编号
lea EAX, [EBX]
6.3 寄存器+偏移寻址
mov EBX, 0x18FF90
# 写内存
mov BYTE PTR DS:[EBX+0xC], 0xA
mov WORD PTR DS:[EBX+0xC], 0xBB
mov DWORD PTR DS:[EBX+0xC], 0x12345678
# 读内存
mov AL, BYTE PTR DS:[EBX+0xC]
mov AX, WORD PTR DS:[EBX+0xC]
mov EAX, DWORD PTR DS:[EBX+0xC]
# 获取内存编号
lea EAX, [EBX+0xC]
6.4 寄存器+寄存器*[1,2,4,8]寻址
mov EBX, 0x18FF90
mov ECX, 0x4
# 写内存
mov BYTE PTR DS:[EBX+ECX*2], 0xA # EBX+ECX*2 = 0x18FF98
mov WORD PTR DS:[EBX+ECX*2], 0xBB
mov DWORD PTR DS:[EBX+ECX*2], 0x12345678
# 读内存
mov AL, BYTE PTR DS:[EBX+ECX*2]
mov AX, WORD PTR DS:[EBX+ECX*2]
mov EAX, DWORD PTR DS:[EBX+ECX*2]
# 获取内存编号
lea EAX, [EBX+ECX*2]
6.5 寄存器+寄存器*[1,2,4,8]+立即数寻址
mov EBX, 0x18FF90
mov ECX, 0x4
# 写内存
mov BYTE PTR DS:[EBX+ECX*2+4], 0xA # EBX+ECX*2+4 = 0x18FF9C
mov WORD PTR DS:[EBX+ECX*2+4], 0xBB
mov DWORD PTR DS:[EBX+ECX*2+4], 0x12345678
# 读内存
mov AL, BYTE PTR DS:[EBX+ECX*2+4]
mov AX, WORD PTR DS:[EBX+ECX*2+4]
mov EAX, DWORD PTR DS:[EBX+ECX*2+4]
# 获取内存编号
lea EAX, [EBX+ECX*2+4]
7. 堆栈
7.1 堆栈模拟(满降栈)
mov EBX, 0x0018FF94 # 表示栈底指针
mov EDX, 0x0018FF94 # 表示栈顶指针
# 压栈
mov DWORD PTR DS:[EDX-4],0xAAAAAAAA # 先放数据, 再移动栈顶指针(通过 sub 指令实现)
sub EDX, 4 # 模拟满降栈((栈顶指针指向最后入栈的数据, 栈顶指针从高地址向低地址移动)
lea EDX, [EDX-4] # 先移动栈顶指针(通过 lea 指令实现), 再先放数据
mov DWORD PTR DS:[EDX],0xBBBBBBBB
# 堆栈寻址
mov ESI,DWORD PTR DS:[EBX-8] # 通过栈底指针+偏移取值
mov ESI,DWORD PTR DS:[EDX+4] # 通过栈顶指针+偏移取值
# 出栈
mov EAX, DWORD PTR DS:[edx]
add edx, 4
lea edx, [EDX+4]
mov eax, DWORD PTR DS:[edx-4]
7.2 push 和 pop
push
# 立即数无论大小, ESP 都是移动 4 字节
push imm8
push imm16
push imm32
push r16 # ESP 移动 2 字节
push r32
push m16 # ESP 移动 2 字节
push m32
pop
pop r16
pop r32
pop m16
pop m32
pushad 和 popad
pushad : 将所有通用寄存器压栈popad : 从栈中弹出 8 个数据, 依次放到通用寄存器中
pushad
mov eax, 0x1
mov ecx, 0x2
mov edx, 0x3
mov ebx, 0x4
mov esi, 0x5
mov edi, 0x6
popad
8. JCC(修改 EIP)
8.1 jmp
jmp r/imm
jmp 0x00401012 # EIP=0x00401012
jmp short 0x00401012 # 与上一条指令效果一样, short 表示跳转范围在 128 以内
8.2 call 和 retn
call : 修改 EIP 地址为目标地址, 并将返回地址压栈, 即 call 指令下一条指令地址retn : 等同于 pop eip(非法指令, 仅用于理解)
Address Hex dump Command
00401000 E8 01000000 CALL 00401006 # 等同于 EIP=0x00401006, push 00401000+5(E8 03000000 长度为 5 字节)
00401005 B8 AAAAAAAA MOV EAX,AAAAAAAA
0040100A C3 RETN # 等同于 pop eip, 即 EIP=0x00401005
8.3 条件跳转
指令 说明 条件 je jz
结果相等则跳转 ZF=1 jne jnz
结果不相等则跳转 ZF=0 js
结果为负则跳转 SF=1 jns
结果为正则跳转 SF=0 jp jpe
结果中 1 的个数为奇数则跳转 PF=1 jnp jpo
结果中 1 的个数为偶数则跳转 PF=0 jo
结果溢出则跳转 OF=1 jno
结果不溢出则跳转 OF=0 jb, jnae
小于则跳转(无符号数) CF=1 jnb jae
大于则跳转(无符号数) CF=0 jbe jna
小于等于则跳转(无符号数) CF=1 or ZF=1 jnbe ja
大于则跳转(无符号数) CF=0 and ZF=0 jl jnge
小于则跳转(有符号数) SF!=OF jnl jge
大于等于跳转(有符号数) SF=OF jle jng
小于等于跳转(有符号数) ZF=1 or SF!=OF jnle jg
大于则跳转(有符号数) ZF=0 and SF=OF
mov eax, 0x100
cmp eax, 0x100
jz 0x00401006