从补丁到POC CVE-2015-0003(2015.3)
从补丁到POC CVE-2015-0003
1. 简介
该漏洞是由于Windows的win32k.sys模块存在对用户层参数验证不完全,导致存在空指针解引用(Null Pointer Dereference)的问题。
实现对漏洞的有效利用,攻击者可以实现权限提升。
影响的系统包括(32bit & 64 bit):
Windows Server 2003
Windows Vista
Windows Server 2008
Windows 7
Windows 8 & Windows 8.1
Windows Server 2012
测试环境:
Win7 32bit
下面对通过补丁对比构造POC和漏洞成因做简单说明。
2. 补丁对比
2.1 初窥
使用IDA的BinDiff插件得到win32k.sys补丁前后的函数对比,部分如下:
结合微软对该漏洞的描述,可以得知与窗口处理函数有关。FindSystemTimer函数补丁前后差异较大,加上xxxDispatchMessage和FindSystemTimer之间的存在调用关系,所以有理由怀疑FindSystemTimer函数和漏洞相关。下面对这两个函数进行简单分析。
2.2 近一点
xxxDispatchMessage
利用BinDiff查看补丁前后此函数的差异,可以发现补丁后其在调用FindSystemTimer时多传递了一个参数。
FindSystemTimer
补丁前后FindSystemTimer函数变化较大。
补丁前,FindSystemTimer会遍历一个链表(_gtmrListHead),如果pTmr->flags,pMsg->lParam和pTmr->pfn通过验证,那么就会返回当前这个pTmr。否则就会返回NULL。
补丁后,FindSystemTimer需要两个参数,pMsg和pWnd。函数的功能看起来还是一样,都是遍历链表得到pTmr,但是需要验证更多的变量:pMsg->wParam和pTmr->ID,pWnd和pTmr->spwnd。
目前,我们可以知道函数FindSystemTimer的嫌疑很大,hWnd和wParam可能和漏洞有关。
3.构造POC
3.1 一条路径
xxxDispatchMessage虽然和问题函数FindSystemTimer存在调用关系,但是xxxDispatchMessage是内核函数,能否在用户层找到一条路径控制xxxDispatchMessage到达调用FindSystemTimer的流程是决定能够构造出POC的关键之一。
微软对函数采用匈牙利命名法对于分析的帮助非常大,内核函数xxxDispatchMessage可以很自然联想到用户层DispatchMessage函数。DispatchMessage函数似乎只是DispatchMessageWorker函数简单的一个过渡:
而DispatchMessageWorker函数会调用内核的NtUserDispatchMessage函数。NtUserDispatchMessage函数对参数做一些有效性检查后,就会调用xxxDispatchMessage函数。在xxxDispatchMessage函数中存在这么一段代码:
其中0x113为WM_TIMER消息,0x118为WM_SYSTIMER消息。当要处理的消息为WM_SYSTIMER时,就会顺利调用FindSystemTimer这个问题函数了。
WM_SYSTIMER为未公开的消息,还好通过Google查询可以知道其和caret blink有关。
目前为止,我们在用户层找到了一条调用路径,可以最终调用可疑函数FindSystemTimer:DispatchMessage(WM_SYSTIMER)àDispatchMessageWorkeràNtUserDispatchMessageàxxxDispatchMessageàFindSystemTimer。
3.2 一个框架
通过以上分析知道,要触发这个漏洞至少需要一个窗口并且能够产生WM_SYSTIMER消息。将一个最基本的win32窗口改一下就可以了,在消息循环之前加入如下代码:
用spy++观察这个窗口可以产生WM_SYSTIMER消息:
3.3 修改
现在得到了一个可以产生目标消息的一个窗口,消息中的hWnd和wParam是我们需要测试的两个参数,怎么让这两个参数随机化又是需要解决的问题。回头看创建caret时所产生的WM_SYSTIMER消息十分的有意思,因为我们并没有一个循环或者定时器,但是其产生的WM_SYSTIMER消息却是具有时间连续性,隔一段时间就会发出一个WM_SYSTIMER消息。所以有理由怀疑创建caret的函数中有一个定时器,并且很有可能是这个定时器发出的WM_SYSTIMER消息。
这次又是微软的匈牙利命名法帮了大忙,果然在xxxCreateCaret函数中找到了一个似乎和定时器相关的函数:
注意此时_SetSystemTimer的第二个参数为0xFFFF,这正好和上面spy++的输出中wParam相等,所以又可以怀疑一下该参数就代表wParam的值了。利用IDA的XREFS功能查看所有调用_SetSystemTimer的地方:
经过测试众多的函数,包括最开始的CreateCaret,似乎只有xxxFlashWindow函数才靠谱:不会崩溃,窗口的消息循环中也可以改变相关的值。用户层对应的是FlashWindow函数,其调用_SetSystemTimer上下文:
去掉创建caret的部分,增加FlashWindow函数,修改以前的框架为:
运行后,利用spy++查看输出,我们期望wParam的值为0xFFF8。
结果显示wParam正是0xFFF8,所以此时我们就拥有了一个可以产生目标消息,并且可以修改相关值的框架。
3.4 Crash
最后利用这个框架来对hWnd和wParam这两个值做随机化处理,在窗口的消息循环中可以添加这样的代码:
将整个代码放在虚拟机运行一段时间后,就可以得到崩溃了:
此时hWnd=0,wParam=0xFFF8(等于原来的值)。
4. 漏洞分析
根据崩溃后的调用堆栈,对此时的eax进行回溯,在函数xxxDispatchMessage内最终可以确定其是一个win32k!tagWnd结构:
所以漏洞的成因就很明显了:FindSystemTimer函数对参数的验证不完全,导致窗口句柄为0所对应的内核对象被解引用。打补丁后,FindSystemTimer函数会对这种情况进行校验改变流程使其不会进去到解引用流程中去。
5. 参考
http://blog.beyondtrust.com/fuzzing-for-ms15-010
by:会飞的猫
转载请注明:http://www.cnblogs.com/flycat-2016