from:xfocus
对不同的windows版本,分为两种漏洞现象
1.windows 2k sp0-sp3,windows xp sp0
2.windows 2k sp4,windows xp sp1
考虑到现在的机器大部分都打上了最新的补丁,只分析第二种情况
[漏洞重现]
配置linux的samba,编辑smb.conf,设置一个如下共享:
[AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA]
comment = Area 51
path = /tmp
public = yes
writable = yes
browseable = yes
在windows下用iexplore.exe或explorer.exe访问这个共享资源时,程序出错。但注意不能用小写字母,否则不能溢出。
[漏洞分析]
shlwapi.dll
001B:70AA33E0 55 PUSH EBP
001B:70AA33E1 8BEC MOV EBP,ESP
001B:70AA33E3 81EC08020000 SUB ESP,00000208 -->堆栈只分配了0x208个字节
001B:70AA33E9 53 PUSH EBX
001B:70AA33EA 56 PUSH ESI
001B:70AA33EB 8B7510 MOV ESI,[EBP+10]
001B:70AA33EE F7DE NEG ESI
001B:70AA33F0 1BF6 SBB ESI,ESI
001B:70AA33F2 B800010000 MOV EAX,00000100
001B:70AA33F7 23F0 AND ESI,EAX
001B:70AA33F9 03F0 ADD ESI,EAX
001B:70AA33FB 833D00B6AC7000 CMP DWORD PTR [70ACB600],00
001B:70AA3402 57 PUSH EDI
001B:70AA3403 7506 JNZ 70AA340B
001B:70AA3405 81CE00000001 OR ESI,01000000
001B:70AA340B 8B7D0C MOV EDI,[EBP+0C]
001B:70AA340E 85FF TEST EDI,EDI
001B:70AA3410 8B5D08 MOV EBX,[EBP+08]
001B:70AA3413 7509 JNZ 70AA341E
001B:70AA3415 53 PUSH EBX
001B:70AA3416 FF153C14A770 CALL [KERNEL32!lstrlenW] -->得到共享名字符串(UNICODE编码)的长度
001B:70AA341C 8BF8 MOV EDI,EAX
001B:70AA341E 85DB TEST EBX,EBX
001B:70AA3420 7425 JZ 70AA3447
001B:70AA3422 53 PUSH EBX
001B:70AA3423 8D85F8FDFFFF LEA EAX,[EBP-0208]
001B:70AA3429 50 PUSH EAX
001B:70AA342A E8360BFFFF CALL SHLWAPI!StrCpyW -->拷贝字符串之前未作长度检查造成溢出
001B:70AA342F 57 PUSH EDI
001B:70AA3430 53 PUSH EBX
001B:70AA3431 57 PUSH EDI
001B:70AA3432 8D85F8FDFFFF LEA EAX,[EBP-0208]
001B:70AA3438 50 PUSH EAX
001B:70AA3439 56 PUSH ESI
001B:70AA343A 6800080000 PUSH 00000800
001B:70AA343F FF154C13A770 CALL [KERNEL32!LCMapStringW]
001B:70AA3445 EB02 JMP 70AA3449
001B:70AA3447 33C0 XOR EAX,EAX
001B:70AA3449 5F POP EDI
001B:70AA344A 5E POP ESI
001B:70AA344B 5B POP EBX
001B:70AA344C C9 LEAVE
001B:70AA344D C20C00 RET 000C
可见是个典型的栈溢出。
该代码是函数PathMakePrettyW中的一段,
Converts a path to all lowercase characters to give the path a consistent appearance.
BOOL PathMakePretty(
LPTSTR lpPath
);
PathMakePretty函数的作用是将一个全大写路径名转换成全小写路径名,当存在小写字母时,不作任何转换,所以就不会触发问题代码。
因为LINUX发送共享名时会将其编码,有语言版本问题,为了通用,我在IP层直接模拟共享资源服务器对请求做响应。
[模拟RPC调用]
这是最麻烦的一步,因为一次访问共享资源动作,将产生数次RPC调用和几十个数据包,要在程序里模拟一个虚拟IP和被攻击者完成共享资源访问,是相当麻烦的。而且手上也没有关于微软RPC调用数据包的格式说明,只能抓包分析。通过用Ethereal工具分析,整理出一次共享资源枚举的大致过程如下:
139端口和445端口的区别不再讨论,就是NETBIOS协议和SMB协议的区别。我们直接从Negotiate协商开始
A枚举B的共享资源
组合1
A --Negotiate Protocol Request -> B //协商请求(A支持的身份认证方式)
A <-Negotiate Protocol Response-- B //协商响应(B选择其中一种,和另外一些安全要求,如是否使用扩展安全协商,这在SMB中间人攻击或取得NTLM认证散列时非常有用)
A --Session Setup Andx Request -> B //认证请求,包括A的用户名和密码
A <-Session Setup Andx Response-- B //认证响应,登录是否成功
这个组合主要是为了完成身份认证,SMB Header中有个四比较重要的数据段
Tree ID、Process ID、User ID和Multiplex ID,在一个请求/回应组中,这四个数据段的数据必须一致。这四个数据段也存在于之后的数据包中
组合2
A --NT Create Andx Request -> B //请求调用某个接口,如SRVSVC、WKSSVC、SPOOLS和WINREG等
A <-NT Create Andx Response-- B //调用响应,这里会返回一个FID,这个ID就是这次调用的handle
A --Bind:call_id UUID:SRVSVC->B //DCE RPC调用请求,其中有个Packet type指明了调用的类型(bind、request等)
A <- Write Andx Response -- B //DCE RPC调用响应
A -- Read Andx Response -> B //DCE RPC调用请求数据,包括数据长度
A <- Bind_ack --> B //DCE RPC调用响应,返回一些数据,包括最大接收长度
A -- SRVSVC NetServerGetInfo Request -> B //SRVSVC接口中的NetServerGetInfo调用请求,取得服务器的信息
A <- SRVSVC NetServerGetInfo Response-- B //SRVSVC接口中的NetServerGetInfo调用响应,返回服务器的信息,内容十分丰富
A --Close Request --> B //关闭调用请求,包含当前调用的FID
A <-Close Request -- B //关闭调用响应
这个组合主要是为了完成一次RPC服务调用,调用请求分为Bind和Request,先bind后request再response,类似于网络套接字里的bind、send和recv。微软提供的RPC服务种类很多,这里相关的主要包括SRVSVC、WKSSVC、SPOOLS和WINREG服务等,每个服务又分多个类型,如NetServerGetInfo、WKS_QUERY_INFO、NetShareEnum、OPENHKLM等等,相当于服务的一个函数,每次组合之后A得到该函数返回的数据。
在若干次组合2之后,A终于请求调用SRVSVC里的NetShareEnum函数来取得共享资源列表,此时就可以把包含精心构造的shellcode数据包给B了。但这时还没有完,之后还有若干次组合2的调用,主要是查看打印机、尝试连接注册表等等操作,最后所有的操作都完成之后,B将执行前面的shlwapi.dll里的问题代码,溢出产生。
可见模拟一次访问共享资源动作是需要模拟数十次RPC调用的,每个服务的数据结构都不相同。要模拟这个过程,可以去详细分析每种服务的数据结构,但这十分麻烦,我采用了折衷的方法,将关键的服务如SRVSVC、WKSSVC、SPOOLS和WINREG等服务的不同函数的响应包抓下来,A发什么函数的请求包,我就响应什么函数的响应包。只要前面提到的四个数据段保持一致,连接是可以成功的,甚至每次返回的FID不变都没关系。这样就完全可以冒充一台不存在的主机和被攻击者完成资源访问了。
[ShellCode]
共享名存在于SRVSVC服务的NetShareEnum函数返回数据中。
如果提供的共享名如下
AAAAAA....RRSSSS...SSSS
RR位置将是返回地址的位置(2个字节经过UNICODE编码成4个字节),esp指向SSSSS...
返回地址采用jmp esp(ff e4)或call esp(ff d4)或push esp,ret(54 c3),在iexplore.exe里可以找到通用跳转地址,但考虑到其他采用ie核心的浏览器不会存在iexplore.exe进程,我们还是从7ffaxxxx地址之后的地方找同一语言版本的通用地址。事前可以先探测对方的语言版本,如http的get请求中就包含了语言类型。
这里没有覆盖异常,不需要搜索内存寻找ShellCode,由于是直接发送包含UNICODE字符的数据包,所以没有语言编码问题,比较简单。
windows 2k sp0-sp3,windows xp sp0版本的溢出是在ntlanman.dll中NTAddConnection函数中产生的,也是一次字符串拷贝造成的,这里可能要用覆盖异常的方法利用,没有仔细研究。