CVE-2021-1732 LPE漏洞分析
概述
CVE-2021-1732是一个发生在windows内核win32kfull模块的LPE漏洞,并且由于创建窗口时调用win32kfull!xxxCreateWindowEx过程中会进行用户模式回调(KeUserModeCallback),从而给了用户态进程利用的机会。
该漏洞由安恒信息在2020年12月在野外攻击样本中发现,在2021年2月份公开披露。相关样本在2020年APT组织蔓灵花针对国内的一次攻击中作为提权组件被发现。
分析
Windows中创建窗口时,会调用API CreateWindowEx,最终在内核会调用至win32kfull!xxxCreateWindowEx。在win10 1909上调试时调用堆栈回溯如下:
... ... win32kfull!xxxCreateWindowEx+0x1259
... ... win32kfull!NtUserCreateWindowEx+0x6a0
... ... nt!KiSystemServiceCopyEnd+0x25
... ... win32u!NtUserCreateWindowEx+0x14
... ... USER32!VerNtUserCreateWindowEx+0x211
... ... USER32!CreateWindowInternal+0x1b4
... ... USER32!CreateWindowExW+0x82
win32kfull模块的xxxCreateWindowEx函数为最终负责窗口对象创建的过程。CVE-2021-1732主要是在win32kfull!xxxCreateWindowEx调用win32kfull! xxxClientAllocWindowClassExtraBytes进行窗口扩展内存时触发。xxxClientAllocWindowClassExtraBytes函数中会调用KeUserModeCallback进行用户模式回调,以在用户模式执行回调。该函数中指定的回调ApiNumber为0x7B,即为user32! _xxxClientAllocWindowClassExtraBytes。相关回调函数表可在PEB->KernelCallBackTable中查看。
查看user32! _xxxClientAllocWindowClassExtraBytes,只是在用户模式当前进程堆中分配了指定大小的空间,并将分配的堆地址通过NtCallbackReturn传回内核。

由于用户模式回调函数的执行是在用户态进行,因此用户可以直接从进程中对该函数进行Hook,改变执行流程。
分析时使用POC为https://github.com/KaLendsi/CVE-2021-1732-Exploit,经过和原始样本的比对,可以发现该POC是对原始样本的完全还原,仅是在部分变量名含义上不正确。
漏洞首先对user32! _xxxClientAllocWindowClassExtraBytes进行Hook,在之后进程每次调用CreateWindowExW创建窗口时将会走到Hook函数处。替换后的KernelCallBackTable如下所示:

接着创建多个普通窗口,后续都会经过Hook函数。对于普通窗口,Hook函数仍旧按照旧流程,为其调用user32! _xxxClientAllocWindowClassExtraBytes。判断依据是传入的参数值,即tagWnd. cbwndExtra,相关细节在创建利用窗口时再说。
不过虽然普通窗口的创建仍是走的正常流程,但是会记录每个创建窗口的对象地址。窗口对象地址利用HMValidateHandle进行泄露。该函数未导出,不过可以通过调用了该函数的其他API进行搜寻,比如IsMenu。

调用方式为HMValidateHandle(HANDLE h, int type),传入窗口句柄和type值,如果句柄类型和参数type一致,返回句柄对应的对象在用户态内存的地址,值得注意的是,该调用成功返回值实际为poi(tagWnd+0x28)。窗口传入type为1。
1:TYPE_WINDOW

如此连续创建多个窗口,查询(VirtualQuery)每个窗口对象所在内存块的基址,记录其中最小的基址。接着除了窗口0和1,调用DestroyWindow销毁其余窗口。保留下的窗口0和1将结合后续将创建的magicWnd进行漏洞利用,而记录的最小基址将用于搜寻magicWnd。
对比窗口0和1分别相对于桌面堆的偏移,较小者和较大者分别记为WndMin、WndMax。偏移值位于窗口对象tagWnd对象偏移0x08处。
tagWnd对象结构部分偏移如下:
+0x00 Handle
+0x08 cLockObj
+0x10 unk
++0x00 ETHREAD
... ...
+++0x220 EPROCESS
... ...
++++0x2e8 UniqueProcessId
++++0x2f0 ActiveProcessLinks
++++0x360 Token
++++0x3e8 InheritedFromUniqueProcessId
... ...
... ...
+0x18
++0x80 桌面堆基址
... ...
+0x20 pSelf
+0x28
++0x00 Handle
++0x08 *(tagWnd+0x28)相对于桌面堆基址的偏移
++0x18 exStyle
++0x1c dwStyle
++0x98 spMenu
... ...
+++0x50 tagWnd
... ...
++0xc8 cbwndExtra,指定Extrabytes字节数
++0xe8 不明flag,flag|=0x800可指定pExtrabytes属性为偏移
++0x128 pExtrabytes,指向分配的Extrabytes内存
... ...
+0xa8 spMenu
... ...
++0x50 tagWnd
... ...
窗口销毁后调用NtUserConsoleControl,指定参数ConsoleControl为6,ConsoleCtrlInfoLength为0x10,将窗口WndMin对象pExtrabytes(0x128)字段属性设置为偏移,设置成功后pExtrabytes字段值为相对于桌面堆的偏移值,而0xe8处的flag将|=0x800。重新申请后的Extrabytes内存大小由poi(poi(tagWnd+0x28)+0xc8)指定。



