二进制各种漏洞原理

1、栈溢出原理

栈溢出属于缓冲区溢出的一种,有时候也称作堆栈溢出。
例如下面的程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
#include <string.h>

int main()
{
char *str = "AAAAAAAAAAAAAAAAAAAAAAAA";
vulnfun(str);
return;
}

int vulnfun(char *str)
{
char stack[10];
strcpy(stack,str); // 这里造成溢出!
}

用OD加载运行:
60ZrOe.png
堆栈中的返回地址变成了0x41414141,即“AAAA”,由于在通过strcpy复制字符串到固定长度的堆栈空间时,未对字符串长度进行限制,导致栈溢出,最后覆盖到返回地址。

2、堆溢出原理

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <windows.h>
#include <stdio.h>

int main ( )
{
HANDLE hHeap;
char *heap;
char str[] = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";

hHeap = HeapCreate(HEAP_GENERATE_EXCEPTIONS, 0x1000, 0xffff);
getchar(); // 用于暂停程序,便于调试器加载

heap = HeapAlloc(hHeap, 0, 0x10);
printf("heap addr:0x%08x\n",heap);

strcpy(heap,str); // 导致堆溢出
HeapFree(hHeap, 0, heap); // 触发崩溃

HeapDestroy(hHeap);
return 0;
}

由于调试堆和常态堆的结构不同,在演示代码中加入getchar函数,用于暂停进程,方便运行heapoverflowexe后用调试器附加进程。debug版本和release版本实际运行的进程中各个内存结构和分配过程也不同,因此测试的时候应该编译成release版本。
运行程序,使用windbg附加调试(一定要附加调试),g运行后程序崩溃
60NYvQ.png
上面的ecx已经被“AAAA”字符串覆盖,导致程序崩溃。
在字符串复制的地址对其下断点。
60UzwR.png
此时堆块已经分配完成,其对应的分配地址位于0x00b504b0,0x00b504b0是堆块数据的起始地址,并非堆头信息的起始地址。对于已分配的堆块,开头都有8字节的HEAP_ENTRY结构,对于空闲堆块,它的开头是HEAP_FREE_ENTRY结构,因此heap的HEAP_ENTRY结构位于0x00b504b0-8 = 0x00b504a8。
在WinDbg上查看两个堆块的信息,这两个堆块目前处于占用状态,共0x10大小的数据空间。
60diEn.png
在WinDbg中使用!heap命令查看HeapCreate创建的整个堆信息,可以发现在堆块heap之后还有个空闲堆块0x00b504c0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
0:000> !heap
Heap Address NT/Segment Heap

760000 NT Heap
9d0000 NT Heap
b50000 NT Heap
0:000> !heap -a 00b50000
Index Address Name Debugging options enabled
3: 00b50000
Segment at 00b50000 to 00b60000 (00001000 bytes committed)
Flags: 00001004
ForceFlags: 00000004
Granularity: 8 bytes
Segment Reserve: 00100000
Segment Commit: 00002000
DeCommit Block Thres: 00000200
DeCommit Total Thres: 00002000
Total Free Size: 00000164
Max. Allocation Size: 7ffdefff
Lock Variable at: 00b50258
Next TagIndex: 0000
Maximum TagIndex: 0000
Tag Entries: 00000000
PsuedoTag Entries: 00000000
Virtual Alloc List: 00b5009c
Uncommitted ranges: 00b5008c
00b51000: 0000f000 (61440 bytes)
FreeList[ 00 ] at 00b500c0: 00b504c8 . 00b504c8
00b504c0: 00018 . 00b20 [100] - free

Segment00 at 00b50000:
Flags: 00000000
Base: 00b50000
First Entry: 00b504a8
Last Entry: 00b60000
Total Pages: 00000010
Total UnCommit: 0000000f
Largest UnCommit:00000000
UnCommitted Ranges: (1)

Heap entries for Segment00 in Heap 00b50000
address: psize . size flags state (requested size)
00b50000: 00000 . 004a8 [101] - busy (4a7)
00b504a8: 004a8 . 00018 [101] - busy (10)
00b504c0: 00018 . 00b20 [100]
00b50fe0: 00b20 . 00020 [111] - busy (1d)
00b51000: 0000f000 - uncommitted bytes.

在复制字符串时,原本只有0x10大小的堆,当填充过多的字符串时就会覆盖到下方的空闲堆00b504c0,在复制前00b504c0空闲块的HEAP_FREE_ENTRY结构数据如下:
60dzPx.png
覆盖后,00b504c0的空闲块的HEAP_FREE_ENTRY结构数据如下:
60wlLQ.png
整个空闲堆头信息都被覆盖了,包括最后的空闲链表中的前后向指针都被成了0x41414141,后面调用HeapFree释放堆块的时候,就会将buf2和后面的空闲堆块0x007104c0合并,修改两个空闲堆块的前后向指针就会引用0x41414141,最后造成崩溃。
602cHf.png

