0x00 :
Windows10 1703 X64 无补丁
0x01 :
如何构造一个触发BSOD的Poc呢,根据网上现存的分析报告我们得到了一个这样触发BSOD的思路.
1.创建两个窗口,一个父窗口,一个滚动条子控件
2.Hook PEB->KernelCallbackTable中的fnDword(),xxxClientAllocWindowClassExtraBytes()函数指针的指向,让其指向我们自定义的处理函数.
3.在fnDword()函数内释放父类窗口
4.在xxxClientAllocWindowClassExtraBytes()函数内调用NtUserSetWindowFNID()函数,并创建新的滚动条控件,使用SetCapture()函数修改滚动条捕获窗口
5.在fnDword() 函数内判断发送的Message是否为0x70,如果为0x70,则向新创建的滚动条控件发送0x1F号消息
6.向 滚动条子控件(Scroll)发送WM_LBUTTONDOWN消息即可触发BSOD
虽然这样确实可以触发BSOD,但是我们根本不知道为什么这样会导致BSOD(Double Free),下面是本人关于CVE-2018-8453的分析报告
触发BSOD的Poc中,完成了1 - 5的准备工作之后,便向滚动条子控件发送了一个WM_LBUTTONDOWN消息
我们知道,向滚动条子控件(Scroll)发送WM_LBUTTONDOWN,消息时,会调用到win32kfull!xxxSBTrackInit函数,该函数主要是来实现滚动条跟随鼠标移动的,该函数首先会创建一个0x80字节大小的Session Pool,用来保存tagSBTrack结构
接着将创建好tagSBTRACK结构的指针,写入到 tagTHREADINFO.tagSBTRACK处,在Windows10 1703 X64中,该结构的偏移地址为tagTHREADINFO+0x278
需要注意的是Lock(&pSBTrack->spwndSBNotify, pwnd->spwndParent),让滚动条子控件引用父类窗口,也就是我们创建的父窗口建立引用+1,此处很重要(PS:当时写Poc时创建滚动条子控件时,属性忘记设置WS_CHILD,导致滚动条窗口的父窗口非创建的父窗口导致无法利用漏洞)
Win32kfull!xxxSBTrackInit函数最后会调用Win32kfull!xxxSBTrackLoop函数,来进行消息循环,消息循环函数Win32kfull!xxxDispatchMessage会使用fnDWORD函数回调R3,这时我们就知道为什么要Hook fnDWORD函数了,在fnDWORD函数里判断是否是滚动条窗口发送的回调,调用DestroyWindow()函数释放主窗口
Win32kfull!DestoryWindow函数会调用Win32kfull!xxxFreeWindow函数来释放窗口,但是该函数经常被调用,我们可以使用条件断点来判断是否是我们要释放的窗口
Ba e1 win32kfull!xxxFreeWindow “.if( poi(rcx) == 释放窗口的句柄 ){}.else{g}”
此时在rcx+0x52处下内存写入断点 rcx+0x52 处为 tagwnd.FNID,也就是导致漏洞的主角(也不能这样说,本质问题还是Kernel CallBack).
Win32kfull!xxxFreeWindow函数会将FNID设置为0x8000 也就是要删除的窗口标记,紧接着我们可以通过xxxClientFreeWindowClassExtraBytes函数的Kernel CallBack回调R3释放窗口扩展结构,该函数会调用user!xxxClientAllocWindowClassExtraBytes,该函数指针我们已经修改了,但释放窗口扩展空间必须要在创建窗口类时设置窗口类的大小,在Poc中我设置为 wndclass.cbWndExtra = 0x8
触发xxxClientAllocWindowClassExtraBytes函数回调后,如何判断是主窗口调用的该函数呢,这里我使用了SetWindowLongA(Window, 0, (ULONG)Window); 将主窗口句柄保存在窗口扩展中.
MSG中,保存了窗口扩展类的地址,里面保存了设置的父窗口指针,通过这个来判断是否为父窗口调用的xxxClientAllocWindowClassExtraBytes函数.
此时,我们创建一个新的滚动条窗口,不设置父类句柄,以及子类属性,并设置正在释放窗口Window的FNID为 0x2A1,本来Window的FNID为0x8000,调用NtUserSetWindowFNID后为0x82A1,接着设置新的捕获窗口.
此时父窗口虽然已经调用Win32kfull!DestroyWindow释放了,但是由于滚动条子窗口还对父窗口有引用,所以并未释放主窗口的TagWnd结构,最后Win32kfull!xxxSBTrackLoop函数结束后,Win32kfull!xxxSBTrackInit对pSBTrack->spwndSBNotify和链接的主窗口解引用
由于是最后一处引用,调用HMAssignmentUnlock时会判断被绑定Win32 Object结构的cLockObj结构是否为1,如果为1代表只有一个引用,修改指针内容后后便立刻调用函数释放该结构,此处释放的函数为(Win32kfull!DestoryWindow)
问题就在这里,由于释放窗口要调用Win32kfull!xxxFreeWindow函数,而FNID为释放窗口的Flag属性,被修改为0x82A1后,会再次调用fnDWORD函数回调R3,并发送为0x70的Message.
判断FNID的内容,来决定是否调用fnDWORD
可以看到此处调用fnDWORD,并且发送的Message为0x70
自定义fnDWORD函数如下
此时,我们向新创建的滚动条控件发送Message为0x1F的消息,最终会调用到Win32kfull!xxxEndScroll函数
通过在Win32kfull!SetCapture设置的捕获窗口,可绕过其验证,直入到释放tagSBTrack结构
此时线程内的tagSBTrack结构已经被释放了,接着回到Win32kfull!xxxSBTrackInit执行代码.
因为tagSBTrack结构已经在Win32kfull!xxxEndScroll函数被释放了,但是Win32kfull!xxxSBTrackInit函数并不知道,再次释放该内存导致Double Free!