在CTF(Capture The Flag)竞赛中,字符串格式化漏洞,特别是与printf
家族函数相关的漏洞,是常见的攻击向量。printf
、sprintf
、fprintf
等函数在处理格式化字符串时,如果参数或格式字符串控制不当,可以导致信息泄露或任意内存写入,从而让攻击者有机会控制程序执行流。以下是一些与格式化字符串漏洞相关的概念和技巧:
读数据(信息泄露)
格式化字符串漏洞最常见的利用之一是读取内存中的数据。这是因为printf
函数会按照格式字符串解析参数列表,如果格式字符串可控,攻击者可以指定任意格式描述符,读取内存中敏感信息,如函数地址、堆栈数据或其他内存区域的内容。
示例:
假设存在一个函数foo
,它接收一个格式化字符串参数format
,如下所示:
1void foo(char *format) {
2 int var = 0xdeadbeef;
3 printf(format, var);
4}
攻击者可以通过提供一个格式化字符串"%x %x %x"
来读取var
和var
后面两个连续的内存位置的值:
1char format[] = "%x %x %x";
2foo(format);
写数据(内存写入)
除了读取数据外,格式化字符串漏洞还可以被用来写入数据。通过使用%n
格式描述符,攻击者可以控制printf
函数写入一个特定地址的值。%n
会写入已经打印的字符数到一个由参数列表中的下一个参数指定的地址。
示例:
假设存在一个函数bar
,它接收一个格式化字符串和一个整数参数address
:
1void bar(char *format, int *address) {
2 printf(format);
3}
4
5int main() {
6 int target = 0;
7 bar("%10$x", &target); // 假设这里攻击者可以控制format参数
8 return target; // 返回target的值
9}
攻击者可以通过提供一个格式化字符串"%10$n"
和一个指向想要写入的地址的指针,来写入10个打印字符的长度。
利用EBP写入
在某些情况下,攻击者可以利用格式化字符串漏洞来写入栈帧基址(EBP)或栈指针(ESP),从而进一步控制程序的执行流。例如,通过写入返回地址或跳转到攻击者控制的代码段。
__printf_chk 和 %a
__printf_chk
是一个在某些编译器和库中用于检测printf
函数调用是否安全的函数。当使用%a
格式描述符时,__printf_chk
会检查是否有一个额外的参数来验证格式字符串的安全性。如果没有,它将引发一个错误,这可以防止一些格式化字符串攻击。
利用技巧:
- 信息收集:使用
%x
,%p
,%s
等格式描述符读取内存中的数据,收集有关程序布局或变量位置的信息。 - 控制流劫持:通过写入返回地址或函数指针,攻击者可以劫持程序控制流,执行任意代码。
- 参数欺骗:通过控制
printf
的参数列表,欺骗函数调用,例如,让printf
函数执行原本应该由scanf
或fgets
处理的输入。