【问题标题】:sprintf function's buffer overflow?sprintf 函数的缓冲区溢出?
【发布时间】:2010-11-26 03:11:41
【问题描述】:
{     
    char buf[8];
    sprintf(buf,"AAAA%3s","XXXXXXXX");
    printf("%s\n",buf);
}

会发生什么?

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

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

【问题讨论】:

    标签: c printf overflow fortify-source


    【解决方案1】:

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

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

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

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

    (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。我不知道这些功能有多好。

    【讨论】:

    • "例如,GCC 有 -fstack-protector。我不熟悉这些功能有多好。" —您永远不必找出答案:P
    • -fstack-protector 很好,但是代码中其他地方的信息泄漏可以绕过任何堆栈金丝雀。在某些情况下,堆栈金丝雀暴力破解也可以成为一种可行的绕过来源
    【解决方案2】:

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

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

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

    【讨论】:

      【解决方案3】:

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

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

      【讨论】:

        【解决方案4】:

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

        【讨论】:

          【解决方案5】:

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

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

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

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

          来源:ECSP-Secure Programmer。

          【讨论】:

            【解决方案6】:

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

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

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

            【讨论】:

            • 真的吗?如果堆栈上的下一个东西是可写变量怎么办?为什么内核会阻止您写入?您是在声称内核知道编程语言的变量在哪里开始和结束吗?
            • 内核不知道堆栈上的缓冲区从哪里开始或结束。请参阅我关于缓冲区溢出漏洞的帖子。
            • @EJP 这是一个有趣的观点。我不知道在那种情况下会发生什么。是的,内核确实知道哪些进程可以写入哪些内存区域。如果你真的想知道试试看。尝试写入超出缓冲区buf[10] = 10 在我的情况下,我每次都会遇到段错误。 (在windows上我得到“这个程序遇到错误需要退出”然后它问我是否要发送错误报告。
            • 这并不能证明什么,除非在下一个位置有另一个变量。有吗?操作系统如何知道变量在哪里结束?还是它只是堆栈中的最后一件事?
            • 我不知道内存结构和设置,但它似乎太一致了,不可能是巧合。我在不同的时间在不同的节目上试过。我可能是错的,如果是这样,我很抱歉我的错误。
            【解决方案7】:

            会发生什么? ...

            {     
                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 平台上的最低标准。

            【讨论】:

            • -1 "在 Windows 上,您应该使用 sprintf_s。" ...失去便携性的所有希望?与许多其他语言相比,C 的最大优势在于它的可移植性!
            • 好的@jww; -1 撤销。无论如何,MSDN 中的描述与(草案)标准中的描述之间存在差异。
            • 来晚了,但除了无偿的“残缺的 gcc”,Microsoft's implementation of the OPTIONAL Annex K is still not portable:“Microsoft Visual Studio 实现了 API 的早期版本。但是,实现不完整,既不符合 C11 也不符合最初的 TR 24731-1。......由于与规范的大量偏差,微软的实现不能被认为是符合或可移植的。”这几乎就像微软在这里实施了供应商锁定......
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2023-03-27
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2015-12-16
            • 1970-01-01
            相关资源
            最近更新 更多