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

会发生什么?

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

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

4

7 回答 7

15

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

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

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

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

(3) 当你调用一个函数时,返回地址,即函数返回后要执行的指令的地址,被压入堆栈(除其他外,通常是这样)。

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

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

通过找出堆栈上的返回地址和返回地址之间的确切偏移量,恶意用户可能会以字符串包含攻击者选择的地址的buf方式操纵应用程序的输入,该地址恰好位于不受控制的函数将覆盖的位置。返回栈上的地址。(注意:如果您可以使用,请更好地使用)。因此,攻击者实施了缓冲区溢出攻击。他可能会使用类似于NOP sled 的技术让您的应用程序为他启动一个shell。如果您正在编写一个在特权用户帐户下运行的应用程序,那么您只是为攻击者提供了进入您客户系统的一级入口,即ACEXXX...sprintfsnprintf洞,如果你愿意的话。

更新

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

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

于 2010-11-26T03:38:55.940 回答
11

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

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

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

于 2010-11-26T03:13:09.110 回答
8

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

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

于 2010-11-26T03:19:16.770 回答
5

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

于 2010-11-26T03:29:26.293 回答
4

sprintf()函数促进了文本的无限制复制,从而使缓冲区容易受到溢出攻击。当进程尝试存储的数据多于固定长度缓冲区中允许的边界时,就会发生缓冲区溢出。

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

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

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

资料来源:ECSP-安全程序员。

于 2010-11-26T04:11:20.223 回答
1

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

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

正如他所说,永远不要这样做。

于 2010-11-26T03:21:33.463 回答
-1

会发生什么?...

{     
    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例如strcpysprintf。当编译器可以推断目标缓冲区大小时,它会使用更安全的变体。如果副本超出目标缓冲区大小,则程序调用abort().

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


为了解决 @prng 关于可移植性的评论strcpy_sprintf_s,sprintf_s和朋友是标准 C。请参阅ISO/IEC TR 24731-1

如果 Linux 和 glibc 上缺少的功能是一个问题,那么您可以抽象出由于 glibc 与预处理器宏的残缺而导致的差异。不管 Linux 和 glibc 做什么,代码都不符合 Windows 平台上的最低标准。

于 2014-09-12T13:31:23.230 回答