1
概述
初见是惊鸿一瞥,重逢是始料未及。
各位“蓝颜知己”我们又见面了,本文是《漏洞分析视角下的CVE-2020-0796漏洞》的兄弟篇。CVE-2020-796漏洞成因已经在前文说明,这次我们从漏洞利用视角来看看CVE-2020-796漏洞。
这次我们分析对象是github.com/danigargu上的本地权限提升(LPE,Local Privilege Escalation)利用(Exploit)。
LPE的exploit是以C++源码形式提供的,这也有便于我们分析exploit开发原理。我们可以用Microsoft Visual Studio编译它。然后以非管理员身份运行cmd程序,执行编译后的文件cve-2020-07960local.exe。如图1所示:
图1
在exploit执行前后的whoami命令显示用户名并未改变。
图2
如图2所示,而是新弹出一个cmd的界面,在其中执行whoami显示用户名已经被修改为nt authority\system。
幸亏本文标题不是《漏洞复现视角下的CVE-2020-0796漏洞》,不然本文就到此为止了。由此可见选一个好标题的重要性,来让我们看看,这一闪而显的黑窗口背后发生了什么。
2
一串神奇的数字
使用之前分析工作中的断点,查看
bp srv2!Srv2DecompressData+0x108 ".printf \"srv2!memmove(Src=0x%I64x, Dst=0x%I64x,Size=%d) \n\", rdx, rcx, r8d;db rdx;.echo"
srv2!memmove(Src=0xffffc68fe8ee2840, Dst=0xffff8d8dec51e0a0,Size=16)
kd> r
目的地址内存:
kd> db 0xffff8d8dec51e0a0 L0n16
源地址内存:
kd> db 0xffffc68fe8ee2840 L0n16
这个Exploit要从一个特定的地址读16字节的数据,然后再写入另一个特定的地方去。根据前文的分析,此处memmove的函数,是要把数据流头部未压缩的数据,移动过来,完成预定的解压缩业务流程。
图3
如图3所示,从我们抓到的流量数据来看,offset大小与memmove的size,以及数据和数据的确能匹配上。我们要搞明白这个来自网络流中,现在是memmove的源地址存放的数据,对这个特定目的地址来说有什么特别的意义。
一番眼花缭乱的操作之后,“我不再是我”。我们需要额外了解一些关于Windows内核提权的小技巧。这是一个在虚拟世界中如何向Windows“证明你自己是你自己”的问题。和现实世界中“大内侍卫凌凌漆的腰牌”有些类似。
3
进程的访问令牌
微软文档中关于访问令牌(Access Token,原文详见引用2)的描述如下:
访问令牌是一个描述进程或线程的安全上下文的对象。令牌中的信息包括与进程或线程关联的用户帐号的标识和特权。当用户登录时,系统通过将用户密码与安全数据库中存储的信息进行比较来验证用户密码。如果密码通过了验证,则系统将生成一个访问令牌。以该用户名义执行的每个进程都有此访问令牌的副本。
当线程与安全对象进行交互或尝试执行需要特权的系统任务时,系统使用访问令牌来标识用户。访问令牌包含以下信息:
用户帐号的安全标识符(SID)
用户所属的组的SID
标识当前登录会话的登录SID
用户或用户组拥有的特权列表
所有者SID
主要组(primary group)的SID
用户创建安全对象而没有指定_安全描述符_时,系统使用的默认DACL
访问令牌的来源
令牌是主令牌(primary token)还是模拟令牌(impersonation token)
限制SID(restricting SIDs)的可选列表
当前的模拟级别
其他统计信息
每个进程都有一个主要令牌,用于描述与该进程关联的用户帐号的安全上下文。默认情况下,当进程的线程与安全对象进行交互时,系统将使用主令牌。此外,线程可以模拟当事人帐号(client account)。模拟允许线程使用当事人的安全上下文与安全对象进行交互。模拟当事人的线程同时具有主令牌和模拟令牌。
4
ATT&CK中的访问令牌操纵攻击技术
MITRE ATT&CK®是一个基于真实世界的观察对手的战术和技术的可全球访问的知识库。ATT&CK知识库被用作在私营部门、政府以及网络安全产品和服务社区中开发特定威胁模型和方法的基础。
在权限提升(Privilege Escalation)一节中的访问令牌操纵(Access Token Manipulation)详细介绍了在Windows系统中通过如何操纵访问令牌来提升权限的技巧。
Windows使用访问令牌来确定正在运行的进程的所有权。用户可以操纵访问令牌以使正在运行的进程看起来像它属于启动该进程的用户以外的其他人。发生这种情况时,该进程还将采用与新令牌关联的安全上下文。例如,Microsoft提倡使用访问令牌作为最佳安全实践。管理员应以标准用户身份登录,通过内置的访问令牌操作命令runas,以管理员特权运行其工具。
攻击者可以使用访问令牌在不同的用户或系统安全上下文下进行操作,并逃避检测。对手可以使用内置的Windows API函数来复制现有进程中的访问令牌;这被称为令牌窃取。对手必须已经在特权用户上下文(即管理员)中才能窃取令牌。但是,攻击者通常使用令牌窃取来将其安全上下文从管理员级别提升到SYSTEM级别。如果帐户在远程系统上具有适当的权限,则对手可以使用令牌作为该令牌的帐户向远程系统进行身份验证。
5
手动修改访问令牌
我们了解到Windows识别进程的身份靠的就是这个令牌。在Windows中应用态的内存可被进程任意读写,为了更好地安全性,Windows把令牌(token)的放进了内核态。
先看其中一个手动修改方法:
WinDBG中的!process扩展命令会显示指定进程或者全部进程的EPROCESS块信息。
kd> !process 0 0 System
其中的PROCESS ffff980dd646a380即表示System进程的EPROCESS的内存地址是0x ffff980dd646a380。可使用dt _EPROCESS指令查看0x ffff980dd646a380地址处的EPROCESS块信息:
kd> dt _EPROCESS ffff980dd646a380
EPROCESS是较大的结构体,我们省略了部分,重点关注0x360偏移处的这个Token结构。该结构是一个_EX_FAST_REF类型。
kd> dt _EX_FAST_REF
观察_EX_FAST_REF字段定义可知,0~4bit是引用计数,其余bit位才是value。
kd> dq ffff980dd646a380+0x360 L1
一般会通过位与法获得最终的值
kd> ? ffffd587`2c60604f & ffffffff`fffffff0
可使用!token扩展指令查看令牌的具体结构
kd> !token ffffd587`2c606040
也可使用dt _TOKEN查看字段定义。
kd> dt _TOKEN
在尝试把替换cmd.exe进程的TOKEN指针之前,我们先执行一下whoami看看。
图4
如图4中,whoami显示,当前账户是admin。
手动查看cmd.exe进程的TOKEN指针和前文所述基本一致,不再赘述。
kd> !process 0 0 cmd.exe
然后把cmd.exe进程的TOKEN指针替换为system的TOKEN指针。
kd> eq ffff980ddcba8080+0x360 ffffd587`2c606040
图5
如图5中,再执行一下whoami显示,当前账户已经由admin变化为nt authority\system。
6
TOKEN+0x40偏移
我们现在来看看memmove()函数的目的地址是否就是cve-2020-0796-local.exe进程的TOKEN指针。
srv2!memmove(Src=0xffff8205e4678590, Dst=0xffff998e13d780a0,Size=16)
kd> r
kd> db rdx L0n16
kd> db rcx L0n16
kd> !process 0 0 cve-2020-0796-local.exe
kd> dq ffff8205e9ad5080+0x360 L1
我们发现memmove()函数的目的地0xffff998e`13d780a0和cve-2020-0796-local.exe进程的TOKEN指针存放地址ffff8205`e9ad53e0相去甚远。但却指向ffff998e`13d78060+0x40处。
kd> dt _TOKEN
从_TOKEN结构体的定义中,我们了解在0x40偏移处是一个_SEP_TOKEN_PRIVILEGES的结构。
kd> db ffff998e`13d78060+0x40 L0x16
kd> dt _SEP_TOKEN_PRIVILEGES ffff998e`13d78060+0x40
单步步过之后,再查看一下:
kd> db ffff998e`13d78060+0x40 L0x16
我们观察到cve-2020-0796-local.exe进程的TOKEN中的Privileges 确实被修改过了。
kd> dt _SEP_TOKEN_PRIVILEGES ffff8d8de`1206040+0x40
这神奇的16字节数据正是之前我们曾观察到的System进程的TOKEN中的Privileges的Present值。Present字段表示启用的特权,Enabled字段表示拥有的特权。这神奇的16字节数据覆盖写进程的Privileges结构之后,系统即拥有并启用了的system进程所有的特权。
我们了解到_SEP_TOKEN_PRIVILEGES是由3个bitmap构成的结构体,通过每bit是否为1来判断进程特权的。看上去应该不会超过64种特权。
7
“有种贪心叫我全要”
如果我们把这16字节全部用0xFF填充呢?
图6
我们使用前文所述的WinDBG手动设置了一下,如图6所示,使用whoami /PRIV命令时会提示“错误:指定的特权不存在”。我们在微软文档中也仅发现了44中特权。
我们修改了Exploit的源码,编译后执行验证,并在之前的断点停下来确认符合预期。
srv2!memmove(Src=0xffff8205e32a2590, Dst=0xffff998e11a4c6b0,Size=16)
kd> r
kd> db rdx L0n16
图7
在图7中,结果显示这并不影响Exploit的效果,
8
特权影响名字?
替换TOKEN不仅可以获取特权,也直接变更了账户。这个可以理解。那么问题来了,虽然修改Privileges结构,可以获得特权,但又是如何影响账户名的呢?
现在我们需要来看看exploit的源码。
图8
如图8中源码显示,在修改本进程的_SEP_TOKEN_PRIVILEGES结构之后,就已经获得system进程的特权,后续调用了inject()函数。
图9
在图9中,inject()函数中通过CreateToolhelp32Snapshot()创建进程快照,使用比较进程名的方法找到"winlogon.exe"进程。然后使用VirtualAllocEx申请了一块带有可执行属性的内存(PAGE_EXECUTE_READWRITE),WriteProcessMemory把shellcode写入目标进程,CreateRemoteThread()最终完成任务。这是一个经典的远程线程注入。
图10
图10中shellcode应该开启cmd的功能。
图11
如图11所示,从进程树上我们能清楚的看出cmd.exe是winlogon.exe的子进程,和powershell.exe并未任何关系。
winlogon.exe是以nt authority\system用户的名义启动的,而子进程继承了父进程winlogon.exe的TOKEN,也就获得了system的特权,在使用whoami命令时显示用户也就是nt authority\system。
选取winlogon.exe作为注入进程只是一个特例,任何以NT AUTHORITY\SYSTEM或者其他特权账户名义启动的进程都有相同的效果。
9
应用层获取内核进程TOKEN的地址编程技巧
前文我们介绍了如何借助WinDBG手动修改位于内核态内存的进程的访问令牌,现在我们需要了解一下exploit如何使用编程技巧从应用层获取了TOKEN的地址的。
从源码上来看,其使用get_process_token()获取到了token在内核中的地址,然后利用CVE-2020-0796漏洞向ktoken+0x40处,即访问令牌中的_SEP_TOKEN_PRIVILEGES字段,写入了数据。
在Windows上有一些众所周知的信息泄漏技巧,如本例中使用的NtQuerySystemInformation函数。这个函数有一些神奇的功能,它会返回许多内核地址。我们主要感兴趣的是此函数能够提供目前分配的每个对象的列表,使用SystemExtendedHandleInformation参数调用NtQuerySystemInformation,我们可以得到SYSTEM_HANDLE_INFORMATION_EX结构。借助此列表,我们可以使用PID和句柄获取所需对象的内核地址。
至此漏洞利用视角下的工作基本完成。
10
综述
本文介绍了2种通过手动修改进程访问令牌或特权结构提升进程权限的技巧,分析了本地权限提升exploit中使用的获取内核地址信息的技巧。带领大家近距离体会了本地权限提升利用过程。后续有机会我们再谈谈关于此漏洞的检测。再次提醒请尽快安装官方补丁。
11
参考&引用
https://github.com/danigargu/CVE-2020-0796