【转载】栈溢出原理及实现

缓冲区溢出

  • 在大缓冲区的数据向小缓冲区复制的过程中,由于没注意小缓冲区的边界,“撑爆”了较小的缓冲区,从而冲掉了和小缓冲区相邻内存区域的其他数据而引起的内存问题。

无论什么计算机架构,进程使用的内存都可以按照功能大致分为4个部分:

  (1)代码区:这个区域存储着被装入的执行的二进制代码,处理器会到这个区域取指并执行。

  (2)数据区:用于存储局部变量。

  (3)堆区:进程可以在堆区中动态的请求一定大小的内存,并在用完之后归还个堆区。动态分配和回收是堆区的特点。

  (4)栈区:用于动态的存储函数之间的调用关系。以保证被调用函数在返回时恢复到母函数中继续执行。

栈与系统栈

  • 栈:指的是一种数据结构,是一种先入后出的数据表。系统栈:指的是内存中的栈,由系统自动维护,他用于实现高级语言中的函数调用。
  • 系统栈:指的是内存中的栈,由系统自动维护,他用于实现高级语言中的函数调用。

函数调用过程

  当函数被调用时,系统栈会为这个函数新开辟一个栈帧,并把它压入栈中,这个栈帧的内存空间被它所属的函数独占,正常情况下是不会和别的函数共享的。

  函数调用大致包括以下几个步骤:

  • (1)参数入栈:将参数从右向左依次压入系统栈。
  • (2)返回地址入栈:将当前代码区调用的下一条指令地址压入栈中,供函数返回时继续执行。
  • (3)代码区跳转:处理器从当前代码区跳到被执行函数入口。
  • (3)栈帧调整:1.保存当前栈帧状态,已被后面恢复本栈帧使用(push ebp)

    2.将当前栈帧切换到新的栈帧(mov ebp,esp)

    3.给新栈帧分配空间(把ESP减去所需空间大小,抬高栈顶)

例如:

  对于_stdcall 调用约定,函数调用时用到的指令序列如下:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

         ;调用前

push 参数3;         ;假设该函数有3个参数,将从右向左依次入栈

push 参数2;

push 参数1;

call  函数地址;      ; 该指令同时完成两件事:(a)向栈中压入当前指令在内存中                            

                                         ;的位置,及保存返回地址

                                         ;(b)跳转到函数地址

push  ebp

mov  ebp,esp

sub   esp,xxx

 

  类似的函数返回步骤:

  • (1)保存返回值,通常将函数返回值保存在寄存器eax中。
  • (2)弹出当前栈帧,恢复上一个栈帧

1

2

3

4

5

add  esp,xxx ; 降低栈顶,回收当前栈帧

pop  ebp      ; 将上一个栈帧底部位置ebp恢复

retn         ; 这个指令有两个作用 (a)弹出当前栈顶元素,即弹出栈帧中的返回地址,

                                 ;至此,栈帧恢复。

                        ;(b)让处理器跳转到弹出的返回地址,恢复调用前的代码

  

  寄存器与函数栈帧

  每一个函数独占自己的栈帧空间。当前运行的函数的栈帧总在栈顶。Win32系统提供两个特殊的寄存器用于标识位于系统的栈顶端的栈帧。

  • (1)ESP:栈指针寄存器,其内存中是一个指针,该指针永远指系统栈最上面的一个栈帧的栈顶。
  • (2)EBP:基址指针寄存器,其内存中是一个指针,该指针永远指向系统栈最上面的要给栈帧的底部。
  • (3)EIP:指令寄存器,其内存中是一个指针,该指针永远指向下一条等待执行的指令地址。

  函数调用约定


调用约定


_cdecl


_fastcall


_stdcall


参数入栈顺序


右→左


右→左


右→左


恢复平衡的位置


调用者


函数本身


