0x00:前言
2018年5月微软发布了一次安全补丁,其中有一个是对内核空指针解引用的修复,本片文章从补丁对比出发,对该内核漏洞进行分析,对应CVE-2018-8120,实验平台是Windows 7 x86 sp1
0x01:补丁对比
对比四月和五月的安全补丁可以定位以下几个关键函数,逐个分析观察可以定位到我们本次分析的的关键函数SetImeInfoEx
可以看到五月的补丁对SetImeInfoEx
多了一层检验
IDA中观察4月补丁反汇编如下,稍微添加了一些注释
1 | signed int __stdcall SetImeInfoEx(signed int pwinsta, const void *piiex) |
5月补丁反汇编如下
1 | signed int __stdcall SetImeInfoEx(signed int pwinsta, const void *piiex) |
可以看到五月的补丁对于参数v3是否为零进行了一次检测,我们对比SetImeInfoEx
函数的实现发现,也就是多了对成员域 spklList
的检测,v3就是我们的spklList
,该函数的主要作用是对扩展结构IMEINFO
进行设置
1 | // nt4 源码 |
同样的修复我们可以在ReorderKeyboardLayouts
函数中看到,也是对spklList
成员域进行了限制
ReorderKeyboardLayouts
函数实现如下,可以看到函数也对spklList
进行了调用,我们这里主要分析SetImeInfoEx
函数
1 | // nt4 源码 |
结合上面微软对于两个函数的修复,我们可以猜测这次的修复主要是对spklList
成员域的错误调用进行修复,从SetImeInfoEx
函数的交叉引用中,因为只有一处交叉引用,所以我们可以追溯到调用函数NtUserSetImeInfoEx
,通过分析可以看到该函数的主要作用是对进程中的窗口进行设置
1 | signed int __stdcall NtUserSetImeInfoEx(char *buf) |
在SetImeInfoEx
函数中,我们可以看到传入的指针PWINDOWSTATION
指向结构体tagWINDOWSTATION
结构如下,也就是窗口站结构,其中偏移 0x14 处可以找到spklList
,我们需要关注的点我会进行注释
1 | 1: kd> dt win32k!tagWINDOWSTATION |
我们继续追溯到spklList
指向的结构tagKL
,可以看到是一个键盘布局对象结构体,结构体成员中我们可以看到成员piiex
指向一个基于tagIMEINFOEX
布局的扩展信息,而在SetImeInfoEx
函数中,该成员作为第二个参数传入,作为内存拷贝的内容,我们还可以发现有两个很相似的指针pklNext
和pklPrev
负责指向布局对象的前后
1 | 1: kd> dt win32k!tagKL |
piiex
指向的tagIMEINFOEX
的结构如下
1 | 1: kd> dt win32k!tagIMEINFOEX |
0x02:漏洞复现
通过上面对每个成员的分析,我们大概知道了函数之间的调用关系,这里再简单总结一下,首先当用户在R3调用CreateWindowStation
生成一个窗口时,新建的 WindowStation 对象其偏移 0x14 位置的 spklList 字段的值默认是零,如果我们调用R0函数NtUserSetImeInfoEx
,传入一个我们定义的 buf ,函数就会将 buf 传给 piiex 在传入 SetImeInfoEx 中,一旦调用了 SetImeInfoEx 函数,因为 spklList 字段是零,所以就会访问到零页内存,导致蓝屏,所以我们构造如下代码
1 |
|
运行发现果然蓝屏了,问题出在 win32k.sys
我们通过蓝屏信息定位到问题地址,确实是我们前面所说的SetImeInfoEx
函数
0x03:漏洞利用
利用思路
我们利用的思路首先可以想到因为是在win 7的环境中,我们可以在零页构造一些结构,所以我们这里首先获得并调用申请零页的函数NtAllocateVirtualMemory
,因为内存对齐的问题我们这里申请大小的参数设置为 1 以申请到零页内存
1 | // 申明函数 |
申请到内存我们就需要开始思考如何进行构造,我们再详细回顾一下漏洞复现例子中的一些函数,根据前面的例子我们知道,需要使用到CreateWindowStation
创建窗口函数,详细的调用方法如下
1 | HWINSTA CreateWindowStationA( |
创建好窗口站对象之后我们还需要将当前进程和窗口站对应起来,需要用到 SetProcessWindowStation
函数将指定的窗口站分配给调用进程。这使进程能够访问窗口站中的对象,如桌面、剪贴板和全局原子。窗口站上的所有后续操作都使用授予hWinSta
的访问权限
1 | BOOL SetProcessWindowStation( |
最后一步就是调用xxNtUserSetImeInfoEx
函数蓝屏,我们这里能做手脚的就是给xxNtUserSetImeInfoEx
函数传入的参数piiex
1 | // nt4 源码 |
我们在IDA中继续分析一下并粗略的构造一个思路,这里我根据结构重新注释修复了一下 IDA 反汇编的结果
1 | bool __stdcall SetImeInfoEx(DWORD *pwinsta, DWORD *piiex) |
需要清楚的是,我们最后SetImeInfoEx
中的拷贝函数会给我们带来什么作用,他会把我们传入的piiex
拷贝到tagKL->piiex
中,拷贝的大小是 0x15C ,我们这里其实想到的是拷贝之后去覆盖 HalDispatchTable+0x4
的位置,然后调用NtQueryIntervalProfile
函数提权,所以我们只需要覆盖四个字节,为了达到更精准的覆盖我们想到了 win10 中的滥用Bitmap对象达到任意地址的读和写,那么在 win 7 中我们如何运用这个手法呢?其实很简单,原理上和 win 10 相同,只是我们现在有个问题,要达到任意地址的读和写,我们必须得让hManagerPrvScan0
指向hworkerPrvScan0
,我们如何实现这个目标呢?聪明的你一定想到了前面的拷贝函数,让我们先粗略的构造一个利用思路:
- 初始化申请零页内存
- 新建一个窗口并与当前线程关联
- 申请并泄露Bitmap中的PrvScan0地址
- 在零页构造结构体绕过检查实现能够调用拷贝函数
- 构造
xxNtUserSetImeInfoEx
函数的参数并调用实现hManagerPrvScan0
指向hworkerPrvScan0
- 将
HalDispatchTable+0x4
内容写为shellcode的内容 - 调用
NtQueryIntervalProfile
函数运行shellcode提权
xxNtUserSetImeInfoEx参数构造
有了思路我们现在就只差时间了,慢慢的调试总能给我们一个完美的结果(吗),我们知道NtUserSetImeInfoEx
函数的参数是一个tagIMEINFOEX
结构而tagKL
则指向这个结构,根据前面IDA中的注释,我们知道我们需要绕过几个地方的检验,从检验中我们可以发现需要做手教的地方分别是tagKL->hkl
和tagKL->piiex
,我们的tagKL->hkl
需要和传入的piiex
地址一致,tagKL->piiex
这个结构有两处检验,第一处是自己不能为空,第二处是tagIMEINFOEX->fLoadFlag
也必须赋值,观察Bitmap的结构,我们知道 +0x2c 偏移处刚好不为零,所以我们考虑如下构造,把tagKL->piiex
赋值为pManagerPrvScan0
,把tagKL->hkl
赋值为pWorkerPrvScan0
,为了使传入的piiex
与我们的tagKL->hkl
相等,我们将其构造为pWorkerPrvScan0
的结构
1 | DWORD* faketagKL = (DWORD*)0x0; |
在xxNtUserSetImeInfoEx
函数之后下断点你会发现已经实现了pManagerPrvScan0->pWorkerPrvScan0
,这时我们就可以尽情的任意读写了
GetShell
最后提权的过程还是和以前一样,覆盖HalDispatchTable+0x4
函数指针,然后调用NtQueryIntervalProfile
函数达到运行shellcode的目的
1 | VOID GetShell() |
最终整合一下思路和代码我们就可以提权了(不要在意这盗版的win 7…),效果如下,详细的代码参考 => 这里
0x04:后记
这个漏洞也可以在win 7 x64下利用,后续我会考虑把64位的利用代码完善一下,思路都差不多,主要修改的地方是偏移和汇编代码的嵌入问题,这个漏洞主要是在零页的构造,如果在win 8中就很难利用,毕竟没有办法在零页申请内存
参考资料: