Pwn ret2text
攻击原理
栈的基本结构
在程序执行过程中,每个函数调用都会在栈上分配一段内存空间,称为栈帧(Stack Frame)。栈帧的结构如下(以64位系统为例):
1 | 高地址 |
函数调用与返回机制
当函数被调用时:
-
call 指令:将下一条指令的地址(返回地址)压入栈中
-
函数序言(prologue):
1
2
3push rbp ; 保存旧的rbp
mov rbp, rsp ; 设置新的栈帧基址
sub rsp, 0x30 ; 为局部变量分配空间
当函数返回时:
-
函数尾声(epilogue):
1
2
3mov rsp, rbp ; 恢复栈指针
pop rbp ; 恢复旧的rbp
ret ; 等价于 pop rip,从栈中弹出返回地址并跳转
栈溢出原理
栈溢出是指向栈上的缓冲区写入超过其分配大小的数据,导致覆盖了栈上的其他数据。
危险函数示例:
-
gets(char *s):不检查输入长度,直接读取到缓冲区 -
scanf("%s", buf):不限制输入长度 -
strcpy(dest, src):不检查源字符串长度
当缓冲区溢出时,数据会向高地址方向覆盖,依次覆盖:
-
局部变量区域的其他变量
-
保存的 rbp(saved rbp)
-
返回地址(saved rip) ← 这是攻击的关键点
ret2text 攻击原理
ret2text(Return to Text) 是一种栈溢出利用技术,核心思想是:
-
覆盖返回地址:通过栈溢出,将函数返回地址覆盖为目标函数(通常是后门函数或
system("/bin/sh"))的地址 -
劫持控制流:当被攻击的函数执行到
ret指令时:ret指令等价于pop rip- 从栈中弹出我们覆盖的地址到
rip寄存器 - 程序跳转到我们指定的地址执行
-
执行目标代码:由于跳转到的是程序中已有的代码段(text段),因此称为 “return to text”
攻击示意图
正常执行流程:
1 | func() 调用 |
ret2text 攻击流程:
1 | func() 调用 |
为什么需要 ret 指令(栈对齐)
在64位系统中,函数调用时要求栈指针 rsp 必须16字节对齐。如果直接跳转到目标函数,栈可能未对齐,导致程序崩溃。
解决方案:在目标函数地址前先放置一个 ret 指令的地址,这样会先执行一次 pop rip,使栈指针增加8字节,从而满足对齐要求。
1 | payload = 填充(offset) + ret_addr + target_func_addr |
执行流程:
-
覆盖返回地址为
ret_addr -
函数返回时执行
ret(pop rip),栈指针 +8 -
再次执行
ret,弹出target_func_addr并跳转 -
此时栈已对齐,目标函数正常执行
例题
[玄武杯 2025]ret2text 64
https://www.nssctf.cn/problem/7304
chechsec
1 | [*] '/home/huayi/Desktop/pwnexp/blog_ctf/ret2text/[玄武杯 2025]ret2text 64/ret2text1' |
最简单的ret2text, 目标是劫持控制流到hint函数, 漏洞点在于gets函数接收输入造成的栈溢出,其中ret是为了栈平衡。
1 | int func() |
exp
1 | from pwn import * |
思路总结
ret2text 是最基础的栈溢出利用技术,核心思想是通过栈溢出覆盖返回地址,将程序控制流劫持到程序中已有的代码段(通常是后门函数或目标函数)。
攻击步骤
-
检查程序保护机制
- 使用
checksec检查程序的保护机制 - 关键点:需要
No canary(无栈保护)才能进行栈溢出 No PIE(无地址随机化)使得函数地址固定,便于利用
- 使用
-
定位漏洞点
- 寻找危险函数:
gets、scanf、strcpy等不检查输入长度的函数 - 分析栈结构,确定缓冲区大小和偏移量
- 寻找危险函数:
-
计算偏移量
- 通过 IDA 或 gdb 分析栈布局
- 偏移量 = 缓冲区大小 + saved rbp(64位为8字节,32位为4字节)
- 例如:
char v1[48]在[rsp+0h] [rbp-30h],偏移量为0x30 + 0x8 = 56字节
-
获取目标函数地址
- 使用
objdump、readelf或 pwntools 的elf.symbols['函数名']获取 - 确保程序没有 PIE 保护,地址固定
- 使用
-
构造 payload
- 64位系统需要注意栈对齐问题(16字节对齐)
- 如果目标函数地址不满足对齐要求,可能需要先跳转到
ret指令(pop rip)进行栈对齐 - payload 结构:
填充字符 * 偏移量 + ret地址(可选)+ 目标函数地址
-
发送 payload 并获取 shell
- 通过栈溢出覆盖返回地址
- 函数返回时,
ret指令会从栈中弹出我们覆盖的地址并跳转 - 成功劫持控制流到目标函数
关键要点
-
栈对齐:64位系统调用函数时要求栈指针
rsp按16字节对齐,如果直接跳转导致栈未对齐,需要先执行ret指令(pop rip)来调整栈指针 -
偏移量计算:必须精确计算偏移量,否则无法正确覆盖返回地址
-
保护机制:ret2text 需要程序没有 canary 和 PIE 保护,NX 保护不影响(因为跳转到代码段而非栈)
