黑客攻防实战:系统篇 第2章(读书笔记)

0x01 栈上的缓冲区溢出解析


源码:

void return_input(void)
{
    char array[30];
    gets(array);
    printf("%s\n", array);
}

void main(void)
{
    return_input();
    return 0;
}

编译:gcc -fno-stack-protector -mpreferred-stack-boundary=2 -ggdb test.c -o test

反编译,形成汇编码:

(gdb) disassemble main
Dump of assembler code for function main:
   0x0804845c <+0>:     push   %ebp
   0x0804845d <+1>:     mov    %esp,%ebp
   0x0804845f <+3>:     call   0x804843b <return_input>
   0x08048464 <+8>:     nop
   0x08048465 <+9>:     pop    %ebp
   0x08048466 <+10>:    ret
End of assembler dump.

(gdb) disassemble return_input
Dump of assembler code for function return_input:
   0x0804843b <+0>:     push   %ebp
   0x0804843c <+1>:     mov    %esp,%ebp
   0x0804843e <+3>:     sub    $0x20,%esp
   0x08048441 <+6>:     lea    -0x1e(%ebp),%eax
   0x08048444 <+9>:     push   %eax
   0x08048445 <+10>:    call   0x8048300 <gets@plt>
   0x0804844a <+15>:    add    $0x4,%esp
   0x0804844d <+18>:    lea    -0x1e(%ebp),%eax
   0x08048450 <+21>:    push   %eax
   0x08048451 <+22>:    call   0x8048310 <puts@plt>
   0x08048456 <+27>:    add    $0x4,%esp
   0x08048459 <+30>:    nop
   0x0804845a <+31>:    leave
   0x0804845b <+32>:    ret
End of assembler dump.


0x02 汇编单步分析


现在0x0804845c出设置断点,即main函数的第一条指令处,然后使用run命令开始执行,之后每一步使用stepi单步执行。

test@test-virtual-machine:~/exploit$ gdb ./overflow
GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.5) 7.11.1
Copyright (C) 2016 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 "i686-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./overflow...done.
(gdb) disassemble main
Dump of assembler code for function main:
   0x0804845c <+0>:     push   %ebp
   0x0804845d <+1>:     mov    %esp,%ebp
   0x0804845f <+3>:     call   0x804843b <return_input>
   0x08048464 <+8>:     nop
   0x08048465 <+9>:     pop    %ebp
   0x08048466 <+10>:    ret
End of assembler dump.
(gdb) b *0x0804845c
Breakpoint 1 at 0x804845c: file overflow.c, line 9.
(gdb) run
Starting program: /home/yerx/exploit/overflow

