BUUCTF [第五空间2019 决赛]PWN5:从格式化字符串到任意地址写的实战通关

发布时间:2026/6/29 21:03:33
BUUCTF [第五空间2019 决赛]PWN5:从格式化字符串到任意地址写的实战通关 1. 漏洞背景与环境分析这道PWN5题目来自第五空间2019年CTF决赛考察的是经典的格式化字符串漏洞利用。我们先来看看题目开启了哪些保护机制。用checksec工具检查会发现程序开启了Canary栈保护、NX不可执行内存以及部分RELRO重定位表只读。这些保护机制意味着传统的栈溢出和直接执行shellcode的方法都行不通了。在IDA Pro中反编译后可以看到程序的主要逻辑首先从/dev/urandom读取一个4字节的随机数存入全局变量dword_804C044然后提示用户输入name这个输入会被直接传递给printf函数这里就存在格式化字符串漏洞。接着程序会要求输入password如果这个password经过atoi转换后等于之前存储的随机数就会给出shell。关键点在于这个随机数是无法预测的但我们可以利用格式化字符串漏洞来修改dword_804C044的值或者劫持atoi的GOT表项。由于没有开启PIE地址随机化所有函数的地址和全局变量的地址都是固定的这为我们的漏洞利用提供了便利。2. 格式化字符串漏洞原理格式化字符串漏洞产生于程序员直接使用用户输入作为printf等函数的格式化参数。正常情况下我们应该这样写printf(%s, buf);但题目中却是printf(buf);当用户输入中包含格式化字符如%x时printf会从栈上读取数据并输出这可能导致信息泄露而%n则可以向指定地址写入数据这就给了我们修改内存的机会。在本题中我们需要先确定输入字符串在栈上的偏移量。常用的探测方法是输入AAAA-%p-%p-%p...观察输出中41414141即AAAA的十六进制表示出现的位置。经过测试我们发现偏移量为10也就是说我们输入的第11个参数从0开始计数会指向我们输入字符串的起始位置。3. 漏洞利用方法一修改全局变量最直接的利用方式就是修改dword_804C044的值。由于我们知道它的地址0x804C044可以构造payload来修改这个地址处的值。具体步骤是将目标地址0x804C044放在payload开头使用格式化字符串%n来向这个地址写入数据通过控制前面输出的字符数来控制写入的值这里有个技巧如果要写入的值很大比如地址值直接输出这么多字符会很耗时。我们可以分多次写入每次写入1或2字节。这就是为什么在PoC中会看到四个地址和四个%n。完整利用代码如下from pwn import * p process(./pwn5) bss_addr 0x804C044 payload p32(bss_addr) p32(bss_addr1) p32(bss_addr2) p32(bss_addr3) payload b%10$n%11$n%12$n%13$n p.sendline(payload) p.sendline(str(0x10101010)) # 这个值要与我们写入的值一致 p.interactive()4. 漏洞利用方法二GOT表劫持另一种更通用的方法是劫持GOT表。在本题中程序使用了atoi函数我们可以把atoi的GOT表项改为system函数的PLT地址。这样当程序调用atoi时实际上会调用system我们只需要传入/bin/sh就能获得shell。使用pwntools的fmtstr_payload可以简化这个过程from pwn import * io process(./pwn5) elf ELF(./pwn5) atoi_got elf.got[atoi] system_plt elf.plt[system] payload fmtstr_payload(10, {atoi_got: system_plt}) io.sendline(payload) io.sendline(b/bin/sh\x00) io.interactive()这种方法的好处是不需要知道随机数的值也不需要修改全局变量直接通过函数调用劫持就能获得shell。5. pwntools的fmtstr_payload详解fmtstr_payload是pwntools中一个非常强大的工具函数它可以自动构造格式化字符串攻击payload。它的基本用法是fmtstr_payload(offset, {address: value})其中offset是格式化字符串的偏移量address是要修改的内存地址value是要写入的值。函数内部会自动计算需要的字符数并选择合适的写入方式%n、%hn或%hhn。对于大地址值它会自动分割成多次小字节写入非常方便。在本题中我们还可以用它来简化全局变量的修改payload fmtstr_payload(10, {0x804C044: 0x1})这样就能把dword_804C044的值改为1然后我们只需要输入1就能通过检查。6. 调试技巧与注意事项在实际操作中有几个调试技巧很有用使用pwndbg的fmtarg命令可以快速计算格式化字符串的偏移量在gdb中设置断点观察内存变化对于远程题目要注意网络字节序和本地测试的差异常见的坑包括忘记发送最后的触发命令如/bin/sh偏移量计算错误没有考虑地址对齐问题在64位和32位环境下格式化字符串的行为差异我在实际解题时发现有时候直接使用FmtStr类会报错这时手动构造payload会更可靠。另外对于不同的libc版本system函数的偏移可能不同需要根据实际情况调整。7. 防护措施与修复建议虽然这道题目主要考察攻击技巧但从防御角度来说修复方法很简单永远不要使用用户输入作为printf的格式化参数使用格式字符串常量如printf(%s, buf)开启完整的RELRO保护启用地址随机化PIE作为CTF选手理解这些防护措施也很重要因为它们会影响我们的漏洞利用方式。比如开启完整RELRO后GOT表就不可写了我们就不能使用GOT劫持的方法。