前言
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。所以我简单地创建下面的测试应用程序:
下图是NtGdiDdDDICreateAllocation的汇编代码:
当运行syscall指令后,将转入内核模式执行.真实的系统调用位于NT!KiSystemStartService。但是,由于有大量的系统调用,所以我们需要在调试器中设置一个条件断点:
运行POC并开启断点
首先显示的系统调用号就是我们提供的0x119E,其实参数1,2,3,4保存在寄存器RCX,RDX,R8和R9中。在IDA中查看相关代码:
查阅代码,我们发现一个有趣的问题:RBX寄存器的内容是什么,这又是从何而来。尝试引用KiSystemServiceStart,我们发现 RBX在以下函数中被设置:
MOV RBX GS:188将Win32SyscallFilter.exe的内核线程结构载入RBX中,验证如下:
研究算法
接下来的问题是RBX + 0x78代表什么,事实证明,它代表一系列的标志位。下图引用的两个标志是GuiThread和RestrictedGuiThread,它们分别位于标志的第6位和和19位。
在我们的例子中,标志位的值如下:
由于线程不是一个GUI线程,所以会重定向的将它转换成一个GUI线程,然后返回相同的指针。继续执行会发生:
Win32kSyscallFilter并没有做任何事。但是接下来的检查很有趣。RestrictedGuiThread标志指明,如果启用系统调用过滤,会在进程级别上进行检测:
因此,对于当前的进程和线程,系统调用过滤没有启用。查看进一步执行,将体现出这个标志位的重要性:
如果开启了系统调用过滤功能,KeServiceDescriptorTableFilter将取代KeServiceDescriptorTableShadow,如果没有开启过滤,则将使用KeServiceDescriptorTableShadow。接下来要观察系统调用表的使用,如下图所示:
在经过运算后,RDI包含系统调用数目。在WIN32K系统的情况下,它的值是0x20。所以,取决于系统调用过滤是否开启,不同的表会被载入R10.这两个选项是:
然后该表将转入真实的函数调用:
跟随以上的算法,我们在调试器中找到:
所以这很显然,系统调用号通过负偏移指向W32pServiceTable结构,然后指向真实的NtGdiDdDDICreateAllocation函数。这是非常好的,但是如果开启了系统调用过滤,会有什么区别呢,这可以使用W32pServiceTableFilter来进行验证:
我们看到在此之前并没有什么区别,这是因为NtGdiDdDDiCreateAllocation并不是过滤的API之一,如果我们选择其他的系统调用,比如NtGdiDdDDiCreateAllocation,它的系统调用号是0x117E。我们基于是否启用系统调用过滤来对比以下两个输出。
首先是未启用系统调用过滤的:
然后是启用系统调用过滤:
我们发现,如果开启了系统调用过滤功能,系统调用是不允许的另一个函数被调用的。该过滤函数验证是否启用系统调用过滤并简单的结束系统调用。
利用的结果
现在我们了解了系统调用过滤是如何工作的,我们需要研究它是如何防止内核漏洞利用的。首先要看看它保护的是什么进程,到目前为止,仅仅是微软Edge可以启用这个功能,目前第三方程序没有可以启用它的接口。这意味着系统调用过滤仅仅关心Microsoft Edge漏洞并且仅限于内核漏洞。下面我们可以看到MicrosoftEdgeCP.exe的进程结构,并启用了系统调用过滤:
回顾我早期文章,关于重新启用tagWND对象作为读写原语的用法的,我想知道是否使用这个方法的任意系统调用都会被过滤,在那个方法中使用的内核模式的函数是:
- NtUserCreateWindowEx
- NtUserDestroyWindow
- NtUserSetWindowLongPtr
- NtUserDefSetText
- NtUserInternalGetWindowText
- NtUserMessageCall
在win32k.sys中没有stub_*方法的函数意味着不会被过滤。结论就是Win32K系统调用过滤不会阻止可能导致漏洞的系统调用,但是当系统调用触发一个漏洞时,它一定会阻止,可能是write-that-wherer或者是一个缓冲区溢出。WIN32K系统调用过滤所提供的保护是巧妙的,但关键在于是否使用了系统调用来触发漏洞。