学习pwn需要的基本汇编语言知识
🙋♀️ 博主介绍:暗流者
⭐ 本期精彩:学习pwn需要的基本汇编语言知识
🏆 热门专栏:带你从C语言和汇编角度入门pwn
🚀 专栏亮点:零基础友好 | 实战案例丰富 | 循序渐进教学 | 代码详细注释
💡 学习收获:从语言角度入门pwn,为您的pwn之路打好基础
🔥 如果觉得文章有帮助,别忘了点赞👍 收藏⭐ 关注🚀,你的支持是我创作的最大动力!
目录
前言:
一、寄存器架构
二,基础指令
一、数据传送指令
二、算术运算指令
三、位操作指令
四、控制转移指令
五、栈操作指令
六、系统调用指令对比
七、核心数据定义指令
八、64位专属指令
三.构建内存空间
一.寄存器构建
二、函数调用中的协作流程
PUSH 执行流程(以 32位 push eax 为例):
POP 执行流程(以 32位 pop ebx 为例):
call 指令
ret 指令
前言:
我计划用几次用C语言和汇编语言的角度讲完整个pwn的基础知识点,会涉及32位汇编和64位汇编的讲解,但是在pwn中的gdb工具分析时,需要你认识内联汇编(支持C语言和汇编出现在同一个程序中),内联汇编没什么新的知识点,但是有些东西还是需要你知道
C语言方面的话并不需要你的能力很强,懂得一些基本的操作(数组,指针,结构),数据结构(链表,栈,树,图)等等,总之我会从C语言的角度去说PWN中的一些基本结构
这是第一天,我将从汇编语言的基础知识点出发,因为可能很多人学汇编语言是学习的16位汇编,但是pwn题目中都是32位和64位汇编,所以在这里我就简单讲一下32位和64位汇编,一些汇编语言的基础操作就不讲了,我只讲一下32位和64位汇编不同的地方,这里我讲一些pwn中常见的汇编知识
一、寄存器架构
32位(x86):
-
通用寄存器(8个):
EAX
,EBX
,ECX
,EDX
,ESI
,EDI
,EBP
,ESP
-
段寄存器:
CS
,DS
,ES
,FS
,GS
,SS
-
特殊寄存器:
EIP
(指令指针),EFLAGS
(状态标志)
64位(x86-64):
-
扩展通用寄存器(16个):
RAX
,RBX
,RCX
,RDX
,RSI
,RDI
,RBP
,RSP
,R8
-R15
(新增8个64位寄存器) -
保留部分寄存器: 段寄存器(除
FS
/GS
外)基本弃用 -
特殊寄存器:
RIP
(64位指令指针),RFLAGS
二,基础指令
一、数据传送指令
指令 | 32位示例 | 64位示例 | 功能说明 |
---|---|---|---|
MOV | mov eax, 10 | mov rax, 10 | 数据传送 |
LEA | lea esi, [ebx+ecx*4] | lea rsi, [rbx+rcx*4] | 地址计算(不访问内存) |
XCHG | xchg eax, ebx | xchg rax, rbx | 交换数据 |
MOVZX | movzx eax, byte [mem] | movzx rax, byte [mem] | 零扩展传送 |
MOVSX | movsx eax, byte [mem] | movsx rax, byte [mem] | 符号扩展传送 |
二、算术运算指令
指令 | 通用示例 | 功能说明 |
---|---|---|
ADD | add eax, 10 / add rax, 10 | 加法 |
SUB | sub ebx, ecx / sub rbx, rcx | 减法 |
INC | inc dword [mem] | 自增(不影响CF标志) |
DEC | dec esi | 自减(不影响CF标志) |
MUL | mul ecx | 无符号乘法(EDX:EAX = EAX * ECX) |
IMUL | imul eax, ebx | 有符号乘法 |
DIV | div ecx | 无符号除法(EAX = EDX:EAX / ECX) |
三、位操作指令
指令 | 通用示例 | 功能说明 |
---|---|---|
AND | and eax, 0Fh | 按位与 |
OR | or ebx, 80h | 按位或 |
XOR | xor eax, eax | 按位异或(常用清零) |
NOT | not ecx | 按位取反 |
SHL | shl edx, 4 | 逻辑左移 |
SHR | shr eax, 1 | 逻辑右移 |
SAL | sal ebx, cl | 算术左移(同SHL) |
SAR | sar ecx, 3 | 算术右移(保留符号位) |
四、控制转移指令
指令 | 32位示例 | 64位示例 | 功能说明 |
---|---|---|---|
JMP | jmp label | jmp label | 无条件跳转 |
CALL | call func | call func | 函数调用 |
RET | ret | ret | 函数返回 |
JE/JZ | je target | je target | 相等/为零时跳转 |
JNE/JNZ | jne target | jne target | 不相等/非零时跳转 |
LOOP | loop label | loop label | ECX/RCX减1,非零跳转 |
五、栈操作指令
指令 | 32位示例 | 64位示例 | 功能说明 |
---|---|---|---|
PUSH | push eax | push rax | 入栈(ESP/RSP递减) |
POP | pop ebx | pop rbx | 出栈(ESP/RSP递增) |
PUSHA | pusha | ✘ 64位移除 | 保存所有通用寄存器 |
POPA | popa | ✘ 64位移除 | 恢复所有通用寄存器 |
ENTER | enter 16, 0 | 极少使用 | 创建栈帧 |
LEAVE | leave | leave | 销毁栈帧 |
六、系统调用指令对比
架构 | 指令 | 参数传递方式 |
---|---|---|
32位 | int 0x80 | EAX=调用号, EBX/ECX/EDX/ESI/EDI/EBP=参数 |
64位 | syscall | RAX=调用号, RDI/RSI/RDX/R10/R8/R9=参数 |
七、核心数据定义指令
指令 | 全称 | 位数 | 功能说明 | 示例 |
---|---|---|---|---|
DB | Define Byte | 8位 | 定义字节数据 | num DB 0x55 |
DW | Define Word | 16位 | 定义字(2字节)数据 | buffer DW 1024 |
DD | Define Doubleword | 32位 | 定义双字(4字节)数据 | pointer DD 0x8040000 |
DQ | Define Quadword | 64位 | 定义四字(8字节)数据 | addr64 DQ 0x7FFFFFFFFFFF |
DT | Define Ten Bytes | 80位 | 定义10字节数据(浮点专用) | float DT 3.1415926535 |
八、64位专属指令
-
RIP相对寻址:
asmlea rax, [rip + label] ; 动态计算地址(位置无关代码)
-
SWAPGS:
asmswapgs ; 切换内核GS寄存器(系统编程)
-
SYSCALL/SYSRET:
asmsyscall ; 快速系统调用 sysret ; 快速系统返回
三.构建内存空间
一.寄存器构建
1.如何构建一段栈空间,需要这三个ebp,esp,ss寄存器
寄存器 | 全称 | 位数 | 核心功能 |
---|---|---|---|
ESP | Extended Stack Pointer | 32位 | 栈顶指针,始终指向当前栈顶位置 |
EBP | Extended Base Pointer | 32位 | 栈帧基址,指向当前函数栈帧起点 |
SS | Stack Segment | 16位 | 栈段寄存器,定义栈内存的基地址 |
高地址 +-----------------+ | 返回地址 | +-----------------+ | 保存的RBP | <-- RBP (当前帧基址) +-----------------+ | 局部变量 | | ... | +-----------------+ | 额外参数 | +-----------------+ 低地址 <-- RSP (栈顶)
二、函数调用中的协作流程
1. 函数进入序言 (Prologue):
asm; 保存调用者的栈帧基址
push ebp ; ESP自动减4,旧EBP入栈
; 建立新栈帧
mov ebp, esp ; EBP指向当前栈顶(即旧EBP位置)
; 分配局部变量空间
sub esp, 16 ; 分配16字节局部变量区
2. 函数内栈操作:
asm; 访问参数(假设32位cdecl调用约定)
mov eax, [ebp+8] ; 第一个参数(返回地址占4字节)
; 访问局部变量
mov [ebp-4], eax ; 存储到第一个局部变量
3. 函数退出尾声 (Epilogue):
asm; 恢复栈指针(释放局部变量)
mov esp, ebp ; ESP回到栈帧起点
; 恢复调用者栈帧
pop ebp ; 恢复旧EBP,ESP自动加4
; 返回(清理参数由调用者负责)
ret
三,栈对齐的问题(32位和64位都需要)
栈对齐(Stack Alignment)是x86架构中极易被忽视却至关重要的机制,尤其在64位系统中直接关系到程序稳定性和性能。本文将通过原理分析、实例演示和解决方案全面解析这一核心问题。
-
函数调用前RSP必须16字节对齐
-
调用指令
call
会压入8字节返回地址
asm; 正确对齐示例
sub rsp, 24 ; 24+8(返回地址)=32 → 16字节对齐
call func
add rsp, 24
4.pop和push指令
pop和push指令用于压栈和出栈
PUSH 执行流程(以 32位 push eax
为例):
-
ESP 减 4(栈指针下移)
ESP = ESP - 4
-
将 EAX 值写入 ESP 指向的地址
[ESP] = EAX
-
更新 EFLAGS 寄存器(不影响标志位)
plaintext
执行前: 执行后: 高地址 高地址 ┌─────────┐ ┌─────────┐ │ │ │ EAX值 │ ← ESP ├─────────┤ ├─────────┤ │ │ ← ESP │ │ └─────────┘ └─────────┘ 低地址 低地址
POP 执行流程(以 32位 pop ebx
为例):
1. 读取 ESP 指向的值`EBX = [ESP]` 2. ESP 加 4(栈指针上移)`ESP = ESP + 4` 3. 更新 EFLAGS 寄存器(不影响标志位)
5.ret和call指令
call
指令
-
作用:
call
指令将当前的 IP(指令指针)或 CS:IP(段寄存器和指令指针)压入栈中,然后跳转到指定的函数地址,从而实现子程序的调用。 -
执行流程
- 1. 将当前的 IP 或 CS 和 IP 压入栈中(相当于 `push IP`)。2. 跳转到目标地址(相当于 `jmp near ptr 标号`)。 - **示例**:`call 标号`(近转移)相当于执行 `push IP` 和 `jmp near ptr 标号` 。
ret
指令
-
作用:
ret
指令用于从子程序返回到调用点,它通过从栈中弹出返回地址,并将其加载到 IP 寄存器中,从而恢复程序的执行流程。 -
执行流程
- :1. 从栈中弹出返回地址(相当于 `pop IP`)。2. 将返回地址加载到 IP 寄存器中,程序继续执行。 - **示例**:`ret` 指令相当于执行 `pop IP`,并更新 SP 寄存器(栈指针)。
总结
-
本博客将从C语言与汇编语言的角度系统讲解PWN基础,涵盖32位和64位汇编差异、寄存器架构、常用指令及栈操作等核心内容,帮助读者深入理解程序执行机制。