缓冲区溢出攻击实验

    本文的实验来源于《Computer Systems A Programmer's Perspective》(深入理解计算机系统》一书中第三章的一个实验。
    作者给出了一个含有缓冲区溢出的程序bufbomb.c,你需要做的,就是注入给缓冲区些特殊的数据,到底利用缓冲区的目的。
//bufbomb.c
/* Bomb program that is solved using a buffer overflow attack */

#include
#include
#include

/* Like gets, except that characters are typed as pairs of hex digits.
   Nondigit characters are ignored.  Stops when encounters newline */
char *getxs(char *dest)
{
  int c;
  int even = 1; /* Have read even number of digits */
  int otherd = 0; /* Other hex digit of pair */
  char *sp = dest;
  while ((c = getchar()) != EOF && c != '/n') {
    if (isxdigit(c)) {
      int val;
      if ('0' <= c && c <= '9')
    val = c - '0';
      else if ('A' <= c && c <= 'F')
    val = c - 'A' + 10;
      else
    val = c - 'a' + 10;
      if (even) {
    otherd = val;
    even = 0;
      } else {
    *sp++ = otherd * 16 + val;
    even = 1;
      }
    }
  }
  *sp++ = '/0';
  return dest;
}

/* $begin getbuf-c */
int getbuf()
{
    char buf[12];
    getxs(buf);
    return 1;
}

void test()
{
  int val;
  printf("Type Hex string:");
  val = getbuf();
  printf("getbuf returned 0x%x/n", val);
}
/* $end getbuf-c */

int main()
{

  int buf[16];
  /* This little hack is an attempt to get the stack to be in a
     stable position
  */
  int offset = (((int) buf) & 0xFFF);
  int *space = (int *) alloca(offset);
  *space = 0; /* So that don't get complaint of unused variable */
  test();
  return 0;
}

    函数getxs的功能类似于库函数gets的功能,除了它是以十六进制数字对的编码方式读入的字符。例如,要读入字符串“0123”,你得给出输入字符串“30 31 32 33”,这个函数会忽略空格。
    分析这个程序,可以得知,正常情况下,这个函数会在getbuf中,调入getxs函数读入数字对,然后不管任何情况下,都会对test函数返回0x1,然后由test中的printf函数打印处getbuf的返回值。
    现在你的任务,就是,利用缓冲区溢出的漏洞,输入些特殊的数字,使得屏幕中打印的是0xdeadbeef。
    首先,你得知道,哪个地方存在缓冲区溢出的漏洞。看看getxs,就可用知道这个函数存在此漏洞。因为,在getbuf中,定义了字符串buf有12个字节的空间,然后,调入getxs函数,并且把buf的起始地址传递给getxs函数。现在分析getxs函数,可以看到这句while ((c=getchar())!= EOF && c!='/n'),很明显,稍微有点C语言基础的人,都可以看出,判定输入结束的标志是EOF或是回车,下面问题就出来了,buf限定的是12个字节,而getxs却不检查是否越界,这就引出了个严重的问题,缓冲区溢出!合理利用这点,可以达到某些需要的功能。
    由于缓冲区的溢出,非常依赖于机器和编译器,因此运行在不同版本的机器或编译器中,需要注入的特定的字符也不一样。
    下面是偶分析这个程序所用的工具和环境:
DEBIAN+GCC:这个不用说了,用的是linux的系统,gcc的编译器
GDB:一个调试工具,非常非常强大,可惜,偶用的不是很熟
OBJDUMP:也不怎么会用,似乎有些强大,偶也仅仅用它的反汇编功能。
    因为要分析它的二进制的代码,因此需要知道它的汇编生成情况,可以用gcc -S -o bufbomb bufbomb.c来产生这个文件的汇编文件,也可以gcc -o bufbomb bufbomb.c 生成可执行文件,然后objdump -d bufbomb来反汇编。
    以上,分析出处缓冲区的漏洞主要存在于getxs中,而调用它的函数是getbuf函数,因此先分析这个函数。