函数本身

  修改邻接变量

  通过上面的知识我们知道,函数的调用细节和栈中的数据分布情况,函数的局部变量在栈中一个挨着一个排着,如果这些局部变量中有数组之类的缓冲区,并且程序中存在数

 组越界的缺陷,那么越界的数组元素就有可能破坏栈中相邻变量的值,甚至破坏栈帧中保存的ebp的值、返回地址等重要数据。

  下面举个例子,来说明一下破坏栈内局部变量对程序安全性的影响:

  

 1 #include <IOSTREAM>
 2 using namespace std;
 3 #define  PASS_WORD "1234567"
 4
 5 int verify_password(char* password)
 6 {
 7     int  authentitated;
 8     char buffer[8];
 9     authentitated = strcmp(password,PASS_WORD);
10     strcpy(buffer,password);
11     return authentitated;
12 }
13
14 int main()
15 {
16     int valid_flag = 0;
17     char password[1024] = {0};
18     while (1)
19     {
20         printf("please input password:");
21         scanf("%s",password);
22         valid_flag = verify_password(password);
23         if(valid_flag)
24         {
25             printf("incorrect password!\r\n");
26         }
27         else
28         {
29             printf("Congratulation! you have passed the verification!\r\n");
30         }
31     }
32     return 0;
33 }

  当我们输入的是qqqqqqq,上述代码verify_password栈帧布局:

  由此可知,在verify_password栈帧中,局部变量authenticated,位于缓冲区buffer[8]的下方,authenticated是int型,在内存中占4个字节,所以,如果能让buffer数组越界,就能够影

 响到authenticated。在程序中,authenticated为0表示验证成功,为1表示验证失败,我们通过让buffer数组越界,达到修改authenticated值得目的。

  通过我们输入可以造成缓冲区溢出,导致authenticated的值被修改。所以当我们输入8个字符,第9个字符,作为结尾的NULL字符,将刚好写到authenticated内存的低位上去,导致

 authenticated由 0x00000001 变为0x00000000,验证通过。

  修改函数返回地址

  上述的的修改邻接变量的方法是很有用的,但是这种漏洞利用对代码的环境要求相对苛刻,更强大、更通用的攻击通过缓冲区溢出改写的目标往往不是一个变量,而是瞄准栈帧的

 最下方的EBP和函数返回地址等栈帧状态。

  也就是说,我们继续增加输入的字符串长度,超出buffer[8]边界,一次淹没authenticated、前栈帧EBP、返回地址。也就是说,控制好字符串长度就可以让字符串中相应的位置字符

 的ASCII码覆盖这些栈帧的状态。

  当我们输入一个足够长的字符串是,程序崩溃,这是由于字符串足够的长,淹没了程序的返回地址,我们知道,当我们程序执行完毕之后,在执行retn指令时,栈顶恰好就是源程

 序的返回地址,”retn”指令会把这个地址pop,弹入eip寄存器中,之后跳转到这个地址去执行。

  程序崩溃的原因是因为,函数返回地址装入eip ,但是eip由于缓冲区溢出,淹没了,将值改变,程序执行找不到对应地址的指令,导致程序崩溃。但是,如果我们给出一个有效

 的地址,就可以让处理器跳转到任意的代码去执行,也就是说,我们可以通过淹没返回地址从而控制程序的执行。

  下面举个例子,通过缓冲区溢出,淹没eip,修改eip寄存器,从而控制程序执行:

 1 #include <IOSTREAM>
 2 using namespace std;
 3 #define  PASS_WORD "1234567"
 4 int verify_password(char* password)
 5 {
 6     int  authentitated;
 7     char szBuffer[8];
 8     authentitated = strcmp(password,PASS_WORD);
 9     strcpy(szBuffer,password);
10     return authentitated;
11 }
12 int main()
13 {
14     int valid_flag = 0;
15     char password[1024] = {0};
16     FILE* fp ;
17     fp=fopen("password.txt","rw+");
18
19     if(fp==NULL)
20     {
21         exit(0);
22     }
23
24     fscanf(fp,"%s",password);
25     valid_flag = verify_password(password);
26
27     if(valid_flag)
28     {
29         printf("incorrect password!\r\n");
30     }
31     else
32     {
33         printf("Congratulation! you have passed the verification!\r\n");
34     }
35     fclose(fp);
36
37     getchar();
38     return 0;
39 }

  通过OD分析可得:

  没有淹没时,verify_password栈帧如下:

  当淹没之后,verify_password栈帧如下:

  eip已经被修改,成功。很开心! 

  当我们可以利用栈溢出这一漏洞,修改eip,我们就可以干一些更牛的事情,让进程执行输入的数据的代码。

   下面举个例子,通过我们向password里添加一些机器指令,实现弹MessageBox。

 1 #include <IOSTREAM>
 2 #include <Windows.h>
 3 using namespace std;
 4 #define  PASS_WORD "1234567"
 5 int verify_password(char* password)
 6 {
 7     int  authentitated;
 8     char szBuffer[44];
 9     authentitated = strcmp(password,PASS_WORD);
10     strcpy(szBuffer,password);
11     return authentitated;
12 }
13
14 int main()
15 {
16     int valid_flag = 0;
17     char password[1024] = {0};
18     FILE* fp ;
19     fp=fopen("password.txt","rw+");
20
21     HMODULE h = LoadLibrary("user32.dll");
22     printf("%x\r\n",h);
23     //0x77760000
24     //0x000774C0
25     //0x777D74C0  //MessageBox地址
26     //0x0018FA88  //buffer 的地址
27
28     if(fp==NULL)
29     {
30         exit(0);
31     }
32     fscanf(fp,"%s",password);
33     valid_flag = verify_password(password);
34
35     if(valid_flag)
36     {
37         printf("incorrect password!\r\n");
38     }
39     else
40     {
41         printf("Congratulation! you have passed the verification!\r\n");
42     }
43     fclose(fp);
44     return 0;
45 }

   直接同过buffer中写入代码,这次的例子中,buffer定义的足够大,就是为了能将我们自己弹窗的代码完整的存放在里面,当我们执行拷贝,栈溢出,淹没了栈帧,将返回地址设

 置为buffer的首地址,此时,当函数栈retn之后,到返回地址继续执行,这就实现了我们的目的。

   通过OD分析可得,在没发生拷贝前,没有栈溢出时,verify_password栈帧如下:

   

   在发生拷贝后,产生了栈溢出,verify_password栈帧如下:

   

   在retn之后,eip指向buffer的基地址,进行程序的向下执行:

  

    最终弹出对话框:

  

   本文的实现,主要是通过参考《0day安全_软件漏洞分析技术(第二版)》进行学习,本文中的代码实现如下,点击下载:

     http://files.cnblogs.com/files/Donoy/%E6%A0%88%E6%BA%A2%E5%87%BA%E5%8E%9F%E7%90%86%E4%B8%8E%E5%AE%9E%E7%8E%B0.zip

  

