进程地址空间的分配如下图:
0xFFFFFFFF +-----------------+
+---Kernel Space--+ Kernel
0xC0000000 +-----------------+
+-----Stack-------+
+-----------------+
+-Memory Mapping--+ printf
0x40000000 +-----------------+
+-----Heap--------+ memory/free
+-----BSS---------+
+-----Data--------+ Load from elf
0x08048000 +-----Text--------+
+-----------------+
如果进程是多线程的,则每个线程都会在用户的栈区开辟一个自己的栈。
了解的上述分布图,知道每个变量、每块内存在系统中的布局,很容易区分一个地址是否为有效地址,
一个变量或者内存的数据被破坏了可以大致判断出是那个变量或者内存使用越界了。
栈
- ESP寄存器始终指向栈的顶部
- EBP寄存器指向函数的一个活动记录成为帧指针
- C语言函数调用入栈的顺序是从右向左
- 如果函数返回一个大的结构体变量,调用该函数的函数要在栈中开辟一个同样大小的空间,然后把该空间的地址作为一个隐式参数传递给该函数,该函数将需要返回的内容拷贝到该地址,然后通过EAX寄存器返回该地址
下图是一个很常见的活动记录:
+--------------------+-------
| Params |
|--------------------|
| Return addr |
ebp-> |--------------------|
| Old ebp |
|--------------------| 活动记录
| Registers |
|--------------------|
| Locals |
|--------------------|
| other data |
esp-> +--------------------+-------
总结栈的基本模型如下:
+------------+------------+
| Param N | High Addr |
+------------+------------+
| Param ... | | 参数入栈的顺序与具体的调用方式有关
+------------+------------+
| Param 1 | |
+------------+------------+
| EIP | | 返回本次调用后,下一条指令的地址
+------------+------------+
| EBP | | 保存调用者的EBP,然后EBP指向此时的栈顶
+------------+------------+
| Local 1 | |
+------------+------------+
| Local ... | |
+------------+------------+
| Local N | Low Addr |
+------------+------------+
堆
malloc
分配小块内存时是在小于0x40000000
的内存中分配的,通过brk/sbrk不断向上扩展- 分配大块内存是是通过
mmap
分配在大于0x40000000
的文件映射区 malloc
分配的内存前面存放该内存的大小,后面是空闲内存块(可能会被malloc
调用分配出去)
内存越界的几种情况
栈溢出
现象
- 某些全局变量被修改
- 某些任务不能正常工作函数调用不正常
- 某些局部变量被修改
原因
- 线程堆栈开辟的太小
- 定义的太大的局部变量
- 函数调用太深
堆栈内部越界
现象
- 某些局部变量被修改
- 函数返回的时候死机
原因
- 临时变量或者数组越界
全局变量或者动态分配的内存越界
现象
- 全局变量被修改
- 内存泄漏(如果动态分配的内存越界,有可能导致被越界的内存无法释放或者不能全部释放)
原因
- 全局或者动态分配的内存越界
一些容易引起内存越界的操作
- 注意
strcpy
sprintf
memcpy
函数目的缓冲区的大小 strncpy
strcpy
目的缓冲区的大小及源缓冲区是否以\0
结尾- 还要注意数组的大小、循环的次数
- 链表的头部和尾部在处理插入和删除节点时的操作
Tips
全局变量被越界
可以用 readelf
工具读出可执行文件的符号表,看下该全局变量前面的变量是哪个,然后看下相关代码是否有越界的情况