1、栈溢出原理
栈溢出属于缓冲区溢出的一种,有时候也称作堆栈溢出。
例如下面的程序:
1 | #include <stdio.h> |
用OD加载运行:
堆栈中的返回地址变成了0x41414141,即“AAAA”,由于在通过strcpy复制字符串到固定长度的堆栈空间时,未对字符串长度进行限制,导致栈溢出,最后覆盖到返回地址。
2、堆溢出原理
代码:
1 | #include <windows.h> |
由于调试堆和常态堆的结构不同,在演示代码中加入getchar函数,用于暂停进程,方便运行heapoverflowexe后用调试器附加进程。debug版本和release版本实际运行的进程中各个内存结构和分配过程也不同,因此测试的时候应该编译成release版本。
运行程序,使用windbg附加调试(一定要附加调试),g运行后程序崩溃
上面的ecx已经被“AAAA”字符串覆盖,导致程序崩溃。
在字符串复制的地址对其下断点。
此时堆块已经分配完成,其对应的分配地址位于0x00b504b0,0x00b504b0是堆块数据的起始地址,并非堆头信息的起始地址。对于已分配的堆块,开头都有8字节的HEAP_ENTRY结构,对于空闲堆块,它的开头是HEAP_FREE_ENTRY结构,因此heap的HEAP_ENTRY结构位于0x00b504b0-8 = 0x00b504a8。
在WinDbg上查看两个堆块的信息,这两个堆块目前处于占用状态,共0x10大小的数据空间。
在WinDbg中使用!heap命令查看HeapCreate创建的整个堆信息,可以发现在堆块heap之后还有个空闲堆块0x00b504c0
1 | 0:000> !heap |
在复制字符串时,原本只有0x10大小的堆,当填充过多的字符串时就会覆盖到下方的空闲堆00b504c0,在复制前00b504c0空闲块的HEAP_FREE_ENTRY结构数据如下:
覆盖后,00b504c0的空闲块的HEAP_FREE_ENTRY结构数据如下:
整个空闲堆头信息都被覆盖了,包括最后的空闲链表中的前后向指针都被成了0x41414141,后面调用HeapFree释放堆块的时候,就会将buf2和后面的空闲堆块0x007104c0合并,修改两个空闲堆块的前后向指针就会引用0x41414141,最后造成崩溃。
3、整数溢出原理
整数分为有符号和无符号的两种类型,有符号数以最高位作为其符号位,不同类型的整数在内存中均有不同的固定取值范围,当我们向其存储的值超过该类型整数的最大值,就会造成整数溢出。
基于栈的整数溢出示例:
1 | #include "stdio.h" |
基于堆的整数溢出示例:
1 | #include "stdio.h" |
4、格式化字符串漏洞
格式化字符串漏洞的产生主要源于对用户输入内容未进行过滤,这些输入数据都是作为参数传递给某些执行格式化操作的函数,如printf、fprintf、vprintf、sprintf等。恶意用户可以使用%s和%x等个数符,从堆栈或其他内存位置输出数据,也可以使用格式符%n向任意地址写入任意数据,配合printf()函数和其他类似功能的函数就可以向任意地址写入被格式化的字节数,可能导致任意代码执行,或从漏洞程序中读取敏感信息,比如密码。
示例:
1 | #include <stdio.h> |
5、双重释放漏洞
双重释放漏洞主要是由对同一块内存进行二次重复释放导致的,利用漏洞可以执行任意代码。
1 | #include <stdio.h> |
6、释放重引用(UAF)漏洞
释放重引用漏洞,顾名思义就是使用已被释放的内存,最终导致内存崩溃或任意代码执行的漏洞。
示例:
1 | #include <stdio.h> |
程序通过分配与buf1大小相等的堆块buf2实现“占坑”,使得buf2分配到已释放的buf1内存位置,但由于buf1指针依然有效,并且指向的内存数据是不可预测的,可能被堆管理器回收,也可能被其他数据占用填充,因此也被称为“悬挂指针”,借助悬挂指针buf1将其赋值为“hack”字符串,进而导致buf2也被篡改为“hack”字符串。
7、数组越界访问漏洞
示例:
1 | #include "stdio.h" |