一、linux的内存布局
1、32位模式下内存的经典布局
图1
32位模式下内存经典布局
注:这种内存布局模式是linux内核2.6.7以前的默认内存布局形式
说明:(1)在32的机器上,loader将可执行文件的各个段次依次载入到从0x80048000(128M)位置开始的空间中。程序能够访问的最后地址是0xbfffffff(3G)的位置,3G以上的位置是给内核使用的,应用程序不能直接访问。
(2)内存布局从低地址到高地址依次为:txet段、data段、bss段、heap、mmap映射区、stack堆栈区。
(3)heap和mmap是相对增长的,也就意味着heap只有1G的虚拟地址空间可供使用。
(4)stack区域不需要映射的,用户可以直接访问该区域。这也是利用堆栈溢出进行攻击的基础。
2、32位模式下内存的默认布局
图2
32位模式下内存的默认布局
注:这种内存布局是linux内核2.6.7之后32位机器的默认内存布局方式。
说明:(1)这种内存布局方式加入了几个Random
offset的随机偏移,这样的话内存溢出的攻击就不会那么容易了。
(2)
栈是自顶向下扩展的,但是栈是有边界的栈大小就有了限制(linux小t乃下可以使用ulimit
-s
命令进行查看其大小)。堆是自底向上扩展的,mmap映射区自顶向下扩展。故mmap和heap是相对扩展的,直至消耗尽虚拟地址空间中的剩余区域,这种结构便于C运行库使用mmap映射区和堆进行内存分配。
3、64位模式下内存的默认布局
图3
64位模式下内存的默认布局
说明:这种内存布局方式沿用的32位模式下内存的经典布局,但是栈和mmap的映射区域不再是从一个固定的地方开始,每次启动时的值都不一样。这样一来,使得使用缓冲区溢出攻击变得更加困难。
二、操作系统内存分配的相关函数
1、总括
heap和mmap映射区域是可以提供给用户程序使用的虚拟内存空间,获得该区域的内存的操作有:
(1)对于heap操作,操作系统提供了brk()系统调用,c运行库提供了sbrk()库函数。
(2)对于mmap映射区的操作,操作系统提供了mmap()和munm()系统调用。
(3)linux内存管理的基本思想:内存延迟分配。即只有在真正访问一个地址的时候才建立这个地址的物理映射。linux内核在用户申请内存的时候,只是给它分配了一个虚拟地址,并没有分配实际的物理地址,只有当用户使用这块内存的时候,内核才会分配具体的物理地址给用户使用。
2、heap操作的相关函数
(1)brk()是系统调用、sbrk()是库函数。c语言的动态内存分配基本函数是malloc(),在linux上的实现是:malloc()函数调用库函数sbrk(),sbrk()的实质是调用brk()函数。brk()是一个简单的系统调用,只是简单的改变mm_struct结构体的成员变量brkd的值。
(2)函数原型:
#include
int brk(void *
addr);
void *
sbrk(intptr_t increment);
//当参数increment为0时,sbrk()返回的是进程当前的brk值,increment为正数时扩展brk值,当increment为负数时收缩brk值
(3)mm_struct结构中的成员变量
start_code
和 end_code 是进程代码段的起始和结束地址、
start_data
和 end_data 是进程数据段的起始和终止地址。
start_stack 是进程堆栈段的起始地址。
start_brk
是进程动态内存分配的起始地址(堆的起始地址)。
brk 是进程动态内存分配当前的终止地址(堆的当前最后地址)。
3、Mmap映射区域操作的相关函数
(1)mmap()函数将一个文件或者其他对象映射进内存。文件被映射到多个页上,如果文件大小不是所有页大小之和,最后一个页不被使用的空间将会清零。munmap执行相反的操作,删除特定地址区域的对象映射。
(2)函数原型
#include
void * mmap(void * addr,size_t length,int
prot,int flags,int fd,off_t offset)
int munmap(void *addr,size_t
length);
参数:start — 映射区的开始地址。
length —
映射区的长度。
prot —
期望的内存保护标志。
flags —
指定映射对象的类型,映射选项和映射页是否可以共享。
fd — 有效的文件描述符。
offset —
被映射对象内容的起点。