在雪豹中创建缓冲区溢出

时间:2011-04-13 19:59:29

标签: c security osx-snow-leopard buffer-overflow fortify-source

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

#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);
}

如果我运行

,我可以使用null终止符覆盖buffer_one的内容
$./overflow 1234567890123456
 buffer_two is at 0x7fff5fbff8d0 and contains '1234567890123456'
 buffer_one is at 0x7fff5fbff8e0 and contains ''

但如果我发送超过16个字符作为参数,程序将发送Abort陷阱。我认为这是Snow Leopard上的某种缓冲保护(可能是ASLR?)。如果if的大小为buffer_two&lt; 16,adresse仍然是16位

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

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

4 个答案:

答案 0 :(得分:3)

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

这正是您在尝试缓冲区溢出漏洞时想要发生的事情!当程序执行strcpy(buffer_one, argv[1]);时,argv[1]的前四个字节进入为buffer_one分配的内存。但接下来的12个开始溢出内存被用于其他事情,最终覆盖了返回地址。在没有看到机器代码的情况下,我无法确定哪些字节确实溢出了返回地址。但我猜测在SIGABRT时EIP的值是0x31323334或类似的东西('1234'的十六进制表示)。关键是要意识到通过能够覆盖返回地址,您可以控制EIP。当您控制EIP时, 可以控制系统 。 (有点过分夸大,但在大多数情况下并不遥远)当你控制EIP时,你可以控制处理器接下来会执行哪些指令(暂时搁置OS /内核实际位于其间的事实)。

现在,如果您确切地找到覆盖返回地址的八个字节,则可以使用缓冲区的地址(0x00007fff5fbff8e0)替换这些字节,而不是返回原始调用者(在本例中为libc),程序将开始执行您提供的指令(AKA 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是填充缓冲区溢出的。)不幸的是,您将无法提供该地址所需的2个额外\x00。其中一个NULL将由strcpy为您放置,但您必须幸运地使用最后一个NULL,并希望您覆盖的地址已经开始0x00(这实际上是高度的可能)。现在,当您使用正确的A数执行此操作时,您可能仍会遇到分段错误甚至可能是非法指令,因为您现在将跳转到大写A并将其作为实际机器指令执行(0x41 =&gt; inc ecx)。

然后最后一块是放入实际的shellcode。鉴于您的缓冲区大小有限,很难提供任何有用的东西,只有12个字节左右。由于您在这种情况下编写代码,最简单的方法可能是使您的缓冲区更大。如果这不是一个选项,那么您可以A)使用buffer_two以及另外16个字节,因为它来自buffer_one或B)在环境变量中提供shellcode并转而使用

如果您希望自己编写实际的shellcode,您必须知道如何执行系统调用以及调用约定以及如何使用它们,以及如何避免shellcode中的NULL字节。另一种选择是使用有效载荷生成器,例如Metasploit附带的有效载荷生成器,这将使它变得更容易(尽管你几乎不会学到它)。

这些技术上是你需要的唯一部分,特别是因为你很清楚地址是什么。但是,很多时候(特别是当shellcode地址未知时),所谓的NOP底座将被放置在shellcode的前面,这样你就不必完全正确地获取地址。 NOP雪橇(无操作的简称)只是数百到数千个NOP指令(0x90),您可以跳到中间,然后在执行继续进入shellcode之前无效。

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

我为文本墙道歉,但我不确定你想研究缓冲区溢出的程度。您还可以查看其他指南,例如Aleph One的原型指南"Smashing the Stack for Fun and Profit"The Shellcoder's Handbook也是一本很好的书籍,我相信其他人可以添加推荐。

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

答案 1 :(得分:1)

如果您正在了解漏洞攻击,那么您需要真正深入了解详细信息。

继续,阅读机器代码!您可能能够找到如何通过Snow Leopard正在使用的任何检查方法来消除溢出。

问题可能比这更简单。没有规则,编译器必须在堆栈上以任何特定顺序放置buffer_onebuffer_two,或者甚至将它们放在堆栈中。请注意,buffer_one实际上适合注册。

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

答案 2 :(得分:1)

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

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

这条线是否正确:

strcpy(buffer_one, argv[1]);

输出看起来就像是将argv[1]复制到buffer_two

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

请注意,"1234567890123456"实际上复制了17个字节,其中包括截断buffer_one的空终止符。

答案 3 :(得分:1)

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

-D_FORTIFY_SOURCE=0