sprintf函数的缓冲区溢出?

时间:2010-11-26 03:11:41

标签: c printf overflow fortify-source

{     
    char buf[8];
    sprintf(buf,"AAAA%3s","XXXXXXXX");
    printf("%s\n",buf);
}

将会发生什么?

缓冲区有8个字符空格,只剩下3个空闲字符,但“XXXXXXXX”长度为8个字符。

我在Windows 7上使用Visual Studion 2008进行测试。结果,打印的程序:AAAXXXXXXX,发生了运行时错误。

7 个答案:

答案 0 :(得分:10)

考虑在你的,更重要的是,类似的案例中发生的事情是很有意义的。正如其他海报所指出的那样,它会调用UB。这可能是真的。然而,世界并不仅仅因为有人没有明确接下来应该发生什么而停止。接下来会发生什么物理,可能是主要安全漏洞

如果您的字符串XXX...来自不受控制的来源,那么您就非常接近于生成缓冲区溢出漏洞。

(1)你的堆栈通常向后“增长”,即地址越小,堆栈就越多。

(2)字符串期望存储属于该字符串的字符,以便在字符n之后存储字符n + 1。

(3)当你调用一个函数时,返回地址,即函数返回后要执行的指令的地址,被推送到堆栈(通常是其中之一)。

现在考虑你的函数的堆栈帧。

|----------------|
| buf [size 8]   |
|----------------|
| (func args)    |
|----------------|
| (other stuff)  |
|----------------|
| return address |
|----------------|

通过找出buf与堆栈上的返回地址之间的确切偏移是什么,恶意用户可以以XXX...字符串包含地址的方式操作对应用程序的输入。攻击者只选择不受控制的sprintf函数将覆盖堆栈上的返回地址。 (注意:如果可以使用,请更好地使用snprintf)。从而攻击者发起buffer overflow攻击。他可能会使用类似NOP sled technique的内容让您的应用程序为他启动shell。如果您正在编写一个在特权用户帐户下运行的应用程序,那么您只需向攻击者提供一个一级入口到您的客户系统,ACE一个洞,如果您愿意的话。

更新

您遇到的运行时错误可能是由于覆盖了返回地址。因为基本上用gargabe填充它,CPU跳转的地址可能包含字节序列,解释为程序文本,导致无效的内存访问(或者地址本身已经坏了)。

应该注意的是,一些编译器可以帮助解决这些类型的错误。例如,GCC有-fstack-protector。我不熟悉这些功能有多好。

答案 1 :(得分:8)

函数sprintf()将在字符串中写入时写入数组,因此会调用未定义的行为。查看您的代码,它可能会写入堆栈上接下来发生的任何事件的前几个字节,或者导致运行时错误,但不保证该行为。

未定义的行为在字面上意味着anything can happen这意味着您的代码可能没有任何错误,导致运行时错误,或导致您的计算机爆炸,赢得彩票,使独角兽出现在你的后院,从死里复活希特勒或暗杀美国总统。请不要这样做。

始终确保您的字符缓冲区有足够的空间来容纳您sprintf()的任何内容 - 加上空终止符的额外字符。一般来说,不要试图乱用不属于你的内存空间。

答案 2 :(得分:6)

您应该尝试在此处使用snprintf()方法described,而不是使用此方法。此方法执行基本相同的功能,但它允许您明确地控制字符数,防止未定义的行为(这是一件好事)

  

snprintf保证不写   超过str的字节大小,所以使用   它可以帮助避免风险   缓冲区溢出   Wiki

答案 3 :(得分:5)

您的格式字符串中有错误/拼写错误。而不是"AAAA%3s"它应该是"AAAA%.3s"。场[最小]宽度和场精度非常不同。前者设置字段将扩展为填充的最小字节数。后者(对于字符串)设置最大将输出的字节数;字符串的其他字节既不会被检查也不会被复制到输出中。

答案 4 :(得分:1)

“In silico”是完全正确的,但可能是因为计算机内核比以前更聪明,它不会让你写入char buf[4];之后的内容并且会杀死你的程序并发出分段故障信号。

这很好,因为如果下一段内存是非常重要,那么它将保持安全,而不是让您的计算机崩溃。

并且正如他所说从不这样做。

答案 5 :(得分:1)

sprintf()函数有助于无限制地复制文本,从而使缓冲区容易受到溢出攻击。当进程尝试存储的数据超过了fixe-length缓冲区中允许的边界时,就会发生缓冲区溢出。

在发现溢出漏洞后,攻击者将观察呼叫如何获取其用户输入,并通过函数调用进行路由。然后攻击者可以写一个exploit,这会让软件做一些不能正常做的事情。这可以从简单地崩溃机器到注入代码,以便攻击者可以远程访问机器。

如果使用不当,C中的许多功能会导致错误。一些功能提供了替代解决方案:

Avoid      prefer
sprintf    snprintf
vsprintf   vsnprintf
strcat     strlcat
strcpy     strlcpy
strncat    strlcat
strncpy    strlcpy

来源:ECSP-Secure Programmer。

答案 6 :(得分:0)

  

会发生什么?   ...

{     
    char buf[8];
    sprintf(buf,"AAAA%3s","XXXXXXXX");
    printf("%s\n",buf);
}

在Windows上,您应该使用sprintf_s。代码应该通过审计失败,因此不应该将其投入生产。有关参考,请参阅Microsoft的Writing Secure Code (Developer Best Practices)。特别是,请参阅第5章。


在Linux上,如果编译器和平台提供FORTIFY_SOURCE,那么上面的代码应该调用abort()。许多现代Linux平台都支持它,所以我期待它。

FORTIFY_SOURCE使用高风险函数的“更安全”变体,例如memcpystrcpysprintf。当编译器可以推导出目标缓冲区大小时,编译器会使用更安全的变体。如果副本超过目标缓冲区大小,则程序将调用abort()

要禁用FORTIFY_SOURCE进行测试,您应该使用-U_FORTIFY_SOURCE-D_FORTIFY_SOURCE=0编译该程序。


要解决@ prng关于可移植性的评论,strcpy_sprintf_ssprintf_s和朋友是标准C.请参阅ISO/IEC TR 24731-1

如果Linux和glibc上缺少的功能是一个问题,那么你可以通过预处理器宏来消除由于glibc瘫痪造成的差异。无论Linux和glibc做什么,代码都不符合Windows平台的最低标准。