Getting Higher Access

Getting Root

Once I had adb access it was obvious it was very limited so, let’s get root. After some digging what seemed to be the easiest option was to change adb shell’s id to 0. How do we do that?

The goal is to change our ADB shell’s user ID from 2000 (shell) to 0 (root).

Every process in Linux has a task_struct kernel structure, which contains a pointer to a cred (credentials) structure. The cred struct stores the process’s uid, gid, and other permissions. The shell user’s uid is 2000 (hex: 0x000007d0). If we find this struct in memory and zero it out, the shell becomes root.

The approach is a memory search-and-overwrite:

  1. Find “adbd\0” in memory using
    bgrep -B32 6164626400 mem.dump > bgrep.out
  2. Clean up the file to
    cat bgrep.out |cut -d: -f2|tr -d ' '|sed '/^\\/d' > bgrep.out.2
  3. Add 0x to all the lines
  4. Remove any non-numbers
  5. Look at the hex dump near each one
    `cat bgrep.out.2 |xargs -n 1 bash -c 'xxd -c8 -s "$(( $0-32 ))" -l 48 mem.dump; echo "\n"'
  6. Look for what looks like two addresses repeating like “40a2 933e” in this example. The reason we are looking for repeating is because the struct has the uid and gid next to each other in memory.
3624a650: 0000 0000 0000 0000  ........                                   
3624a658: 40a2 933e 0000 ffff  @..>....                          
3624a660: 40a2 933e 0000 ffff  @..>....        
3624a668: 0000 0000 0000 0000  ........                    
3624a670: 6164 6264 0065 722f  adbd.er/                                  
3624a678: 3000 0000 0000 0000  0.......   
  1. Reverse the address for endianness 40a2 933e0x3e93a240 this is the address of the cred struct
  2. Look at hex dump there
    xxd -c 8 -s $(( 0x3e93a240)) dump_0x4M.bin |less
  3. We see “2000” in hex →0x07d0 or flipped in endianess “d007”
3e93a240: 1c00 0000 d007 0000  ........
3e93a248: d007 0000 d007 0000  ........
3e93a250: d007 0000 d007 0000  ........
3e93a258: d007 0000 d007 0000  ........
3e93a260: d007 0000 3f00 0000  ....?...
  1. Write over with “00000000” add an offset (in our case it was 0x4M) to the address so it will be 0x7e93a240
> mdw 0x7e93a240 32
0x7e93a240: 00000024 000007d0 000007d0 000007d0 000007d0 000007d0 000007d0 000007d0 
0x7e93a260: 000007d0 0000003f 00000000 00000000 00000000 00000000 00000000 00000000 
0x7e93a280: 000000c0 00000000 00000000 00000000 00000000 00000000 43e45700 ffff0000 
0x7e93a2a0: 00000000 00000000 00000000 00000000 00000000 00000000 362e2e80 ffff0000 
> mww 0x7e93a244 00000000
> mww 0x7e93a248 00000000
  1. Test adb shell id → Should give you root

I did script out most of these steps so it wasn’t so manual. ### Bypassing SELinux

Even with root, SELinux (Security-Enhanced Linux) blocks most useful actions. SELinux enforces a policy that says what each process is allowed to do , our shell context (u:r:shell:s0) is highly restricted even as uid 0.

We need to patch the kernel function avc_denied(). This function is called whenever an SELinux policy check fails , it’s what generates the “avc: denied” log messages and returns the -EACCES error. If we make it always return 0 (success) instead, every SELinux check passes.

avc_denied We want to change the orr that sets the error return to orr w0, wzr, wzr , which sets the return to 0 (success). We change orr w0, wzr, #xffffffff3 to orr w0, wzr, wzr. From opcode: 321c77e0 to opcode 0x2A1F03E0

ffff80001053beb8 <avc_denied>:
ffff80001053beb8:       d100c3ff        sub     sp, sp, #0x30
ffff80001053bebc:       a9027bfd        stp     x29, x30, [sp, #32]
ffff80001053bec0:       910083fd        add     x29, sp, #0x20
ffff80001053bec4:       37000127        tbnz    w7, #0, ffff80001053bee8 <avc_denied+0x30>
ffff80001053bec8:       3940040c        ldrb    w12, [x0, #1]
ffff80001053becc:       f9400bab        ldr     x11, [x29, #16]
ffff80001053bed0:       2a0303ea        mov     w10, w3
ffff80001053bed4:       2a0203e8        mov     w8, w2
ffff80001053bed8:       2a0103e9        mov     w9, w1
ffff80001053bedc:       340000ac        cbz     w12, ffff80001053bef0 <avc_denied+0x38>
ffff80001053bee0:       3940416c        ldrb    w12, [x11, #16]
ffff80001053bee4:       3700006c        tbnz    w12, #0, ffff80001053bef0 <avc_denied+0x38>
ffff80001053bee8:       321c77e0        orr     w0, wzr, #0xfffffff3 ≪PATCH
ffff80001053beec:       1400000f        b       ffff80001053bf28 <avc_denied+0x70>
ffff80001053bef0:       f9400800        ldr     x0, [x0, #16]
ffff80001053bef4:       b9400d6b        ldr     w11, [x11, #12]
ffff80001053bef8:       b90013e7        str     w7, [sp, #16]
ffff80001053befc:       320003e1        orr     w1, wzr, #0x1
ffff80001053bf00:       2a0403e2        mov     w2, w4
ffff80001053bf04:       2a0503e3        mov     w3, w5
ffff80001053bf08:       2a0603e4        mov     w4, w6
ffff80001053bf0c:       2a0903e5        mov     w5, w9
ffff80001053bf10:       2a0803e6        mov     w6, w8
ffff80001053bf14:       2a0a03e7        mov     w7, w10
ffff80001053bf18:       f90007ff        str     xzr, [sp, #8]
ffff80001053bf1c:       b90003eb        str     w11, [sp]
ffff80001053bf20:       97fffeb2        bl      ffff80001053b9e8 <avc_update_node>
ffff80001053bf24:       2a1f03e0        mov     w0, wzr
ffff80001053bf28:       a9427bfd        ldp     x29, x30, [sp, #32]
ffff80001053bf2c:       9100c3ff        add     sp, sp, #0x30
ffff80001053bf30:       d65f03c0        ret

Finding the patch location: The kernel doesn’t use KASLR (Kernel Address Space Layout Randomization) on this device, which means the kernel code is always loaded at the same address every boot. This makes our job much easier.

We search for the function in the RAM dump using a unique byte sequence E0771c320f000014 which corresponds to two consecutive instructions in avc_denied

321c77e0    orr  w0, wzr, #0xfffffff3   # sets return value to -EACCES (permission denied)
1400000f    b    <exit>                  # jumps to function exit

Search using perl

/usr/bin/perl -0777 -ne '
    while (/(\xE0\x77\x1c\x32\x0f\x00\x00\x14){1}/g) {
        $match = substr($_, $-[0], $+[0]-$-[0]);
        printf "%d:%s\n", $-[0], unpack("H*",$match)
    }
' mem.dump

Add our offset:

# Example output from the perl search above:
# 9682664:e0771c320f000014
printf "0x%x\n" $((0x40000000 + 9682664))
# Result: 0x4093bee8

Applying the patch in OpenOCD:

> mdw 0x4093bee8 4
0x4093bee8: 321c77e0 1400000f ...

> mww 0x4093bee8 0x2A1F03E0

> mdw 0x4093bee8 4
0x4093bee8: 2a1f03e0 1400000f ...

Verify SELinux is now permissive by checking the ADB log:

adb logcat | grep avc
# You'll see "permissive=1" instead of "permissive=0" in denied messages

Extracting eMMC

With root and SELinux disabled, we can now dump the full eMMC flash. The eMMC shows up as /dev/block/mmcblk2 inside Android. We use dd to read the raw block device and pipe it out over ADB:

adb exec-out "dd if=/dev/block/mmcblk2 bs=4M" > mmcblk2.img

We also need the boot partitions (where the bootloader lives):

adb exec-out "dd if=/dev/block/mmcblk2boot0 bs=4M" > mmcblk2boot0.img
adb exec-out "dd if=/dev/block/mmcblk2boot1 bs=4M" > mmcblk2boot1.img
adb exec-out "dd if=/dev/block/mmcblk2rpmb bs=4M"  > mmcblk2rpmb.img

These four files are the complete contents of the eMMC flash. mmcblk2.img contains all the Android partitions. mmcblk2boot0.img is the most important , it contains the SPL (Secondary Program Loader) and the FIT image with the bootloader chain.

Images

Images
1. mmcblk2boot0
2. mmcblk2boot1
3. mmcblk2rpmb
4. mmcblk2.img

Next: Part 3a: Bypass SDP programming mode