格式化字符串漏洞
二进制安全学习路径 | 模块2 | 课程3
1. 引言
格式化字符串漏洞是二进制安全领域中一种非常危险且常见的漏洞类型。当程序没有正确处理包含格式说明符的字符串时,攻击者可以利用这些漏洞读取敏感内存数据,甚至修改程序的执行流程。本课程将深入介绍格式化字符串漏洞的原理、常见利用方式以及防御措施。
学习目标: 理解格式化字符串漏洞的原理和成因,掌握漏洞的检测和利用方法,学习如何编写安全的代码来防御此类漏洞。
2. 格式化字符串基础
2.1 什么是格式化字符串
格式化字符串是包含特殊控制字符(格式说明符)的字符串,用于指定如何格式化和显示数据。在C语言中,printf()、sprintf()、fprintf()等函数使用格式化字符串来控制输出格式。
2.2 常见格式说明符
格式说明符以%开头,常见的包括:
- %d - 有符号十进制整数
- %u - 无符号十进制整数
- %x - 十六进制整数
- %s - 字符串
- %c - 字符
- %p - 指针地址
- %n - 将已写入的字符数存入对应的整数指针参数
3. 格式化字符串漏洞原理
3.1 漏洞成因
格式化字符串漏洞产生的根本原因是:当程序将用户可控制的输入直接作为格式化字符串参数传递给相关函数时,攻击者可以注入格式说明符来获取或修改程序的内存内容。
3.2 漏洞危害
格式化字符串漏洞可以导致:
- 信息泄露(读取栈上或任意内存的数据)
- 内存写入(通过%n写入任意地址)
- 程序崩溃
- 代码执行(通过覆盖函数指针或返回地址)
安全提示: 格式化字符串漏洞在程序中尤其危险,因为它既可以用于信息泄露,又可以用于代码执行,且利用方式相对简单。
4. 内存结构与格式化函数
4.1 栈与格式化函数的关系
格式化函数(如printf)在解析格式字符串时,会按顺序从栈上获取对应的参数。当格式说明符的数量大于提供的参数数量时,函数会继续从栈上获取值,导致可能泄露栈内容。
格式化字符串处理时的栈结构示意图
4.2 参数获取机制
printf族函数如何处理格式说明符:
- 解析格式字符串,识别格式说明符
- 对于每个格式说明符,从栈上按顺序获取一个参数
- 根据说明符类型对参数进行适当的格式化
5. 格式化字符串漏洞利用
5.1 信息泄露
使用%x或%p格式说明符可以泄露栈上的数据:
5.2 定位输入在栈中的位置
为了更精确地利用格式化字符串漏洞,通常需要定位输入字符串在栈中的位置:
5.3 直接参数访问
利用参数索引可以更精确地访问栈上的值:
5.4 使用%n进行内存写入
%n格式说明符可以将已输出的字符数量写入到指定地址:
5.5 部分写入技术
对于较大的数值,可以使用%hn(写入2字节)或%hhn(写入1字节)进行部分写入:
6. 实际漏洞示例
6.1 基础示例
6.2 信息泄露示例
6.3 内存写入示例
7. 漏洞检测与防御
7.1 代码审计
检查所有格式化函数的使用,确保格式字符串不受用户控制:
- printf(), fprintf(), sprintf(), snprintf()
- vprintf(), vfprintf(), vsprintf(), vsnprintf()
7.2 静态分析工具
使用静态分析工具可以帮助检测格式化字符串漏洞:
- Clang Static Analyzer
- Flawfinder
- RATS (Rough Auditing Tool for Security)
- Fortify Source (GCC/Clang 编译器选项)
7.3 防御措施
保护代码免受格式化字符串攻击的最佳实践:
- 始终使用格式字符串常量,而不是变量
- 如果必须使用变量作为格式字符串,先验证其内容
- 使用 "%s" 作为格式说明符来输出用户提供的字符串
- 启用编译时保护:-Wformat -Wformat-security -D_FORTIFY_SOURCE=2
8. 高级利用技术
8.1 GOT覆盖
全局偏移表(GOT)是动态链接程序中用于解析函数地址的表。通过格式化字符串漏洞可以覆盖GOT表项,将函数调用重定向到攻击者控制的代码。
8.2 返回地址覆盖
类似于缓冲区溢出攻击,可以使用格式化字符串漏洞覆盖栈上的返回地址,控制程序执行流程。
8.3 ASLR绕过技术
地址空间布局随机化(ASLR)增加了攻击难度,但可以通过以下方式绕过:
- 使用格式化字符串泄露库函数地址
- 根据泄露的地址计算偏移
- 构造攻击链
8.4 多次写入技术
对于需要写入大数值的情况,可以使用多次小数值写入:
9. 真实案例分析
9.1 CTF题目分析
许多CTF竞赛都包含格式化字符串相关的挑战,通常需要利用漏洞来泄露关键信息或修改程序行为以获取flag。
9.2 CVE案例研究
以下是一些著名的格式化字符串漏洞CVE案例:
- CVE-2012-0809: Sudo格式化字符串漏洞
- CVE-2015-8617: ProFTPD格式化字符串漏洞
- CVE-2018-6952: QEMU格式化字符串漏洞
10. 安全编码实践
10.1 开发规范
安全开发规范应包含以下几点:
- 所有格式化函数调用必须使用静态格式字符串
- 禁止将用户输入直接传递给格式化函数
- 使用安全的包装函数
10.2 代码审查清单
代码审查时应关注:
- 所有printf系列函数的使用
- 自定义的格式化输出函数
- 使用变量作为格式字符串的情况
10.3 编译器和工具保护
使用以下编译选项加强保护: