04_printf漏洞概述以及调试
https://bbs.kanxue.com/thread-250858.htm 大神的格式化字符串文章
printf
格式化字符串漏洞是一类较为常见的安全漏洞,主要出现在程序通过不安全的方式处理用户提供的输入,并将其直接传递给 printf
系列函数(例如 printf
、sprintf
、fprintf
等)时。由于格式化字符串的灵活性,如果没有正确过滤或限制用户输入,攻击者可以利用格式化字符串功能执行一些未预期的操作,例如泄露内存内容、覆盖程序内存等,最终可能导致远程代码执行或程序崩溃。
漏洞成因
printf
系列函数允许使用格式化字符串(如 %d
、%s
、%x
等)来格式化输出。当程序直接使用用户输入作为格式化字符串,而没有进行适当的验证或限制时,攻击者可以精心构造恶意的格式化字符串来利用漏洞。
漏洞示例
考虑以下简单的 C 代码:
#include <stdio.h>
int main() {
char input[100];
printf("Please enter your input: ");
scanf("%s", input);
printf(input); // 潜在的格式化字符串漏洞
return 0;
}
在这个代码中,printf
直接使用用户提供的 input
作为格式化字符串,没有进行任何过滤。如果用户输入类似于 %x
,printf
会将栈上的数据以十六进制的形式打印出来,而不是简单地输出用户输入的字符串。这种行为可以用于窥探栈上的敏感信息,比如函数返回地址、局部变量等。
漏洞利用
信息泄露:使用格式化符号如
%x
或%p
,攻击者可以逐个打印栈上的内容,从而泄露敏感信息。例如,如果用户输入"%x %x %x %x"
,攻击者可以看到程序栈上的四个值。内存写入:通过
%n
格式化符号,攻击者可以控制printf
函数将打印的字符数写入指定的内存地址。如果攻击者能控制写入的地址和内容,则可以改变程序的行为,甚至执行任意代码。例如,用户输入"AAAA%n"
将会把字符数4
写入栈上的一个地址。
printf("AAAA%n", &i); // 会将 4 写入变量 i 中
漏洞的潜在危害
信息泄露:可以通过读取栈上的数据泄露敏感信息,如密码、函数地址、返回地址等,甚至是程序的源代码地址。
远程代码执行:攻击者可以通过修改程序的返回地址、函数指针或其他控制流来执行任意代码。
拒绝服务(DoS):利用
%s
格式符可以让程序尝试打印某个无法访问的地址,导致程序崩溃。
典型的攻击步骤
信息收集:使用
%x
、%p
等格式符来收集栈信息,确定关键的内存地址。覆盖数据:通过
%n
格式符将攻击者选择的值写入目标内存地址,从而改变程序执行流程,达到覆盖返回地址等目的。利用:通过覆盖内存地址或控制关键数据,实现任意代码执行或崩溃攻击。
修复与预防
使用格式化字符串:应避免直接将用户输入作为格式化字符串传递给
printf
系列函数。应明确指定格式化字符串,例如:
printf("%s", input); // 安全:指定了格式化符为 %s
输入验证:在接受用户输入时,应该对输入进行严格验证和过滤,防止恶意构造的字符串进入到
printf
函数。使用安全函数:尽量使用更安全的函数替代,如
snprintf
,以防止缓冲区溢出和格式化字符串漏洞。编译器保护:现代编译器提供了一些保护机制,如格式化字符串检查(
-Wformat
、-Wformat-security
),可以在编译时警告潜在的格式化字符串漏洞。
泄露canary值
通过格式化字符串漏洞可以泄露堆栈上的canary值,从而绕过栈保护机制(Stack Canary)。栈保护机制(如 GCC 的 -fstack-protector
选项)通过在函数栈帧中插入一个称为canary的特殊值,在函数返回时检查其是否被修改,以防止栈溢出攻击。通常,canary 值是一个随机数,在程序的启动过程中生成,且与攻击者不可预知。
泄露canary的步骤
格式化字符串漏洞允许攻击者访问堆栈上的数据,通过精心构造的输入,可以读取栈上的 canary
值。一旦 canary
值泄露,攻击者就可以使用这个值来绕过栈保护机制并进行进一步的攻击。
典型的攻击思路如下:
探测canary位置:利用格式化字符串漏洞,攻击者通过像
%x
或%p
这样的格式符,不断读取栈上的内容,试图找到canary
值的位置。因为canary
通常位于返回地址和局部变量之间,它会被放置在栈中靠近函数栈帧的尾部。泄露canary值:一旦找到
canary
的位置,攻击者可以通过使用适当数量的%x
或%p
格式符,将该位置的值打印出来。例如:
printf(user_input); // 存在格式化字符串漏洞
如果 user_input
是 "%12$x"
,且 canary
在第12个栈位置,那么 printf
会输出 canary
的值。
利用泄露的canary值进行缓冲区溢出攻击:泄露到
canary
值后,攻击者可以构造精确的攻击载荷,确保canary
的值在栈溢出过程中不会被改变,以避免触发栈保护机制。同时,攻击者可以修改返回地址或其他栈上的数据,从而执行任意代码。
示例
假设程序中存在以下代码:
#include <stdio.h>
#include <string.h>
void vulnerable_function(char *input) {
char buffer[64];
printf(input); // 潜在格式化字符串漏洞
strcpy(buffer, input); // 潜在缓冲区溢出
}
int main() {
char input[100];
printf("Please enter input: ");
scanf("%s", input);
vulnerable_function(input);
return 0;
}
攻击者可以先通过格式化字符串漏洞泄露 canary
值:
如果输入
"AAA %12$x"
,printf
可能输出canary
值(假设在第12个位置)。
接着,攻击者可以利用这个 canary
值构造精确的溢出载荷,将返回地址覆盖为恶意代码的地址,同时将正确的 canary
值放回栈上,绕过栈保护机制。
如何防止canary泄露
避免格式化字符串漏洞:关键点在于避免直接将不可信的用户输入传递给
printf
等函数,始终指定格式化字符串,如:
printf("%s", input); // 避免将用户输入直接作为格式化字符串
启用更多保护机制:
地址空间布局随机化(ASLR):通过随机化堆栈、堆和代码段的地址,使攻击者更难确定
canary
和其他内存地址的位置。加强编译器保护:除了
-fstack-protector
外,还可以使用-fstack-protector-all
进行更严格的栈保护。