时间: 2024-10-19 01:59:35

【转载】栈溢出原理及实现的相关文章

[转载] Thrift原理简析(JAVA)

转载自http://shift-alt-ctrl.iteye.com/blog/1987416 Apache Thrift是一个跨语言的服务框架,本质上为RPC,同时具有序列化.发序列化机制:当我们开发的service需要开放出去的时候,就会遇到跨语言调用的问题,JAVA语言开发了一个UserService用来提供获取用户信息的服务,如果服务消费端有PHP/Python/C++等,我们不可能为所有的语言都适配出相应的调用方式,有时候我们会很无奈的使用Http来作为访问协议;但是如果服务消费端不能

[转载]NGINX原理分析 之 SLAB分配机制

作者:邹祁峰 邮箱:[email protected] 博客:http://blog.csdn.net/qifengzou 日期:2013.09.15 23:19 转载请注明来自"祁峰"的CSDN博客 1 引言 众所周知,操作系统使用伙伴系统管理内存,不仅会造成大量的内存碎片,同时处理效率也较低下.SLAB是一种内存管理机制,其拥有较高的处理效率,同时也有效的避免内存碎片的产生,其核心思想是预分配.其按照SIZE对内存进行分类管理的,当申请一块大小为SIZE的内存时,分配器就从SIZE

[转载] ConcurrentHashMap原理分析

