安全路透社
当前位置:安全路透社 > 安全客 > 正文

【技术分享】深入分析Win32k系统调用过滤机制

http://p8.qhimg.com/t01ff83be54a939d735.jpg

翻译:胖胖秦

预估稿费:140RMB

,或登陆网页版


前言


Windows内核漏洞的利用具有高风险,经常用于浏览器沙箱逃逸。许多年以来,发现的大多数漏洞都是来源于Win32k.sys驱动,它负责处理来自GDI32.DLL和user32.dll的调用。为了缓解这些漏洞,微软在window10上实现了Win32系统调用过滤.总体思路是在进程入口处尝试阻止大量的发往win32.sys的系统调用,以便阻止未知的漏洞利用。我没有找到实现的相关细节,也不确定效果如何。我发现的唯一资料就是由Peter Hlavaty发表在 Rainbow Over the Windows的相关文章。

系统调用101


我理解过滤技术的第一个方法就是研究系统调用是如何执行。分析是基于Windows 10周年更新的64位版本。通常,系统调用在gdi32.dll或者user32.dll的函数中被初始化,最终会在win32u.dll中调用真实的系统调用。但是,我们可以直接使用汇编来显示系统调用,在以下的POC中,我查询到系统调用号为0x119E的WIN32K函数是NtGdiDdDDICreateAllocation。所以我简单地创建下面的测试应用程序:

http://p0.qhimg.com/t0116a54e6a578687f7.png

下图是NtGdiDdDDICreateAllocation的汇编代码:

http://p2.qhimg.com/t01cd53b49ccf713ba5.png

当运行syscall指令后,将转入内核模式执行.真实的系统调用位于NT!KiSystemStartService。但是,由于有大量的系统调用,所以我们需要在调试器中设置一个条件断点:

http://p1.qhimg.com/t01776d2bbaaf5d0ccb.png

运行POC并开启断点

http://p1.qhimg.com/t0146f41ffdd237d4f7.png

首先显示的系统调用号就是我们提供的0x119E,其实参数1,2,3,4保存在寄存器RCX,RDX,R8和R9中。在IDA中查看相关代码:

http://p4.qhimg.com/t01c9c3312b64c58d77.png

查阅代码,我们发现一个有趣的问题:RBX寄存器的内容是什么,这又是从何而来。尝试引用KiSystemServiceStart,我们发现 RBX在以下函数中被设置:

http://p7.qhimg.com/t01703bc14818dc8777.png

MOV RBX GS:188将Win32SyscallFilter.exe的内核线程结构载入RBX中,验证如下:

http://p1.qhimg.com/t012c589e7d5d6d42ad.png


研究算法


接下来的问题是RBX + 0x78代表什么,事实证明,它代表一系列的标志位。下图引用的两个标志是GuiThread和RestrictedGuiThread,它们分别位于标志的第6位和和19位。

在我们的例子中,标志位的值如下:

http://p2.qhimg.com/t0179b7f551377dcd4a.png

由于线程不是一个GUI线程,所以会重定向的将它转换成一个GUI线程,然后返回相同的指针。继续执行会发生:

http://p6.qhimg.com/t01626d7d664d528c61.png

Win32kSyscallFilter并没有做任何事。但是接下来的检查很有趣。RestrictedGuiThread标志指明,如果启用系统调用过滤,会在进程级别上进行检测:

http://p1.qhimg.com/t01d1730a728e38c84e.png

因此,对于当前的进程和线程,系统调用过滤没有启用。查看进一步执行,将体现出这个标志位的重要性:

http://p0.qhimg.com/t0107e11916bc88f132.png

如果开启了系统调用过滤功能,KeServiceDescriptorTableFilter将取代KeServiceDescriptorTableShadow,如果没有开启过滤,则将使用KeServiceDescriptorTableShadow。接下来要观察系统调用表的使用,如下图所示:

http://p6.qhimg.com/t0108d9eb1575d3e87e.png

在经过运算后,RDI包含系统调用数目。在WIN32K系统的情况下,它的值是0x20。所以,取决于系统调用过滤是否开启,不同的表会被载入R10.这两个选项是:

http://p3.qhimg.com/t01aa6248bfdfb2c50f.png

然后该表将转入真实的函数调用:

http://p5.qhimg.com/t01cdb79b25243a6bfe.png

跟随以上的算法,我们在调试器中找到:

http://p5.qhimg.com/t0114db97242afd9379.png

所以这很显然,系统调用号通过负偏移指向W32pServiceTable结构,然后指向真实的NtGdiDdDDICreateAllocation函数。这是非常好的,但是如果开启了系统调用过滤,会有什么区别呢,这可以使用W32pServiceTableFilter来进行验证:

http://p7.qhimg.com/t01a681d685c8b5508f.png

我们看到在此之前并没有什么区别,这是因为NtGdiDdDDiCreateAllocation并不是过滤的API之一,如果我们选择其他的系统调用,比如NtGdiDdDDiCreateAllocation,它的系统调用号是0x117E。我们基于是否启用系统调用过滤来对比以下两个输出。

首先是未启用系统调用过滤的:

http://p5.qhimg.com/t0134ef60346282e417.png

然后是启用系统调用过滤:

http://p0.qhimg.com/t015a99e73cbc3a9d3f.png

我们发现,如果开启了系统调用过滤功能,系统调用是不允许的另一个函数被调用的。该过滤函数验证是否启用系统调用过滤并简单的结束系统调用。

利用的结果


现在我们了解了系统调用过滤是如何工作的,我们需要研究它是如何防止内核漏洞利用的。首先要看看它保护的是什么进程,到目前为止,仅仅是微软Edge可以启用这个功能,目前第三方程序没有可以启用它的接口。这意味着系统调用过滤仅仅关心Microsoft Edge漏洞并且仅限于内核漏洞。下面我们可以看到MicrosoftEdgeCP.exe的进程结构,并启用了系统调用过滤:

http://p8.qhimg.com/t01b9d4e72fcf5ec38f.png

http://p8.qhimg.com/t014792449aa787c9c8.png

回顾我早期文章,关于重新启用tagWND对象作为读写原语的用法的,我想知道是否使用这个方法的任意系统调用都会被过滤,在那个方法中使用的内核模式的函数是:

NtUserCreateWindowEx
NtUserDestroyWindow
NtUserSetWindowLongPtr
NtUserDefSetText
NtUserInternalGetWindowText
NtUserMessageCall

在win32k.sys中没有stub_*方法的函数意味着不会被过滤。结论就是Win32K系统调用过滤不会阻止可能导致漏洞的系统调用,但是当系统调用触发一个漏洞时,它一定会阻止,可能是write-that-wherer或者是一个缓冲区溢出。WIN32K系统调用过滤所提供的保护是巧妙的,但关键在于是否使用了系统调用来触发漏洞。


原文链接:https://improsec.com/blog//win32k-system-call-filtering-deep-dive

未经允许不得转载:安全路透社 » 【技术分享】深入分析Win32k系统调用过滤机制

赞 (0)
分享到:更多 ()

评论 0

评论前必须登录!

登陆 注册