Userspace Interaction with the Hypervisor

Want to bypass the OS and jump straight to the kernel debugger from your user-mode application? This blog reveals a specific opcode sequence that achieves this for EL0 user software, even though BRK is typically intercepted by the kernel.
Userspace Interaction with the Hypervisor

OS-Independent Breakpoints

Sometimes it's desirable to break into the kernel debugger from user software running in EL0. Inserting a BRK opcode usually results in it being intercepted by the operating system kernel. But if you want to end up directly in the kernel debugger instead, you can use the following opcode sequence:

EL1 (64-bit)

hvc #0x7242

iOS EL0 (64-bit)

mrs xzr, cntpct_el0


hvc #0x7242

Linux EL0 (64-bit), Android EL0 (64-bit)

mrs xzr, pmcr_el0


hvc #0x7242

Linux EL0 (32-bit), Android EL0 (32-bit)

mrc p15,#0,r0,c9,c12,#0


hvc #0x7242

After running this code, the device will enter a paused state. If a debugger is attached, this will result in the debugger registering a debug event (similar to a breakpoint).

Console Output from Anywhere in the VM

Sometimes, when writing patches to kernel or user applications, it's nice to be able to print output without having to ask the operating system to do so. It can be fairly hard to get to debug output from some places, such as EL0 software that has no standard output and disabled debugging calls.

This feature helps you get debug output from patches, shellcodes, or maybe just helper programs without having to deal with operating system constraints.

Both EL0 and EL1 codes can print directly to the device's console via a special HVC (hypervisor call). The form of the HVC itself depends on the environment:

EL1 (64-bit)

hvc #0x6C43

ciOS EL0 (64-bit)

mrs xzr, cntpct_el0


hvc #0x6C43

Linux EL0 (64-bit), Android EL0 (64-bit)

mrs xzr, pmcr_el0


hvc #0x6C43

Linux EL0 (32-bit), Android EL0 (32-bit)

mrc p15,#0,r0,c9,c12,#0


hvc #0x6C43

To use the HVC, set registers x0 .. x2 (or r0 .. r2 for 32-bit code) to the following values:

CONSLOG_REQ_STR:

prints a zero-terminated (C) string

x0 = 0xFFFF0000


x1 = pointer to string

CONSLOG_REQ_U64:

prints a number as a hex

x0 = 0xFFFF0001


x1 = number

CONSLOG_REQ_S64:

prints a number as a signed decimal

x0 = 0xFFFF0002


x1 = number

CONSLOG_REQ_HEX:

prints a buffer as a hex dump

x0 = 0xFFFF0003


x1 = pointer to buffer


x2 = size in bytes

The memory printing calls (CONSLOG_REQ_STR and CONSLOG_REQ_HEX) return status in x0. If negative, the call failed. If not negative, returns the number of bytes retrieved from the buffer (or string). The other calls simply return zero in x0.

Text printed through this output path will show up in the device console, in cyan color using ANSI control characters.

An example of the string printing function, including handling EL0 page faults, is in the Corellium GitHub repository at guest-tools - the conslog program will echo its standard input to the VM console (in the highly visible cyan color of hypervisor messages).

Discover more insights into mobile app virtualization and userspace interactions — book a meeting with Corellium today!