Accessing Kernel and Physical RAM from Userland

Technical Writeups June 2021
null

Corellium VMs offer user programs running inside the CHARM hypervisor a way to access either kernel or physical views of VM RAM. This allows users to write research tools that do not rely on other -- more complex -- paths to gain that privilege, which is normally reserved to the kernel.

The access interface has two parts. The first part allows for obtaining information on the kernel location in memory, as well as current values of relevant Arm system registers. The second part operates like a privileged equivalent to the memcpy function, adding the ability to copy data across typically privileged address space boundaries.

Note that kernel memory page faults are not supported; they will result in partial copy (and return value set appropriately). User page faults are supported.

Only 64-bit EL0 code can use this interface, and is accessed via the use of HVC instructions. The exact syntax form differs slightly between iOS and Android VMs. Both are shown below, and are also available in the Corellium GitHub as guest-tools, along with examples.

iOS EL0 (64-bit)

 .align 4
.global _get_kern_info
_get_kern_info:
    mrs xzr, cntpct_el0
    hvc #0x9402
    ret

.align 4
.global _unicopy
_unicopy:
    uxtb x0, w0
    orr x5, x0, #0x100
    mov x4, #0
    mov x6, #16
1:
    cbz x3, 2f
    tst x5, #12
    bne 4f
    ldrb w0, [x2]
4:
    tst x5, #3
    bne 5f
    strb w0, [x1]
5: 
    mov x0, x5
    mrs xzr, cntpct_el0
    hvc #0x9402
    cbnz x0, 3f
    sub x6, x6, #1
    cbz x6, 2f
    b 1b
3:
    mov x6, #16
    add x1, x1, x0
    add x2, x2, x0
    sub x3, x3, x0
    add x4, x4, x0
    b 1b
2: 
    mov x0, x4
    ret

Android and Linux EL0 (64-bit)

.align 4
.global get_kern_info
get_kern_info:
    mrs xzr, pmcr_el0
    hvc #0x9402
    ret

.align 4
.global unicopy
unicopy:
    uxtb x0, w0
    orr x5, x0, #0x100
    mov x4, #0
    mov x6, #16
1: 
    cbz x3, 2f
    tst x5, #12
    bne 4f
    ldrb w0, [x2]
4:
    tst x5, #3
    bne 5f
    strb w0, [x1]
5:
    mov x0, x5
    mrs xzr, pmcr_el0
    hvc #0x9402
    cbnz x0, 3f
    sub x6, x6, #1
    cbz x6, 2f
    b 1b
3: 
    mov x6, #16
    add x1, x1, x0
    add x2, x2, x0
    sub x3, x3, x0
    add x4, x4, x0
    b 1b
2: 
    mov x0, x4
    ret

The following header file declares the interface for both:

#define KERN_INFO_VA             0x00
#define KERN_INFO_PA             0x01

#define KERN_INFO_TPIDR_EL1      0x40
#define KERN_INFO_TTBR0_EL1      0x41
#define KERN_INFO_TTBR1_EL1      0x42
#define KERN_INFO_TCR_EL1        0x43
#define KERN_INFO_VBAR_EL1       0x44
#define KERN_INFO_CONTEXTIDR_EL1 0x45

// access kernel-specific information based on the specified KERN_INFO_* parameter
uintptr_t get_kern_info(unsigned int key);

#define UNICOPY_DST_USER   0 // Copy to user virtual address space
#define UNICOPY_DST_KERN   1 // Copy to kernel virtual address space
#define UNICOPY_DST_PHYS   2 // Copy to physical address
#define UNICOPY_SRC_USER   0 // Copy from user virtual address space
#define UNICOPY_SRC_KERN   4 // Copy from kernel address space
#define UNICOPY_SRC_PHYS   8 // Copy from physical address

// returns amount of data copied successfully
// mode: the bitwise OR of a UNICOPY_DST_* and UNICOPY_SRC* parameter, specifying the 
//       address space to copy to and from.
// dst:  the address to copy to in the UNICODE_DST_* address space
// src:  the address to copy from in the UNICODE_SRC_* address space
// size: the number of bytes to copy from one address space to the other.
size_t unicopy(unsigned int mode, uintptr_t dst, uintptr_t src, size_t size);