sprintf函数缓冲区溢出?

发布于 2024-10-04 07:32:17 字数 253 浏览 5 评论 0原文

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

会发生什么?

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

我在Windows 7上用Visual Studio 2008做了测试,结果程序打印了AAAXXXXXXX,出现了运行时错误。

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

What will happen?

The buffer has 8 characters worth of space and only 3 free characters left, however, "XXXXXXXX" is 8 characters long.

I did a test with Visual Studio 2008 on Windows 7. As a result, the program printed AAAXXXXXXX, and a run-time error happened.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

你丑哭了我 2024-10-11 07:32:17

考虑一下您的案例以及更重要的是类似的案例中发生的情况是很有意义的。正如其他发帖者所指出的,它调用了 UB。这可能是真的。然而,世界并不会仅仅因为有人没有定义接下来到底会发生什么而停止运转。接下来物理发生的事情很可能是一个重大安全漏洞

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

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

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

(3) 当调用函数时,返回地址(即函数返回后要执行的指令的地址)被推送到堆栈(通常除其他外)。

现在考虑函数的堆栈框架。

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

通过找出 buf 和堆栈上的返回地址之间的偏移量,恶意用户可能会以 XXX... 的方式操纵应用程序的输入。 string 包含攻击者在不受控制的 sprintf 函数将覆盖堆栈上的返回地址的位置处选择的地址。 (注意:如果可以的话,最好使用 snprintf)。因此,攻击者发起了缓冲区溢出攻击。他可能会使用NOP sled 技术之类的东西让您的应用程序启动shell 给他。如果您正在编写一个在特权用户帐户下运行的应用程序,那么您只需向攻击者提供一个进入您的客户系统的一级入口,即 ACE 漏洞,如果你愿意的话。

更新

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

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

It makes a lot of sense to consider what happens in your and, more importantly, similar, cases. As other posters have noted, it invokes UB. That's probably true. However, the world does not stop simply because someone did not define what exactly should happen next. And what physically happens next, may well be a major security hole.

If your string XXX... comes from uncontrolled sources, you are very close to generating a buffer overflow vulnerability.

(1) Your stack typically "grows" backwards, i.e. the smaller the addresses, the more the stack is filled.

(2) Strings expect the characters belonging to that string to be stored so that character n+1 is stored after character n.

(3) When you call a function, the return address, i.e. the address of the instruction that is to be executed after the function returns, is pushed to the stack (among other things, typically).

Now consider a stack frame of your function.

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

By finding out what exactly the offset between buf and the return address on the stack is, a malicious user may manipulate input to your application in a way that the XXX... string contains an address of the attacker's choosing at just the point where the uncontrolled sprintf function will overwrite the return address on the stack. (NB: Better use snprintf if it's available to you). Thereby the attacker mounted a buffer overflow attack. He might use something like the NOP sled technique to have your application start a shell for him. If you were writing an application that ran under a privileged user account, you'd just have provided an attacker with a first-grade entry to your costumer's system, an ACE hole, if you will.

Update

The run-time error you experience may well be due to an overwritten return address. Since you filled it with, basically, gargabe, the address the CPU jumped to did probably contain byte sequences that, interpreted as program text, cause an invalid memory access (or the address itself was already bad).

It should be noted that some compilers can help against these kinds of errors. GCC, for example, has the -fstack-protector. I'm not familiar with how good those features are.

恬淡成诗 2024-10-11 07:32:17

函数 sprintf() 会像写入字符串一样写入数组,因此会调用未定义的行为。查看您的代码,它可能会写入堆栈上下一个发生的任何内容的前几个字节,或者导致运行时错误,但无法保证该行为。

未定义的行为实际上意味着任何事情都可能发生。< /strong> 这意味着你的代码可能不会做任何错误,导致运行时错误,或者导致你的计算机爆炸,中彩票,让独角兽出现在你的后院,复活希特勒或刺杀美国总统。请不要这样做。

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

