前情回顾
之前CVE-2019-0708补丁刚出来一天吧,就去分析补丁,进行了补丁对比,当时的分析把大家都给带偏了
CVE-2019-0708 微软远程桌面服务远程代码执行漏洞分析之补丁分析
现在来看,感觉自己当时是比较蠢了,既然只是针对MS_T120这个名字的Channel的_IcaBindChannel
的第3个参数设置为31,而其他Channel没有写死,那么极大概率不是31这个参数可控不可控的问题了
当然后来也知道了31其实是Channel ID,而且这个是一个UAF漏洞,当然也可以说是Double Free(其实Double Free是UAF的特殊情况,因为这个USE是free而已)
漏洞简述
因为MS_T120这个channel是内部Channel,MS_T120 Channel被绑定两次(内部绑定一次,然后我们又绑定一次——id不是31)。由于绑定的时候没有限制,所以绑定在两个不同的ID下,因此MS_T120 Channel就有两个引用,假如我们关闭channel,就触发一次free,而系统默认也会free,那就变成了Double Free了。
实验环境
win 7 32位 旗舰版
漏洞分析
发送POC
1 | kd> g |
可以看到是termdd!_IcaFreeChannel
调用nt!ExDeleteResourceLite
后崩溃了
在free的时候崩溃,那么很有可能就是double free了
在IcaRebindVirtualChannels
和IcaBindVirtualChannels
中我们都可以看到IcaFindChannelByName
函数,我们看看这个函数,
1 | int __stdcall IcaFindChannelByName(int a1, int a2, char *a3) |
通过这一行,我们可以看到free的地址是8a998878
1 | 90cc6948 90238060 8a998878 8a998884 868b5670 termdd!_IcaFreeChannel+0x44 |
看看channel name是不是MS_T120(0x94这个便宜系统不同,应该不一样的)
1 | kd> da 8a998878+0x94 |
可以看到确实是的,我们现在还不能完全确认是MS_T120 channel的UAF。
要进入步确认我们就需要看看这个MS_T120 channel实在哪里创建,是不是释放了两次
现在free函数已经知道了,那么申请内存的函数呢?
我们看看有哪些函数调用了IcaFindChannelByName
可以看到有一个IcaCreateChannel
函数,很可能就是创建Channel,申请内存的函数,我们跟过去,又发现一个_IcaAllocateChannel
进去看看,看到申请的函数了,就是它了
那我们下两个记录断点(这个需要查看汇编,看看ExAllocatePoolWithTag
的返回值,还有_IcaFreeChannel的参数
)
1 | bu termdd!_IcaAllocateChannel+0x1c ".printf \"AllocateChannel Addresss: 0x%x\n\",@eax;.echo;gc" |
再发送poc
发送payload
1 | AllocateChannel Addresss: 0x88eecf38 |
我们看到,对0x88f6e1a8这个地址free了两次
1 | FreeChannel Addresss: 0x88f6e1a8 |
那就完全确认这个是UAF,也可以说是DOUBLE FREE(2 free)
在上面的基础,我们再下一个断点,看看是否同一个channel绑定了两个ID
1 | bu termdd!_IcaBindChannel ".echo _IcaBindChannel ==================;kv;gc" |
我已经把垃圾信息过滤了,日志如下:
1 | kd> g |
我们首先确定0x88fd5738这个地址的channel是不是MS_T120
1 | kd> da 0x88fd5738+0x94 |
再看看termdd!_IcaBindChannel
的栈,针对的都是88fd5738
这个地址,但是我们看第3个参数第一次是0x1f(其实就是十进制的31),而第二次是03,那就明显看到将同一个channel绑定了两个ID,导致有了两个引用,所以修复的时候强制指定为31,不管你绑定多少次,ID都是31
1 | a52c99e0 90238be9 88fd5738 00000005 0000001f termdd!_IcaBindChannel (FPO: [Non-Fpo]) |
最后我们再来看看他们调用栈的不同点
第一个调用栈,可以看到从NtCreateFile到termdd!IcaDispatch再到termdd!IcaCreateChannel,就是系统创建的这个channel,分配这个channel后进行了_IcaBindChannel操作
1 | ChildEBP RetAddr Args to Child |
而第二次明显是由我们触发的,termdd!IcaDeviceControl到termdd!IcaBindVirtualChannels
1 | ChildEBP RetAddr Args to Child |
那么到最后我们我们关闭连接,我们绑定的MS_T120 channel free了一次,系统自己再free一次,那就造成了double free了
可以看看最后两次free的调用栈,第一次我们主动释放了ID 为03的channel,第二次是我们关闭了连接导致的释放(ID为31),明显看到第二次的栈上有tssecsrv!CDefaultDataManager::Disconnect
(由于多次调试,下面的跟上面的地址会不一样)
1 | FreeChannel Addresss: 0x88ca9d48 |
漏洞利用简介
由于是double free,其实就是uaf利用思路,我们在第二次free的时候向上回溯
1 | ChildEBP RetAddr Args to Child |
发现IcaChannelInputInternal有虚函数调用,可以从这劫持控制流
看汇编也就是这里劫持控制流
要控制channel的数据,必须得在其第一次free了之后占位,我们申请同样大小的内存
我们看看申请的大小是0xc8
那么只要控制channel内存的0x8C偏移,劫持v12虚函数指针
但是我们要劫持到哪呢,没有信息泄露啊
现在exp的一般的做法是内核堆喷射,在Non-paged Pool进行堆喷,win7在这个地址上面是没有DEP的,所以直接喷内核shellcode就好了,而且win7的Non-paged Pool的起始地址比较固定,那还好命中一些
一切就绪就可以劫持控制流了
我们也可以看到堆喷射出的shellcode有很多
1 | kd> s 86000000 L 2000000 60 e8 00 00 00 00 5b e8 |