080484e7 :
 80484e7:    55                       push   %ebp
 80484e8:    89 e5                    mov    %esp,%ebp
 80484ea:    83 ec 28                 sub    $0x28,%esp;开辟0x28字节的局部空间
 80484ed:    8d 45 e8                 lea    0xffffffe8(%ebp),%eax;%ebp-24是buf的地址处,因此,实际上给buf开辟了24字节的空间。
 80484f0:    89 04 24                 mov    %eax,(%esp)
 80484f3:    e8 1c ff ff ff           call   8048414
 80484f8:    b8 01 00 00 00           mov    $0x1,%eax
 80484fd:    c9                       leave 
 80484fe:    c3                       ret

    现在分析这个函数的堆栈情况(从上到下,地址递减)

返回地址    (ebp+4)
保存的ebp   (ebp)
buf的空间   24字节

    很明显,如果改动了buf[24]~buf[27]处,在leave的时候,ebp,就会是你在buf[24]~buf[27]处存放的数据了。改动buf[28]~buf[31]处,就改动了返回地址的函数。
    根据实验要求,要求打印处0xdeadbeef数字,然后分析调用getbuf的函数test。
080484ff :
 80484ff:    55                       push   %ebp
 8048500:    89 e5                    mov    %esp,%ebp
 8048502:    83 ec 18                 sub    $0x18,%esp
 8048505:    c7 04 24 94 86 04 08     movl   $0x8048694,(%esp)
 804850c:    e8 1b fe ff ff           call   804832c <_init+0x48>
 8048511:    e8 d1 ff ff ff           call   80484e7
 8048516:    89 45 fc                 mov    %eax,0xfffffffc(%ebp)
 8048519:    8b 45 fc                 mov    0xfffffffc(%ebp),%eax
 804851c:    89 44 24 04              mov    %eax,0x4(%esp)
 8048520:    c7 04 24 a5 86 04 08     movl   $0x80486a5,(%esp);0x80486a5 是打印格式字符处的地址处
 8048527:    e8 00 fe ff ff           call   804832c <_init+0x48>
 804852c:    c9                       leave 
 804852d:    c3                       ret

    在80486a5处调用的函数,可以分析出,实际上就是printf函数,那么,明显可知,前面两个mov进堆栈的数据,就是printf的参数,分析可得,0x80486a4,是打印格式字符串的地址。0x4(%esp)处,就是需要打印的数据了,也就是getbuf的返回值0x1了。这两个参数,都需要压入堆栈。
    实际中,getbuf的ret指令,返回到8048516出,然后把getbuf的返回值进入堆栈的。那么,可以考虑,能不能提前在实际的堆栈中,放入printf的参数,然后直接返回到printf的指令处呢?实际证明,这是可以的。这样子,就可以达到目的了!
    需要仔细分析的是如何这样做。
    回到getbuf函数,已经分析出buf[24]~buf[27]放的是ebp,也就是在leave的时候,通过popl指令弹出的,这个,通常不改动它的,因为,参数,通常,都是基于它的偏移定位的,否则,容易出错,因此,用gdb跟踪到test中的getbuf的下一个语句,实际上,就是
call   80484e7 下面就可以了,然后查看ebp的值是0xbfffefe8,因此,要给buf[24]~buf[27]注入的数据,必须是0xbfffefe8,然后查看返回地址,也就是printf的地址,是8048527,因此,给buf[28]~buf[31]注入数据0x8048527。嘿嘿。
    这样,在getbuf中,执行ret指令的时候,直接返回到8048527处了。ret执行的动作是popl到eip中,在调用printf的时候,参数,又是压入堆栈中,因此,buf[28]~buf[31]处,存放打印格式字符串的地址0x8048527(由printf前面的参数进栈的指令得到的),然后就是叫需要打印的数据进入堆栈,也就是buf[32]~buf[35]处了。
    嘿嘿,这样分析后,需要注入的数字非常明显了。
    前24个数字也就是buf[0]~buf[23],随便注入,因为这没有什么用,然后就是非常关键的了,顺序注入ebp,返回地址,格式字符串的地址,和欲打印的值就可以了。
    也就是(30)24个 e8 ef ff bf 27 85 04 08 a5 86 04 08 ef be ad ed

转载请注明blog.csdn.net/besich
作者:BSCH
QQ:3178488(请注明)
EMAIL:[email protected]
时间:2005.4.27

你可能感兴趣的:(杂象)