转载自http://blog.csdn.net/liuzhengkang/article/details/2916620 集合是编程中最常用的数据结构.而谈到并发,几乎总是离不开集合这类高级数据结构的支持.比如两个线程需要同时访问一个中间临界区(Queue),比如常会用缓存作为外部文件的副本(HashMap).这篇文章主要分析jdk1.5的3种并发集合类型(concurrent,copyonright,queue)中的ConcurrentHashMap,让我们从原理上细致的了解它们,能够让我们在

(转载)ConcurrentHashMap 原理

集合是编程中最常用的数据结构.而谈到并发,几乎总是离不开集合这类高级数据结构的支持.比如两个线程需要同时访问一个中间临界区 (Queue),比如常会用缓存作为外部文件的副本(HashMap).这篇文章主要分析jdk1.5的3种并发集合类型 (concurrent,copyonright,queue)中的ConcurrentHashMap,让我们从原理上细致的了解它们,能够让我们在深 度项目开发中获益非浅. 在tiger之前,我们使用得最多的数据结构之一就是HashMap和Hashtable.大家

栈溢出原理与实现

缓冲区溢出 在大缓冲区的数据向小缓冲区复制的过程镇南关,由于没注意小缓冲区的边界,“撑爆”了较小的缓冲区,从而冲掉了和小缓冲区相邻内存区域的其他数据而引起的内存问题. 无论什么计算机架构,进程使用的内存都可以按照功能大致分为4个部分: (1)代码区:这个区域存储着被装入的执行的二进制代码,处理器会到这个区域取指并执行. (2)数据区:用于存储局部变量. (3)堆区:进程可以在堆区中动态的请求一定大小的内存,并在用完之后归还个堆区.动态分配和回收是堆区的特点. (4)栈区:用于动态的存储函数之间的

[转载]步进电机原理介绍与基于STM32的SPWM驱动步进电机,使用软件实现电机细分

文章摘自: http://bbs.eeworld.com.cn/thread-370591-1-1.html 一.混合式步进电机的结构和驱动原理 电机原理这部分不想讲的太复杂了,拆开一台电机看看就明白了.      电机的转子是一个永磁体, 它的上面有若干个磁极SN组成,这些磁极固定的摆放成一定角度.电机的定子是几个串联的线圈构成的磁体. 出线一般是四条线标记为A+,A-,B+,B-.A相与B相是不通的,用万用表很容易区分出来,至于各相的+-出线实际是不用考虑的,任意一相正负对调电机将反转.另外

栈溢出原理与 shellcode 开发

 ESP:该指针永远指向系统栈最上面一个栈帧的栈顶 EBP:该指针永远指向系统栈最上面一个栈帧的底部 01  修改函数返回地址 #include<stdio.h> #include<string.h> #define PASSWORD "1234567" int verify_password (char *password) { int authenticated; char buffer[8]; authenticated=strcmp(password,P

栈溢出原理与实践

0x1 基础知识 CPU:寄存器 内存:物理内存地址.虚拟内存地址 栈:可以看做一种数据结构,先进后出的数据表.有压栈(PUSH).有弹栈(POP).标识栈的属性有两个:栈顶(TOP).栈底(BASE) 可以把栈看做一副扑克,PUSH,往上放一张牌(压入数据),POP,拿走一张牌(弹出数据). 0x2 函数调用在内存中的栈中发生了什么? 编译后的代码在内存中分布是散乱的,当CPU执行到myfun()函数时,会从代码区域跳进myfun函数中执行里面的机器指令区域(我们写的函数中的代码),在那里取值

[转载]ClassLoader原理

JVM规范定义了两种类型的类装载器:启动内装载器(bootstrap)和用户自定义装载器(user-defined class loader).  一.    ClassLoader基本概念1.ClassLoader分类类装载器是用来把类(class)装载进JVM的.JVM规范定义了两种类型的类装载器:启动内装载器(bootstrap)和用户自定义装载器(user-defined class loader). JVM在运行时会产生三个ClassLoader:Bootstrap ClassLoad