freeBuf
主站

分类

云安全 AI安全 开发安全 终端安全 数据安全 Web安全 基础安全 企业安全 关基安全 移动安全 系统安全 其他安全

特色

热点 工具 漏洞 人物志 活动 安全招聘 攻防演练 政策法规

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

win32k.sys漏洞挖掘思路解读
2020-05-14 16:26:48
所属地 北京

1研究背景

4月1日,以色列安全研究员Gil Dabah在博客上发布了一篇关于win32k漏洞研究文章,描述了如何通过内核对象的Destroy函数和win32k user-mode callback缓解措施的特性来寻找UAF漏洞的新思路。

为此,启明星辰ADLab对win32k相关内核机制进行研究分析,并对这类漏洞的挖掘思路进行详细解读分析。

2 win32k漏洞缓解与对抗

2.1 win32k user-mode callback漏洞

由于设计原因,win32k驱动需要处理很多用户层的回调,这些回调给win32k模块的安全带来了非常大的隐患,并在过去10年时间贡献了大量的漏洞。

为了便于漏洞描述,以如下伪代码进行举例分析。

 上述代码执行效果如下图所示,用户层执行的某函数通过syscall传入内核层,当内核层代码执行到somecallback这一句时,用户层可以在用户定义的callback函数中获得代码执行的机会,如果用户在callback函数调用了DestroyWindow函数销毁窗口p,内核层的相应销毁代码将会被执行,p的相应内存被释放,回调执行完毕,NtUserSysCall函数继续执行,当执行到xxxSetWindowStyle(p)一句时,由于p的内存已经被释放从而导致UAF漏洞的产生。

image.png

2.2 user-mode callback漏洞缓解机制

为了防止上述问题的产生,微软在对象中引入了一个引用计数(对象+0x8处),对象分配时引用计数为1,当执行对象的Destroy函数时引用计数减1,当引用计数为0时对象会被真正释放。微软通过锁的概念为对象添加和减少引用计数,在win32k中为对象管理引用计数的锁有两种分别是临时锁(相应函数为ThreadLock/ ThreadUnlock)和永久锁(相应函数为HMAssignmentLock/ HMAssignmentUnlock)。经过加固之后代码表现为如下形式:

通过上述代码,可以保证即使callback被执行,p在xxxSetWindowStyle函数执行的时候也不会被释放。

2.3缓解机制的对抗技术

上一节提到了对象的引用计数,如果对象的引用计数为正,即使执行对象的destroy函数,对象没有真正被释放,仍旧存留在内存中,这种对象被微软开发者称为僵尸(Zombie)对象。一旦僵尸对象的引用计数减少到0它将会消失,但是在此之前它仍旧存在内存中,只是用户层无法访问该对象。

同时为了防止僵尸对象继续存留在内存中,锁的释放函数(ThreadUnlock/ HMAssignmentUnlock)一般会包含对象的释放环节。

对象的Destroy函数还有一个特性就是在释放对象的同时,Destroy函数也会释放对象的子资源,其过程可以简要描述如下。

DestroyWindow在第一次调用时释放子资源,一旦窗口不再被引用,句柄管理器就会再次完全销毁它,一般情况下,第二次销毁Destroy函数不会在去处理子资源,因为第一次已经释放了所有的子资源。

但是事情往往不是这么简单,事实上即使是一个已经调用过相应Destroy函数释放的僵尸对象,仍然有机会对其本身进行一些更改(回调之后内核代码仍会对对象进行一些操作),我们把这种情况叫做Zombie Reload,当该僵尸对象由于引用计数为0而被真正释放时,之前的更改操作将会给内核带来一些隐患。

对于如下代码片段:

我们在用户层回调中对pwnd执行了Destroy函数,然后通过InternalSetTimer为之设置了一个计时器,当ThreadUnlock将pwnd真正释放的时候,计时器也将被释放,那么接下来对计时器的操作将会导致UAF漏洞的产生。

3 案例分析

上一节我们讨论了对象的引用计数和锁给对象带来的新的安全隐患,但是真正的挑战在于我们如何确定一段代码中存在漏洞,关键点是确保在unlock函数中释放的对象在运行到有问题的代码时其引用计数应该为1,只有这样我们才能在用户层回调调用其Destroy函数,并通过unlock函数将这个对象真正释放掉(上锁的时候会做+1处理),这也是我们接下来需要讨论的。下面我们通过一个案例来分析漏洞挖掘思路。

3.1漏洞成因

