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使用“更安全”的高风险函数变体,如memcpy
, strcpy
和sprintf
。 当编译器可以推导出目标缓冲区大小时,编译器会使用更安全的变体。 如果副本超过目标缓冲区大小,则程序调用abort()
。
要禁用FORTIFY_SOURCE进行测试,应使用-U_FORTIFY_SOURCE
或-D_FORTIFY_SOURCE=0
编译程序。
为了解决@ strcpy_s
关于可移植性的评论, strcpy_s
, printf_s
, sprintf_s
和朋友是标准C.参见ISO / IEC TR 24731-1 。
如果Linux和glibc上缺少的function是一个问题,那么你可以抽象出由于带有预处理器宏的glibc瘫痪而产生的差异。 无论Linux和glibc做什么,代码都不符合Windows平台的最低标准。