译者注:老生长谈的东西了,在x64中修改token提权的方法和x86大同小异。本文适合新手去了解学习Windows内核token提权的方法。
注意:在内核中的到处混用会导致BSOD蓝屏以及数据的丢失。强烈建议在虚拟机或其他非工作系统中测试。
运行中的Windows进程所关联的用户帐户和访问权限由一个叫做令牌(token)的内核对象仲裁。用于跟踪各种特定进程数据的内核数据结构包含了一个指向token的指针。当进程试图去执行各种操作时,比如打开一个文件,token中的账户权限会用于和所需的权限进行比较,以此决定该操作是否可行。
因为token指针只是内核内存中的数据,对于在内核模式中执行的代码来说,将其更改为指向不同的token以赋予该进程一个不同的权限集,这不足为道。这强调了保护系统不受本地用户利用的漏洞影响的重要性,这些漏洞可以导致在内核中执行代码。
本文会提供一个说明和示例exp代码,用于将一个进程提权到系统管理员权限。从我的设备驱动开发一文中修改设备驱动程序并进行测试,这将被用作向内核中注入可执行代码的方法。
我们将使用标准的用户权限启动命令行(cmd.exe)用于演练,然后使用内核调试器手动定位更高权限进程的token,并使得运行的cmd.exe也提权到系统级权限。
首先,找到System进程的16进制地址:
它指向一个_EPROCESS
结构,下面是该结构的一些字段:
token是一个保存在偏移0x208的指针值,我们可以打印出它的值:
你可能已经注意到在_EPROCESS
结构体中,token字段是以_EX_FAST_REF
来声明的而不是期望的_TOKEN
结构。_EX_FAST_REF
结构是一种技巧,它依赖于一种假定,在16字节的边界上需要将内核数据结构对齐到内存中。这意味着一个指向token或其他任何内核对象的指针最低的4个位永远都是0(十六进制就是最后一个数永远为0)。Windows因此可以自由的使用该指针的低4位用于其他目的(在本例中为可用于内部优化的引用计数)。
从_EX_FAST_REF
中获取实际的指针只需要简单的修改最后的一位十六进制数位0即可。通过程序实现的话,就将最低4位的值与0值按位与:
可以通过dt _TOKEN
或更好的!token
扩展命令来显示一个token:
注意到安全标志符(SID)的值S-1-5-18是内置的本地系统账户的SID(查看微软的well-known SIDs reference)。
下一步就是定位cmd.exe进程的_EPROCESS
结构并替换偏移0x208的token指针值为System的token地址。
最后,转到命令行,使用whoami来看看用户账户。你也可以通过运行命令或访问那些你所知道的需要系统管理权限的文件来验证。
通过代码来实现上述的过程非常简单,相比较几年来广泛使用的x86提权的代码,x64仅有着一些次要的差异。
我反汇编了nt!PsGetCurrentProcess
函数来查看如何获取当前进程的_EPROCESS
地址。系统中运行的所有进程的_EPROCESS
结构体都通过ActiveProcessLinks
字段被链在一个双向链表中。我们可以通过下面的这些链接来定位System进程,查找进程ID为4即可。
我在Cygwin中使用了NASM来汇编该份代码(本地win32 NASM二进制亦可)。编译:
nasm priv.asm
这会产生一个原生二进制输出文件—priv(没有文件后缀扩展)。
注意NASM生成的int 3
指令的双字节操作码为0xCD 0x03
而不是标准的调试器单字节断点0xCC
。着将在内核调试中引发问题因为它假定在内存中的下一个指令仅仅是向前一字节而不是双字节。命中断点后,通过手动调整RIP寄存器向后移动一字节即可继续正常工作。最好的办法还是先用db 0cch
生成正确的操作码。
我的设备驱动程序开发一文中展示了一个驱动程序示例,它通过一个设备I/O控制接口接受一个用户模式字符串,简单的在内核调试器中打印该字符串。为了测试上面的exp代码,我修改了该驱动来将传递进来的数据作为代码执行。
这当然需要内存页被标记为可执行,否则数据执行保护(DEP)会触发异常。实际上通过IOCTL接口(METHOD_DIRECT)传递进来的缓冲区默认是可执行的,我也表示很惊讶。我不确定是否永远如此,并且我可以确认这和x64系统上内核内存使用的大页面有关,这使得内存保护不切实际(内存只能在虚拟内存页大小的粒度上设置为不可执行)。
此后我修改了用户模式测试程序,使用下面的函数来从priv二进制文件中读取数据而不再是传递一个硬编码的字符串:
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)