2.分析环境:
操作系统:windows7 X86 sp1
IP:172.16.11.2
调试器:Windbg,用于调试windows内核组件
反汇编器:IDA pro,用于反编译win32k.sys
漏洞软件:Mlaware Defender,用于hook相关系统函数
3.分析目的:
了解Windows本地提权漏洞(CVE-2018-8120)原理
验证Windows本地提权漏洞(CVE-2018-8120)
了解漏洞修复方法
4.漏洞原理:
该漏洞的触发点是窗口tagWINDOWSTATION对象的指针成员域spklList指向的可能是空地址,如果同时该窗口关联当前进程,那么调用系统服务函数NtUserSetImeInfoEx设置输入法扩展信息时,会间接调用SetImeInfoEx函数访问spklList指针指向的位于用户进程地址空间的零页内存.
如果当前进程的零页内存未被映射,函数SetImeInfoEx的访问操作将引起缺页异常,导致系统BSOD;同样,如果当前进程的零页内存被提前映射成我们精心构造的数据,则有可能恶意利用,造成任意代码执行漏洞
5.分析步骤:
一.windbg调试本地内核
该模式使得我们可以本地调试windows系统的内核,但是,本地调试内核模式不能使用执行命令,断点命令和堆栈跟踪命令
下面是windbg本地内核调试模式的配置步骤:
使用管理员身份打开cmd,执行bcdedit /debug on 开启调试模式
使用管理员身份打开cmd,执行bcdedit /debug on 开启调试模式
使用管理员权限打开windbg(一定是管理员权限,不然不起作用),然后依次选择
File‐>Kernel Debugging‐>Local‐>确定
经过上面的设置就可以进行本地内核调试
二.查看SSTD表和SSTD shadow表
在windows系统中,系统内核函数分为两种:
1.常用的系统服务,实现在内核文件中;
2.与图形显示和用户界面相关的系统服务,实现在win32k.sys文件中全部的系统服务在系统运行期间都存储在系统的内存区,系统使用两个系统服务地址表
KiServiceTableWin32pServiceTable管理这些系统服务,同时设置两个系统服务描述表(SDT)管理系统服务地址表,这两个系统服务描述表是ServiceDescriptorTable(SSDT)和ServiceDescriptorTableShadow(SSDTShadow)
其中,前者只包含KiServiceTable表,后者包含KiServiceTable和Win32pServiceTable两个表,而且SSDT是可以直接调用访问的,SSDTShadow不可以直接调用访问
SDT对象的结构体如下:
typedef struct _KSYSTEM_SERVICE_TABLE
{
PULONG ServiceTableBase; // 系统服务地址表地址
PULONG ServiceCounterTableBase;
PULONG NumberOfService; // 服务函数的个数
ULONG ParamTableBase; // 该系统服务的参数表
} KSYSTEM_SERVICE_TABLE, *PKSYSTEM_SERVICE_TABLE;
通过windbg本地内核调试查看相关系统服务描述表实际结构分布:
上面两个是两种SDT表中的结构,每个表中的前两行分别表示两个系统服务地址表KiServiceTable表和Win32pServiceTable表的相关数据信息结合上面的结构体可以看出,KiServiceTable的地址是0x83cbfd9c,其中包含0x191个系统服务;
Win32pServiceTable的地址是0x98446000,其中包含0x339个系统服务,而因为上面的是SSDT表,不包含WinpServiceTable表,所以第一个表中的第二行数据为空再查看系统服务地址表存储具体的内容:
从上面可以看出系统服务地址表中存储的都是四个字节的函数指针,这些指针指向的就是对应的系统服务函数
三.查看窗口站结构体信息
窗口站(Windows Station)相当于是一个容器对象,每一个窗口站包含一个剪切板,一个原子表和一个或者多个桌面对象通过windbg来查看窗口站对象在内核中的结构体实例:
上面就是窗口站tagWINDOWSTATION的结构体定义,其中在偏移0x14处的spklList指针指向关联的键盘布局tagKL对象链表首节点(这个指针就是漏洞利用的关键)
键盘布局的结构体定义如下:
键盘布局tagKL结构体中在偏移0x2c处的piiex指针指向关联的输入法扩展信息结构体对象,也就是SetImeInfoEx函数内存拷贝的目标地址
当用户进程调用CreateWindowStation函数等相关函数创建新的窗口站时,最终会调用内核函数xxxCreateWindowStation执行窗口站的创建,但是在该函数执行期间,被创建的新窗口站实例的spklList指针并没有被初始化,指向的是空地址
四.分析SetImeInfoEx函数
函数SetImeInfoEx是一个win32k组件中的内核函数,主要负责将输入法扩展信息tagIMEINFOEX对象拷贝到目标键盘布局tagKL对象的结构体指针piiex指向的输入法信息对象的缓冲区,也就是承担一个快递员的角色,但是也就是在送快递过程中出现了问题
使用IDA对win32.sys进行初步的逆向,还原一下这个配送流程:
IDA加载win32k.sys组件并手动载入符号表
选择File‐‐>loadfile‐‐>pdbfile,然后点击弹出窗口的OK选项
在函数框中使用Ctrl+F查找SetImeInfoEx函数,并使用F5反编译出函数的伪代码:
从上面的伪代码中可以看出,函数SetImeInfoEx首先从参数a1指向的窗口站对象中获取spklList指针(a1是窗口站地址指针,偏移0x14就是spklList指针),也就是指向键盘布局链表tagKL首节点地址的指针下面就是遍历键盘布局对象链表,并判断每个被遍历的节点对象的成员域hkl是否与源输入法扩展信息对象的成员域hkl相等若等跳出循环验证piiex指针是否指向真实键盘布局对象缓冲区,变量LoadFlag是否为false,所有条件都成立,可进行内存拷贝
五.利用Poc验证漏洞
根据SetImeInfoEx函数的伪代码,可以看到当函数执行到while循环里面,将第一次调用这个spklList指针,去尝试访问该指针本该指向的键盘布局tagKlList指针,去尝试访问该指针本该指向的键盘布局tagKL对象链表首节点的时候由于对新建的窗口站,其spklList指针是空的,却意外地访问读取零页内存,而且是未被映射的零页内存,这样就会导致缺页异常,进而使得系统BOSD
因此触发漏洞的条件是要将spklList指针指向空地址的窗口站关联到进程中具体实现就是先通过接口函数CreateWindowStation创建一个窗口站,然后调用
NTUserSetImeInfoEx函数关联该窗口站和进程因为NtUserSetImeInfoEx函数未导出,使用Malware Defender来hook得到序列号,再通过序列号计算出服务号
在桌面运行Malware Defender,选择钩子‐‐>Win32k服务表,查看系统服务序列号
得到NtUserSetImeInfoEx函数的序号是550,那么该函数的系统服务号就是0x1000+0x226=0x1226。之后还需要得到SystemCallStub函数地址来调用内核函数
实现最后的poc代码:
#include <Windows.h>
#include <stdio.h>
__declspec(naked) void NtSetUserImeInfoEx(PVOID imeinfoex)
{
__asm
{
mov eax, 0x1226 //将NtUserSetImeInfoEx函数的服务号传入eax中
mov edx, 0x771870b0 // 将SystemCallStub函数地址传入edx中
call dword ptr[edx] //调用SystemCallStub函数
ret 0x04
} } i
nt main()
{ H
WINSTA hSta = CreateWindowStationW(0, 0, READ_CONTROL, 0); //使用
CreateWindowStation函数创建一个窗口站
SetProcessWindowStation(hSta);
char ime[0x800];
NtSetUserImeInfoEx((PVOID)&ime); //调用NtUserSetImeInfoEx函数触发漏洞,
致使系统BSODreturn 0;
}
以管理员身份打开cmd,执行以下命令:
bcdedit /debug off //关闭调试模式
shutdown ‐r ‐t 0 //重启
编译运行Poc,生成exe,成功触发漏洞,致使系统BSOD。
使用管理员权限打开windbg(一定是管理员权限,不然不起作用),然后依次选择
File‐>Kernel Debugging‐>Local‐>确定
经过上面的设置就可以进行本地内核调试
二.查看SSTD表和SSTD shadow表
在windows系统中,系统内核函数分为两种:
1.常用的系统服务,实现在内核文件中;
2.与图形显示和用户界面相关的系统服务,实现在win32k.sys文件中全部的系统服务在系统运行期间都存储在系统的内存区,系统使用两个系统服务地址表
KiServiceTableWin32pServiceTable管理这些系统服务,同时设置两个系统服务描述表(SDT)管理系统服务地址表,这两个系统服务描述表是ServiceDescriptorTable(SSDT)和ServiceDescriptorTableShadow(SSDTShadow)
其中,前者只包含KiServiceTable表,后者包含KiServiceTable和Win32pServiceTable两个表,而且SSDT是可以直接调用访问的,SSDTShadow不可以直接调用访问
SDT对象的结构体如下:
typedef struct _KSYSTEM_SERVICE_TABLE
{
PULONG ServiceTableBase; // 系统服务地址表地址
PULONG ServiceCounterTableBase;
PULONG NumberOfService; // 服务函数的个数
ULONG ParamTableBase; // 该系统服务的参数表
} KSYSTEM_SERVICE_TABLE, *PKSYSTEM_SERVICE_TABLE;
typedef struct _KSYSTEM_SERVICE_TABLE
{
PULONG ServiceTableBase; // 系统服务地址表地址
PULONG ServiceCounterTableBase;
PULONG NumberOfService; // 服务函数的个数
ULONG ParamTableBase; // 该系统服务的参数表
} KSYSTEM_SERVICE_TABLE, *PKSYSTEM_SERVICE_TABLE;
通过windbg本地内核调试查看相关系统服务描述表实际结构分布:
上面两个是两种SDT表中的结构,每个表中的前两行分别表示两个系统服务地址表KiServiceTable表和Win32pServiceTable表的相关数据信息结合上面的结构体可以看出,KiServiceTable的地址是0x83cbfd9c,其中包含0x191个系统服务;
Win32pServiceTable的地址是0x98446000,其中包含0x339个系统服务,而因为上面的是SSDT表,不包含WinpServiceTable表,所以第一个表中的第二行数据为空再查看系统服务地址表存储具体的内容:
从上面可以看出系统服务地址表中存储的都是四个字节的函数指针,这些指针指向的就是对应的系统服务函数
三.查看窗口站结构体信息
窗口站(Windows Station)相当于是一个容器对象,每一个窗口站包含一个剪切板,一个原子表和一个或者多个桌面对象通过windbg来查看窗口站对象在内核中的结构体实例:
上面就是窗口站tagWINDOWSTATION的结构体定义,其中在偏移0x14处的spklList指针指向关联的键盘布局tagKL对象链表首节点(这个指针就是漏洞利用的关键)
键盘布局的结构体定义如下:
键盘布局tagKL结构体中在偏移0x2c处的piiex指针指向关联的输入法扩展信息结构体对象,也就是SetImeInfoEx函数内存拷贝的目标地址
当用户进程调用CreateWindowStation函数等相关函数创建新的窗口站时,最终会调用内核函数xxxCreateWindowStation执行窗口站的创建,但是在该函数执行期间,被创建的新窗口站实例的spklList指针并没有被初始化,指向的是空地址
函数SetImeInfoEx是一个win32k组件中的内核函数,主要负责将输入法扩展信息tagIMEINFOEX对象拷贝到目标键盘布局tagKL对象的结构体指针piiex指向的输入法信息对象的缓冲区,也就是承担一个快递员的角色,但是也就是在送快递过程中出现了问题
使用IDA对win32.sys进行初步的逆向,还原一下这个配送流程:
IDA加载win32k.sys组件并手动载入符号表
选择File‐‐>loadfile‐‐>pdbfile,然后点击弹出窗口的OK选项
在函数框中使用Ctrl+F查找SetImeInfoEx函数,并使用F5反编译出函数的伪代码:
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)