win32k.sys缝隙挖掘思路解读
颁布功夫 2020-05-09一、钻研布景
4月1日,,,,,以色列安全钻研员Gil Dabah在博客上颁布了一篇关于win32k缝隙钻研文章,,,,,描述了若何通过内查对象的Destroy函数和win32k user-mode callback缓解措施的个性来寻找UAF缝隙的新思路。。。。。。。
为此,,,,,8827太阳集团ADLab对win32k有关内核机造进行钻研分析,,,,,并对这类缝隙的挖掘思路进行具体解读分析。。。。。。。
二、win32k缝隙缓解与匹敌
2.1 win32k user-mode callback缝隙
由于设计原因,,,,,win32k驱动必要处置好多用户层的回调,,,,,这些回调给win32k�??????榈陌踩戳思却蟮囊迹�,,,,并在从前10年功夫贡献了大量的缝隙。。。。。。。
为了便于缝霞述,,,,,以如下伪代码进行举例分析。。。。。。。
NtUserSysCall()
{
PWND p = CreateWindowEx(…);
somecallback();
xxxSetWindowStyle(p);
}
上述代码执行成效如下图所示,,,,,用户层执行的某函数通过syscall传入内核层,,,,,当内核层代码执行到somecallback这一句时,,,,,用户层能够在用户界说的callback函数中获得代码执行的机遇,,,,,若是用户在callback函数挪用了DestroyWindow函数销毁窗口p,,,,,内核层的相应销毁代码将会被执行,,,,,p的相应内存被开释,,,,,回调执行结束,,,,,NtUserSysCall函数持续执行,,,,,当执行到xxxSetWindowStyle(p)一句时,,,,,由于p的内存已经被开释从而导致UAF缝隙的产生。。。。。。。
2.2 user-mode callback缝隙缓解机造
为了预防上述问题的产生,,,,,微软在对象中引入了一个引用计数(对象+0x8处),,,,,对象分配时引用计数为1,,,,,当执行对象的Destroy函数时引用计数减1,,,,,当引用计数为0时对象会被真正开释。。。。。。。微软通过锁的概想为对象增长和削减引用计数,,,,,在win32k中为对象治理引用计数的锁有两种别离是一时锁(相应函数为ThreadLock/ ThreadUnlock)和永远锁(相应函数为HMAssignmentLock/ HMAssignmentUnlock)。。。。。。。经过加固之后世码阐发为如下大局:
NtUserSysCall()
{
PWND p = CreateWindowEx(…);
ThreadLock(p);
Somecallback();
xxxSetWindowStyle(p);
ThreadUnlock();
}
通过上述代码,,,,,能够保障即便callback被执行,,,,,p在xxxSetWindowStyle函数执行的时辰也不会被开释。。。。。。。
2.3缓解机造的匹敌技术
上一节提到了对象的引用计数,,,,,若是对象的引用计数为正,,,,,即便执行对象的destroy函数,,,,,对象没有真正被开释,,,,,仍旧存留在内存中,,,,,这种对象被微软开发者称为僵尸(Zombie)对象。。。。。。。一旦僵尸对象的引用计数削减到0它将会隐没,,,,,但是在此之前它仍旧存在内存中,,,,,只是用户层无法接见该对象。。。。。。。
同时为了预防僵尸对象持续存留在内存中,,,,,锁的开释函数(ThreadUnlock/ HMAssignmentUnlock)通常会蕴含对象的开释环节。。。。。。。
对象的Destroy函数还有一个个性就是在开释对象的同时,,,,,Destroy函数也会开释对象的子资源,,,,,其过程能够简要描述如下。。。。。。。
void xxxDestroyWindow(PWND pwnd)
{
xxxFW_DestroyAllChildren(); // Destroy child windows, if exist!
if (NULL != pwnd->spmenu) // If there’s a menu, remove and destroy it.
{
PMENU tmp = pwnd->spmenu;
if (HMAssignmentUnlock(&pwnd->spmenu)) // If it’s still locked
{
DestroyMenu(tmp); // Try destroying it (it can remain a zombie).
}
}
DereferenceClass(pwnd);
if (HMMarkObjectDestroy(pwnd)) // Check for zero refs!
HmFreeObject(pwnd); // Only now free the object and handle pair.
}
DestroyWindow在第一次挪用时开释子资源,,,,,一旦窗口不再被引用,,,,,句柄治理器就会再次齐全销毁它,,,,,通常情况下,,,,,第二次销毁Destroy函数不会在去向理子资源,,,,,由于第一次已经开释了所有的子资源。。。。。。。
但是事件往往不是这么单一,,,,,事实上即便是一个已经挪用过相应Destroy函数开释的僵尸对象,,,,,依然有机遇对其自身进行一些更改(回调之后内核代码仍会对对象进行一些操作),,,,,我们把这种情况叫做Zombie Reload,,,,,当该僵尸对象由于引用计数为0而被真正开释时,,,,,之前的更改操作将会给内核带来一些隐患。。。。。。。
对于如下代码片段:
ThreadLock(pwnd);
xxxSomeCallback(); // Here we can destroy pwnd from user-mode.
InternalSetTimer(pwnd, ...); // reuse pwnd without check wether it is destroyed
ThreadUnlock();
SomefunctionUseTimer(); //UAF of Timer
我们在用户层回调中对pwnd执行了Destroy函数,,,,,而后通过InternalSetTimer为之设置了一个计时器,,,,,当ThreadUnlock将pwnd真正开释的时辰,,,,,计时器也将被开释,,,,,那么接下来对计时器的操作将会导致UAF缝隙的产生。。。。。。。
三、案例分析
上一节我们会商了对象的引用计数和锁给对象带来的新的安全隐患,,,,,但是真正的挑战在于我们若何确定一段代码中存在缝隙,,,,,关键点是确保在unlock函数中开释的对象在运行到有问题的代码时其引用计数应该为1,,,,,只有这样我们能力在用户层回调挪用其Destroy函数,,,,,并通过unlock函数将这个对象真正开释掉(上锁的时辰会做+1处置),,,,,这也是我们接下来必要会商的。。。。。。。下面我们通过一个案例来分析缝隙挖掘思路。。。。。。。
3.1缝隙成因
下图是xxxMnOpenHierarchy函数的代码片段。。。。。。。
图中通过xxxCreateWindowEx能够获得一个返回用户层执行callback函数的机遇,,,,,xxxCreateWindowEx创建的窗口将作为父窗口*(struct tagWND **)(**v3 + 8)(上图红框)的子窗口,,,,,若是我们能够通过ThreadUnlock开释父窗口,,,,,那么子窗口v32也会被开释,,,,,所以当后续的safe_cast_fnid_to_PMENUWND函数将v32作为参数执行时就会产生问题,,,,,值妥贴心的是通过回调开释v32是行不通的,,,,,若是这样xxxCreateWindowEx将会返回0,,,,,无法通过if判断。。。。。。。
这里的问题就在于若何保障父窗口在ThreadUnlock函数执行的时辰引用计数为1,,,,,由于要执行xxxMnOpenHierarchy函数必要将父窗口关联到一个menu窗口上,,,,,此时父窗口和menu窗口将会被一个永远锁锁住!!�。。。。�,,,,下面我们介绍若何绕过永远锁。。。。。。。
3.2 缝隙挖掘思路
首先我们创建了g_hMenuOwner和g_hNewOwner两个窗口,,,,,其中g_hMenuOwner的菜单句柄为hMenu,,,,,它也是g_hNewOwner的所有者。。。。。。。
在上述创建过程中,,,,,内核通过LockPopuMenu函数别离为hMenu和g_hMenuOwner增长了永远锁,,,,,为了达成开释主张,,,,,这个永远锁必要被绕过。。。。。。。
此时锁和所有者的关系是这样的:
接下来我们通过SetWindowsHookEx给窗口增长了WH_CBT钩子,,,,,并让窗口进入新闻循环钟祝。。。。。。
SendMessage操作为g_hMenuOwner增长一个一时锁,,,,,由于后续的所有攻击都是在message的回调中进行,,,,,所以对于g_hMenuOwner来说这个一时锁是无法开释的,,,,,若是想要机关一个缝隙利用环境首先必要用一些步骤来绕过它。。。。。。。
此刻的情况造成了下图所示:
当新闻为HCBT_CREATEWND时,,,,,我们第一次达到xxxMNOpenHierarchy函数内部的xxxCreateWindowEx。。。。。。。
这里能够通过界说关于HCBT_CREATEWND新闻的处置得到执行用户层回调代码的机遇,,,,,这一步的重要主张是为了获取Menu的Wnd。。。。。。。
当接管到的新闻为WM_ENTERIDLE时,,,,,我们在窗口的新闻回调中通过PostMessage下发新闻。。。。。。。
发送新闻后,,,,,驱动法式来到了xxxMNKeyDown函数内部挪用xxxSendMessage处。。。。。。。
通过WM_NEXTMENU新闻的回调函数起头为LPARAM赋值,,,,,赋值操作是为了批改hMenu的Owner,,,,,这样就能够将Owner的一时锁绕过。。。。。。。
此时内核会接到销毁menu的新闻,,,,,通过用户层的回调函数返回1阻止menu的销毁。。。。。。。
xxxMNKeyDown函数通过UnlockPopupMenu将g_hMenuOwner身上的永远锁被去掉。。。。。。。
取而代之的是g_hNewOwner加上了一个锁,,,,,hMenu的Owner也从g_hMenuOwner造成了g_hNewOwner。。。。。。。
这时,,,,,锁的关系造成了:
接下来法式第二次进入到xxxMNOpenHierarchy函数并通过xxxSendMessage发送了新闻。。。。。。。
此时通过设置WM_INITMENUPOPUP回调来获得用户层执行的机遇,,,,,WM_INITMENUPOPUP回调函数通过SetWindowsHookEx函数设置了一个新的hook,,,,,主张是为了在xxxMnOpenHierarchy函数创建子窗口的时辰获得用户层执行权限。。。。。。。
xxxMnOpenHierarchy函数持续向下执行,,,,,再次来到xxxCreateWindowEx处。。。。。。。
xxxCreateWindowEx挪用了刚刚设置的回调函数childMenuHookProc。。。。。。。
在回调函数childMenuHookProc中,,,,,SendMessage发送了WM_NEXTMENU新闻,,,,,通过该界说该新闻的回调函数再次批改参数LPARAM,,,,,这是为了去掉g_hNewOwner身上的永远锁。。。。。。。
Menu的Owner关系再次被扭转,,,,,xxxMNKeyDown通过函数UnlockPopMenu去掉g_hNewOwner身上的永远锁。。。。。。。并将这个锁沉新加在了g_hMenuOwner上。。。。。。。
这个时辰,,,,,所有的锁都已经转移到了g_hMenuOwner身上,,,,,而由于WH_CBT钩子已经被移除,,,,,menu将被弃用,,,,,g_hNewOwner将把新创建的窗口link到自己身上。。。。。。。这个时辰情况造成了下面的样子,,,,,g_hNewOwner身上已经没有必要绕过的锁了。。。。。。。
接着childMenuHookProc通过SetWindowsHookEx函数又一次设置了回调函数并通过SetWindowLongPtr函数来挪用它,,,,,回调函数销毁了g_hNewOwner和xxxCreateWindowEx天生的新窗口。。。。。。。
xxxCreateWindowEx返回的值为ffff871b80239130,,,,,这就是xxxCreateWindowEx创建的子窗口。。。。。。。
接下来就能够通过ThreadUnlock来销毁g_hNewOwner和其新创建的子窗口来得到一个UAF缝隙。。。。。。。
四、总 结
本文对win32k缝隙挖掘新思路进行了具体解读,,,,,其中蕴含将unlock函数和对象的Destroy函数的个性关联在一路,,,,,并把对象的子资源作为攻击指标寻找新的攻击面的缝隙挖掘思路。。。。。。。另表,,,,,若何通过对象内部的个性去绕过锁对对象的锁定的思路和技巧,,,,,也极度拥有借鉴意思。。。。。。。


京公网安备11010802024551号