3、整数溢出原理

整数分为有符号和无符号的两种类型,有符号数以最高位作为其符号位,不同类型的整数在内存中均有不同的固定取值范围,当我们向其存储的值超过该类型整数的最大值,就会造成整数溢出。
基于栈的整数溢出示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include "stdio.h"
#include "string.h"

int main(int argc, char *argv){

int i;
char buf[8]; // 栈缓冲区
unsigned short int size; // 无符号短整数取值范围:0 ~ 65535
char overflow[65550];

memset(overflow,65,sizeof(overflow)); // 填充为“A”字符

printf("请输入数值:\n");
scanf("%d",&i);

size = i;
printf("size:%d\n",size); // 输出系统识别出来的size数值
printf("i:%d\n",i); // 输出系统识别出来的i数据

if (size > 8)
return -1;
memcpy(buf,overflow,i); // 栈溢出

return 0;

}

基于堆的整数溢出示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include "stdio.h"
#include "windows.h"


int main(int argc, char * argv)
{
int* heap;
unsigned short int size;
char *pheap1, *pheap2;
HANDLE hHeap;

printf("ÊäÈësizeÊýÖµ£º\n");
scanf("%d",&size);

hHeap = HeapCreate(HEAP_GENERATE_EXCEPTIONS, 0x100, 0xfff);

if (size <= 0x50)
{
size -= 5;
printf("size£º%d\n",size);
pheap1 = HeapAlloc(hHeap, 0, size);
pheap2 = HeapAlloc(hHeap, 0, 0x50);
}

HeapFree(hHeap, 0, pheap1);
HeapFree(hHeap, 0, pheap2);

return 0;
}

4、格式化字符串漏洞

格式化字符串漏洞的产生主要源于对用户输入内容未进行过滤,这些输入数据都是作为参数传递给某些执行格式化操作的函数,如printf、fprintf、vprintf、sprintf等。恶意用户可以使用%s和%x等个数符,从堆栈或其他内存位置输出数据,也可以使用格式符%n向任意地址写入任意数据,配合printf()函数和其他类似功能的函数就可以向任意地址写入被格式化的字节数,可能导致任意代码执行,或从漏洞程序中读取敏感信息,比如密码。
示例:

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
#include <string.h>

int main (int argc, char *argv[])
{
char buff[1024]; // 设置栈空间

strncpy(buff,argv[1],sizeof(buff)-1);
printf(buff); //触发漏洞

return 0;
}

60zd39.png

5、双重释放漏洞

双重释放漏洞主要是由对同一块内存进行二次重复释放导致的,利用漏洞可以执行任意代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <stdio.h>
#include "windows.h"

int main (int argc, char *argv[])
{
void *p1,*p2,*p3;

p1 = malloc(100);
printf("Alloc p1£º%p\n",p1);
p2 = malloc(100);
printf("Alloc p2£º%p\n",p2);
p3 = malloc(100);
printf("Alloc p3£º%p\n",p3);

printf("Free p2\n");
free(p2);
printf("Double Free p2\n");
free(p2);
printf("Free p1\n");
free(p1);
printf("Free p3\n");
free(p3);

return 0;
}

编译执行后,在多次二次释放p2堆块后,程序崩溃。
6BSOIO.png

6、释放重引用(UAF)漏洞

释放重引用漏洞,顾名思义就是使用已被释放的内存,最终导致内存崩溃或任意代码执行的漏洞。
示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <stdio.h>

#define size 32

int main(int argc, char **argv) {

char *buf1;
char *buf2;

buf1 = (char *) malloc(size);
printf("buf1:0x%p\n", buf1);
free(buf1);

// 分配 buf2 去“占坑”buf1 的内存位置
buf2 = (char *) malloc(size);
printf("buf2:0x%p\n\n", buf2);

// 对buf2进行内存清零
memset(buf2, 0, size);
printf("buf2:%d\n", *buf2);

// 重引用已释放的buf1指针,但却导致buf2值被篡改
printf("==== Use After Free ===\n");
strncpy(buf1, "hack", 5);
printf("buf2:%s\n\n", buf2);

free(buf2);
}

6Bijv4.png
程序通过分配与buf1大小相等的堆块buf2实现“占坑”,使得buf2分配到已释放的buf1内存位置,但由于buf1指针依然有效,并且指向的内存数据是不可预测的,可能被堆管理器回收,也可能被其他数据占用填充,因此也被称为“悬挂指针”,借助悬挂指针buf1将其赋值为“hack”字符串,进而导致buf2也被篡改为“hack”字符串。

7、数组越界访问漏洞

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include "stdio.h"

int main(){
int index;
int array[3] = {0x11, 0x22, 0x33};

printf("输入数组索引下标:");
scanf("%d", &index);

printf("输出数组元素:array[%d] = 0x%x\n", index, array[index]);

return 0;
}

6BAC9K.png