【问题标题】:What is a buffer overflow and how do I cause one?什么是缓冲区溢出,如何导致?
【发布时间】:2010-10-09 02:50:19
【问题描述】:

我听说过缓冲区溢出,我想知道如何导致。

谁能给我看一个小的缓冲区溢出示例? 新的(它们的用途是什么?)

【问题讨论】:

  • 为什么要产生缓冲区溢出?
  • 参见:进入 C 标准库。
  • “见:进入 C 标准库”——这是在开玩笑吗?
  • @NTDLS:这很狡猾,但非常严重......获取是危险的,并且在生产代码中完全站不住脚。

标签: c++ buffer-overflow fortify-source


【解决方案1】:

缓冲区溢出的经典示例:

// noone will ever have the time to type more than 64 characters...
char buf[64];
gets(buf); // let user put his name

单独的缓冲区溢出通常不是故意发生的。它最常发生是因为所谓的“一个接一个”错误。这意味着您将数组大小错误地计算了一个 - 可能是因为您忘记考虑终止空字符,或者因为其他一些东西。

但它也可以用于一些邪恶的东西。事实上,用户早就知道这个漏洞,然后插入说 70 个字符,最后一个字符包含一些特殊字节,这些字节会覆盖一些堆栈槽 - 如果用户真的很棘手,他/她会点击堆栈中的返回地址槽, 并覆盖它,使其向前跳转到刚刚插入的缓冲区:因为用户输入的不是他的名字,而是他之前编译并转储的 shell 代码。然后那个将被执行。有一些问题。例如,您必须安排在该二进制代码中不包含“\n”(因为gets 将停止读取那里)。对于其他与危险字符串函数混淆的方式,二进制零是有问题的,因为字符串函数停止复制到缓冲区。人们也使用xor 两次相同的值来产生零,而没有明确写入零字节。

这是经典的做法。但是有一些安全块可以告诉我们发生了这样的事情,以及其他使堆栈不可执行的东西。但我想有比我刚才解释的更好的技巧。一些汇编人员现在可能会告诉你关于这方面的长篇大论:)

如何避免

始终使用也接受最大长度参数的函数,如果您不能100%确定缓冲区是否真的足够大。不要玩“哦,数字不会超过5个字符”之类的游戏——它总有一天会失败。请记住,科学家曾说过一枚火箭的数量不会超过某个数量级,因为火箭永远不会那么快。但是有一天,它实际上更快了,结果是整数溢出和火箭坠毁(这是关于Ariane 5 中的一个错误,这是历史上最昂贵的计算机错误之一)。

例如,使用fgets 代替gets。而不是 sprintf 在合适和可用的地方使用 snprintf(或者只是 C++ 风格的东西,比如 istream 和其他东西)

【讨论】:

  • 缓冲区溢出 = 缓冲区溢出?
  • 我不知道后一个术语。维基百科似乎说他们的意思是一样的。
【解决方案2】:

缓冲区溢出基本上是指内存的精心设计的部分(或缓冲区)被写入其预期范围之外。如果攻击者能够设法从程序外部实现这一点,它可能会导致安全问题,因为它可能允许他们操纵任意内存位置,尽管许多现代操作系统可以防止这种最坏的情况发生。

虽然在预期范围之外读取和写入通常被认为是一个坏主意,但术语“缓冲区溢出”通常保留用于在范围之外写入,因为这可能会导致攻击者轻松修改代码的运行方式。在 Wikipedia 上有一篇关于 buffer overflows 以及它们可用于漏洞利用的各种方式的好文章。

就如何自己编程而言,这很简单:

char a[4];
strcpy(a,"a string longer than 4 characters"); // write past end of buffer (buffer overflow)
printf("%s\n",a[6]); // read past end of buffer (also not a good idea)

是否编译以及运行时会发生什么可能取决于您的操作系统和编译器。

【讨论】:

  • 当您在缓冲区末尾之外写入而不是读取时,缓冲区溢出通常更具破坏性[例如,char x[2]; strcpy (x,"hello");] - 这是因为它经常塞满许多其他变量和/或堆栈帧。
  • 不必写超过数组的边界就被认为是缓冲区溢出?在那种情况下,我认为一个改变 a[200] 内容的例子会更好。
  • @david 如果您阅读了您引用的维基百科文章的第一段,则缓冲区溢出仅在您在缓冲区边界之外“写入”时发生,您的示例不是 适当的缓冲区溢出。
  • 这仍然不是缓冲区溢出。 a 是指向字符串的指针,第二行只是更改该引用。另外, a 是一个数组,因此它甚至不是有效的左值,并且您的代码将无法编译。一个简单的例子是 strcpy(a, "a string long than 4 characters");