(由于中间反复调试过几次,截图之间的数据可能有些对不上)
然后创建一个magic窗口WndMagic,同之前一样,会执行到xxxClientAllocWindowClassExtraBytes的Hook函数处。此时将进入另一分支,触发Hook函数真正作用流程。判断方式是传入的参数值,之前创建的普通窗口和现在的magic窗口指定的cbWndExtra值是不同的,普通窗口固定为32字节,magic窗口为一个随机值。

而wndClass.cbWndExtra值将被赋值到窗口对象poi(tagWnd+0x28)+0xc8处,并作为ExtraBytes内存分配时的大小指定值,然后进行用户模式回调。用户态回调函数执行结束后返回内存地址到内核,赋值到poi(tagWnd+0x28)+0x128处。而Hook函数的目的就是为了返回一个虚假偏移,指向其他地址,实现可任意地址写的功能。


窗口创建过程中,执行到Hook函数中,通过比对传入的参数值和随机值,可确定此次创建是WndMagic。不过此时win32kfull! xxxCreateWindowEx尚未执行完毕,所以HWND句柄值还未返回,尚不可知。然而在进行额外内存进行创建时,窗口对象部分属性已经完成初始化,比如句柄值、窗口属性、扩展属性等。

所以通过匹配cbWndExtra值,再比对窗口扩展属性值exStyle(此次利用中所有窗口属性值都设置为了WS_EX_NOACTIVATE [0x8000000]),一致的情况下可以大概率确认WndMagic位置,自然可通过偏移获取到相应属性值。

获取WndMagic窗口句柄后,调用NtUserConsoleControl设置magic窗口pExtrabytes属性为相对于桌面堆的偏移。接着再借助NtCallbackReturn将普通窗口WndMin对象poi(tagWnd+0x28)+0x08处的值传回内核,从而结束回调。而poi(tagWnd+0x28)+0x08的值为poi(tagWnd+0x28)基于桌面堆基址的内存偏移,因此这里将导致WndMagic对象pExtrabytes值实际是指向WndMin窗口对象的偏移。

之后调用SetWindowLongW,指定参数为(WndMagic句柄、Index=0x128、WndMin对象在内存中的偏移),返回数据应为原偏移处的旧数据,所以此处返回值为Hook函数中返回的WndMin虚假偏移。
LONG SetWindowLongW(
[in] HWND hWnd,
[in] int nIndex,
[in] LONG dwNewLong
);
调用API SetWindowLongW最终执行到win32kfull! xxxSetWindowLong。Index大于等于0的情况下会执行到下图所示的位置。而此次利用中wndClass.cbClsExtra指定为0 ,poi(tagWnd+0x28)+0xfc也持续为0,可以忽略。因为poi(tagWnd+0x28)+0xe8已被设置0x800属性,所以poi(poi(tagWnd+0x28)+0x128)+DesktopHeapBaseAddr+Index=tagWnd_WndMin+0x128。也就是说虽是对WndMagic进行的操作,实际上实对WndMin对象pExtrabytes字段的写入,值为自身WndMin在桌面堆中的偏移。

然后执行SetWindowLongW(hWndMagic, offset_0xc8, 0xFFFFFFF),设置WndMin对象poi(tagWnd+0x28)+0xc8处cbwndExtra值设为0xFFFFFFF,扩大可以写入的范围,在xxxSetWindowLong和xxxSetWindowLongPtr中都存在对该值和Index的大小比较判定。


现在WndMagic可控制WndMin,而WndMax对象偏移已知,因此也可控制,可以实现任意位置写。接着就是对任意位置数据读,这里采用的的是API GetMenuBarInfo,对Menu Bat信息的获取,这种利用一次可以读取8字节内容。
BOOL GetMenuBarInfo(
[in] HWND hwnd,
[in] LONG idObject,
[in] LONG idItem,
[in, out] PMENUBARINFO pmbi
);
利用中构造了一个fakeMenu,将复制给WndMax,SetWindowLongPtr指定Index为-12,且窗口dwStyle为WS_CHILDWINDOW(0x40000000L),那么窗口spMenu字段可以被设置为指定的值。spMenu字段有两处位置,poi(tagWnd+0x28)+0x98和tagWnd+0xa8。而SetWindowLongPtr成功调用后返回的值为窗口的原spMenu,记录该值。

