操作系统学习笔记(十)---常见内存错误&内存溢出攻击与防御

一、常见内存错误

void *malloc(size_t size);

返回值类型:void * 该类型表明malloc返回的地址空间中的数据类型是不确定,必须经过强制类型转换才可以使用。

返回值:成功时,返回malloc申请的空间的起始地址,失败时,返回NULL。

 

void free(void *ptr);

free与malloc配合使用,释放分配给该指针的内存,关于怎么知道释放多少字节,可能会在一个相近的位置存储1个整数值。

                  

j未初始化而赋值给*pi,所以bar()                         head指针可能为null,未做判断,导致可能

中使用的i是垃圾值(不确定的)。                      无法读到任何东西

genIPR()中malloc分配的内存只有40bytes,ipr-1000不在分配的物理地址的范围,所以i,j是不合法的使用指针的读操作(该物理地址可能未分配物理内存)。

同理,genIPW中的是不合法的指针的写操作,对可能未分配的物理内存进行写是不合法的。

该函数返回的是一个指向栈中数组result的首地址的指针,函数返回时栈就被释放了,该地址对应的物理内存也被释放了,所以在栈外无法使用该指针进行读/写操作。

只分配了10个字节,str[11]=’\0’却对第12个字节赋值(或者说超出范围的字节),越界了。

堆管理错误,main函数中分配的内存和foo函数中malloc函数用的是同一指针,这样在调用foo函数后pi的位置被改变了,原来main函数分配的内存无法被释放,导致内存泄漏。

潜在的内存泄漏,指针位置移动了

错误的堆管理

genFUM中释放了栈的内存

genFUM1中释放了未分配的内存,free(fum+1)应该为free(fum)

genFUM2中对已经释放的内存又free了一次,这是不允许的。

 

二、内存溢出攻击与防御

CALL,RET和LEAVE

call指令的步骤:首先是将返回地址(也就是call指令要执行时EIP的值)压入栈顶,然后是将程序跳转到当前调用的方法的起始地址。执行push和jump指令。

leave指令是将栈指针指向帧指针,然后POP备份的原帧指针到%EBP。

ret指令则是将栈顶的返回地址弹出到EIP,然后按照EIP此时指示的指令地址继续执行程序。

栈溢出攻击原理:

通过Shellcode对buf填充造成缓冲区溢出,且将图中的return addr填充为攻击代码段的起始地址,这样程序执行完ret指令后就会执行Shellcode,即攻击代码段。

比较经典的方法就是将想要执行的指令机器码写到buf数组中,然后改写函数返回地址为buf的起始地址,这样ret命令执行后将会跳转到buf起始地址,开始执行buf数组中的机器码。

三种主要的内存溢出攻击防御方法

cananry:在图中caller’s ebp下方(即buf可能覆盖需要保护的值之前)设置金丝雀值,函数返回之前检查金丝雀值是否被覆盖,金丝雀值的生成是随机的。

DEP:Data Execution Prevention

使用DEP的目的是阻止恶意插入代码的执行,其运行机制是,利用DEP标记只包含数据的内存位置为非可执行(NX),当应用程序试图从标记为NX的内存位置执行代码时,操作系统的DEP逻辑将阻止应用程序这样做,从而达到保护系统防止溢出。(标记shellcode为不可执行的?)

ASLRAddress space layout randomization):地址空间布局随机化

如果程序的堆栈位置是随机的,那么攻击者就无法知道name数组的起始地址,也就无法将main函数的返回地址改写为shellcode中攻击指令的起始地址从而实施他的攻击了。(如让buf数组的起始地址随机化)

内存布局随机化需要操作系统和编译器的密切配合,而全局的随机化是非常难实现的。堆栈位置随机化和动态链接库映射位置随机化的实现的代价比较小,Linux系统一般都是默认开启的。而程序加载位置随机化则要求编译器生成的代码被加载到任意位置都可以正常运行,在Linux系统下,会引起较大的性能开销,因此Linux系统下一般的用户程序都是加载到固定位置运行的。

猜你喜欢

转载自blog.csdn.net/qq_37205708/article/details/86555616
今日推荐