相对于长篇大论的漏洞分析文章来说,我更期望漏洞分析的文章可以以一种提纲挈领的方式,来满足不同读者的需求,因此本文会介绍漏洞的背景信息,漏洞成因,利用的思路,以及涉及到的知识点等:
- 对于需要了解的读者:可以从背景信息和漏洞信息等提取感兴趣的点进行了解
- 对于深入研究的读者,可以从涉及到的知识点入手,先掌握基础储备后,再开始分析漏洞,分析中你遇到的所有技术点都有提及,便你一路畅通~
- 对于需要利用的读者:可以直接下载源码,编译执行,执行效果为创建system权限的
cmd
- 其他读者:(〃’▽’〃)
本文将尝试使用这种方式来分析Windows内核提权漏洞CVE-2021-1732,若有不足及错误,欢迎指正。
漏洞利用的代码中,提到了新的Spray Handle的方式和直接修改Token提权的方式,在20H2中依然可以使用~
背景信息
摘自安恒威胁情报中心:
2020年12月中旬,安恒威胁情报中心猎影实验室发布了《蔓灵花(BITTER)组织近期针对我国政府部门、科研机构发起攻击》,文中已经给出并分析了该组织攻击的一些组件,在后续的跟进分析中我们又发现了一个全新的组件,经过分析,我们发现该组件利用了一个未知的Windows内核提权漏洞,且利用代码适配了Windows10 1909操作系统。我们随即将相关信息报送给微软,经过微软的确认,我们确信这是一个win32kfull模块的0Day漏洞,在最新版本的Windows10 20H2全补丁环境下也能触发!
漏洞信息
CVE-2021-1732
- 漏洞模块:Win32kfull.sys(Windows内核用来实现图形化系统的核心驱动程序),目前Windows端的大部分的提权漏洞都发生在该组件中。
- 影响版本:
Win10 1803 - 20H2, WinServer 2019, 2004
- 漏洞类型:
Type Confusion(类型混淆漏洞)
调用栈
漏洞分析
以下分析在20H2版本上:
名词解释
tagWND
:是Windows内核用来描述用户创建的窗口的内核数据结构,保存着窗口相关的所有信息。tagBody
:伪名称,代指tagWND+0x28
处的值,保存着tagWND
的主体信息。由于Win32k TypeIsolation
的作用,Windows内核将大部分User Object
和Gdi Object
,比如Window,Bitmap,Palette等的对象头和对象主体之间分别从不同的位置开辟空间。具体实现逻辑可参考HMAllocObject
函数。
正常逻辑
CreateWindowsEx
最终调用内核函数xxxCreateWindowEx
来实现窗口创建的动作。xxxCreateWindowEx
内部会调用xxxClientAllocWindowClassExtraBytes
来创建额外的空间,之后将返回的地址保存在tagBody+0x128处
。xxxClientAllocWindowClassExtraBytes
会切换到用户态执行开辟空间的动作,空间申请完成后,调用NtCallbackReturn
返回到内核执行点继续执行。
漏洞逻辑
漏洞的触发发生上述正常流程的第二步,返回用户态执行开辟空间的代码中,若此刻调用NtUserConsoleControl
NtUserConsoleControl
内部会修改tagBody+0xE8
处的Flag,通过逻辑或的方式添加0x800标志
,*(tagBody+0xE8) |= 0x800
0x800
这个标志tagBody+0x128
处保存的值是否一个相对偏移,若是,则值为相对DesktopHeap
基址的偏移,若不是则保存用户态的地址。- 标志位的修改,导致后续对
tagBody+0x128
处的值被作为偏移来使用,最终在xxxDestroyWindow
发生使用,从而产生崩溃。
原因概括
综上这是一个类型混淆的漏洞,由于不正确设置tagBody+0xE8
处的标志位,而导致tagBody+0x128
处的值被异常使用。
POC细节
POC可参考附件中代码,下面介绍POC开发中需要注意到的细节:
- 返回用户态的回调函数,可以通过Hook的方式劫持流程。
- 在Hook函数中调用
NtUserConsoleControl
需要使用当前正在创建的窗口的句柄,但是创建窗口逻辑尚未完成,因此,需要通过作者发明的Spray Handle
方式来获取句柄,具体可参考代码。 - 在Hook后,调用
NtCallbackReturn
返回执行流程和返回值到内核,从而修改tagBody+0x128
处的值。
利用思路
- 利用的第一个关键在于
NtUserSetWindowLongPtr
函数内部会使用tagBody+0x128
处的值作为要修改的地址,因此:- 通过
NtUserCallbackReturn
修改tagBody+0x128
处的值为创建的窗口相对于DesktopHeap
的偏移。 - 通过
NtUserSetWindowLongPtr
修改tagBody
中的任意值。
- 通过
- 利用第一步,修改
tagBody.style |= WFCHILD
,之后就可以通过SetWindowLongPtr
指定index
为GWLP_ID
,修改tagBody.smenu
为任意值。 - 调用
GetMenuBarInfo
,可以读取第二步设置的任意地址中的值。此刻便有了Read Primitive
,之后通过多次读取当前线程的信息,可以获得DesktopHeap
基址。 - 利用第一步,修改相邻窗口对象的
tagBody+0x128
处的值,即获得了内核下的Write Primitive
- 利用获得的
R/W Primitive
,则可以进一步修改当前进程的Token
,从而获得权限提升。
涉及到的知识点
从POC到利用的过程中需要掌握的知识点有:
NtUserConsoleControl
过程逆向分析- 基础知识点:窗口的对象管理细节,可参考文章《Windows User Object的句柄管理》,从该文中可以获得以下几点:
Spray Handle
的方式。- 对象头和中保存的信息为何物,以及申请的内存空间位置。
NtUserSetWindowLongPtr
内部流程,需要掌握到index > 0和GWLP_ID
的情况。GetMenuBarInfo
,需要掌握内核下的实现流程及细节,决定着如何获取数据,以及如何将返回的数据值重组。- 修改
Token
提权,可参考《内核漏洞攻防》一文中,Token进阶提权的方式。
其他说明
- 该漏洞在分析时参考了安恒威胁情报中心分析的文章:0DAY攻击!首次发现蔓灵花组织在针对国内的攻击活动中使用WINDOWS内核提权0DAY漏洞(CVE-2021-1732),包括文中漏洞流程图片亦来自该文,特此感谢。然而,本文的POC和利用开发过程中,均独立研究实现,未参考任何已公开的CVE-2021-1732源码,代码中新的
Spray Handle
方式和Token修改方式均为作者初创,算是创新点。 - 代码地址:https://github.com/Pai-Po/CVE-2021-1732
- 效果: