Automatically Discovering Windows Kernel Information Leak Vulnerabilities

author : fanxiaocao(@TinySecEx) and @pjf_ of IceSword Lab , Qihoo 360


TL;DR

This Patch-Tuesday MS fixed 6 kernel information leak vulnerabilities reported by us, the details are at the end of this article.
I had already show how to fuzz the windows kernel via JS , today we will introduce a new method to discover windows kernel vulnerabilities automatically without fuzzing.
I selected a small part from the work in the past few months to spread out this topic.

KASLR

In Windows Vista and above, Microsoft enable Kernel Address Space Layout Randomization (KASLR) by default to prevent exploitation by placing various objects at random addresses, rather than fixed ones. It is an effective method against exploitation using Return-oriented Programming (ROP) attack.

Beginning with Windows 8, KASLR is enhanced with a newly introduced function ExIsRestrictedCaller.
Programs under medium integrity are not able to invoke functions such as NtQuerySystemInformation to obtain addresses of kernel modules, kernel objects or pools.

The functions include but not limited to:

NtQuerySystemInformation

* SystemModuleInformation 
* SystemModuleInformationEx 
* SystemLocksInformation 
* SystemStackTraceInformation 
* SystemHandleInformation 
* SystemExtendedHandleInformation 
* SystemObjectInformation 
* SystemBigPoolInformation 
* SystemSessionBigPoolInformation 
* SystemProcessInformation
* SystemFullProcessInformation

NtQueryInfomationThread

NtQueryInfomationProcess

The above is the traditional way to get the kernel module address and kernel object address, as the kernel normal feature.
But after win8, low integrity application will fail in calling these functions.

In order to bypass KASLR, a direct countermeasure is to discover vulnerabilities that leak valuable information from the kernel mode to calculate the address of kernel module or kernel object.

Kernel Information Leak

As a kind of kernel vulnerability, it has its own uniqueness. For example, for the traditional memory damage vulnerabilities, the vulnerability itself will affect the running of the kernel. With the help of verifier and other tools, you can easily capture this exception among the normal traffic.
But the kernel information leak vulnerability does not trigger any exception, nor does it affect the running of the kernel, which makes it more difficult to be discovered.
Vulnerabilities objectively exist, what we need to do is to find them at lowest cost.

Discover ideas

When kernel information leak vulnerability occurs, the kernel will certainly write some valuable data to the user buffer.
So if we monitor all the writing behaviors to user buffer in the kernel, we will be able to find them.

Of course, the system does not provide this feature.
I capture the process with the help of a hardware virtualization based framework of pjf,
who is the author of the famous windows kernel anti-rootkit tool named iceSword.

In order not to affect the dest system itself, I monitored in the VMWARE guest and write some log files, and then further analyze them in the host system.

In the host machine, after decoding and analyzing the logs:

Then we have the human-readable logs:

Further Analysis

Now we have operation records in user memory buffer written by kernel.
Most of them are just normal functions.

We need remove nosiy data to find out the key information.
Two skills are needed.

Poison the kernel stack

Poisoning or polluting the target is a common idea.
At network penetration testing, there are also ARP and DNS cache poisoning.

Here is the kernel stack poisoning, refers to the pollution to the entire unused kernel stack space.

If a variable on a kernel stack is not initialized, then when this variable is written to the user buffer, there will be a magic value in the record written by me. Wherever these is a magic value, there is a leak.

I noticed that j00ru also used similar techniques in his BochsPwn project.

KiFastCallEntry Hook

In order to poison the kernel stack, I hooked nt!KiFastCallEntry.
So that when a syscall invoked, I can poisoning the entire unused kernel stack space.

Firstly, I used IoGetStackLimits to get the current thread stack range, and then from the bottom of the stack to the current stack location of the entire space are filled with 0xAA.

So when I entered the syscall, all the contents of the local variables on the kernel stack will be filled into 0xAA.

Poison the kernel pool

Similarly, for dynamically allocated memory, I used hook nt!ExAllocatePoolWithTag and so on, and polluted its POOL content.

If the kernel stack/heap variable is not properly initialized, it is possible to write this magic value to the user buffer.

With the help of the logs we captured, we can immediately find this vulnerability.
In order to remove the coincidence, I also used a number of magic value such as 0xAAAAAAAA , 0xBBBBBBB to exclude false positives.

A typical result after excluding the interference is as follows.