【解决方案3】:

在现代 linux 操作系统中,如果没有一些额外的实验,就无法利用缓冲区溢出。 为什么 ?因为在这个现代 GNU C 编译器中,您将被 ASLR(地址堆栈层随机化)和 堆栈保护器 阻止。你不会轻易找到内存,因为内存会落入由ASLR引起的随机内存。如果您尝试溢出程序,您将被堆栈保护器阻止。

首先你需要把 ASLR 设为 0 默认值为 2

root@bt:~# cat /proc/sys/kernel/randomize_va_space
2
root@bt:~# echo 0 > /proc/sys/kernel/randomize_va_space
root@bt:~# cat /proc/sys/kernel/randomize_va_space
0
root@bt:~#

在这种情况下,您可能从 Internet 获得的 OLD STYLE 缓冲区溢出教程。或 aleph one 教程现在将不再在您的系统中运行。

现在让程序漏洞应对缓冲区溢出场景

---------------------bof.c--------------------------
#include <stdio.h>
#include <string.h>

int main(int argc, char** argv)
{
        char buffer[400];
        strcpy(buffer, argv[1]);

        return 0;
}
---------------------EOF-----------------------------

查看 strcpy 函数在没有堆栈保护器的情况下是危险的,因为函数没有检查我们将输入多少字节。 使用额外选项 -fno-stack-protector dan -mpreferred-stack-boundary=2 编译以在您的 C 程序中取消堆栈保护器

root@bt:~# gcc -g -o bof -fno-stack-protector -mpreferred-stack-boundary=2 bof.c
root@bt:~# chown root:root bof
root@bt:~# chmod 4755 bof

现在我们已经完成了具有 SUID root 访问场景的缓冲区溢出 C 程序。 现在让我们搜索一下我们需要将多少字节放入缓冲区以造成程序分段错误

root@bt:~# ./bof `perl -e 'print "A" x 400'`
root@bt:~# ./bof `perl -e 'print "A" x 403'`
root@bt:~# ./bof `perl -e 'print "A" x 404'`
Segmentation fault
root@bt:~#

你看我们需要 404 字节来制造程序分段错误(崩溃)现在我们需要多少字节来覆盖 EIP ? EIP是指令将在之后执行。所以黑客确实会在程序的二进制 SUID 中覆盖 EIP 以邪恶的指令。如果程序在 SUID 根目录下,该指令将在根目录下运行。

root@bt:~# gdb -q bof
(gdb) list
1       #include <stdio.h>
2       #include <string.h>
3
4       int main(int argc, char** argv)
5       {
6               char buffer[400];
7               strcpy(buffer, argv[1]);
8
9               return 0;
10      }
(gdb) run `perl -e 'print "A" x 404'`
Starting program: /root/bof `perl -e 'print "A" x 404'`

Program received signal SIGSEGV, Segmentation fault.
0xb7e86606 in __libc_start_main () from /lib/tls/i686/cmov/libc.so.6
(gdb) run `perl -e 'print "A" x 405'`
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /root/bof `perl -e 'print "A" x 405'`

Program received signal SIGSEGV, Segmentation fault.
0xb7e800a9 in ?? () from /lib/tls/i686/cmov/libc.so.6
(gdb)

程序 GOT 分段错误返回代码。让我们输入更多字节并查看 EIP 寄存器。

(gdb) run `perl -e 'print "A" x 406'`
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /root/bof `perl -e 'print "A" x 406'`

Program received signal SIGSEGV, Segmentation fault.
0xb7004141 in ?? ()
(gdb)

(gdb) run `perl -e 'print "A" x 407'`
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /root/bof `perl -e 'print "A" x 407'`

Program received signal SIGSEGV, Segmentation fault.
0x00414141 in ?? ()
(gdb)

还有一点

(gdb) run `perl -e 'print "A" x 408'`
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /root/bof `perl -e 'print "A" x 408'`

Program received signal SIGSEGV, Segmentation fault.
0x41414141 in ?? ()
(gdb)