下图是xxxMnOpenHierarchy函数的代码片段。

image.png

图中通过xxxCreateWindowEx可以获得一个返回用户层执行callback函数的机会,xxxCreateWindowEx创建的窗口将作为父窗口*(structtagWND **)(**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的所有者。

image.png

在上述创建过程中内核通过LockPopuMenu函数分别为hMenu和g_hMenuOwner添加了永久锁,为了达成释放目的,这个永久锁需要被绕过。

image.pngimage.png

此时锁和所有者的关系是这样的:

image.png

接下来我们通过SetWindowsHookEx给窗口添加了WH_CBT钩子,并让窗口进入消息循环中。

image.png

SendMessage操作为g_hMenuOwner添加一个临时锁,由于后续的所有攻击都是在message的回调中进行,所以对于g_hMenuOwner来说这个临时锁是无法释放的,如果想要构造一个漏洞利用环境首先需要用一些方法来绕过它。

image.png

现在的情况变成了下图所示:

image.png

当消息为HCBT_CREATEWND时,我们第一次到达xxxMNOpenHierarchy函数内部的xxxCreateWindowEx。

image.png

这里可以通过定义关于HCBT_CREATEWND消息的处理得到执行用户层回调代码的机会,这一步的主要目的是为了获取Menu的Wnd。

image.png

当接收到的消息为WM_ENTERIDLE时,我们在窗口的消息回调中通过PostMessage下发消息。

image.png

发送消息后,驱动程序来到了xxxMNKeyDown函数内部调用xxxSendMessage处。

image.png

通过WM_NEXTMENU消息的回调函数开始为LPARAM赋值,赋值操作是为了修改hMenu的Owner,这样就可以将Owner的临时锁绕过。

image.png

此时内核会接到销毁menu的消息,通过用户层的回调函数返回1阻止menu的销毁。

image.pngxxxMNKeyDown函数通过UnlockPopupMenu将g_hMenuOwner身上的永久锁被去掉。

image.png

取而代之的是g_hNewOwner加上了一个锁,hMenu的Owner也从g_hMenuOwner变成了g_hNewOwner。

image.png

这时,锁的关系变成了:

image.png

接下来程序第二次进入到xxxMNOpenHierarchy函数并通过xxxSendMessage发送了消息。

image.png

此时通过设置WM_INITMENUPOPUP回调来获得用户层执行的机会,WM_INITMENUPOPUP回调函数通过SetWindowsHookEx函数设置了一个新的hook,目的是为了在xxxMnOpenHierarchy函数创建子窗口的时候获得用户层执行权限。

image.png

xxxMnOpenHierarchy函数继续向下执行,再次来到xxxCreateWindowEx处。

image.png

xxxCreateWindowEx调用了刚刚设置的回调函数childMenuHookProc。

image.png

在回调函数childMenuHookProc中,SendMessage发送了WM_NEXTMENU消息,通过该定义该消息的回调函数再次修改参数LPARAM,这是为了去掉g_hNewOwner身上的永久锁。

image.png

Menu的Owner关系再次被改变,xxxMNKeyDown通过函数UnlockPopMenu去掉g_hNewOwner身上的永久锁。并将这个锁重新加在了g_hMenuOwner上。

image.pngimage.png

这个时候,所有的锁都已经转移到了g_hMenuOwner身上,而由于WH_CBT钩子已经被移除,menu将被弃用,g_hNewOwner将把新创建的窗口link到自己身上。这个时候情况变成了下面的样子,g_hNewOwner身上已经没有需要绕过的锁了。

image.png

接着childMenuHookProc通过SetWindowsHookEx函数又一次设置了回调函数并通过SetWindowLongPtr函数来调用它,回调函数销毁了g_hNewOwner和xxxCreateWindowEx生成的新窗口。

image.png

xxxCreateWindowEx返回的值为ffff871b80239130,这就是xxxCreateWindowEx创建的子窗口。

image.png

接下来就可以通过ThreadUnlock来销毁g_hNewOwner和其新创建的子窗口来得到一个UAF漏洞。

image.png

4总结

本文对win32k漏洞挖掘新思路进行了详细解读,其中包括将unlock函数和对象的Destroy函数的特性关联在一起,并把对象的子资源作为攻击目标寻找新的攻击面的漏洞挖掘思路。另外,如何通过对象内部的特性去绕过锁对对象的锁定的思路和技巧,也非常具有借鉴意义。


image.png


# 漏洞分析
本文为 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录