You can see that in a short monitoring process, it caught the 161 leaks in the system!
Of course, this is not exhaustive. There are not so many independent vulnerabilities, but some vulnerabilities made repeated leaks.

At this point we caught a real information leak vulnerability, there is stack information, supplemented by a simple manual analysis, we can got the details.
This is also the story behind the CVE-2017-8482.

Difference comparison

For the kernel information leak caused by the uninitialized stack, we can poison them at first and then find them.
But for the direct disclosure of key information, such as the module and the object address written directly, it cannot be found in this way.

In the process of the system running, the kernel itself will frequently write data to the user buffer, a lot of data is in the kernel address range, but in fact it is not a valid address, but a noise data.
There are many such noise data, such as strings, pixels, rect, region, etc. which are likely happen to be a kernel address. We need to rule out the noise and found a real leak.

Here we filter out some meaningful addresses, such as:

  1. Module address, must be inside in the system module list
  2. object address
  3. POOL address

After the environment changes, such as restarting the system, it must be able to leak the same type of data at the same location.

After the exclusion of the normal function of the system, such as NtQuerySystemInformation and similar functions, the left data’s credibility is very high.

The leak of module address

For example CVE-2017-8485

You can see that the results at this time is very obvious - the same stack, the same location, are leaked nt! ObpReferenceObjectByHandleWithTag + 0x19f

The leak of object address

Due to leakage of object address and POOL address not fixed by Microsoft this month, I cannot describe the details.

More

You can see that we do not need a fuzzer, only through the code coverage generated by normal running of the system itself, we found these vulnerabilities.
Any normal program running can improve this coverage.
In fact, in the actual work, I only use the game and the browser to improve coverage and got good results.
A game finished, ten kernel vulnerabilities on the hand.

The case of this month

CVE-2017-8470

CVE-2017-8474

CVE-2017-8476

CVE-2017-8482

CVE-2017-8485

自动化挖掘 windows 内核信息泄漏漏洞

author : fanxiaocao(@TinySecEx) and @pjf_ of IceSword Lab , Qihoo 360


前言

2017年6月补丁日,修复了我们之前报告的6个内核信息泄漏漏洞 , 文章末尾有细节。
前年我演示过如何用JS来fuzz 内核,今天我们要给大家带来的是不依赖fuzz,来自动化挖掘内核漏洞。
从最近的几个月工作里,选取了一个小点,说下内核信息泄漏类型漏洞的挖掘。

背景

windows vista 之后,微软对内核默认启用了了ASLR ,简称KASLR.
KASLR 随机化了模块的加载基址 , 内核对象的地址等,缓解了漏洞的利用。

在win8 之后,这项安全特性的得到了进一步的增强。
引入 nt!ExIsRestrictedCaller 来阻止Low integrity 的程序调用某些可以泄漏出模块基址,内核对象地址等关键信息的函数。
包括但不限于:

NtQuerySystemInformation

* SystemModuleInformation 
* SystemModuleInformationEx 
* SystemLocksInformation 
* SystemStackTraceInformation 
* SystemHandleInformation 
* SystemExtendedHandleInformation 
* SystemObjectInformation 
* SystemBigPoolInformation 
* SystemSessionBigPoolInformation 
* SystemProcessInformation
* SystemFullProcessInformation

NtQueryInfomationThread

NtQueryInfomationProcess

以上是传统的可以获取 内核模块地址和内核对象地址的方法 , 作为内核正常的功能。
但对于integrity 在medium 以下的程序,在win8 以后调用会失败。

KASLR 作为一项漏洞利用缓解措施,其中的一个目的就是为了使得构建通用的ROP-CHAIN 更为困难.
作为漏洞的利用者来说,挖掘出信息泄漏漏洞,来直接泄漏出所需要的模块基址,就是直接对抗KASLR的办法。

特点

作为内核漏洞的一种,在挖掘的过程中有特殊的地方。比如,对于传统内存损坏类漏洞而言,漏洞本身就会影响系统的正常运行,使用verifier等工具,能较为方便的捕获这种异常。
但是信息泄漏类型的漏洞,并不会触发异常,也不会干扰系统的正常运行,这使得发现它们较为困难。
漏洞是客观存在的,我们需要做的以尽可能小的成本去发现它们。

挖掘思路

泄漏发生时,内核必然会把关键的信息写入用户态的内存,如果我们监控所有内核态写用户态地址的写操作,就能捕获这个行为。
当然系统并没有提供这个功能,这一过程由@pjf的一个专门的基于硬件虚拟化的挖掘框架进行捕获。

