漏洞类型及攻略方法
全写出来不太可能,图示大概这样:
Stack
以x86环境为例
每个进程在内存区域底部都有一个stack
- 上半部分是在32位机器中运行32位程序
- 下半部分是在64位机器中运行32位程序
它与C语言函数密切相关
1 | int main(void){ |
例如,调用这个函数时stack状态如下:
ESP/EBP不容易理解,这里解释一下
- ESP寄存器始终指向栈顶
- EBP寄存器始终指向栈底
- EBP-0x8能偶访问本地变量2
- EBP-0x4能够访问本地变量1
- EBP+0x8能够访问参数1
- EBP+0xC能够访问参数2
调用原函数也有相同的帧并保存EBP寄存器,当函数被调用时,EBP会以新的帧为基础更新。因此,调用原函数的帧暂时保存到stack中
栈溢出
对策及解决方法
首先来看经典问题
曾经,stack和heap都是有X权限的
BOF:buffer overflow
写入数据超出区域
覆盖写入后续区域
例如这段代码:
int main(void){ int a, b; .. func(a, b); .. return 0; } void func(int a, int b){ char buf1[4]; char buf2[4]; strcpy(buf2,"AAAABBBB"); }
int main(void){ int a; char s[256]; .. read(stdin, s, 256); func(a, s); .. return 0; } void func(int a, char* s){ char buf1[4]; char buf2[4]; strcpy(buf2, s); }1
2
3
4
5
6
7
8
9
10
11
12
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019040108.jpg)
- 如果用户可以输入任意值,会发生什么?
- 如果没有size限制,就可以任意进行覆盖
- 甚至可以指定返回地址
- 例如这段代码:
-push byte 0x3 ; eax = NR_read pop eax push byte 0x4 ; ebx = fd pop ebx push byte 0xff; edx = size pop edx call next next: pop ecx ; ecx = buf(=ret_addr) int 0x80 ; read(fd, buf, size)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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019040109.jpg)
- 添加shellcode
- 如果Evil Return addr指向shellcode
- 那么返回前会执行shellcode并执行shell(/bin/sh)
- 可以执行任意代码
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019040110.jpg)
## 1-猜测地址
- stack地址是动态的,取决于环境
- 如何让它到达shellcode
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019040111.jpg)
- 可以略微转移
- nop-sled
- nop = 0x90(空指令)
- 尽可能多的拼接
- 如果到达nop-sled的某个地方,shellcode将自动执行
- 多次尝试
- brute force
- 如果不知道地址,那么可以尝试所有的可能
- 结合使用nop-sled可以减少尝试次数
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019040112.jpg)
## 2-溢出量的问题
- 考虑不能过多溢出的情况
- nop-sled无法准备好(或者太短了)
- 不能猜测地址
- brute force需要太长时间
- 如果运气好的话,能够执行shellcode
- 实践中很少会成功
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019040201.jpg)
- 使用保存栈地址的ESP比较好
- ret2esp
- 但是,如果代码中没有jmp esp或者call esp就很糟糕
- 实际上很可能找不到
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019040202.jpg)
## 3-溢出量的问题
- 只能够少量溢出的情况
- nop-sled无法构造
- 甚至连填充shellcode都不够
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019040203.jpg)
- 可以使用较短的代码来读取后面更多内容
- Stager
- 通过溢出写入较短的汇编代码
- 先运行这部分代码,读取其他shellcode
- 通过读取的内容来控制程序运行流程
- 具体大概像这样( 只有17个byte):
-stager的优点
- 使用Shellcode时,目的是首先执行Stager
- 如果Stager移动,Shellcode也会移动
- 因为它很短,很容易绕过限制
- 可以轻松更改后续shellcode
对策 - Stack不可执行(NX)
如果stack和heap不能执行就太好了
这就是NX(No eXecute / Never eXecute)
有各种实现,例如NX比特位,PaX,Exec Shield(Redhat)等
- 硬件实现或软件实现
结果都是让stack之类不可执行
考虑绕过NX
- 该怎么做
- (例)创建可写的固定地址,编写shellcode并执行
- 通过mmap或者mprotect获取RWX权限的内存区域
- 在那里写入shellcode并返回
问题点
- 如何调用到mmap/mprotect?
- 通过溢出如何写入到那里
- 溢出是在栈向下写入的攻击
- 不能够反向写入
ret2plt
- 让栈的状态,在函数调用的瞬间是相同的(非常重要的概念)