Skip to content

How to exploit cve 2017 4901

nonick edited this page Jul 24, 2017 · 1 revision

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.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.

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.

We are able to invoke RPCI as normal use inside vmware guest,so we don't need admin privileges to exploit this bug.

Bug in Drag and Drop RPCI

This vulnerability exists in DnD version 3.

Ida shows it more clearly.

char __fastcall DnD_TransportBufAppendPacket(DnDTransportBuffer *a1, packet *a2, unsigned __int64 a3)
{
  DnDTransportBuffer *v3; // rbx@1
  __int64 v4; // rcx@1
  packet *v5; // rdi@1
  int v6; // eax@3
  int v7; // edx@3
  __int64 v8; // rax@9
  __int64 v9; // rcx@10
  char result; // al@11

  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;
}

Above code shows no checks in totalsize property, so what will happen if we enlarge the totalsize in second packet?

Reversing

It is necessary 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.

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 of the RPCI handler have the same definition:

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 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.

char __fastcall vmxDndVer(__int64 a1, __int64 a2, __int64 a3, int a4, const char **a5, _DWORD *a6)
{
  char result; // al@2
  __int64 v7; // r9@4
  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.

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:

-----------          -----------          -----------
|         |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.

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.

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.

.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.

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!