栈溢出,梦开始的地方
相关代码在仓库HEVDExploits
Without GS 存在问题的区域
1 2 3 4 case 0x222003 :DbgPrintEx (0x4D u, 3u , "****** HEVD_IOCTL_BUFFER_OVERFLOW_STACK ******\n" );FakeObjectNonPagedPoolNxIoctlHandler = BufferOverflowStackIoctlHandler (Irp, CurrentStackLocation); v7 = "****** HEVD_IOCTL_BUFFER_OVERFLOW_STACK ******\n" ;
Ioctl获得用户态参数
1 2 3 4 5 6 7 8 9 10 11 12 13 __int64 __fastcall BufferOverflowStackIoctlHandler (_IRP *Irp, _IO_STACK_LOCATION *IrpSp) { void *Type3InputBuffer; __int64 result; size_t InputBufferLength; Type3InputBuffer = IrpSp->Parameters.DeviceIoControl.Type3InputBuffer; result = 3221225473 i64; InputBufferLength = IrpSp->Parameters.DeviceIoControl.InputBufferLength; if ( Type3InputBuffer ) return TriggerBufferOverflowStack (Type3InputBuffer, InputBufferLength); return result; }
读取到内核栈中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 __int64 __fastcall TriggerBufferOverflowStack (void *UserBuffer, size_t Size) { unsigned int KernelBuffer[512 ]; memset (KernelBuffer, 0 , sizeof (KernelBuffer)); ProbeForRead (UserBuffer, 0x800 ui64, 1u ); DbgPrintEx (0x4D u, 3u , "[+] UserBuffer: 0x%p\n" , UserBuffer); DbgPrintEx (0x4D u, 3u , "[+] UserBuffer Size: 0x%zX\n" , Size); DbgPrintEx (0x4D u, 3u , "[+] KernelBuffer: 0x%p\n" , KernelBuffer); DbgPrintEx (0x4D u, 3u , "[+] KernelBuffer Size: 0x%zX\n" , 0x800 ui64); DbgPrintEx (0x4D u, 3u , "[+] Triggering Buffer Overflow in Stack\n" ); memmove (KernelBuffer, UserBuffer, Size); return 0 i64; }
ProbeForRead 例程检查用户模式缓冲区是否实际驻留在地址空间的用户部分,并且是否正确对齐。
1 2 3 4 5 void ProbeForRead ( [in] const volatile VOID *Address, [in] SIZE_T Length, [in] ULONG Alignment ) ;
从汇编看,不存在cookie,并且存在exception信息
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 PAGE:00000001400866 AE call memmove PAGE:00000001400866B 3 jmp short loc_1400866D0 PAGE:00000001400866B 3 ; } PAGE:00000001400866B 5 ; --------------------------------------------------------------------------- PAGE:00000001400866B 5 PAGE:00000001400866B 5 $LN6_0: ; DATA XREF: .rdata:000000014000269 C↑o PAGE:00000001400866B 5 ; __except(1 ) PAGE:00000001400866B 5 mov ebx, eax PAGE:00000001400866B 7 mov r9d, eax PAGE:00000001400866B A lea r8, aExceptionCode0 ; "[-] Exception Code: 0x%X\n" PAGE:00000001400866 C1 mov edx, 3 ; Level PAGE:00000001400866 C6 lea ecx, [Size+4 Ah] ; ComponentId PAGE:00000001400866 C9 call cs:__imp_DbgPrintEx PAGE:00000001400866 CF nop PAGE:00000001400866 D0 PAGE:00000001400866 D0 loc_1400866D0: ; CODE XREF: TriggerBufferOverflowStack+CF↑j PAGE:00000001400866 D0 mov eax, ebx PAGE:00000001400866 D2 lea r11, [rsp+838 h+var_18] PAGE:00000001400866 DA mov rbx, [r11+20 h] PAGE:00000001400866 DE mov rsi, [r11+28 h] PAGE:00000001400866E2 mov rdi, [r11+30 h] PAGE:00000001400866E6 mov rsp, r11 PAGE:00000001400866E9 pop r15 PAGE:00000001400866 EB pop r14 PAGE:00000001400866 ED pop r12 PAGE:00000001400866 EF retn PAGE:00000001400866 EF ; }
首先,我们需要确定返回地址偏移,IDA标记了返回地址的地址
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 -0000000000000818 KernelBuffer dd 512 dup(?) -0000000000000018 var_18 db ? -0000000000000017 db ? ; undefined -0000000000000016 db ? ; undefined -0000000000000015 db ? ; undefined -0000000000000014 db ? ; undefined -0000000000000013 db ? ; undefined -0000000000000012 db ? ; undefined -0000000000000011 db ? ; undefined -0000000000000010 db ? ; undefined -000000000000000F db ? ; undefined -000000000000000E db ? ; undefined -000000000000000D db ? ; undefined -000000000000000C db ? ; undefined -000000000000000B db ? ; undefined -000000000000000A db ? ; undefined -0000000000000009 db ? ; undefined -0000000000000008 db ? ; undefined -0000000000000007 db ? ; undefined -0000000000000006 db ? ; undefined -0000000000000005 db ? ; undefined -0000000000000004 db ? ; undefined -0000000000000003 db ? ; undefined -0000000000000002 db ? ; undefined -0000000000000001 db ? ; undefined +0000000000000000 r db 8 dup(?)
调试一下,确实如此
1 2 3 4 5 6 7 8 RtlFillMemory (lpBuffer, 0x1000 , 0x43 );PDWORD64 rop = (PDWORD64)((PCHAR)lpBuffer + 0x818 ); *rop = 0xdeadbeef ; 4 : kd> r rsprsp=fffff3082725a708 4 : kd> t00000000 `deadbeef ?? ???
因此
ROP关闭SMEP/SMAP
跳转到用户态进行执行shellcode
With GS code如下
1 2 3 4 5 case 0x222007 :DbgPrintEx (0x4D u, 3u , "****** HEVD_IOCTL_BUFFER_OVERFLOW_STACK_GS ******\n" );FakeObjectNonPagedPoolNxIoctlHandler = BufferOverflowStackGSIoctlHandler (Irp, CurrentStackLocation); v7 = "****** HEVD_IOCTL_BUFFER_OVERFLOW_STACK_GS ******\n" ; goto LABEL_64;
不同点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 __int64 __fastcall TriggerBufferOverflowStackGS (void *UserBuffer, size_t Size) { unsigned __int8 KernelBuffer[512 ]; memset (KernelBuffer, 0 , sizeof (KernelBuffer)); ProbeForRead (UserBuffer, 0x200 ui64, 1u ); DbgPrintEx (0x4D u, 3u , "[+] UserBuffer: 0x%p\n" , UserBuffer); DbgPrintEx (0x4D u, 3u , "[+] UserBuffer Size: 0x%zX\n" , Size); DbgPrintEx (0x4D u, 3u , "[+] KernelBuffer: 0x%p\n" , KernelBuffer); DbgPrintEx (0x4D u, 3u , "[+] KernelBuffer Size: 0x%zX\n" , 0x200 ui64); DbgPrintEx (0x4D u, 3u , "[+] Triggering Buffer Overflow in Stack (GS)\n" ); memmove (KernelBuffer, UserBuffer, Size); return 0 i64; }
看汇编可以看出来存在cookie的比较
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 PAGE:00000001400867E4 call memmove PAGE:00000001400867E9 jmp short loc_140086806 PAGE:00000001400867E9 ; } PAGE:00000001400867 EB ; --------------------------------------------------------------------------- PAGE:00000001400867 EB PAGE:00000001400867 EB $LN6_1: ; DATA XREF: .rdata:00000001400026 D0↑o PAGE:00000001400867 EB ; __except(1 ) PAGE:00000001400867 EB mov ebx, eax PAGE:00000001400867 ED mov r9d, eax PAGE:00000001400867F 0 lea r8, aExceptionCode0 ; "[-] Exception Code: 0x%X\n" PAGE:00000001400867F 7 mov edx, 3 ; Level PAGE:00000001400867F C lea ecx, [Size+4 Ah] ; ComponentId PAGE:00000001400867F F call cs:__imp_DbgPrintEx PAGE:0000000140086805 nop PAGE:0000000140086806 PAGE:0000000140086806 loc_140086806: ; CODE XREF: TriggerBufferOverflowStackGS+D9↑j PAGE:0000000140086806 mov eax, ebx PAGE:0000000140086808 mov UserBuffer, [rsp+258 h+var_38] PAGE:0000000140086810 xor UserBuffer, rsp ; StackCookie PAGE:0000000140086813 call __security_check_cookie PAGE:0000000140086818 mov rbx, [rsp+258 h+arg_10] PAGE:0000000140086820 add rsp, 230 h PAGE:0000000140086827 pop r15 PAGE:0000000140086829 pop r14 PAGE:000000014008682B pop r12 PAGE:000000014008682 D pop rdi PAGE:000000014008682 E pop rsi PAGE:000000014008682F retn
GS: Cookie 用于在使用 /GS(缓冲区安全检查) 编译的代码中和使用异常处理的代码中提供缓冲区溢出保护。 进入受到溢出保护的函数时,Cookie 被置于堆栈之上;退出时,会将堆栈上的值与全局 Cookie 进行比较。 它们之间存在任何差异则表示已经发生缓冲区溢出,并导致该程序的立即终止。
这个var_38就是cookie的值,最后进行一个比较
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 -0000000000000238 KernelBuffer db 512 dup (?)-0000000000000038 var_38 dq ?-0000000000000030 db ? ; undefined-000000000000002F db ? ; undefined-000000000000002 E db ? ; undefined-000000000000002 D db ? ; undefined-000000000000002 C db ? ; undefined-000000000000002B db ? ; undefined-000000000000002 A db ? ; undefined-0000000000000029 db ? ; undefined-0000000000000028 db ? ; undefined-0000000000000027 db ? ; undefined-0000000000000026 db ? ; undefined-0000000000000025 db ? ; undefined-0000000000000024 db ? ; undefined-0000000000000023 db ? ; undefined-0000000000000022 db ? ; undefined-0000000000000021 db ? ; undefined-0000000000000020 db ? ; undefined-000000000000001F db ? ; undefined-000000000000001 E db ? ; undefined-000000000000001 D db ? ; undefined-000000000000001 C db ? ; undefined-000000000000001B db ? ; undefined-000000000000001 A db ? ; undefined-0000000000000019 db ? ; undefined-0000000000000018 db ? ; undefined-0000000000000017 db ? ; undefined-0000000000000016 db ? ; undefined-0000000000000015 db ? ; undefined-0000000000000014 db ? ; undefined-0000000000000013 db ? ; undefined-0000000000000012 db ? ; undefined-0000000000000011 db ? ; undefined-0000000000000010 db ? ; undefined-000000000000000F db ? ; undefined-000000000000000 E db ? ; undefined-000000000000000 D db ? ; undefined-000000000000000 C db ? ; undefined-000000000000000B db ? ; undefined-000000000000000 A db ? ; undefined-0000000000000009 db ? ; undefined-0000000000000008 db ? ; undefined-0000000000000007 db ? ; undefined-0000000000000006 db ? ; undefined-0000000000000005 db ? ; undefined-0000000000000004 db ? ; undefined-0000000000000003 db ? ; undefined-0000000000000002 db ? ; undefined-0000000000000001 db ? ; undefined+0000000000000000 r db 8 dup (?)
其初始化过程
1 2 3 PAGE:0000000140086724 mov rax, cs:__security_cookie PAGE:000000014008672B xor rax, rsp PAGE:000000014008672 E mov [rsp+258 h+var_38], rax
绕过cookie的思路:
在x86下可以通过修改 SEH 中的 exception handler 地址并触发异常来控制程序的执行流程。但是这个方法在这里并不可行,因为环境是 64 位系统,而只有 32 位系统的 SEH 信息保存在栈中,64 位系统的 SEH 不在栈上(1) 。因此无法使用 SEH 进行漏洞利用。
修改 .data 和 栈上存储的 cookie 值。要做到这点,需要找到一个任意写漏洞,而 HEVD 显然是有这个漏洞的,就在 TriggerArbitraryWrite
函数中。除此之外,此次漏洞函数中还额外对 security_cookie 进行了一次栈顶的异或操作,因此还要获取栈顶的数值
覆盖虚函数指针 条件:函数参数中有对象或结构体的指针;参数是放在栈上的。由于测试环境是 64 位系统,参数保存在寄存器中,因此不考虑。
读取 cookie 数值并计算出 xored security_cookie 数值 需要一个任意读漏洞读取 cookie 的数值,以及想办法获取计算 xored security_cookie 时 RSP 寄存器的数值。
如何获取rsp的值?
首先利用 NtQuerySystemInformation
函数获取当前进程的 PSYSTEM_EXTENDED_PROCESS_INFORMATION
信息,该信息中包含了进程中每个线程的 StackBase 和 StackLimit 信息,StackBase 表示栈的起始地址,StackLimit 表示栈范围内可分配的最小地址,由于栈空间是向下分配的,因此 StackBase 的数值大于 StackLimit 的数值。
what to where: 其实是任意地址读写。
where内核地址,what写数据 => 任意写
where是用户buffer,what是内核地址 => 任意读
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 case 0x22200B : DbgPrintEx (0x4D u, 3u , "****** HEVD_IOCTL_ARBITRARY_WRITE ******\n" ); FakeObjectNonPagedPoolNxIoctlHandler = ArbitraryWriteIoctlHandler (Irp, CurrentStackLocation); v7 = "****** HEVD_IOCTL_ARBITRARY_WRITE ******\n" ; goto LABEL_64; __int64 __fastcall TriggerArbitraryWrite (_WRITE_WHAT_WHERE *UserWriteWhatWhere) { unsigned __int64 *What; unsigned __int64 *Where; ProbeForRead (UserWriteWhatWhere, 0x10 ui64, 1u ); What = UserWriteWhatWhere->What; Where = UserWriteWhatWhere->Where; DbgPrintEx (0x4D u, 3u , "[+] UserWriteWhatWhere: 0x%p\n" , UserWriteWhatWhere); DbgPrintEx (0x4D u, 3u , "[+] WRITE_WHAT_WHERE Size: 0x%zX\n" , 0x10 ui64); DbgPrintEx (0x4D u, 3u , "[+] UserWriteWhatWhere->What: 0x%p\n" , What); DbgPrintEx (0x4D u, 3u , "[+] UserWriteWhatWhere->Where: 0x%p\n" , Where); DbgPrintEx (0x4D u, 3u , "[+] Triggering Arbitrary Write\n" ); *Where = *What; return 0 i64; }
使用NtQuerySystemInformation(ZwQuerySystemInformation)获得内核信息
1 2 3 4 5 6 __kernel_entry NTSTATUS NtQuerySystemInformation ( [in] SYSTEM_INFORMATION_CLASS SystemInformationClass, [in, out] PVOID SystemInformation, [in] ULONG SystemInformationLength, [out, optional] PULONG ReturnLength ) ;
SYSTEM_INFORMATION_CLASS
是一个枚举类型,文章 存在这个结构体,我们关心如下的一个就行
1 2 3 typedef enum _SYSTEM_INFORMATION_CLASS { SystemExtendedProcessInformation = 0x39 } SYSTEM_INFORMATION_CLASS;
SystemInformation
是查询结果
SystemInformationLength
是SystemInformation缓冲区大小
ReturnLength
返回实际大小,如果大于SystemInformationLength
函数调用会失败
相关利用:List process information including process architecture and username
查询的结果是 PSYSTEM_EXTENDED_PROCESS_INFORMATION
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 typedef struct _SYSTEM_EXTENDED_PROCESS_INFORMATION { ULONG NextEntryOffset; ULONG NumberOfThreads; LARGE_INTEGER SpareLi1; LARGE_INTEGER SpareLi2; LARGE_INTEGER SpareLi3; LARGE_INTEGER CreateTime; LARGE_INTEGER UserTime; LARGE_INTEGER KernelTime; UNICODE_STRING ImageName; KPRIORITY BasePriority; ULONG ProcessId; ULONG InheritedFromUniqueProcessId; ULONG HandleCount; ULONG SessionId; PVOID PageDirectoryBase; VM_COUNTERS VirtualMemoryCounters; SIZE_T PrivatePageCount; IO_COUNTERS IoCounters; SYSTEM_EXTENDED_THREAD_INFORMATION Threads[1 ]; } SYSTEM_EXTENDED_PROCESS_INFORMATION, * PSYSTEM_EXTENDED_PROCESS_INFORMATION;
其中存在一个NumberOfThread的变量,我们可以遍历SYSTEM_EXTENDED_THREAD_INFORMATION Threads[1];
来获得StackBase和StackLimit
1 2 3 4 5 6 7 8 9 10 11 typedef struct _SYSTEM_EXTENDED_THREAD_INFORMATION { SYSTEM_THREAD_INFORMATION ThreadInfo; PVOID StackBase; PVOID StackLimit; PVOID Win32StartAddress; PVOID TebBase; ULONG_PTR Reserved2; ULONG_PTR Reserved3; ULONG_PTR Reserved4; } SYSTEM_EXTENDED_THREAD_INFORMATION, * PSYSTEM_EXTENDED_THREAD_INFORMATION;
securityCookie在模块中的位置:
1 2 .data:0000000140003010 ; uintptr_t _security_cookie .data:0000000140003010 __security_cookie dq 2B 992DDFA232h
参考文章指出,每次触发驱动的 handler 函数时,一定会调用 nt!NtDeviceIoControlFile
函数,并且返回地址在 nt!NtDeviceIoControlFile+0x56
。
TriggerStackOverflow与获得栈的基址偏移是否相同?多跑几次
1 2 3 4 5 Python>0xfffff88b1fdbf000 -0xfffff88b1fdc44b0 -0x54b0 Python>0xfffff88b1dc8d000 -0xfffff88b1dc924b0 -0x54b0
看起来是一样的,因此我们直接获得rsp地址
memmove时寄存器的值
1 2 3 4 5 6 RAX: 0000000000000000 RBX: 0000000000000000 RCX: FFFFEC8EDD1724D0 RDX: 00000250870E0000 RSI: 0000000000000008 RDI: 00000250870E0000 RIP: FFFFF801211467E4 RSP: FFFFEC8EDD1724B0 RBP: FFFFAC01F21BCE50 R8: 0000000000000008 R9: 000000000000004 D R10: 0000000000000000 R11: FFFFEC8EDD1724A8 R12: 0000000000000200 R13: FFFFAC01F2AC9E10 R14: 000000000000004 D R15: 0000000000000003
如果直接写入,栈溢出触发Exception,直接崩溃
缓冲区太大了(0x1000)后来把缓冲区该小一点(0x270),发现是可以
看了几篇文章,使用了修改页表的方式 bypass SMEP,有时间学习一下
参考