概述

  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)+0x98tagWnd+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://ti.dbappsecurity.com.cn/blog/articles/2021/02/10/windows-kernel-zero-day-exploit-is-used-by-bitter-apt-in-targeted-attack-cn/

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漏洞分析的更多相关文章

  1. 漏洞分析:CVE 2021-3156

    漏洞分析:CVE 2021-3156 漏洞简述 漏洞名称:sudo堆溢出本地提权 漏洞编号:CVE-2021-3156 漏洞类型:堆溢出 漏洞影响:本地提权 利用难度:较高 基础权限:需要普通用户权限 ...

  2. Java反序列化漏洞分析

    相关学习资料 http://www.freebuf.com/vuls/90840.html https://security.tencent.com/index.php/blog/msg/97 htt ...

  3. FFmpeg任意文件读取漏洞分析

    这次的漏洞实际上与之前曝出的一个 CVE 非常之类似,可以说是旧瓶装新酒,老树开新花. 之前漏洞的一篇分析文章: SSRF 和本地文件泄露(CVE-2016-1897/8)http://static. ...

  4. CVE-2016-10190 FFmpeg Http协议 heap buffer overflow漏洞分析及利用

    作者:栈长@蚂蚁金服巴斯光年安全实验室 -------- 1. 背景 FFmpeg是一个著名的处理音视频的开源项目,非常多的播放器.转码器以及视频网站都用到了FFmpeg作为内核或者是处理流媒体的工具 ...

  5. Elasticsearch 核心插件Kibana 本地文件包含漏洞分析(CVE-2018-17246)

    不久前Elasticsearch发布了最新安全公告, Elasticsearch Kibana 6.4.3之前版本和5.6.13之前版本中的Console插件存在严重的本地文件包含漏洞可导致拒绝服务攻 ...

  6. ThinkCMF X2.2.2多处SQL注入漏洞分析

       1.     漏洞描述 ThinkCMF是一款基于ThinkPHP+MySQL开发的中文内容管理框架,其中X系列基于ThinkPHP 3.2.3开发,最后更新到2.2.2版本.最近刚好在渗透测试 ...

  7. 看个AV也中招之cve-2010-2553漏洞分析

    试想:某一天,你的基友给你了一个视频文件,号称是陈老师拍的苍老师的老师题材的最新电影.avi,你满心欢喜,在确定文件格式确实为avi格式后,愉快的脱下裤子准备欣赏,打开后却发现什么也没有,而随后你的基 ...

  8. CVE-2010-3971 CSS内存破坏漏洞分析

    看了仙果版主的议题演讲,其中提到cve-2010-3971是一个浏览器漏洞利用中的里程碑.于是找来POC,尝试分析一下. 1.漏洞重现 XP SP3+ie6.0环境 poc如下: poc.htm &l ...

  9. jackjson学习2+CVE-2019-14379漏洞分析

    最近想着分析jackson,jackson和fastjson有点相似,浅蓝大神的文章很好,个人受益匪浅 昨天简单说了下jackson的用法,现在继续拓扑,补充前置知识,前置知识补充的足够多,那么漏洞分 ...

随机推荐

  1. HDU 2044 一只小蜜蜂... (斐波那契数列)

    原题链接:http://acm.hdu.edu.cn/showproblem.php?pid=2044 题目分析:其实仔细读题就会发现其中的规律, 其中:这是一个典型的斐波那契数列. 代码如下: #i ...

  2. 软件开发架构与网络之OSI七层协议(五层)

    本期内容概要 python回顾 软件开发架构 网络理论前瞻 osi七层协议(五层) 以太网协议 IP协议 port协议 交换机 路由器 局域网 广域网 TCP协议 三次握手 四次挥手 UDP协议 内容 ...

  3. 打开Cmd的方式与基础Dos命令

    基础的Dos命令 打开Cmd的方式 开始->Windows系统->命令提示符 Win键 + R输入cmd打开控制台 在任意的文件夹下面,按住shift键+鼠标右键点击在此处打开powers ...

  4. http://dl-ssl.google.com/android上不去解决方案

    转:https://blog.csdn.net/j04110414/article/details/44149653/ 一. 更新sdk,遇到了更新下载失败问题: Fetching https://d ...

  5. 扒一扒@Retryable注解,很优雅,有点意思!

    你好呀,我是歪歪. 前几天我 Review 代码的时候发现项目里面有一坨逻辑写的非常的不好,一眼望去简直就是丑陋之极. 我都不知道为什么会有这样的代码存在项目里面,于是我看了一眼提交记录准备叫对应的同 ...

  6. 听不懂x86,arm?

    之前听别人讲x86或者ARM,我心里有一些疑惑,为什么他们不考虑32位还是64位的? 直到和师傅交流了一下: I:32位机是不是不支持部署k3os? T:这个年头哪里还有32位机? T:现在说x86, ...

  7. android+opencv+opencl: cv::dft()的opencl版本的性能分析

    在小米mix 2s + 高通骁龙 845 + Adreno 630 上测试了opencl版本的cv::dft(). 测试数据 先看表格里面的描述: 名称 函数名 最大时间(ms) 平均时间(ms) 说 ...

  8. echart的x轴或y轴区间标签如何从大到小排列

    1.有时候我们做echart时,从后台接收返回回来的数据,没有按顺序排列,这里我遇到的是区间的值,看图 我这里是处理好了的,一开始,50-100这个区间在数组的最后一列,也就是在150-200后面的这 ...

  9. gin中的文件上传

    1. 单文件上传 package main import ( "fmt" "github.com/gin-gonic/gin" "log" ...

  10. python采用json.dump和json.load存储数据

    #!/usr/bin/python # -*- coding: UTF-8 -*- import json numbers = [2,3,4,7,11,13] filename = 'numbers. ...