|
1 | 1 | 内存管理是C++最令人切齿痛恨的问题,也是C++最有争议的问题,C++高手从中获得了更好的性能,更大的自由,C++菜鸟的收获则是一遍一遍的检查代码和对C++的痛恨,但内存管理在C++中无处不在,内存泄漏几乎在每个C++程序中都会发生。 |
2 | 2 |
|
3 | | -在C++中,程序占用的内存分成5个区: |
| 3 | +在C/C++中,进程地址空间分成5个区: |
4 | 4 |
|
5 | | -1. 正文(text)段:正文段是用来存放可执行文件的操作指令,也就是说是它是可执行程序在内存中的镜像。代码段需要防止在运行时被非法修改,所以只准许读取操作,而不允许写入(修改)操作——它是不可写的。 |
| 5 | +1. 正文(text)段:正文段是用来存放可执行文件的操作指令,也就是说是它是可执行程序在内存中的镜像。代码段需要防止在运行时被非法修改,所以只准许读取操作,而不允许写入口处(修改)操作——它是不可写的。 |
6 | 6 | 2. `DATA段(数据段)`:初始化数据段包含程序中明确地赋初值的变量,例如初始化后的全局变量和静态局部变量。 |
7 | 7 | 3. `BSS段(未初始化数据段)`:BSS段包含了程序中未初始化的全局变量,程序开始执行前,内核将此段中的数据初始化为0或者空指针。 |
8 | 8 | 4. 堆(heap):堆是用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减。当进程调用malloc等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张);当利用free等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减) |
@@ -100,11 +100,60 @@ malloc 的一个具体使用例子在 [gist](https://gist.github.com/xuelangZF/5 |
100 | 100 | * 忘记了释放内存,造成内存泄露。动态内存的申请与释放必须配对,程序中malloc与free的使用次数一定要相同,否则肯定有错误(new/delete)。 |
101 | 101 | * 释放了内存却继续使用它。 |
102 | 102 |
|
| 103 | +# 缓冲区溢出 |
| 104 | + |
| 105 | +缓冲区是一块可读写的连续的计算机内存区域,高级语言定义的变量、数组、结构体等在运行时可以说都是保存在缓冲区内的。除了代码段和受操作系统保护的数据区域,其他的内存区域都可以作为`缓冲区`,因此缓冲区溢出的位置可能在.Data 和 .BSS段,也可能在堆、栈段。 |
| 106 | + |
| 107 | +* .Data段和.BSS段存储了用户程序的全局变量,静态变量等; |
| 108 | +* 栈空间存储了用户程序的`函数栈帧`(包括参数、局部数据等),用来实现函数调用机制。 |
| 109 | +* 堆空间存储了程序运行时动态申请的内存数据等。 |
| 110 | + |
| 111 | +在C/C++语言中,通常使用`字符数组`和`malloc/new`内存分配函数来分配缓冲区。使用这些缓冲区时,理想的情况是程序检查数据长度,不允许输入超过缓冲区长度的字符。但是绝大多数程序并不会保证数据长度总是与所分配的缓冲区空间相匹配,这就会导致`缓冲区溢出`问题。 |
| 112 | + |
| 113 | +## 函数栈帧 |
| 114 | + |
| 115 | +栈的主要功能是实现函数的调用,在介绍栈溢出原理之前,需要弄清函数调用时栈空间发生了怎样的变化。每次函数调用时,系统会把函数的返回地址(函数调用指令后紧跟指令的地址),一些关键的寄存器值保存在栈内,函数的实际参数和局部变量(包括数据、结构体、对象等)也会保存在栈内。这些数据统称为`函数调用的栈帧`,而且每次函数调用都会有个独立的栈帧,这也为递归函数的实现提供了可能。 |
| 116 | + |
| 117 | +![][2] |
| 118 | + |
| 119 | +如图所示,定义了一个简单的函数function,它接受一个整形参数,做一次乘法操作并返回。当调用function(0)时,arg参数记录了值0入栈,并将call function指令下一条指令的地址0x00bd16f0保存到栈内,然后跳转到function函数内部执行。每个函数定义都会有函数头和函数尾代码,如图绿框表示。因为函数内需要用ebp寄存器保存函数栈帧基址,因此先保存ebp原来的值到栈内,然后将栈指针esp内容保存到ebp。函数返回前需要做相反的操作——将esp指针恢复,并弹出ebp。 |
| 120 | + |
| 121 | +之所以会有缓冲区溢出的可能,主要是因为栈空间内保存了函数的返回地址。该地址保存了函数调用结束后后续执行的指令的位置,对于计算机安全来说,该信息是很敏感的。如果有人恶意修改了这个返回地址,并使该返回地址指向了一个新的代码位置,程序便能从其它位置继续执行。也就是说攻击者可以利用缓冲区溢出来窜改进程运行时栈,从而改变程序正常流向,轻则导致程序崩溃,重则系统特权被窃取。 |
| 122 | + |
| 123 | +## 溢出原理 |
| 124 | + |
| 125 | +从根本上讲,在程序将数据读入或复制到缓冲区中的任何时候,它需要在复制之前检查是否有足够的空间。遗憾的是,C 和 C++ 附带的大量危险函数(或普遍使用的库)无法做到这点。程序对这些函数的任何使用都是一个警告信号,因为除非慎重地使用它们,否则它们就会成为程序缺陷。 |
| 126 | + |
| 127 | +比如在使用不安全的strcpy库函数时,系统会盲目地将data的全部数据拷贝到buffer指向的内存区域。buffer的长度是有限的,一旦data的数据长度超过BUF_LEN,便会产生缓冲区溢出。如下图所示: |
| 128 | + |
| 129 | +![溢出示例][3] |
| 130 | + |
| 131 | +由于栈是低地址方向增长的,因此局部数组buffer的指针在缓冲区的下方。当把data的数据拷贝到buffer内时,超过缓冲区区域的高地址部分数据会“淹没”原本的其他栈帧数据,根据淹没数据的内容不同,可能会有产生以下情况: |
| 132 | + |
| 133 | +1. 淹没了其他的局部变量。如果被淹没的局部变量是条件变量,那么可能会改变函数原本的执行流程。这种方式可以用于破解简单的软件验证。 |
| 134 | +2. 淹没了ebp的值。修改了函数执行结束后要恢复的栈指针,将会导致栈帧失去平衡。 |
| 135 | +3. 淹没了返回地址。这是栈溢出原理的核心所在,通过淹没的方式修改函数的返回地址,使程序代码执行“意外”的流程! |
| 136 | +4. 淹没参数变量。修改函数的参数变量也可能改变当前函数的执行结果和流程。 |
| 137 | +5. 淹没上级函数的栈帧,情况与上述4点类似,只不过影响的是上级函数的执行。当然这里的前提是保证函数能正常返回,即函数地址不能被随意修改。 |
| 138 | + |
| 139 | +如果在data本身的数据内就保存了一系列的指令的二进制代码,一旦栈溢出修改了函数的返回地址,并将该地址指向这段二进制代码的真实位置,那么就完成了基本的溢出攻击行为。 |
| 140 | + |
| 141 | +# 内存泄漏 |
| 142 | + |
| 143 | + |
| 144 | + |
| 145 | + |
| 146 | + |
103 | 147 | # 更多阅读 |
104 | | - |
| 148 | + |
105 | 149 | [细说new与malloc的10点区别](http://www.cnblogs.com/QG-whz/p/5140930.html) |
106 | 150 | [Where are static variables stored (in C/C++)?](http://stackoverflow.com/questions/93039/where-are-static-variables-stored-in-c-c) |
107 | 151 | [Memory management in C: The heap and the stack](http://www.inf.udec.cl/~leo/teoX.pdf) |
| 152 | +[Doc: Valgrind](http://valgrind.org/docs/manual/quick-start.html#quick-start.intro) |
| 153 | +[缓冲区溢出详解](http://www.cnblogs.com/clover-toeic/p/3737011.html) |
| 154 | +[缓冲区溢出攻击](http://www.cnblogs.com/fanzhidongyzby/p/3250405.html) |
108 | 155 |
|
109 | | -[1]: http://7xrlu9.com1.z0.glb.clouddn.com/C++_Memory_1.jpg |
| 156 | +[1]: http://7xrlu9.com1.z0.glb.clouddn.com/C++_Memory_1.jpg |
| 157 | +[2]: http://7xrlu9.com1.z0.glb.clouddn.com/C++_Memory_2.jpg |
| 158 | +[3]: http://7xrlu9.com1.z0.glb.clouddn.com/C++_Memory_3.jpg |
110 | 159 |
|
0 commit comments