Breakpoint 1, main () at overflow.c:9
9       {
(gdb)


  1. 初始状态,在执行第一条指令,即push %ebp之前,先看一下各个寄存器和堆栈的初始状态。

    ebp:

    ebp            0x0      0x0
    
    

    esp:

    (gdb) i r esp
    esp            0xbffff56c       0xbffff56c
    
    

    eip:

    (gdb) i r eip
    eip            0x804845c        0x804845c 

    eax:

    (gdb) i r eax
    eax            0xb7fbadbc       -1208242756
    
    

    初始状态如图所示

    黑客攻防实战:系统篇 第2章(读书笔记)_第1张图片

  2. 执行main函数第一条指令,push %ebp

    指令意义:将ebp的内容入栈。即先将esp地址下移四个字节,然后把ebp寄存器的内容写入esp寄存器中保存的地址中。

    所以寄存器和堆栈的状态应该是下图的样子:

    黑客攻防实战:系统篇 第2章(读书笔记)_第2张图片

    查看一下寄存器和堆栈信息,验证我们的图示

    ebp:

    (gdb) i r ebp
    ebp            0x0      0x0
    
    

    esp:

    (gdb) i r esp
    esp            0xbffff568       0xbffff568
    
    

    0xbffff568:

    (gdb) x/20x 0xbffff568
    0xbffff568:     0x00000000      0xb7e1f637      0x00000001      0xbffff604
    0xbffff578:     0xbffff60c      0x00000000      0x00000000      0x00000000
    0xbffff588:     0xb7fb9000      0xb7fffc04      0xb7fff000      0x00000000
    0xbffff598:     0xb7fb9000      0xb7fb9000      0x00000000      0xf6e2e950
    0xbffff5a8:     0xcae3e740      0x00000000      0x00000000      0x00000000
    
    

    0xbffff56c:

    (gdb) x/20x 0xbffff56c
    0xbffff56c:     0xb7e1f637      0x00000001      0xbffff604      0xbffff60c
    0xbffff57c:     0x00000000      0x00000000      0x00000000      0xb7fb9000
    0xbffff58c:     0xb7fffc04      0xb7fff000      0x00000000      0xb7fb9000
    0xbffff59c:     0xb7fb9000      0x00000000      0xf6e2e950      0xcae3e740
    0xbffff5ac:     0x00000000      0x00000000      0x00000000      0x00000001
    
    

    eip:

    (gdb) i r eip
    eip            0x804845d        0x804845d 
    
    

    可以看出和上图显示的是一致的。

  3. 执行main函数第二条指令,mov %esp,%ebp

    指令意义:将esp寄存器的内容复制给ebp寄存器。

    所以堆栈和寄存器变化如图所示:

    黑客攻防实战:系统篇 第2章(读书笔记)_第3张图片

    这里就不列出来gdb查看的信息了和上图是一样的。

  4. 执行main函数第三步指令,call 0x804843b

    指令意义:调用return_input。call指令分两步执行,1.将call指令后面一条指令地址入栈,即esp保存的内容减少先下移四个字节,然后将0x08048464 <+8>: nop的地址0x08048464存入esp保存的地址中。2.跳转到return_input函数的第一条指令位置,即将eip的值修改为0x804843b。

    所以堆栈和寄存器的变化如图所示:

    黑客攻防实战:系统篇 第2章(读书笔记)_第4张图片

  5. 执行return_input函数第一条指令,push %ebp

    和main函数第一条指令是一样的,这个指令就是保存调用函数的栈基址(ebp),方便子函数调用结束之后将调用函数的堆栈恢复成调用之前的状态的。

    该指令的意义:将ebp的内容入栈。即先将esp保存的地址下移四个字节,然后把ebp寄存器的内容写入esp寄存器保存的地址中。

    所以堆栈和寄存器的变化如图所示:

    黑客攻防实战:系统篇 第2章(读书笔记)_第5张图片

  6. 执行return_input函数第二条指令,mov %esp,%ebp

    该指令的意义:将esp寄存器保存的内容复制给ebp,就是设置新函数的栈基址,从这个地址开始就是return_input函数的栈底了。

    堆栈和寄存器的变化如图所示:

    黑客攻防实战:系统篇 第2章(读书笔记)_第6张图片

    注意:从图可以看出,通过ebp寄存器我们就可以找出所有的调用函数的栈基址,比如从ebp寄存器找到return_input函数的栈基址为0xbffff560,在0xbffff560中找到0xbffff568,这个地址就是main函数的栈基址,然后通过0xbffff568保存的内容就可以找到调用main函数之前的栈基址。

  7. 执行return_input函数第三条指令,sub $0x20,%esp

    该指令的意义:将esp保存的地址下移32个字节,这个指令就是在为该函数中的临时变量和本地变量分配空间。return_input函数只有一个30个字符数组变量,为什么分配了32个字节呢,这是为了字节对齐,方便cpu寻址。

    堆栈和寄存器的变化如图所示:

    黑客攻防实战:系统篇 第2章(读书笔记)_第7张图片

  8. 执行return_input函数第四条指令,lea -0x1e(%ebp),%eax

    该指令的意义:将ebp保存的地址下移30个字节的地址存入eax寄存器,用来保存gets函数传入的参数array,这里传递的是指针,所以保存的是地址。注意lea -0x1e(%ebp),%eax和mov -0x1e(%ebp),%eax的区别,mov -0x1e(%ebp),%eax是把ebp保存的地址下移30个字节的地址中保存的内容给eax,例如,这里的ebp保存的内容是0xbffff560,0xbffff560-0x1e就是0xbffff542,假设0xbffff542中保存的内容是0x01,那么mov指令eax的内容就是0x01,而lea指令eax中保存的内容就是0xbffff542。

    堆栈和寄存器的变化如图所示:

    黑客攻防实战:系统篇 第2章(读书笔记)_第8张图片

  9. 执行return_input函数第五条指令,push %eax

    该指令的意义:将eax保存的内容入栈,就是在保存参数,后面进入子函数将使用保存的这个参数进行操作,比如这个例子中的array数组,gets子函数中给array数组内容赋值,就需要先通过push的这个地址要到array数组指针的地址,然后通过这个地址给每一个字节赋值。

    堆栈和寄存器的变化如图所示:

    黑客攻防实战:系统篇 第2章(读书笔记)_第9张图片

  10. 执行return_input函数第六条指令,call 0x8048300 gets@plt

    该指令的意义:就是调用gets函数,和main函数调用return_input一样。

    堆栈和寄存器的变化如图所示:

    黑客攻防实战:系统篇 第2章(读书笔记)_第10张图片

  11. 执行gets函数,指令就不单独解析了,这里直接说明输入一串字符之后内存堆栈的变化

    输入:AAAAAAAAAABBBBBBBBBBCCCCCCCCC(10个A,10个B,9个C)为什么是9个C呢,因为数组容量是30个字节,最后一个字节需要给’\0’,代表字符串结束。稍后会讲如果给10个C会出现什么情况。

    堆栈和寄存器的变化如图所示:

    黑客攻防实战:系统篇 第2章(读书笔记)_第11张图片

  12. 执行return_input函数第八条指令,add $0x4,%esp

    该指令的意义:gets函数执行完了,gets函数的参数使用了,就需要释放掉,但是这里0xbffff53c地址代表的不是array的地址。gets函数的原型是char *gets(char *string);所以,这里0xbffff53c是string这个参数的地址,gets(array)实际上是把array地址,即0xbffff542赋值给string所在地址的内存中,也就是0xbffff53c。

    堆栈和寄存器的变化如图所示:

    黑客攻防实战:系统篇 第2章(读书笔记)_第12张图片

  13. 执行return_input函数第九,十条指令,lea -0x1e(%ebp),%eax和push %eax

    该指令的意义:和gets函数调用之前一样,需要把array对应的地址给eax,然后将把存在eax的array地址入栈。这里是留着给printf用的。

    堆栈和寄存器的变化如图所示:

    黑客攻防实战:系统篇 第2章(读书笔记)_第13张图片

  14. 执行return_input函数第十一,十二条指令,call 0x8048310 puts@plt,add $0x4,%esp

    这个指令就不讲了,可以参照上面gets的指令对应进行对比就行了。唯一要注意的变化就是0xbffff538存储的返回地址变为printf后一条指令的地址,而不是gets后一条指令的地址了。

    堆栈和寄存器的变化如图所示:

    黑客攻防实战:系统篇 第2章(读书笔记)_第14张图片

  15. 执行return_input函数第十三条指令,leave

    该指令的意义:leave指令等价于如下两条指令,先将ebp的内容给esp,即恢复调用函数也就是main函数栈状态到执行call指令之后,执行return_input函数第一条指令之前。

    mov %ebp %esp
    pop %ebp
    

    堆栈和寄存器的变化如图所示:

    黑客攻防实战:系统篇 第2章(读书笔记)_第15张图片

  16. 执行return_input函数第十四条指令,ret

    该指令的意义:ret指令将esp中的内容出栈,然后将eip跳转到该指令地址处。这样堆栈状态恢复到了main函数的堆栈状态。

    堆栈和寄存器的变化如图所示:

    黑客攻防实战:系统篇 第2章(读书笔记)_第16张图片

后续就是main函数继续执行,就不在赘述了。

综上所述,就是整个函数执行的流程,下面讲讲如何改变函数的执行流程的。

0x03 改变函数的执行流程


在上面流程中的11条时,输入的字符是10个A,10个B,9个C,因为字符数组要包含一个’\0’代表字符串结束。

那么如果输入的字符超过这个字符数组的长度会怎么样呢?

下面就是正确输入的堆栈状态:

黑客攻防实战:系统篇 第2章(读书笔记)_第17张图片

从上图可以看到如果输入超过数组长度,那么在0xbffff55c中的以上的内容会被输入的内容覆盖。比如我们输入10个C,那么0xbffff560的内容就会变成0xbffff500,如果多输入一些,比如覆盖掉0xbffff564的内容,这个地址里面的内容是返回后需要跳转到的指令地址,如果我们把这个内容覆盖为我们希望他执行的位置就可以改变函数的流程了。

例如:使用call return_input的指令地址替换0xbffff564位置的内容,那么当return_input执行完之后会继续执行一遍,就会看到两次输出。

具体实现:

  1. 查看return_input指令的地址:0x0804845f <+3>: call 0x804843b

  2. 需要些多少长度的内容,10个A,10个B,9个C字符,刚好写到0xbffff55f,再写4个,就到了0xbffff564这里位置了,所以需要些10个A,10个B,10个C,4个D,然后是0x3b对应的字符,0x84对应的字符,然后是0x04对应的字符,然后是0x08对应的字符。由于这些字符不是可打印字符,所以无法直接通过键盘进行输入。可以使用管道通过pfrintf “AAAAAAAAAABBBBBBBBBBCCCCCCCCCCDDDD\x5f\x84\x04\x08” | ./overload来输入特殊的字符

    结果如下:

    yerx@yerx-virtual-machine:~/exploit$ printf "AAAAAAAAAABBBBBBBBBBCCCCCCCCCCDDDD\x5f\x84\x04\x08" | ./overflow
    AAAAAAAAAABBBBBBBBBBCCCCCCCCCCDDDD_▒
    AAAAAAAAAABBBBBBBBBBCCCCCCCCCCDDDDd▒
    

    成功将字符输出了两次。

你可能感兴趣的:(安全)