但是此时窗口并不是子窗口类型,所以在这之前需要对该字段手动进行设置。调用SetWindowLongPtrA,参数为(hWndMin, offset_0x18+WndMax_offset-WndMin_offset, poi(poi(tagWnd+0x28)+0x18)^0x4000000000000000),可以将WndMax窗口类型添加上WS_CHILDWINDOW属性,从而通过检测。

为WndMax设置WS_CHILDWINDOW属性,并添加spMenu后,再次调用SetWindowLongPtrA恢复其dwStyle,去除WS_CHILDWINDOW属性,原因是后续在使用GetMenuBarInfo读取指定地址数据时,窗口不能为子窗口类型。
WndMax的fake spMenu设置完成,且已获取了旧spMenu,记为old_spMenu。而在spMenu结构的0x50偏移处是spMenu所属窗口对象地址,即poi(spMenu+0x50)==tagWnd。

了解以上信息后,需要对指定地址进行读,该漏洞利用对GetMenuBarInfo进行了封装,传入地址,封装函数返回该地址下的内容。
对GetMenuBarInfo的利用核心主要是指定idObject为-3,idItem为1,pmbi接收数据。API最终会走到win32kfull! xxxGetMenuBarInfo函数,传参数据同GetMenuBarInfo。对该函数分析,可以看到需要对一些特殊的位置进行伪造,从而进入目的代码处。其中poi(tagWnd+0x28)+0x58和poi(tagWnd+0x28)+0x5C处的值常为0,忽略。

最终读取时,可以看到pmbi->left读取值为poi(poi(poi(poi(menu)+0x58))+0x40),pmbi->top为poi(poi(poi(poi(menu)+0x58))+0x44),其中poi(poi(poi(menu)+0x58))值可由用户进行控制,令其为X,也就意味着我们通过控制X值,可以读取X+0x40处的8字节内容,即pmbi.rcBar.left+(pmbi.rcBar.top<<32)。那么只需要控制X为欲要读取的目的地址减去0x40,即可获取相应数据。
回到漏洞利用时封装的读取函数中,函数中首先向X指向的内存中每4个字节填写一个相对于X基址的偏移值,这样GetMenuBarInfo读取回的pmbi.rcBar.left即为目标读取地址应减去的差值。这么做的目的可能是为了防止系统版本的不同导致的差值不同,比如此次调试时win10 1909就为0x40。


然后第二次调用GetMenuBarInfo,传入(目的读取地址- pmbi.rcBar.left),即可获取目的地址8字节内容。

这么一步步通过读取,可以获取到EPROCESS,然后通过ActiveProcessLinks,遍历找到当前进程和system进程EPROCESS位置。


再次两次调用SetWindowLongPtrA,替换当前进程Token为system进程,获取system权限。第一次将当前进程Token地址写入WndMax对象pExtrabytes处,第二次将system进程Token写入当前进程Token中。完成提权。

