格式化字符串的极限
字数 1438 2025-08-29 22:41:01
格式化字符串漏洞高级利用技术
1. 格式化字符串漏洞基础回顾
格式化字符串漏洞发生在使用类似printf的函数时,当用户能够控制格式化字符串参数时,可能导致信息泄露或任意内存写入。
基本利用方式:
%x、%p:泄露栈内存%n:写入内存(将已输出的字符数写入指定地址)%hhn:写入一个字节%hn:写入两个字节
2. 高级利用场景分析
2.1 非栈上格式化字符串利用
在题目描述的场景中,我们面临以下限制:
- 保护全开(ASLR、NX等)
- 只给了一次非栈上格式化字符串的机会
- 提供了栈地址
- 返回地址和后门地址只差一个字节
2.2 指针跳板技术
传统思路:
- 构造指针链:a → b → ret → main
- 修改b地址偏移main的最后一个字节
但这种方法在实际操作中存在问题:
- 只能修改第一个地址,第二个地址无法修改
- 推测原因是
$指定写入和按参数顺序写入的操作是分开进行的
2.3 关键发现:%hhn的特性
重要特性:
- 不管输入的字节数是多少,
%hhn只会将总字节数的最后一个字节写入对应偏移处 - 只要比前面全部输入的总字节数多,就能控制写入的最后一个字节
3. 实际利用技术详解
3.1 利用payload构造
示例payload:
payload = b'%p'*13 + b'%' + str((stack&0xffff)-138).encode() + b'c%hn' + b'%' + str(0x10008-(stack&0xffff)).encode() + b'c%45$hhn'
解析:
%p*13:泄露13个参数,同时为后续写入做准备%xxxc%hn:将xxx数据加上%p泄露的字符数写入第15个参数(13+1+1=15)stack&0xffff:返回地址的最后两个字节- -138:减去前面
%p泄露的字符数(需要调试确定)
%yyyc%45$hhn:将yyy数据写入第45个参数0x10008:确保只修改最后一个字节为0x08
3.2 字节数计算技巧
- 需要精确计算前面输出的字符数
- 不同版本的libc可能有差异
- 注意
\n字符的影响(示例中多接受了一个\n)
3.3 强网拟态赛题扩展
更复杂的场景:
- 给出栈地址的最后两个字节
- 一次非栈上fmt机会
- 调用
_exit退出 - 保护除canary外全开
解决方案:
- 劫持printf的返回地址为main,实现多次利用
- 补全0x100字节避免read合并两次输入
- 注意
%计数:每个%代表一个偏移 - 最终将printf返回地址改为ret,栈顶改为one_gadget
4. 关键技巧总结
-
偏移计算:
- 每个格式化指示符(
%p,%x,%n等)都计入偏移 %xxxc中的%也计入偏移
- 每个格式化指示符(
-
字节数控制:
- 使用
%hhn精确控制单个字节修改 - 确保写入值大于前面所有输出的总字节数
- 使用
-
指针跳板优化:
- 利用地址已知的部分(如提供的栈地址)
- 通过多次写入构造完整的攻击链
-
调试技巧:
- 精确计算输出字符数
- 注意环境差异(libc版本、输入处理等)
5. 防御措施
虽然题目中保护全开,但实际防御格式化字符串漏洞的方法:
- 始终使用固定字符串作为格式化字符串参数
- 使用
printf("%s", user_input)而非printf(user_input) - 启用所有现代保护机制(ASLR, NX, Stack Canary等)
- 静态分析检查不安全的格式化函数使用
6. 扩展思考
- 不同libc版本下的行为差异
- 更复杂的多阶段利用场景
- 结合其他漏洞(如堆漏洞)的综合利用
- 在更严格限制条件下的利用方法
通过掌握这些高级格式化字符串利用技术,可以在CTF比赛和实际安全研究中更有效地分析和利用这类漏洞。