为了不干扰目标系统本身的操作,我在虚拟机里执行监控,获取必要的信息,在写成log后,再在宿主机进行二次分析。


在物理机里,解码日志并加载符号,做一些处理之后

就得到这样的一批日志。

二次分析

现在我们有了一段实际运行过程中内核写到用户态内存的所有记录。这里面绝大多数都是正常的功能,
我们需要排除掉干扰,找出数据是关键信息的。
这里主要用到了两个技巧。

污染内核栈

毒化或者说污染目标数据,是一种常见的思路。在网络攻防里,也有ARP 和DNS缓存的投毒。
这里所说的内核栈毒化,指的就是污染整个未使用的内核栈空间。如果某个内核栈上的变量没有初始化,
那么在这个变量被写到到用户态时,写入的数据里就有我所标记的magic value ,找出这个magic value所在的记录,就是泄漏的发生点。
同时我注意到,j00ru 在他的BochsPwn项目里也曾使用了类似的技巧。

KiFastCallEntry Hook

为了有时机污染内核栈,我Hook 了KiFastCallEntry , 在每个系统调用发生时,污染当前栈以下剩余栈空间。

首先使用 IoGetStackLimits 获取当前线程的范围,然后从栈底部到当前栈位置的整个空间都被填充为0xAA 。
这样进入系统调用之后,凡是内核堆栈上的局部变量的内容,都会被污染成0xAA。

污染内核POOL

类似的,对于动态分配的内存,我采用hook ExAllocatePoolWithTag等,并污染其POOL内容的方式。

这样,无论是栈上的,还是堆上的,只要是未初始化的,内容都被我们污染了。
如果这个内核堆栈变量没有正确的初始化,就有可能将这个magic value写入到用户态的内存。结合我们捕获的日志,就能马上发现这个信息泄漏。

为了排除掉巧合,使用了多次变换magic value 如 0xAAAAAAAA , 0xBBBBBBBB 的办法来进行排除误报。

排除干扰之后的一次典型的结果如下

可以看到,在某次短暂的监控过程中,就抓到了系统里 161 次泄漏。
当然这没有排重,并不是有这么多个独立的漏洞,而是某些漏洞在反复的泄漏。
此时我们就抓到了一个真正的信息泄漏漏洞,有堆栈信息,再辅以简单的人工分析,就能知道细节,
这也是 CVE-2017-8482 背后的故事。

差异比对

对于未初始化堆栈所导致的内核信息泄漏,我们可以用污染然后查找标记的方式发现。
对于直接泄漏了关键信息的,比如直接写入了模块,对象,POOL地址类型的,就不能用这种方法发现了。

在系统运行过程中,内核本身就会频繁的向用户态写入数据,很多数据在内核地址范围内,但实际上并不是有效的地址,只是一种噪音数据。
这种噪音数据有很多,像字符串,像素,位置信息等都有可能恰好是一个内核地址,我们需要排除掉这些噪音,发现真正的泄漏。

这里我们过滤出一部分有意义的地址,比如

  1. 模块地址,必须在内核模块地址范围内。
  2. object地址
  3. POOL地址

在环境改变,比如重启系统之后 ,必须还能在相同的位置泄漏相同类型的数据。

在排除掉系统正常的功能如 NtQuerySystemInformation 之类的之后,得到的数据,可信度就非常高了。

泄漏模块地址

CVE-2017-8485 为例,比对之后得到的结果

可以看到,此时的结果就非常直观了,相同的堆栈来源在相同的位置下,都泄漏了nt!ObpReferenceObjectByHandleWithTag+0x19f
这个地址。

泄漏object地址

由于泄漏object地址和POOL地址的本月微软还没来得及出补丁,不能描述细节。

可以看到其中的一个案例,某个函数泄漏一个相同object的地址。
值得一提的是,对于这种不是从堆栈上复制数据产生的泄漏,是无法用污染堆栈的方法发现的。

最后

可以看到,我们不需要专门的fuzz,仅仅依靠系统本身的运行产生的代码覆盖,就发现了这些漏洞。
任何程序的正常运行,都能提高这个覆盖率。
事实上,在实际的挖掘过程中,我仅仅使用了运行游戏和浏览器的办法就取得了良好的效果 , 一局游戏打完,十个内核洞也就挖到了。

本月案例

CVE-2017-8470

CVE-2017-8474

CVE-2017-8476

CVE-2017-8482

CVE-2017-8485