The function sprintf() will write past the array as it writes in the string, and therefore invokes undefined behavior. Looking at your code, it'll probably write over the first few bytes of whatever happens to be next on the stack, or cause a runtime error, but that behavior is not guaranteed.

Undefined behavior quite literally means anything can happen. That means your code may do nothing wrong, cause a runtime error, or cause your computer to explode, win the lottery, make unicorns appear in your backyard, raise Hitler from the dead or assassinate the President of the United States. Please don't do this.

Always ensure that your character buffer has enough space to hold whatever you are sprintf()-ing into it plus an extra character for the null terminator. In general, don't try to mess around with memory spaces that are not yours.

聆听风音 2024-10-11 07:32:17

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

snprintf保证不写
str 中超过 size 字节,因此使用
它可以帮助避免风险
缓冲区溢出
维基

Instead of using this method you should try using the snprintf() method as described here. This method performs essentially the same function but it allows you to control the number of characters explictly, preventing undefined behaviour (this is a good thing)

snprintf is guaranteed not to write
more than size bytes into str, so use
of it can help avoid the risk of a
buffer overflow
Wiki

〆一缕阳光ご 2024-10-11 07:32:17

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

You have a bug/typo in your format string. Instead of "AAAA%3s" it should be "AAAA%.3s". Field [minimum] width and field precision are very different. The former sets the minimum number of bytes the field will expand to fill. The latter (for strings) sets the maximum number of bytes that will be output; additional bytes of the string are neither inspected nor copied to the output.

抚你发端 2024-10-11 07:32:17

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

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

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

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

来源:ECSP-Secure Programmer。

The sprintf() function facilitates unbounded copying of text, in turn leaving the buffer susceptible to overflow attack. A buffer overflow occurs when a process attemps to store more data than the boundaries allow in the fixe-length buffer.

After discovering overflow vulnerability, attackers will observe how the call obtains its user input and it is routed through the function call. Attackers can then write an exploit, which makes the software do things it would not do normally. This can range from simply crashing the machine to injecting code so that the attacker can gain remote access to the machine.

Many functins in C lead to erros if not properly used. Some functions provide alternative solutions:

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

Source: ECSP-Secure Programmer.

送你一个梦 2024-10-11 07:32:17

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

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

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

"In silico" is quite right, but likely because computer kernels are much smarter than they used to be, it won't let you write into what comes after char buf[4]; and will kill your program and issue a segmentation fault signal.

This is nice because if the next piece of memory is something really important, it will be kept safe instead of crashing your computer.

And also as he said NEVER do this.

乱世争霸 2024-10-11 07:32:17

会发生什么?
...

<前><代码>{
字符缓冲区[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 编译程序。


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

如果 Linux 和 glibc 上缺少功能是一个问题,那么您可以使用预处理器宏来消除由于 glibc 损坏而导致的差异。无论 Linux 和 glibc 做什么,代码都不符合 Windows 平台上的最低标准。

what will happen?
...

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

On Windows, you are supposed to use sprintf_s. The code should fail an audit, so it should not make it into production. For reference, see Microsoft's Writing Secure Code (Developer Best Practices). In particular, see Chapter 5.


On Linux, if the compiler and platform provides FORTIFY_SOURCE, then the code above should result in a call to abort(). Many modern Linux platforms support it, so I would expect it.

FORTIFY_SOURCE uses "safer" variants of high risk functions like memcpy, strcpy and sprintf. The compiler uses the safer variants when it can deduce the destination buffer size. If the copy would exceed the destination buffer size, then the program calls abort().

To disable FORTIFY_SOURCE for testing, you should compile the program with -U_FORTIFY_SOURCE or -D_FORTIFY_SOURCE=0.


To address @prng's comment regarding portability, strcpy_s, printf_s, sprintf_s and friends are standard C. See ISO/IEC TR 24731-1.

If the missing functionality on Linux and glibc is a problem, then you can abstract away the differences due to a crippled glibc with preprocessor macros. Regardless of what Linux and glibc does, the code does not meet minimum standards on the Windows platform.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文