相对于长篇大论的漏洞分析文章来说,我更期望漏洞分析的文章可以以一种提纲挈领的方式,来满足不同读者的需求,因此本文会介绍漏洞的背景信息,漏洞成因,利用的思路,以及涉及到的知识点等:

  • 对于需要了解的读者:可以从背景信息和漏洞信息等提取感兴趣的点进行了解
  • 对于深入研究的读者,可以从涉及到的知识点入手,先掌握基础储备后,再开始分析漏洞,分析中你遇到的所有技术点都有提及,便你一路畅通~
  • 对于需要利用的读者:可以直接下载源码,编译执行,执行效果为创建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(类型混淆漏洞)

调用栈

image-20210423163655495.png

漏洞分析

以下分析在20H2版本上:

word-image-47.png

名词解释

  1. tagWND:是Windows内核用来描述用户创建的窗口的内核数据结构,保存着窗口相关的所有信息。
  2. tagBody:伪名称,代指tagWND+0x28处的值,保存着tagWND的主体信息。由于Win32k TypeIsolation的作用,Windows内核将大部分User ObjectGdi Object,比如Window,Bitmap,Palette等的对象头和对象主体之间分别从不同的位置开辟空间。具体实现逻辑可参考HMAllocObject函数。

正常逻辑

  1. CreateWindowsEx最终调用内核函数xxxCreateWindowEx来实现窗口创建的动作。
  2. xxxCreateWindowEx 内部会调用xxxClientAllocWindowClassExtraBytes来创建额外的空间,之后将返回的地址保存在tagBody+0x128处
  3. xxxClientAllocWindowClassExtraBytes会切换到用户态执行开辟空间的动作,空间申请完成后,调用NtCallbackReturn返回到内核执行点继续执行。

漏洞逻辑

漏洞的触发发生上述正常流程的第二步,返回用户态执行开辟空间的代码中,若此刻调用NtUserConsoleControl

  1. NtUserConsoleControl内部会修改tagBody+0xE8处的Flag,通过逻辑或的方式添加0x800标志*(tagBody+0xE8) |= 0x800
  2. 0x800这个标志tagBody+0x128处保存的值是否一个相对偏移,若是,则值为相对DesktopHeap基址的偏移,若不是则保存用户态的地址。
  3. 标志位的修改,导致后续对tagBody+0x128处的值被作为偏移来使用,最终在xxxDestroyWindow发生使用,从而产生崩溃。

原因概括

综上这是一个类型混淆的漏洞,由于不正确设置tagBody+0xE8处的标志位,而导致tagBody+0x128处的值被异常使用。

POC细节

POC可参考附件中代码,下面介绍POC开发中需要注意到的细节:

  1. 返回用户态的回调函数,可以通过Hook的方式劫持流程。
  2. 在Hook函数中调用NtUserConsoleControl需要使用当前正在创建的窗口的句柄,但是创建窗口逻辑尚未完成,因此,需要通过作者发明的Spray Handle方式来获取句柄,具体可参考代码。
  3. 在Hook后,调用NtCallbackReturn返回执行流程和返回值到内核,从而修改tagBody+0x128处的值。

利用思路

  1. 利用的第一个关键在于NtUserSetWindowLongPtr函数内部会使用tagBody+0x128处的值作为要修改的地址,因此:
    1. 通过NtUserCallbackReturn修改tagBody+0x128处的值为创建的窗口相对于DesktopHeap的偏移。
    2. 通过NtUserSetWindowLongPtr修改tagBody中的任意值。
  2. 利用第一步,修改tagBody.style |= WFCHILD,之后就可以通过SetWindowLongPtr指定indexGWLP_ID,修改tagBody.smenu为任意值。
  3. 调用GetMenuBarInfo,可以读取第二步设置的任意地址中的值。此刻便有了Read Primitive,之后通过多次读取当前线程的信息,可以获得DesktopHeap基址。
  4. 利用第一步,修改相邻窗口对象的tagBody+0x128处的值,即获得了内核下的Write Primitive
  5. 利用获得的R/W Primitive,则可以进一步修改当前进程的 Token,从而获得权限提升。

涉及到的知识点

从POC到利用的过程中需要掌握的知识点有:

  1. NtUserConsoleControl过程逆向分析
  2. 基础知识点:窗口的对象管理细节,可参考文章《Windows User Object的句柄管理》,从该文中可以获得以下几点:
    1. Spray Handle的方式。
    2. 对象头和中保存的信息为何物,以及申请的内存空间位置。
  3. NtUserSetWindowLongPtr内部流程,需要掌握到index > 0和GWLP_ID的情况。
  4. GetMenuBarInfo,需要掌握内核下的实现流程及细节,决定着如何获取数据,以及如何将返回的数据值重组。
  5. 修改Token提权,可参考《内核漏洞攻防》一文中,Token进阶提权的方式。

其他说明

  1. 该漏洞在分析时参考了安恒威胁情报中心分析的文章:0DAY攻击!首次发现蔓灵花组织在针对国内的攻击活动中使用WINDOWS内核提权0DAY漏洞(CVE-2021-1732),包括文中漏洞流程图片亦来自该文,特此感谢。然而,本文的POC和利用开发过程中,均独立研究实现,未参考任何已公开的CVE-2021-1732源码,代码中新的Spray Handle方式和Token修改方式均为作者初创,算是创新点。
  2. 代码地址:https://github.com/Pai-Po/CVE-2021-1732
  3. 效果:
    image-20210423163249560.png