首页
社区
课程
招聘
[原创] 对任意地址写漏洞的一次分析
发表于: 2019-7-3 13:59 8766

[原创] 对任意地址写漏洞的一次分析

2019-7-3 13:59
8766

TERMDD.SYS是Windows Server 2008 R2中的远程桌面服务器驱动程序,该漏洞会造成对任意地址写操作(不是CVE-2019-0708),根据网上资料描述最新的CVE-2019-0708后的补丁也存在此漏洞,在网上并未查到其他信息,也无法确定是否是已知CVE。这个漏洞触发与ms11-011类似,都是在使用RtlQueryRegistryValues函数时枚举指定的注册表键值时造成栈溢出,从而导致对任意地址写操作。

分析环境:
调试宿主机: windows 10 x64
目标机子: windows 7 sp1 x64
调试器: windbgx.exe

termdd.sys在调用RtlQueryRegistryValues获取FlowControlDisable、FlowControlDisplayBandwidth、FlowControlChannelBandwidth、FlowControlChargePostCompression键值时,FlowControlDisplayBandwidth键类型本应该是REG_DWORD值,经过经过构造后改为REG_BINARY,在调用ZwQueryValueKey获取键值后,紧接着会将获取到的数据交给RtlpCallQueryRegistryRoutine函数解析,由于获取到键值大小并非是REG_DWORD,这时候发生发生栈溢出。大致运行逻辑,如下所示:

从这里将会详细的分析该漏洞的成因。通过MSDN查询到RtlQueryRegistryValues函数信息如下,我们需要关注的是第三个参数PRTL_QUERY_REGISTRY_TABLE,该结构体会在调用后返回需要查询的注册表键值的详细信息。相关代码以及函数,如下所示:

以上代码会在栈中存放4个PRTL_QUERY_REGISTRY_TABLE结构体,每个结构体大小是0x38个字节。
1.绿色和蓝色圈起来的部分代表其中的第一个和第二个结构体。
2.被红色方框圈起来的部分代表每个结构体的EntryContext成员,该成员是PVOID 型,指针所指内存大小是4字节。
3.被黄色所标记的是每个结构体的Flags成员,当成员为RTL_QUERY_REGISTRY_DIRECT (0x20)时,通过RtlQueryRegistryValues函数获取到的对应键值会存放在结构体的EntryContext成员中。
在这里需要关注的是第一个结构体的EntryContext成员他所指地址为0xFFFFF880045F3654与第一个结构体开始的位置0xFFFFF880045F3660相差仅为12字节。

RtlQueryRegistryValues会依次查询PRTL_QUERY_REGISTRY_TABLE 结构体中键名所对应键值,大概调用流程为调用RtlInitUnicodeString 初始键名,然后调用ZwQueryValueKey获取键值,最后调RtlpCallQueryRegistryRoutine将获取到的键值数据填充或调用到PRTL_QUERY_REGISTRY_TABLE 的QueryRoutine(回调函数)或EntryContext中,这个是根据Flags成员来决定的。如下代码摘抄自微软的wrk,后文也会引用其他代码,在这里不再叙述。RtlQueryRegistryValues函数部分代码,如下所示:

RTL_QUERY_REGISTRY_DIRECT (0x20)标志代表将获取到的键值数据以UNICODE_STRING结构体填写,存放在PRTL_QUERY_REGISTRY_TABLE 第四个参数EntryContext所指的内存中,该标志间接导致后面的对任意地址写操作。以下是RtlpCallQueryRegistryRoutine函数部分代码,如下所示:

接下来会调用RtlpQueryRegistryDirect (0x20)将内容从ValueData拷贝到Destination,如果键类型属于REG_SZ、REG_EXPAND_SZ、REG_MULTI_SZ 那么会将Destination 所指内存初始化为UNICODE_STRING结构体,然后拷贝到所指buff中。如果ValueLength小于等于sizeof(ULONG),那么会直接拷贝到Destination 所指内存上。除去以上俩者,还会对Destination所指内存取内容,判断取到的数值是否大于键值数据长度,如果是大于那么会将内容直接拷贝到Destination所指内存上。

假设我们的注册表类型属于REG_SZ ,那么我们的(PVOID)Destination所指内存会被初始化为UNICODE_STRING结构体,结合开头提到的栈分析,Destination所指向的内存是4字节(且距第一个结构体开始位置仅为12字节),而UNICODE_STRING结构体大小需要16字节,这16个字节包含有后3个结构体Destination所指向的内存,比对开头的栈分析可以更加明显看出,溢出的位置。下图为溢出后的栈,如下图所示:

通过之前的分析,如果将第一个要获取的键,键类型改为REG_SZ,会覆盖后三个PRTL_QUERY_REGISTRY_TABLE结构体的EntryContext成员所指内存。如果记得前面介绍RtlpQueryRegistryDirect函数拷贝数据的三种方式,其中最后一种方式会判断EntryContext成员所指内存内容数值是否大于当前获取到的键值长度,如果是,那么会将数据直接拷贝到EntryContext所指内存上,在次结合上图的栈分析看到溢出部分恰好属于第二个结构体EntryContext所指内容,这个时候在采集第二个结构体键值时,满足于RtlpQueryRegistryDirect函数第三种拷贝方式,直接拷贝,会将第二个注册表键值直接拷贝到EntryContext所指内存地址上。

由前面可知:
1.PRTL_QUERY_REGISTRY_TABLE每个结构体大小为0x38。
2.标志位为0x20会将数据拷贝到EntryContext成员所指内存。
3.当注册表键无法打开或者读取时,会将默认数据(PRTL_QUERY_REGISTRY_TABLE下的DefaultData成员)拷贝到PRTL_QUERY_REGISTRY_TABLE下的EntryContext成员所指内存,大小由PRTL_QUERY_REGISTRY_TABLE下的DefaultLength控制。

现在将第二个注册表的键类型定义为REG_BINARY,键值内容,填充0对齐到PRTL_QUERY_REGISTRY_TABLE结构体,其中PRTL_QUERY_REGISTRY_TABLE下的EntryContext(目的地址)、DefaultData(源地址)、DefaultLength(数据长度),由我们控制,当栈溢出发生后完成第三四个结构体内容的覆盖,从而对写任意地址。构造好的数据(地址由于多次调试以不在和上述图地址匹配),如下图所示:

《exploit 远程桌面服务驱动》—— 猪会被杀掉


[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

最后于 2019-7-8 10:13 被dx苹果的心愿编辑 ,原因: 图片丢死
收藏
免费 4
支持
分享
最新回复 (2)
雪    币: 47147
活跃值: (20445)
能力值: (RANK:350 )
在线值:
发帖
回帖
粉丝
2
图片丢失了,麻烦楼主一重新帖一下。
2019-7-7 18:26
0
雪    币: 3738
活跃值: (3872)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
3
感谢分享!
2019-9-11 10:35
0
游客
登录 | 注册 方可回帖
返回
//