参考
https://www.freebuf.com/vuls/271177.html
https://github.com/KaLendsi/CVE-2021-1732-Exploit
https://xiaodaozhi.com/exploit/29.html
https://theevilbit.github.io/posts/a_simple_protection_against_hmvalidatehandle_technique/
CVE-2021-1732 LPE漏洞分析的更多相关文章
- 漏洞分析:CVE 2021-3156
漏洞分析:CVE 2021-3156 漏洞简述 漏洞名称:sudo堆溢出本地提权 漏洞编号:CVE-2021-3156 漏洞类型:堆溢出 漏洞影响:本地提权 利用难度:较高 基础权限:需要普通用户权限 ...
- Java反序列化漏洞分析
相关学习资料 http://www.freebuf.com/vuls/90840.html https://security.tencent.com/index.php/blog/msg/97 htt ...
- FFmpeg任意文件读取漏洞分析
这次的漏洞实际上与之前曝出的一个 CVE 非常之类似,可以说是旧瓶装新酒,老树开新花. 之前漏洞的一篇分析文章: SSRF 和本地文件泄露(CVE-2016-1897/8)http://static. ...
- CVE-2016-10190 FFmpeg Http协议 heap buffer overflow漏洞分析及利用
作者:栈长@蚂蚁金服巴斯光年安全实验室 -------- 1. 背景 FFmpeg是一个著名的处理音视频的开源项目,非常多的播放器.转码器以及视频网站都用到了FFmpeg作为内核或者是处理流媒体的工具 ...
- Elasticsearch 核心插件Kibana 本地文件包含漏洞分析(CVE-2018-17246)
不久前Elasticsearch发布了最新安全公告, Elasticsearch Kibana 6.4.3之前版本和5.6.13之前版本中的Console插件存在严重的本地文件包含漏洞可导致拒绝服务攻 ...
- ThinkCMF X2.2.2多处SQL注入漏洞分析
1. 漏洞描述 ThinkCMF是一款基于ThinkPHP+MySQL开发的中文内容管理框架,其中X系列基于ThinkPHP 3.2.3开发,最后更新到2.2.2版本.最近刚好在渗透测试 ...
- 看个AV也中招之cve-2010-2553漏洞分析
试想:某一天,你的基友给你了一个视频文件,号称是陈老师拍的苍老师的老师题材的最新电影.avi,你满心欢喜,在确定文件格式确实为avi格式后,愉快的脱下裤子准备欣赏,打开后却发现什么也没有,而随后你的基 ...
- CVE-2010-3971 CSS内存破坏漏洞分析
看了仙果版主的议题演讲,其中提到cve-2010-3971是一个浏览器漏洞利用中的里程碑.于是找来POC,尝试分析一下. 1.漏洞重现 XP SP3+ie6.0环境 poc如下: poc.htm &l ...
- jackjson学习2+CVE-2019-14379漏洞分析
最近想着分析jackson,jackson和fastjson有点相似,浅蓝大神的文章很好,个人受益匪浅 昨天简单说了下jackson的用法,现在继续拓扑,补充前置知识,前置知识补充的足够多,那么漏洞分 ...
随机推荐
- vue 因为使用scope后选择器和标签出现[data-v
使用scope的以后出现datd-v,例如: <div data-v-2311c06a class="button-warp"> <button data-v-2 ...
- JAVA8-STREAM 使用说明
概述 本人在java开发过程中,有些知识点需要记录整理,我尽量严谨的叙述我学习的经过和心得,以便备份和和大家一起进步学习,此篇文章是在网上多出搜集整理验证,结尾会注明出处,今天学习一个java8新的功 ...
- 解惑rJava R与Java的高速通道
解惑rJava R与Java的高速通道 R的极客理想系列文章,涵盖了R的思想,使用,工具,创新等的一系列要点,以我个人的学习和体验去诠释R的强大. R语言作为统计学一门语言,一直在小众领域闪耀着光芒. ...
- 【vps】Centos 7安装python3.8.5
[vps]Centos 7安装python3.8.5 前言 由于服务器的搬迁,从香港搬到了大陆,原来的香港服务器即将到期,所以趁着大陆服务器在备案的时候,将新服务器的配置先配置一下.这篇文章就是分享C ...
- 【笔记】thanos ruler组件
阅读官网文档后的笔记:https://thanos.io/tip/components/rule.md/ 感受 官网第一个话就强调风险,看来坑很多,能不用尽量不用 recording rule &am ...
- zabbix报错整理
1.cannot connect to [[172.16.2.225]:10050]: [113] No route to host 这种一般是网络连接问题 排查:在server上telnet 172 ...
- APP 性能分析工作台——你的最佳桌面端性能分析助手
目前 MARS-App 性能分析工作台版本为开发者提供Fastbot桌面版的服务. 旨在帮助开发者们更快.更便捷地开启智能测试之旅,成倍提升稳定性测试的效率. 作者:字节跳动终端技术--王凯 背景 F ...
- Kubeadm部署K8S(kubernetes)集群(测试、学习环境)-单主双从
1. kubernetes介绍 1.1 kubernetes简介 kubernetes的本质是一组服务器集群,它可以在集群的每个节点上运行特定的程序,来对节点中的容器进行管理.目的是实现资源管理的自动 ...
- SpringBoot+Minio搭建不再爆肝秃头的分布式文件服务器
前言 1).有人一定会问,为什么不用FastDFS?众所周知,FastDFS的原生安装非常复杂,有过安装经验的人大体都明白,虽然可以利用别人做好的docker直接安装,但真正使用过程中也可能出现许多莫 ...
- Homework_2
禁 止 吃 瓜 我是小鱼 刚才有个同学问我小鱼发生肾么事了 我说怎么回事? 给我发了一个张截图,我一看! 噢!原来是昨天发布第二次寒假作业了 我大一了啊没有闪 来!偷袭!我三岁的小同志 当时就流眼泪了 ...