9

作为大学计算机安全课程的一部分,我很快将学习缓冲区溢出以及如何将它们用作漏洞利用。我正在尝试使用以下代码进行一些简单的缓冲区溢出:

#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[])
{
    char buffer_one[4], buffer_two[16];

    strcpy(buffer_one, "one");
    strcpy(buffer_two, "two");

    strcpy(buffer_one, argv[1]);

    printf("buffer_two is at %p and contains \'%s\'\n", buffer_two, buffer_two);
    printf("buffer_one is at %p and contains \'%s\'\n", buffer_one, buffer_one);
}

如果我运行,我可以用空终止符覆盖 buffer_one 的内容

$./overflow 1234567890123456
 buffer_two is at 0x7fff5fbff8d0 and contains '1234567890123456'
 buffer_one is at 0x7fff5fbff8e0 and contains ''

但是如果我发送超过 16 个字符作为参数,程序会发送 Abort 陷阱。我想这是雪豹上的某种缓冲保护(也许是 ASLR?)。如果让 buffer_two 的大小 < 16,则地址仍然相隔 16 位

我正在运行gcc -o overflow overflow.c -fno-stack-protector以删除堆栈保护

除了安装运行 linux dist 的 VM 之外,有没有解决这个问题的方法?

4

4 回答 4

3

buffer_one为什么会发生这种情况的关键是位于buffer_two内存中的事实。这意味着当你溢出时buffer_one,你并没有溢出到buffer_two. 相反,您会溢出到用于保存其他内容的堆栈内存中,例如保存的ebp指针,最重要的是返回地址。

这正是您在尝试使用缓冲区溢出漏洞时想要发生的事情!当程序执行strcpy(buffer_one, argv[1]);前四个字节从argv[1]go 到分配给buffer_one. 但是接下来的 12 开始溢出用于其他事情的内存,最终覆盖了返回地址。在没有看到机器代码的情况下,我无法确定究竟是哪些字节溢出了返回地址。但我猜测 SIGABRT 时 EIP 的值是 0x31323334 或类似的值(“1234”的十六进制表示)。关键是要意识到,通过覆盖返回地址,您可以控制 EIP。当你控制 EIP 时, 你就控制了系统. (有点夸张,但在大多数情况下并不遥远)当您控制 EIP 时,您可以控制处理器接下来将执行哪些指令(暂时不考虑操作系统/内核实际上介于两者之间的事实)。

现在,如果您确切地发现哪八个字节覆盖了返回地址,您可以将这些字节替换为缓冲区的地址0x00007fff5fbff8e0(又名 shellcode)。请注意,您必须在最重要的位置填写隐含的 0,并将地址提供为实际不可打印的 ASCII 字符(0x00 0x00 0x7f 0xff 0x5f等等),而不是实际的数字/字符7ff5等。如果使用 x86-64 架构,您还必须考虑 little-endianness 并将其向后提供 -0xe0 0xf8 0xbf等等。提供这些不可打印字符最容易使用反引号和使用简短 Python 或 Perl 脚本的命令替换来完成:

./overflow `python -c 'print "AAAAAAAAAAAAAAAA\xe0\xf8\xbf\x5f\xff\x7f"'`

(A 正在填充以溢出缓冲区。)不幸的是,您将无法提供\x00该地址所需的 2 个附加值。这些 NULL 中的一个将由 为您放置strcpy,但您必须对最后一个 NULL 感到幸运,并希望您正在覆盖的地址已经开始0x00(实际上很有可能)。现在,当您使用正确数量的 A 执行此操作时,您可能仍然会遇到分段错误甚至可能是非法指令,因为您现在将跳转到大写的 A 并将它们作为实际机器指令 ( 0x41=> inc ecx) 执行。

最后最后一部分是放入实际的 shellcode。鉴于您有限的缓冲区大小,很难仅在 12 个字节左右提供任何有用的东西。由于您是在这种情况下编写代码,因此最简单的方法可能是使缓冲区更大。如果这不是一个选项,那么您可以 A) 也可以使用buffer_two16 个字节,因为它之前buffer_one或 B) 在环境变量中提供 shellcode 并跳转到那个。

如果您想自己编写实际的 shellcode,您必须知道如何执行系统调用、调用约定是什么以及如何使用它们,以及如何避免 shellcode 中出现 NULL 字节。另一种选择是使用一个有效载荷生成器,例如 Metasploit 中包含的那个,这将使它更容易(尽管你不会学到那么多)。

从技术上讲,这些是您唯一需要的部分,尤其是因为您对地址将是什么有了很好的了解。然而,很多时候(尤其是在不知道 shellcode 地址的情况下)一个所谓的 NOP sled 会放在 shellcode 前面,这样您就不必完全正确地获取地址。NOP sled(No Operation 的缩写)只是成百上千的 NOP 指令(0x90),您可以跳到中间,然后在继续执行到 shellcode 之前无效。

如果您在 GDB 中跟踪所有内容并且执行正确地跳转到了 shellcode,但您仍然遇到访问冲突,这可能是因为在堆栈页面上设置了 NX 位,这意味着处理器将拒绝将堆栈中的数据作为指令执行。我不确定是否execstack包含在 OSX 中,但如果包含,您可以将其用于测试目的以禁用 NX 位 ( execstack -s overflow)。

我为文字墙道歉,但我不确定你想研究缓冲区溢出多远。您还可以查看其他指南,例如 Aleph One 的原型指南“Smashing the Stack for Fun and Profit”Shellcoder's Handbook也是一本值得一看的好书,我相信其他人可以添加建议。

TL;DR:简而言之,您正在溢出缓冲区并用垃圾覆盖保存的指针和返回地址。

于 2011-04-14T00:45:45.587 回答
1

如果您正在学习漏洞利用,那么您将需要真正深入研究细节。

来吧,阅读机器代码!您可能会发现如何通过 Snow Leopard 使用的任何检查方法使溢出滑过。

问题也可能比这更简单。没有规定编译器必须以buffer_one任何buffer_two特定顺序将它们放在堆栈上,甚至根本不将它们放在堆栈上。请注意,buffer_one这实际上适合寄存器。

当然这里不是这种情况,但我看到buffer_two 放在buffer_one之前。这意味着将溢出写入buffer_one永远不会写入buffer_two. 我无法解释为什么它最终包含'',但f8d0肯定是 f8e0内存中。

于 2011-04-13T20:12:46.867 回答
1

您是否尝试在编译时禁用 FORTIFY_SOURCE?

-D_FORTIFY_SOURCE=0

于 2014-07-15T15:09:42.267 回答
1

x86 上堆栈上的数据是 4 字节对齐的。如果长度不是 4 字节的倍数,则在buffer_two和之间放置填充。将其更改为 12 或更少,它们应该相隔 12 个字节,等等。buffer_onebuffer_two

[更新] 我忽略了地址大小。您在 64 位系统上,您的堆栈是 8 字节对齐的。在您的缓冲区大小更改至少 8 个字节之前,地址差异不会改变。

此行是否正确:

strcpy(buffer_one, argv[1]);

输出看起来像您正在复制argv[1]buffer_two.

鉴于这种情况,当它崩溃时你复制了多少?17 个字节?18?如果它超过 24,您将开​​始以导致中止的方式破坏堆栈。

请注意,"1234567890123456"实际上是复制 17 个字节,其中包括空终止符 truncating buffer_one

于 2011-04-13T20:15:37.083 回答