在学习漏洞的时候,按照0Day2书中第24章第1节的内容进行学习的,这章本来是远程拒绝服务的漏洞(CVE-2009-3103),但是当我在网上搜索这个漏洞的EXP时,意外的发现了Srv2.sys模块中的另一个漏洞(CVE-2009-2532),而这个漏洞竟然可以实现远程任意代码执行,诶,这我就不困了,然后顺手两个漏洞一起分析了,把Srv2.sys模块对数据包的接收处理过程逆向了一遍,了解了其中的漏洞利用原理。
首先在SRV2.sys模块中,SrvReceiveHandler(这个函数会处理发送过来的数据包)
其主要功能是检查数据包长度是否正确:
并且分配一个WorkItem项,并将SrvProcessPacket()地址(数据包处理函数)填入到该项对应位置处,并填写WorkItem项中其他字段内容,用于后续的创建线程函数调用该函数进行数据包处理。
等待系统创建线程后续从队列中取出该WorkItem项时,系统调用SrvProcessPacket函数处理数据包。
这是该函数中唯一一处修改esi值的地方,此处的操作就是将esi - 16。这里根据微软官方文档及其他内核结构体猜测,从WorkItem队列中取出该WorkItem项时是通过双向链表取出来的,而为了指向WorkItem结构体的起始位置需要-16Byte。
接下来就调用SrvProcessPacket处理数据包了:
在SrvReceiveHandler函数中对WorkItem项的结构体进行赋值时:将SrvProcessPacket()函数的地址写入 esi+1Ch 中。
在函数SrvProcWorkerThread()函数创建新的线程时调用 esi+1Ch 处的函数,该位置处正好是SrvProcessPacket函数的地址,即为调用SrvProcessPacket函数。
在SrvProcessPacket函数中会调用Smb2ValidateProviderCallback()这个函数。
这部分是通过动态调试得到的结果。具体是怎么因为什么没有充分的证据链,因为太菜了没有相关的编程经验,所以没办法通过静态调试弄清楚,不过通过静态调试也看到了点眉目。
并且会在处理完数据包后,执行SrvProcComleteRequest()函数进行请求处理完成操作,而在这个函数中实现了任意地址写的功能(通过任意写先写出了跳板指令),并且也实现了任意代码执行的功能,是我们任意代码执行exp中利用的点。
而在Smb2ValidateProviderCallback()函数中,存在一个漏洞,该漏洞可以造成BSOD,实现远程拒绝服务。(CVE-2009-3103):
esi 指向接收到的数据包指针(数据包去除掉 NETBIOS Header)
首先esi指向收到的数据包,esi+0Ch字段为PIDHigh字段,正常情况下PIDHigh字段值应该为0,但是这里没有对eax的值就是PIDHigh字段进行限制,如果我们的PIDHigh字段的值为畸形函数的话,就会造成数组越界,通过查看ValidateRoutines数组值:
(可知如果是正常情况PIDHigh字段为0,那么eax将指向Smb2ValidateNegotiate()函数。)
接下来将进行 call eax操作,就会执行eax指向的函数,如果是正常情况下将执行Smb2ValidateNegotiate()函数,如果eax就会指向一个非法的地址,这就会造成BSOD,进而实现远程拒绝服务攻击。(总感觉这里其实也可以利用来进行任意代码执行)
我们发送的数据包:
buff = b'\x00' #Message Type : Session message
buff += b'\x00' #Flags
buff += b'\x00\x90' #Count of data bytes (netbios header not included)
buff += b'\xff\x53\x4d\x42' #Server Component: SMB Protocol[4]
buff += b'\x72' #Command
buff += b'\x00\x00\x00\x00' #Status
buff += b'\x18' #Flags
buff += b'\x53\xc8' #Flags2
buff += b'\x00\x26' #PIDHigh
#buff += b'\xFF\xFF'
buff += b'\x00\x00\x00\x00\x00\x00\x00\x00' #SecurityFeatures
buff += b'\x00\x00' #Reserved
buff += b'\xff\xff' #TID
buff += b'\xff\xfe' #PIDLow
buff += b'\x00\x00' #UID
buff += b'\x00\x00' #MID
buff += b'\x00' #WordCount
buff += b'\x6d\x00' #ByteCount
buff += b'\x02\x50\x43\x20\x4e\x45\x54' #Data
buff += b'\x57\x4f\x52\x4b\x20\x50\x52\x4f\x47\x52\x41\x4d\x20\x31'
buff += b'\x2e\x30\x00\x02\x4c\x41\x4e\x4d\x41\x4e\x31\x2e\x30\x00'
buff += b'\x02\x57\x69\x6e\x64\x6f\x77\x73\x20\x66\x6f\x72\x20\x57'
buff += b'\x6f\x72\x6b\x67\x72\x6f\x75\x70\x73\x20\x33\x2e\x31\x61'
buff += b'\x00\x02\x4c\x4d\x31\x2e\x32\x58\x30\x30\x32\x00\x02\x4c'
buff += b'\x41\x4e\x4d\x41\x4e\x32\x2e\x31\x00\x02\x4e\x54\x20\x4c'
buff += b'\x4d\x20\x30\x2e\x31\x32\x00\x02\x53\x4d\x42\x20\x32\x2e'
buff += b'\x30\x30\x32\x00'
通过动态调试了解到esi指向的内容为我们发送的数据包:
而在SrvProcCompleteRequest()函数中,存在两个个漏洞,这两个漏洞联合利用可以实现远程任意代码执行攻击。(CVE-2009-2532):
通过精心构造数据包(这部分构造的数据包有的字段并没有必要,我觉得当时作者将所有处理数据包的函数都逆向后写的exp,其中有些流程是不会走到的,所以有些字段是多余的)
(u32)(p+0x3C+4) = READ_ADDR;
(u8)(p+0xCC+4) = 0xCC;
(u32)(p+0xAC+4) = TRAMPOLINE_ADDR + off_pass;
(u32)(p+0x0CE+4) = 0x0;
(u32)(p+0x30+4) = READ_ADDR;
(u32)(p+0x78+4) = READ_ADDR;
(u32)(p+0x168+4) = 0x00000000;
(u8)(p+0xE0+4) = 0x90 | 0x04;
这部分刚好调用DPC例程,实际调用的函数为:SrvScavengerThread,不会对我们的攻击产生影响。(这部分存在一个问题就是为什么SrvScavengerThread函数不会对我们的ShellCode产生影响,没有具体的依据,不过该函数通过函数名可知是清除线程的函数,不会对我们的ShellCode产生影响,这里由于缺少编程经验,所以无法判断该函数的影响)。
首先构造的数据包可以通过流程调用SrvProcPartialCompleteCompoundedRequest()函数,利用该函数可以实现任意地址写的功能。
进入SrvProcPartialCompleteCompoundedRequest()函数中,传递过来的是我们发送的数据包。
到达漏洞利用点。
可以看到其中eax可控,为数据包中头偏移0xAC地址处指向的内容+0xBC(p指向数据包,eax = [p+0xAC] + 0xBC),所以我们可以实现任意地址写功能,通过多次发送数据包实现写任意的功能(每发送一次数据包就会将这个地址处的内容++,*(eax)++),exp中利用这个任意写功能实现向内存中写入跳板指令,供之后的shellcode执行提供跳板。
这也是这个exp的亮点之一,通过这种方式实现向任意地址写的攻击,不是传统的简单的任意地址写的攻击,是一个比较好的漏洞利用思路。
而从数据包传送进SrvProcCompleteRequest()函数到执行完SrvProcPartialCompleteCompoundedRequest()函数,其中会对数据包的某些位置进行判断,所以在数据包的相应字段应该写入对应的数值:
首先在SrvProcCompleteRequest()函数中要执行到SrvProcPartialCompleteCompoundedRequest()函数中间会有如下判断:
2.(u32)(p + 0xAC + 4) = TRAMPOLINE_ADDR + off_pass;
在SrvProcPartialCompleteCompoundedRequest()函数中,要执行完毕中间会有如下判断:
3.(u8)(p + 0xE0 + 4) = 0x90 | 0x04;
而在通过任意地址写功能向目标地址写入跳板指令后,接下来就是通过跳板指令执行真正的攻击代码了。
可以看到其中eax可控,为数据包中头偏移0x168地址处指向的内容(p指向数据包,eax = [p+0x168] ),进而实现了任意代码执行功能。
这里部分值得注意的点就是esi指向我们发送的数据包,也就是我们输入的内容,可以通过esi将控制程序流程转入到我们发送的数据中。
所以exp中之前任意地址写入的数据就是: 0xC35646(inc esi,push esi,ret)(inc esi是因为0xff会干扰ShellCode执行,所以跳过)
这样程序就转入到了我们输入的数据中,实现了任意代码执行。
看exp中的ShellCode也挺有意思的,其先发送一个数据包通过之前设置的跳板指令执行在Srv2.sys模块中实现了一个类似的Hook(在Srv2.sys中开了个后门),之后任何发送过来的数据包都会被执行。
EXP放在了附件中,在Github上搜到的。
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课