/
analysis.txt
executable file
·107 lines (92 loc) · 4.77 KB
/
analysis.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
01.07.2019. Research Note: Patch diff vmxnet3 bug from GeekPwn2018
VMSA-2018-0027 reports two bugs from GeekPwn: CVE-2018-6981 (RCE) and CVE-2018-6981 (infoleak). The latter affects only ESXi. Both bugs are d.t. uninitialized variable usage.
Patch diff - culprit basic block with added code:
sub_140193260 proc near
// skip ...
loc_140193398:
mov rax, [rbx+0D0h]
lea rdx, [rsp+0A8h+var_78]
mov rcx, [rbx+128h]
mov r8d, 1
mov [rsp+0A8h+var_88], rdx
add rcx, 8; Dst
mov edx, 2B0h
mov r9d, [rax+0B8h]
call sub_140447AE0
// -- patch starts here -- below code is not present in the vulnerable binary
test al, al
jnz short loc_1401933E0
loc_1401933cf:
lea rcx, aVmxnet3UserCou; "VMXNET3 user: Could not read DSDevRead"...
call sub_14046F430
jmp loc_1401935C5; jumptable 00000001401932D7 default case
// -- end of patch --
loc_1401933e0:
lea rdx, [rsp+0A8h+var_78]
mov rcx, rbx
call sub_140193790
lea rcx, [rsp+0A8h+var_78]
call sub_140447470
mov rcx, rbx
call sub_140202400
jmp loc_1401935C5; jumptable 00000001401932D7 default case
// skip ...
sub_140193260 procedure is responsible for handling of GET and SET commands sent to the adapter by the guest VM via writing to the physical memory of the BAR1 register. In vmxnet3_drv.c it's called like: VMXNET3_WRITE_BAR1_REG(adapter, VMXNET3_REG_CMD, VMXNET3_CMD_*).
The specific case which is patched (loc_140193398) is responsible for handling of the SET command with the magic value 0xCAFE0004 of the VMXNET3_REG_CMD offset of the BAR1 register. It's not clear which command is this, as the number of cases in the binary switch does not corresponds to the number of magics in the guest driver source code, indicating possible version mismatch. If the numeric value is correct, then it should be VMXNET3_CMD_UPDATE_MAC_FILTERS.
What happens here is the guest VM sends a particular SET command to the vmxnet3 adapter by writing the magic value 0xCAFE0004 to BAR1 register VMXNET3_REG_CMD. As the host code receives the changed register value, it calls "sub_140447AE0" (read_DSDevRead* for later reference), passing the stack variable var_78 as an argument. In case that read_DSDevRead fails (that was unchecked in the vulnerable binary), the variable remains uninitialized and passed to sub_140193790 and sub_140447470, where the actual memory corruption presumably happens.
The code inside of read_DSDevRead is patched too: a memset on the stack variable argument is added in case of a failure, which confirms the above assessment:
Vulnerable sub_140447AE0 / read_DSDevRead:
char __fastcall read_DSDevRead_vuln(unsigned __int64 a1, unsigned __int64 a2, unsigned int a3, unsigned int a4, __int64 culprit)
{
unsigned __int64 v5; // r10
v5 = *(_QWORD *)(qword_140DA45A8 + 19776);
if ( (a1 > v5 || !a2 || a2 > v5 - a1 + 1) && !_bittest((const signed int *)&a3, 0xDu) )
return 0;
sub_140445DF0(a1, a2, a3, a4, culprit);
return 1;
}
Patched:
char __fastcall read_DSDevRead_patched(void *Dst, unsigned __int64 a2, unsigned int a3, unsigned int a4, void *culprit)
{
unsigned __int64 v5; // r10
char result; // al
v5 = *(_QWORD *)(qword_140DA45A8 + 0x4D40);
if ( (unsigned __int64)Dst <= v5 && a2 && a2 <= v5 - (unsigned __int64)Dst + 1
|| _bittest((const signed int *)&a3, 0xDu) )
{
sub_140445B50((unsigned __int64)Dst, a2, a3, a4, (__int64)culprit);
result = 1;
}
else
{
memset(culprit, 0, 0x60ui64);
result = 0;
}
return result;
}
DSDevRead is the name of a sub-structure within vmxnet_drv's shared memory structure, defined as follows:
struct Vmxnet3_DriverShared {
__le32 magic;
/* make devRead start at 64bit boundaries */
__le32 pad;
struct Vmxnet3_DSDevRead devRead;
__le32 ecr;
__le32 reserved;
union {
__le32 reserved1[4];
union Vmxnet3_CmdInfo cmdInfo; /* only valid in the context of
* executing the relevant
* command
*/
} cu;
};
struct Vmxnet3_DSDevRead {
/* read-only region for device, read by dev in response to a SET cmd */
struct Vmxnet3_MiscConf misc;
struct Vmxnet3_IntrConf intrConf;
struct Vmxnet3_RxFilterConf rxFilterConf;
struct Vmxnet3_VariableLenConfDesc rssConfDesc;
struct Vmxnet3_VariableLenConfDesc pmConfDesc;
struct Vmxnet3_VariableLenConfDesc pluginConfDesc;
};
So, the host code tries to read physical memory of Vmxnet3_DSDevRead in responce to the SET command, but the reading operation fails => uninitialized variable is used.