前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >内核漏洞利用:通过WARBIRD在Windows 10上提升权限

内核漏洞利用:通过WARBIRD在Windows 10上提升权限

作者头像
FB客服
发布2018-02-26 11:25:08
1.5K0
发布2018-02-26 11:25:08
举报
文章被收录于专栏:FreeBufFreeBuf

在这篇文章中,我想谈一谈通过基于Windows内核的exploit来提升权限。之所以没有使用像HackSys Extreme Vulnerable Windows Driver这样的东西,是因为想做点不同的事情。恰逢Google Project Zero近期公布了漏洞,由于mjurczyk把漏洞进行了记录,所以感觉便于理解。该漏洞还被Microsoft标记为“Wont-Fix”,意思就是32位的Windows 10 Creator版本仍然存在漏洞。

漏洞概览

根据披露出的消息,我们可以了解到这个漏洞影响了32位Windows 10 Creators版本的更新。这个漏洞的存在是由于一个新的信息类“WARBIRD”被添加到NtQuerySystemInformation中,但是这个类在32位Windows 10上被错误处理了。 当触发漏洞时,内核指令指针被设置为NULL,在现代操作系统中,内存地址0h通常被限制以避免这些类型的漏洞被利用。然而谷歌已经确认,在Windows上启用了16位支持,特别是通过NTVDM使用NULL地址来支持16位程序执行的情况下,漏洞实际上是可利用的。 在编写exploit之前,我们需要先搭建环境。

搭建实验环境

为了能搭建实验环境,我们需要一些虚拟机:

Windows 10 Creators Update x86 - 这是靶机 带有WinDBG的Windows系统 - 这个供调试使用

在靶机上,我们需要使用以下命令启用16位支持:

FONDUE.exe /enable-feature:NTVDM

我们还需要启用内核调试,这可以通过以下命令来完成:

在执行之后,你会获得一个WinDBG用来在启动时建立主机连接的密钥。在我们的调试目的的主机中,我们需要启动WinDBG,并通过“File -> Kernel Debug”设置我们的内核调试会话:

重新启动靶机后,在WinDBG中会打开内核调试会话,并且会在利用过程中更容易地探索内核状态:

完成之后,我们就移步到exploit使用的重要概念—进程注入

进程注入

如果参考所公开的信息,那么将看到以下内容:

如果我们创建一个16位程序(如debug.exe),并将利用代码注入ntvdm,那么就可以防止系统在尝试写入nt!WbAddLookupEntryEx中的地址0时立即崩溃。

了解这个对于利用来说是非常重要的,我们需要理解进程注入是如何运作的,以及如何使用这种技术让NTVDM在其地址空间执行我们的代码,从而允许我们使用NULL映射页面。 Windows中的进程注入通常使用许多Win32 API(出于练习目的,我们会忽略其他技术,如Atom Bombing),具体为:

OpenProcess VirtualAllocEx WriteProcessMemory CreateRemoteThread

你可以从API的描述中了解每个调用的作用,为了完整起见,还是在下面详细说明一下每个调用的作用:

OpenProcess.aspx):这个调用将从PID中检索一个Windows进程,从而允许我们对进程进一步地操作。 VirtualAllocEx.aspx):这个调用用于在目标进程中分配内存,为我们预留要添加自定义代码的空间,或者将参数传递给一个远程线程。 WriteProcessMemory.aspx):提供一个地址和一个进程句柄,这个调用可以允许我们将数据复制到远程进程地址空间。 CreateRemoteThread.aspx):这将允许我们在远程进程中创建一个新的线程,并指定执行的位置。

通过调用这些API,我们就可以将shellcode注入到NTVDM进程中,但是为了更简单一点,我们把一个DLL加载到NTVDM中。这样做有很多好处,DLL可以在Visual Studio这样的软件中创建,它还包含漏洞利用代码,且不必担心在运行时解析API的情况。 为了加载我们的DLL,我们将使用另一个Win32 API调用LoadLibrary,它可以获取DLL的路径并将其动态加载到进程地址空间中。 现在我们需要构建注入工具:

1.使用OpenProcess获取NTVDM进程的句柄。 2.使用VirtualAllocEx分配足够的空间以便复制我们的LoadLibrary参数值,这将成为用于利用的DLL的路径。 3.使用WriteProcessMemory将用于利用的DLL路径写入远程分配的内存。 4.最后,使用CreateRemoteThread生成一个线程并在远程进程中执行LoadLibrary调用,将复制的DLL路径地址作为参数传递。

如果我们用一个非常简单的DLL来运行,那么就可以看到NTVDM完美地调用了我们的代码:

构建exploit

目前我们可以将任意DLL加载到NTVDM进程中了,现在就需要开始考虑如何构建我们的exploit。一个建议提供了以下示例来触发此漏洞:

如果我们将这段代码添加到一个DLL中,并将其注入到NTVDM进程中,我们发现WinDBG会触发以下断点:

在这里我们可以看到EIP是00000000h,中断已经成功地被触发了….真棒,现在就控制内核的代码执行了:) 接下来就需要撸起袖子开始编写shellcode,该shellcode将会被exploit执行。

编写Shellcode

