The first method of testing for this is to check is to create a tweak using Theos. To get all the necessary information, I use a combination of other tools, namely:
Examining the changes, we find that they’re all in request type 5, corresponding toGUESTRPC_FINALIZE. The user controls the argument which is& 0x21and passed toguestrpc_close_backdoor.
Control ofa3allows us to go down the first branch in a previously inaccessible manner, letting us free the buffer ata1+0x8, which corresponds to the buffer used internally to store the reply data passed back to the user. However, this same buffer will also be freed with command type 6,GUESTRPC_CLOSE, resulting in a controlled double free which we can turn into use-after-free. (The other patch nop’d out code responsible for NULLing out the reply buffer, which would have prevented this codepath from being exploited.)
Given that the bug is very similar to a traditional CTF heap pwnable, we can already envision a rough path forward, for which we’ll fill in details shortly:
Obtain a leak, ideally of thevmware-vmxbinary text section
Use tcache to allocate a chunk on top of a function pointer
Obtainripandrdicontrol and invokesystem("/usr/bin/xcalc &")
Heap internals and obtaining a leak
Firstly, it should be stated that the vmx heap appears to have little churn in a mostly idle VM, at least in the heap section used for guestrpc requests. This means that the exploit can relatively reliable even if the VM has been running for a bit or if the user was previously using the system.
In order to obtain a heap leak, we’ll perform the following series of operations
Allocate three channels [A], [B], and [C]
Send theinfo-setcommmand to channel [A], which allows us to store arbitrary data of arbitrary size (up to a limit) in the host heap.
Open channel [B] and issue ainfo-getto retrieve the data we just set
Issue the reply length and reply read commands on channel [B]
Invoke the buggy finalize command on channel [B], freeing the underlying reply buffer
Invokeinfo-geton channel [C] and receive the reply length, which allocates a buffer at the same address we just
Close channel [B], freeing the buffer again
Read out the reply on channel [C] to leak our data
Eachvmware-vmxprocess has a number of associated threads, including one thread per guest vCPU. This means that the underlying glibc heap has both the tcache mechanism active, as well as several different heap arenas. Although we can avoid mixing up our tcache chunks by pinning our cpu in the guest to a single core, we still cannot directly leak alibcpointer because only themain_arenain the glibc heap resides there. Instead, we can only leak a pointer to our individual thread arena, which is less useful in our case.
[#0] Id 1, Name: "vmware-vmx", stopped, reason: STOPPED [#1] Id 2, Name: "vmx-vthread-300", stopped, reason: STOPPED [#2] Id 3, Name: "vmx-vthread-301", stopped, reason: STOPPED [#3] Id 4, Name: "vmx-mks", stopped, reason: STOPPED [#4] Id 5, Name: "vmx-svga", stopped, reason: STOPPED [#5] Id 6, Name: "threaded-ml", stopped, reason: STOPPED [#6] Id 7, Name: "vmx-vcpu-0", stopped, reason: STOPPED <-- our vCPU thread [#7] Id 8, Name: "vmx-vcpu-1", stopped, reason: STOPPED [#8] Id 9, Name: "vmx-vcpu-2", stopped, reason: STOPPED [#9] Id 10, Name: "vmx-vcpu-3", stopped, reason: STOPPED [#10] Id 11, Name: "vmx-vthread-353", stopped, reason: STOPPED . . . .
To get around this, we’ll modify the above flow to spray some other object with a vtable pointer. I came acrossthis writeupby Amat Cama which detailed his exploitation in 2017 using drag-n-drop and copy-paste structures, which are allocated when you send a guestrpc command in the host vCPU heap.
Therefore, I updated the above flow as follows to leak out a vtable/vmware-vmx-bss pointer
Allocate four channels [A], [B], [C], and [D]
Send theinfo-setcommmand to channel [A], which allows us to store arbitrary data of arbitrary size (up to a limit) in the host heap.
Open channel [B] and issue ainfo-getto retrieve the data we just set
Issue the reply length and reply read commands on channel [B]
Invoke the buggy finalize command on channel [B], freeing the underlying reply buffer
Invokeinfo-geton channel [C] and receive the reply length, which allocates a buffer at the same address we just
Close channel [B], freeing the buffer again
Sendvmx.capability.dnd_versionon channel [D], which allocates an object with a vtable on top of the chunk referenced by [C]
Read out the reply on channel [C] to leak the vtable pointer
One thing I did notice is that the copy-paste and drag-n-drop structures appear to only allocate their vtable-containing objects once per guest execution lifetime. This could complicate leaking pointers inside VMs where guest tools are installed and actively being used. In a more reliable exploit, we would hope to create a more repeatable arbitrary read and write primtive, maybe with these heap constructions alone. From there, we could trace backwards to leak our vmx binary.