理解缓冲区溢出

这几天一直在做csapp里面的3.38,他是让你自己实现一个缓冲区溢出程序.代码如下:

/* Bomb program that is solved using a buffer overflow attack */

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>

/* 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;
}

这边有几个概念要说一下:
1 在一个函数的栈桢里面,有两个指针,一个是栈指针(存储在%esp),一个是桢指针(存储在%ebp),其中栈指针指向的是栈顶,也就是说它会不断变化,而桢指针指向的是栈低,他是固定的。而栈指针和桢指针之间方得就是,不能存储在寄存器中的一些局部变量.

2所有的寄存器被所有的函数所共享,因此这里IA32对寄存器去有了一个规定,那就是:

%eax,%edx,%ecx是调用者寄存器,%ebx,%edi,%esi是被调用者寄存器,举个例子,假设方法p调用q,则q可以随便覆盖%eax,%edx,%ecx中的而不会破坏p的使用,但是如果他要使用%ebx,%edi,%esi中的值,则必须先将这些寄存器进栈,谈后才能操作.

更详细的有关过程调用的介绍,可以自己去看csapp的第三章.

题目是让你输入一个16进制字符串,从而能使 getbuf返回0xdeadbeef.这个要实现的话,之要实现下面几个目标就可以了:
覆盖函数的返回地址(也就是test调用getbuf时压入栈的地址)。使这个地址指向你自己的执行代码,而你自己的执行代码所要实现的就是就是改变函数的返回值,修改完返回值之后,你要将返回你本该返回的地址,(这边就是函数test的地址)。

这边先看一下,函数调用结束时的汇编代码:

leave
ret


leave指令相当于
 movl %ebp,%esp
popl %ebp

也就是说当函数要返回时,会先将栈指针指向桢指针所指的内容,也就是说删除掉了局部变量之类的东西,这时再弹出%ebp,这时的栈顶,也就是调用这个函数的函数地址了,而ret指令则就是弹出这个地址,然后跳转到这个地址。

知道了这个,我们就很容易构造我们的目标程序了,我们的目标程序也就是先将0xdeadbeef付给%eax,然后再将函数test的地址入栈,最后ret.这边假设我们的test的地址是$0x048503bf.
mov $0xdeadbeef ,%eax
push $0x048503bf
ret


然后我们将这段代码先编译,然后反汇编,就能得到我们的目标代码.

所以,我们这边的构造的字符串的结构式这样的:
(目标代码)(当test调用getbuf时的%ebp的内容)(我们的目标代码的地址,也就是我们构造的这个字符串buf的地址,也就是%ebp的地址减去12)

这边为什么没有给出完整的程序呢,这是因为,在linux2.6下他对栈有保护,它使用一种叫做ExecShield的技术,简而言之就是他会使对堆栈内的代码不可执行,并且每次执行的时候会随即的改变堆栈的地址.因此如果我们要尝试缓冲区溢出的话,使用gdb调试,他会报一个segmentation fault的错误..

有关ExecShield的详细信息,可以看这个:

http://www.linuxgoo.com/bbs/archiver/58645.html
虽然没有实践成功,可是自己的理解应该是没有什么错误的.

你可能感兴趣的:(C++,c,F#,C#,bbs)