sprintf函数的缓冲区溢出?

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

会发生什么?

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

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

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

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

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

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

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

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

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

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

更新

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

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

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

未定义的行为在字面上意味着任何事情都可能发生 。 这意味着您的代码可能没有任何错误,导致运行时错误,或导致您的计算机爆炸,赢得彩票,让独角兽出现在您的后院,从死者中提升希特勒或暗杀美国总统。 请不要这样做。

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

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

保证snprintf不会将大于字节的字节写入str,因此使用它可以帮助避免缓冲区溢出Wiki的风险

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

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

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

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

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

在发现溢出漏洞后,攻击者将观察呼叫如何获得其用户输入并通过函数调用进行路由。 然后,攻击者可以编写漏洞利用程序 ,这会使软件执行通常无法执行的操作。 这可以从简单地崩溃机器到注入代码,以便攻击者可以远程访问机器。

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

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

资料来源:ECSP-Secure Programmer。

会发生什么? …

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

在Windows上,您应该使用sprintf_s 。 代码应该通过审计失败,因此不应该将其投入生产。 有关参考,请参阅Microsoft的编写安全代码(开发人员最佳实践) 。 特别是,请参阅第5章。


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

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

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


为了解决@ strcpy_s关于可移植性的评论, strcpy_sprintf_ssprintf_s和朋友是标准C.参见ISO / IEC TR 24731-1 。

如果Linux和glibc上缺少的function是一个问题,那么你可以抽象出由于带有预处理器宏的glibc瘫痪而产生的差异。 无论Linux和glibc做什么,代码都不符合Windows平台的最低标准。