(gdb) i r
eax            0x0      0
ecx            0xbffff0b7       -1073745737
edx            0x199    409
ebx            0xb7fc9ff4       -1208180748
esp            0xbffff250       0xbffff250
ebp            0x41414141       0x41414141
esi            0x8048400        134513664
edi            0x8048310        134513424
eip            0x41414141       0x41414141 <-- overwriten !!
eflags         0x210246 [ PF ZF IF RF ID ]
cs             0x73     115
ss             0x7b     123
ds             0x7b     123
es             0x7b     123
fs             0x0      0
gs             0x33     51
(gdb)

现在您可以进行下一步了...

【讨论】:

    【解决方案4】:

    缓冲区溢出只是写到缓冲区末尾:

    int main(int argc, const char* argv[])
    {
        char buf[10];
        memset(buf, 0, 11);
        return 0;
    }
    

    【讨论】:

      【解决方案5】:

      除了已经说过的内容之外,请记住,当缓冲区溢出发生时,您的程序可能会“崩溃”,也可能不会“崩溃”。它应该崩溃,你应该希望它崩溃——但是如果缓冲区溢出“溢出”到你的应用程序也分配的另一个地址——你的应用程序可能会在更长的时间内正常运行。

      如果您使用的是更高版本的 Microsoft Visual Studio - 我建议您使用 stdlib 中新的安全版本,例如 sprintf_s insted of sprintf 等...

      【讨论】:

      • 还有snprintf,优点是标准化(ISO C 99)。还有asprintf(GNU 和 BSD libc)、g_strdup_printf(Glib)。
      【解决方案6】:

      这应该足以重现它:

      void buffer_overflow() 
      {
          char * foo = "foo";
          char buffer[10];
      
          for(int it = 0; it < 1000; it++) {
              buffer[it] = '*';
          }
      
          char accessViolation = foo[0];
      }
      

      【讨论】:

        【解决方案7】:

        “经典”缓冲区溢出示例是:

        int main(int argc, char *argv[])
        {
            char buffer[10];
            strcpy(buffer, argv[1]);
        }
        

        这让您可以使用缓冲区溢出参数并将它们调整到您心中的内容。 “Hacking - The Art of Exploitation”一书(链接到亚马逊)非常详细地介绍了如何解决缓冲区溢出问题(显然纯粹是一种智力练习)。

        【讨论】:

          【解决方案8】:

          如果你想检查你的程序是否有缓冲区溢出,你可以使用像Valgrind 这样的工具来运行它。他们会为您找到一些内存管理错误。

          【讨论】:

            【解决方案9】:

            这是对您收到的答案的一般评论。例如:

            int main(int argc, char *argv[])
            {
                char buffer[10];
                strcpy(buffer, argv[1]);
            }
            

            还有:

            int main(int argc, const char* argv[])
            {
                char buf[10];
                memset(buf, 0, 11);
                return 0;
            }
            

            在现代 Linux 平台上,这可能无法按预期或预期工作。由于 FORTIFY_SOURCE 安全功能,它可能无法正常工作。

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

            要为您的测试禁用 FORTIFY_SOURCE,您应该使用 -U_FORTIFY_SOURCE-D_FORTIFY_SOURCE=0 编译程序。

            【讨论】:

              【解决方案10】:

              在这种情况下,缓冲区是为特定目的而留出的一部分内存,缓冲区溢出是当对缓冲区的写操作持续超过末尾时发生的情况(写入具有不同目的的内存) .这总是是一个错误。

              缓冲区溢出攻击是利用这个漏洞来完成程序作者不打算实现的事情。

              【讨论】:

                【解决方案11】:

                给出正确答案后:要深入了解该主题,您可能需要收听 Podcast Security Now。在Episode 39(不久前)中,他们深入讨论了这个问题。这是一种无需消化整本书即可获得更深入理解的快速方法。

                (如果您比较注重视觉,您会在链接中找到包含多个大小版本的存档以及成绩单)。音频不是这个主题的完美媒体,但史蒂夫正在努力解决这个问题。

                【讨论】:

                  【解决方案12】:

                  缓冲区溢出是插入的字符超出了分配的内存可以容纳的范围。

                  【讨论】:

                  • 与现有答案相比,此答案是否增加了任何新内容?请不要重复,为好的答案点赞
                  猜你喜欢
                  • 1970-01-01
                  • 2021-06-15
                  • 1970-01-01
                  • 2013-02-17
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多