0x1 漏洞描述
CVE-2018-8120漏洞是一个位于win32k模块中的SetImeInfoEx
函数的任意地址覆盖漏洞,漏洞产生的根本原因是没有对tagWINDOWSTATION
结构的spklList
成员做有效性验证,就对其进行解引用,如果spklList
为NUll的话,继而对其进行解引用,导致漏洞触发。
0x2 漏洞分析
根据SetImeInfoEx
的反汇编结果,可知pklFirst = pwinsta->spklList;
,在取出tagWINDOWSTATION结构体的spklList
的成员之后,并没有对pklFirst
进行有效性验证,便对pklFirst
进行解引用操作,while ( pklFirst->hkl != piiex->hkl)
,假设pklFirst
为NULL,便导致任意地址覆盖漏洞。
换言之,我们只需要进行构造一个tagWINDOWSTATION
,使得tagWINDOWSTATION+0x14偏移的spklList
成员为NULL,然后在对spklList
进行解引用时。继而程序崩溃,导致任意地址覆盖漏洞。
0x3 漏洞验证
对SetImeInfoEx进行交叉引用,发现是由NtUserSetImeInfoEx
函数调用,首先,将传入的参数tagIMEINFOEX复制到piiex,然后创建一个WindowsStation,并将这两个作为参数传入SetImeInfoEx。
由于NtUserSetImeInfoEx
函数并没有导出,只能通过系统调用的方式进行调用。首先将参数放到esi寄存器中,将调用号放到eax寄存机中,系统调用号可以利用PCHunter,在内核钩子–SSDT中,找到NtUserSetImeInfoEx的编号为550,也就是226,然后加上0x1000的偏移,就是0x1226.
根据NtUserSetImeInfoEx
函数和SetImeInfoEx
的伪代码,函数验证需要两个基本条件,第一,根据传入SetImeInfoEx
参数为tagWINDOWSTATION
,需要使用CreateWindowStation
函数创建一个WindowStation结构。第二,根据v2 = _GetProcessWindowStation(0);
可以知道,需要将创建的WindowStation设置为当前进程的。
0x4 漏洞利用
根据任意地址覆盖漏洞常规的利用方法,将ShellCode地址复制到HalQuerySystemInformation地址上,然后调用NtQueryIntervalProfile
执行即可。
首先,我们开辟零页内存和获取HalQuerySystemInformation地址,开辟零页内存是为了后续能够有空间存放HalQuerySystemInformation地址和Shellcode,不至于在触发漏洞的时候崩溃。
根据伪代码描述,只需要v4,即pklFirst->piiex,存放的是HalQuerySystemInformation,piiex存放的是Shellcode,然后调用NtUserSetImeInfoEx
触发任意地址读写,将Shellcode覆写到HalQuerySystemInformation地址上,最后调用NtQueryIntervalProfile执行即可。
|
|
但是,在后续的调试中,发现并不是执行memcpy的操作,导致覆写失败。
0x05 Bitmap GDI
BitmapGDI,通过Bitmap对象泄露可供读写的内核区域,从而将任意地址覆写漏洞转化为任意地址读写漏洞。R3通过使用CreateBitmap
函数创建一个Bitmap对象,在Bitmap对象中有一个指针pvScan0,指向一段内存域名。pvScan0指针可以在R3通过GetBitmaps以及SetBitmaps函数进行操作。至此,通过这两个函数,可以将一个任意地址复写漏洞转化成一个任意地址读写漏洞。
CreateBitmap
函数原型如下,
当调用CreateBitmap
之后,会在进程PEB偏移+0x94的GdiSharedHandleTable
数组中增加一个索引。
该索引是一个_GDICELL
结构。
GDICELL
结构的第一个成员pKernelAddress
指向的是一个SURFACE
对象,结构体定义如下,其中比较重要的是BASEOBJECT
和SURFOBJ
对象,pvScan0
指针便位于SURFOBJ
对象中。
下图可以清晰的观察SURFACE
结构的内存布局,有两个主要的结构。一个叫 BASEOBJECT
对象,每一个 GDI 对象都有的一个头部。另一个叫SURFOBJ
对象,保存了包括我们参数信息的实际结构。BASEOBJECT
结构位于SURFOBJ
之前,在寻找pvScan0
指针过程中,我们只需要知道这个结构大小即可。在x86中,BASEOBJECT的大小是0x10
,而在x64中,BASEOBJECT的大小是0x18
。在图中,可以清楚的看到pvScan0指针指向PixelData区域。
|
|
接着如何使用BitmapGDI技术将一个任意地址覆写漏洞,改造成一个任意地址读写漏洞。首先,我们的目标是获取pvScan0指针,根据上面的接收pvScan0位于SURFACE
对象中的SURFOBJ
对象第0x20偏移处。而SURFACE
对象需要根据GDICELL
结构的第一个成员pKernelAddress
确定的。而GDICELL
是GdiSharedHandleTable表中的其中一个索引。所以确定pvScan0指针需要分三步。
第一:根据PEB+0x94的偏移确定GDICELL数组的首地址。
第二根据CreateBitmap返回的HBITMAP对象,以此作为索引确定GDICELL
结构。
第三,获取GDICELL
对象的第一个成员pKernelAddress
指向的SURFACE对象,在SURFACE对象的0x30偏移就是pvScan0指针。
现在我们知道了pvScan0指针,那么怎么利用漏洞修改pvScan0指针指向的内容呢?首先,创建两个Bitmap对象:Work以及Manager。并获取两个BitMap对象的pvScan0指针。分别记做workerpvScan0和managerpvScan0指针。
|
|
然后通过任意地址覆写漏洞,改写pvScan0指针。将workerpvScan0指针覆写到managerpvScan0指针。
接着通过Set\GetBitmaps,修改\获取pvScan0指针指向的内容。即就是将ManageBitmap对象中的pvScan0指向的内存区域修改为pHalQuerySystemInformation地址,然后再将WorkerBitmap对象的pvScan0指向的内存区域修改为Shellcode地址
这一部分可以这样理解,首先pvScan0指针指向的是一段内核区域,通过覆写漏洞,将workerpvScan0指向的地址覆盖到managerpvScan0指向的地址,然后先修改hManger的内核区域为HalQuerySystemInformation,接着修改hWorker的内核区域,也就是hManager的内核区域,也就是HalQuerySystemInformation地址为ShellCode地址。