对于这个exploit,我想要编写shellcode来尝试获取“cmd.exe”会话的SYSTEM权限。不幸的是,shellcode做类似这样的事情并不像“bind tcp”那样简单,但是这里有几个质量较好的教程,例如@ samdb的优秀文档。 在这篇文章中,我们会创建自己的shellcode。要做到这一点,我们需要了解一些内核结构。(在这方面,如果你还没有阅读我以前的文章,【点击原文】阅读)。 与之前的文章类似,我们编写的shellcode将负责找到与cmd.exe和System进程对应的EPROCESS结构,然后将access token从System复制到cmd.exe,从而将我们提升到SYSTEM用户。 首先,我们需要找到我们的cmd.exe进程的EPROCESS。 为此,我们将从fs寄存器开始,它在32位Windows内核中指向nt!_KPCR结构。 KPCR是“内核处理器控制区”,其拥有关于当前正在执行的处理器状态的信息,我们还可以用来获取进程和线程的信息和许多有用的字段。 为了得到WinDBG中的fs寄存器地址,可以使用下面的命令:

dg fs

然后会返回像下图这样的东西:

上面的例子中,在地址80dd7000h中有我们的nt!_KPCR结构。知道了这一点,我们就可以使用下方命令查看KPCR的内容:

dt nt!_KPCR 80dd7000

偏移120h(在_KPCR结构的末尾)是nt!_KPRCB结构,可以通过以下命令查看:

dt nt!_KPRCB 80dd7000+0x120

这样我们就可以看到KPRCB结构,看起来就像这样:

我们在这里找的是在偏移4h(或KPCR开始的124h)处找到的,并且是对应于当前正在执行的线程的nt!_KTHREAD结构。我们可以使用下方命令转储这些信息:

dt nt!_KTHREAD 0x87507940

在偏移地址150h处,我们找到一个指向Process的指针,它对应于我们正在寻找的EPROCESS结构:

现在就可以访问EPROCESS结构了,我们可以使用ActiveProcessLinks属性(实际上是一个指向LIST_ENTRY的指针,它是一个双向链表)来枚举当前正在运行的所有进程,直到找到cmd.exe进程和SYSTEM进程。 要找到“cmd.exe”,需要使用在偏移150h处找到的EPROCESS.ImageFileName属性:

对于System进程,我们知道通常PID为“4”,所以可以在偏移量为b4h的UniqueProcessId属性中找。

构建后,最终的shellcode如下所示:

从内核返回

不幸的是,在处理内核漏洞利用时,如果不先确认操作系统是否处于安全状态,我们就不能让我们的利用返回。 当内核地址空间中的内存被破坏时,保持操作系统正常运行会变得非常困难,这个exploit也不例外。 简单地通过ret或ret 0xc指令将执行返回给内核将导致如下所示结果:

在这方面,能做的只有试图让内核恢复到安全状态以继续执行。 在这个exploit的情况下,我们需要了解为什么以及如何调用我们的函数。这个问题围绕着如下的结构:

我们也知道,在shellcode被调用的地方,调用堆栈看起来像这样:

如果我们反汇编shellcode之前的最后一个函数WbFindLookupEntry,就可以找到调用shellcode的位置:

在这里,dword ptr [ebx+14h]调用实际上是调用上述结构中的cmp_func属性,这意味着在进入shellcode时,ebx寄存器指向_WARBIRD_EXTENSION结构。 如果我们用WinDBG检查内存,会看到以下内容:

这表明,虽然struct memory的初始值是NULL,但偏移量为0x4的count属性被设置为1,导致内核试图对shellcode进行多次调用。为了避免这种情况,我们需要将count属性更新为0。 接下来,我们必须在没有任何异常的情况下从NtQuerySystemInformation调用中返回,在尝试清理_WARBIRD_EXTENSION结构并取得了一些成功,且经历许多蓝屏之后,我发现让内核恢复到正常状态的最快方法是简单地遍历每个堆栈帧,直到在ExpQuerySystemInformation继续执行。要做到这一点,我们需要检查每个执行的函数,直到执行结果被传递给shellcode,并将寄存器和内存值恢复到它们的原始值。 完成后,看起来像这样:

如果在上面更新shellcode,并重新运行exploit,会出现错误检查:

这是可以预料的,因为我们只是忽略了在内核中恢复APC执行的任何形式。 在这个例子中,我们可以简单地通过更新当前线程来修正这个错误:

如果在恢复APC后继续,我们发现会遇到了另一个异常:

这是由于我们的方法只是跳过内核释放锁获取的过程。为了让我们退出syscall,我们需要更新shellcode,通过将我们的线程值清零来从线程中删除锁定:

最后一步

剩下要做的就是编译shellcode并把它转换成一个C buffer,这个C buffer将被我们注入的DLL使用。 为了编译shellcode,我通常会使用nasm,在这个例子中可以这样调用:

nasm shellcode.asm -o shellcode.bin -f bin

然后我们可以使用Radare2提取一个不错的C buffer:

radare2 -b 32 -c 'pc' ./shellcode.bin

这里给了我们最终的exploit DLL的源代码:

完成后,运行一下试试:

通过Windows 内核的提升权限利用到此就结束了。项目可以从Github上下载。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2017-12-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 FreeBuf 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 漏洞概览
  • 搭建实验环境
  • 进程注入
  • 构建exploit
  • 编写Shellcode
  • 从内核返回
  • 最后一步
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档