(October 2016)

Freeing my tablet (Android hacking, SW and HW)

Born free, and life is worth living; but only worth living, cause you're born free
For the TL;DR crowd: I wanted to run a Debian chroot in my tablet; and there was no open-source rooting process for it. That triggered me enough to have a deeper look at Android, and eventually completely dominate my tablet.

Caveat - a long read... involving both HW and SW;
...but you'll probably learn a thing or two.

The backstory...

Two years ago, I left my country. There's a sad story there, but let's just say that I chose to spend some time in Malta, working remotely for a nice Irish startup - while living in an apartment 30 meters by the sea :-)

After finishing my work everyday at 16:00, 15 minutes later I would go for a swim.

Malta

I miss the glorious Mediterranean sun...

I would then enjoy long afternoon walks by the beach, listening to music and catching up on my favorite online forums (Reddit/programming, Hacker News, Slashdot, etc) - using my tablet.

Until my tablet died. Suddenly.

A cruel and unusual death that involved noise, a bad smell (electrolytic capacitor bidding farewell?) and then... total silence.

I could have had a look into it - but I knew it was high time I got a new one anyway.

I started looking at the various offerings, and being a nerd of a frugal nature, decided to only look at the best HW bang for the buck, completely ignoring the SW aspects.

"Software? Bah, I'll sort that out later" - Famous Last Words (TM)

The main actor...

I ended up with this:

My tablet

Asus MemoPad 10" ME103K

In August of 2015, 175 Euros got me a tablet with a 10 inch screen, 1GB of RAM, a quad-core Qualcomm Snapdragon, and 16GB of storage - with more space available if one needed it via an SD card slot.

In my preliminary use, the tablet was quite fast in web browsing (with ad blocking of course - otherwise the ads bring down even desktop browsers to their knees). That's what I mostly expect from my tablet - adequate browsing, and reading PDFs while listening to music.

Oh, and running Debian in a chroot of course. Many benefits there; I am a Linux/Unix/BSD/embedded guy, living my life in the command line - and I need to be able to do everything that I do with my main computers, on my tablet as well (like running Privoxy, dictd, my local Flask servers that are accessible only from the tablet, tunneling via local SSH's SOCKS tunnels to tether beyond the idiocy of providers... you get the idea).

And I trust Debian. Far more than I trust the Android ecosystem.

That was when it hit me; I searched, and searched, and searched...

Pain

...there was no open [2] rooting process for this tablet.

No TWRP, no CyanogenMod, nothing.

Nowhere.

(Gulp)

...

I spent my free time over the next weeks studying Android. Not from an application developer standpoint, mind you; only from the low-level aspects that an embedded software engineer cares about.

Understanding the Android boot process

An Android device boots from a soldered-in flash chip, that acts as a "hard disk". The kernel sees it usually as device /dev/mmcblk0, and it is split in various partitions.

In my tablet, after starting an adb shell, I can see this...

shell@K01E_2:/ $ ls -l /dev/block/platform/msm_sdcc.1/by-name

lrwxrwxrwx. 1 root root 20 Oct  8 23:11 ADF -> /dev/block/mmcblk0p9
lrwxrwxrwx. 1 root root 20 Oct  8 23:11 APD -> /dev/block/mmcblk0p8
lrwxrwxrwx. 1 root root 21 Oct  8 23:11 DDR -> /dev/block/mmcblk0p28
lrwxrwxrwx. 1 root root 21 Oct  8 23:11 aboot -> /dev/block/mmcblk0p14
lrwxrwxrwx. 1 root root 21 Oct  8 23:11 abootb -> /dev/block/mmcblk0p20
lrwxrwxrwx. 1 root root 21 Oct  8 23:11 abootu -> /dev/block/mmcblk0p23
lrwxrwxrwx. 1 root root 21 Oct  8 23:11 boot -> /dev/block/mmcblk0p16
lrwxrwxrwx. 1 root root 21 Oct  8 23:11 cache -> /dev/block/mmcblk0p25
lrwxrwxrwx. 1 root root 21 Oct  8 23:11 fsg -> /dev/block/mmcblk0p10
lrwxrwxrwx. 1 root root 20 Oct  8 23:11 m9kefs1 -> /dev/block/mmcblk0p5
lrwxrwxrwx. 1 root root 20 Oct  8 23:11 m9kefs2 -> /dev/block/mmcblk0p6
lrwxrwxrwx. 1 root root 20 Oct  8 23:11 m9kefs3 -> /dev/block/mmcblk0p7
lrwxrwxrwx. 1 root root 21 Oct  8 23:11 m9kefsc -> /dev/block/mmcblk0p30
lrwxrwxrwx. 1 root root 21 Oct  8 23:11 metadata -> /dev/block/mmcblk0p31
lrwxrwxrwx. 1 root root 21 Oct  8 23:11 misc -> /dev/block/mmcblk0p26
lrwxrwxrwx. 1 root root 20 Oct  8 23:11 modemst1 -> /dev/block/mmcblk0p2
lrwxrwxrwx. 1 root root 20 Oct  8 23:11 modemst2 -> /dev/block/mmcblk0p3
lrwxrwxrwx. 1 root root 20 Oct  8 23:11 persist -> /dev/block/mmcblk0p4
lrwxrwxrwx. 1 root root 20 Oct  8 23:11 radio -> /dev/block/mmcblk0p1
lrwxrwxrwx. 1 root root 21 Oct  8 23:11 recovery -> /dev/block/mmcblk0p27
lrwxrwxrwx. 1 root root 21 Oct  8 23:11 rpm -> /dev/block/mmcblk0p15
lrwxrwxrwx. 1 root root 21 Oct  8 23:11 rpmb -> /dev/block/mmcblk0p21
lrwxrwxrwx. 1 root root 21 Oct  8 23:11 sbl1 -> /dev/block/mmcblk0p11
lrwxrwxrwx. 1 root root 21 Oct  8 23:11 sbl2 -> /dev/block/mmcblk0p12
lrwxrwxrwx. 1 root root 21 Oct  8 23:11 sbl2b -> /dev/block/mmcblk0p18
lrwxrwxrwx. 1 root root 21 Oct  8 23:11 sbl3 -> /dev/block/mmcblk0p13
lrwxrwxrwx. 1 root root 21 Oct  8 23:11 sbl3b -> /dev/block/mmcblk0p19
lrwxrwxrwx. 1 root root 21 Oct  8 23:11 ssd -> /dev/block/mmcblk0p29
lrwxrwxrwx. 1 root root 21 Oct  8 23:11 system -> /dev/block/mmcblk0p24
lrwxrwxrwx. 1 root root 21 Oct  8 23:11 tz -> /dev/block/mmcblk0p17
lrwxrwxrwx. 1 root root 21 Oct  8 23:11 tzb -> /dev/block/mmcblk0p22
lrwxrwxrwx. 1 root root 21 Oct  8 23:11 userdata -> /dev/block/mmcblk0p32

Golly gee, Mr Google, that's a lot of partitions

My ME103K starts it's booting process by verifying the aboot partition is not tampered with. There's some sort of signature verification going on there, so even if you obtained root access and dd-ed over a tweaked version of the aboot partition data, you'd end up with a 175 Euro brick (unless you can desolder the flash chip, extract it from the PCB, re-program it, and solder it again. Not my cup of tea - huge respect to those that can, though).

aboot is what is officially called the bootloader. Once it is verified and starts running, it will perform the same song and dance for the boot partition; checking that it is nice and proper, loading its contents and executing them.

fastboot oem unlock

In principle, this is where Android vendors are supposed to allow you to hijack the process, and boot your own boot image, using...

host$ adb reboot bootloader

host$ # tablet reboots into aboot, and pauses just BEFORE
host$ # loading the boot partition data
Bootloader snapshot
host$ # Actually, please load my own bootable image
host$ fastboot boot myDamnImage.img

But to be able to do that, the bootloader must be unlocked; that is, it must allow you to load your own boot images.

Some - but not all - devices, allow you to do this:

host$ fastboot oem unlock

Mine did (thank god).

I do believe though that when you do this, the tablet is supposed to be wiped automatically... since by allowing booting of a custom image, an external entity can pretty much gather every data residing inside the tablet - and completely violate your privacy.

My MemoPad didn't.

Hmm.

Creating my own boot image

It was time to create my own boot image. I wish I could dd the boot partition over from the flash chip and start from there - but I was not root, and therefore not allowed to dd from partitions.

Looking for a fallback, I checked the last Over-The-Air update sent by Asus to these tablets. I was quickly able to extract the boot image and the accompanying file system from inside it (initrd, in Linux terms):

host$ ls -l
total 803592
drwxr-xr-x 2 ttsiod ttsiod      4096 Oct 21 01:22 ./
drwxr-xr-x 6 ttsiod ttsiod      4096 Sep 25 23:34 ../
-rw-rw-r-- 1 ttsiod ttsiod 822861864 Aug  7  2015 UL-K01E-WW-12.16.1.12-user.zip

host$ unzip -l UL-K01E-WW-12.16.1.12-user.zip | grep boot.img
  7368704  2011-03-22 11:21   boot.img

host$ unzip UL-K01E-WW-12.16.1.12-user.zip boot.img
Archive:  UL-K01E-WW-12.16.1.12-user.zip
signed by SignApk
  inflating: boot.img                

host$ mkdir contents
host$ cd contents
host$ abootimg -x ../boot.img 

writing boot image config in bootimg.cfg
extracting kernel in zImage
extracting ramdisk in initrd.img

host$ mkdir rootfs
host$ cd  rootfs
host$ zcat ../initrd.img | cpio -ivd
4040 blocks

host$ ls -l

lrwxrwxrwx 1 ttsiod ttsiod     13 Oct 23 08:30 charger -> /sbin/healthd
drwxrwx--x 2 ttsiod ttsiod   4096 Oct 23 08:30 data
-rw-r--r-- 1 ttsiod ttsiod    350 Oct 23 08:30 default.prop
drwxr-xr-x 2 ttsiod ttsiod   4096 Oct 23 08:30 dev
-rw-r--r-- 1 ttsiod ttsiod  37014 Oct 23 08:30 file_contexts
-rw-r----- 1 ttsiod ttsiod   1242 Oct 23 08:30 fstab.qcom
-rwxr-x--- 1 ttsiod ttsiod 235904 Oct 23 08:30 init
-rwxr-x--- 1 ttsiod ttsiod   2614 Oct 23 08:30 init.class_main.sh
-rwxr-x--- 1 ttsiod ttsiod   1284 Oct 23 08:30 init.environ.rc
-rwxr-x--- 1 ttsiod ttsiod   6758 Oct 23 08:30 init.qcom.class_core.sh
-rwxr-x--- 1 ttsiod ttsiod     88 Oct 23 08:30 init.qcom.diag.rc
-rwxr-x--- 1 ttsiod ttsiod   5604 Oct 23 08:30 init.qcom.early_boot.sh
-rwxr-x--- 1 ttsiod ttsiod  24602 Oct 23 08:30 init.qcom.rc
-rwxr-x--- 1 ttsiod ttsiod   3563 Oct 23 08:30 init.qcom.sh
-rwxr-x--- 1 ttsiod ttsiod   2838 Oct 23 08:30 init.qcom.syspart_fixup.sh
-rwxr-x--- 1 ttsiod ttsiod  38327 Oct 23 08:30 init.qcom.usb.rc
-rwxr-x--- 1 ttsiod ttsiod  22527 Oct 23 08:30 init.rc
-rwxr-x--- 1 ttsiod ttsiod   8346 Oct 23 08:30 init.target.rc
-rwxr-x--- 1 ttsiod ttsiod   1927 Oct 23 08:30 init.trace.rc
-rwxr-x--- 1 ttsiod ttsiod   3902 Oct 23 08:30 init.usb.rc
-rwxr-x--- 1 ttsiod ttsiod    301 Oct 23 08:30 init.zygote32.rc
drwxr-xr-x 2 ttsiod ttsiod   4096 Oct 23 08:30 proc
-rw-r--r-- 1 ttsiod ttsiod   3138 Oct 23 08:30 property_contexts
drwxr-xr-x 3 ttsiod ttsiod   4096 Oct 23 08:30 res
drwxr-x--- 2 ttsiod ttsiod   4096 Oct 23 08:30 sbin
-rw-r--r-- 1 ttsiod ttsiod    746 Oct 23 08:30 seapp_contexts
-rw-r--r-- 1 ttsiod ttsiod     76 Oct 23 08:30 selinux_version
-rw-r--r-- 1 ttsiod ttsiod 177701 Oct 23 08:30 sepolicy
-rw-r--r-- 1 ttsiod ttsiod  10468 Oct 23 08:30 service_contexts
drwxr-xr-x 2 ttsiod ttsiod   4096 Oct 23 08:30 sys
drwxr-xr-x 2 ttsiod ttsiod   4096 Oct 23 08:30 system
-rw-r--r-- 1 ttsiod ttsiod   8349 Oct 23 08:30 ueventd.qcom.rc
-rw-r--r-- 1 ttsiod ttsiod   4617 Oct 23 08:30 ueventd.rc

Hello - lots of stuff here.

The Linux kernel starts /init, which in the case of Android reads what actions to perform from init.rc - which includes other init.* to setup the various SW and HW subsystems.

init.rc includes actions like spawning adbd - allowing us to connect to the tablet over adb:

host$ less init.rc
...
# adbd is controlled via property triggers in init.<platform>.usb.rc
service adbd /sbin/adbd --root_seclabel=u:r:su:s0
    class core
    socket adbd stream 660 system system
    disabled
    seclabel u:r:adbd:s0
...

And what we usually have in /etc/fstab in other Unixes, resides inside fstab.deviceName in Android (in my case, fstab.qcom):

host$ cat fstab.qcom
/dev/block/platform/msm_sdcc.1/by-name/system   /system           ext4 ...
/dev/block/platform/msm_sdcc.1/by-name/userdata /data             ext4 ...
/devices/platform/msm_sdcc.3/mmc_host           /storage/MicroSD  vfat ...
/devices/platform/msm_hsusb_host                /storage/USBdisk1 vfat ...
/devices/platform/msm_ehci_host.0               /storage/USBdisk2 vfat ...

This part looks interesting as well; in principle, I can edit this file and have e.g. the /system partition point to my external SD card, which I CAN control (i.e. plug it in my laptop, write anything I want in it, then plug it in the tablet and reboot).

But I can't read the current /system partition to do this - since I am not root, I can't take a snap, and store it in an external SD card.

Not yet :-)

Anyway - since the filesystem in the boot image is being modified, might as well cross-compile busybox and add it in. I copied it under /sbin and created all the symlinks:

host$ cd sbin
host$ ls -l 
...
lrwxrwxrwx 1 root root       9 Sep 28  2015 addgroup -> ./busybox
lrwxrwxrwx 1 root root       9 Sep 28  2015 add-shell -> ./busybox
lrwxrwxrwx 1 root root       9 Sep 28  2015 adduser -> ./busybox
lrwxrwxrwx 1 root root       9 Sep 28  2015 adjtimex -> ./busybox
lrwxrwxrwx 1 root root       9 Sep 28  2015 ar -> ./busybox
lrwxrwxrwx 1 root root       9 Sep 28  2015 arp -> ./busybox
lrwxrwxrwx 1 root root       9 Sep 28  2015 arping -> ./busybox
lrwxrwxrwx 1 root root       9 Sep 28  2015 ash -> ./busybox
lrwxrwxrwx 1 root root       9 Sep 28  2015 awk -> ./busybox
...
lrwxrwxrwx 1 root root       9 Sep 28  2015 telnet -> ./busybox
lrwxrwxrwx 1 root root       9 Sep 28  2015 telnetd -> ./busybox
...
lrwxrwxrwx 1 root root       9 Sep 28  2015 xz -> ./busybox
lrwxrwxrwx 1 root root       9 Sep 28  2015 xzcat -> ./busybox
lrwxrwxrwx 1 root root       9 Sep 28  2015 yes -> ./busybox
lrwxrwxrwx 1 root root       9 Sep 28  2015 zcat -> ./busybox
lrwxrwxrwx 1 root root       9 Sep 28  2015 zcip -> ./busybox

Busybox is very useful - allowing running tons of stuff from inside the tablet, even without chrooting.

This modified filesystem needs to be placed back in a boot image:

host$ cd rootfs/
host$ find . | cpio --create --format='newc' |  \
    gzip > ../initrd_busybox.cpio.gz
host$ cd ..
host$ abootimg --create ../new_boot.img -f bootimg.cfg  \
    -k zImage -r initrd_busybox.cpio.gz

Now, since busybox was added in, this image is bigger than the original one - so abootimg will fail here, with...

updated is too big for the Boot Image (8441856 vs 7393280 bytes)

Editing bootimg.cfg and updating the expected length in field bootsize is simple enough - but since I will be doing lots of experiments with many attempted boot images, I had to automate this a bit:

host$ cat bootimg.cfg | grep bootsize
bootsize = 0xDEAD

host$ cat autoboot.sh
#!/bin/bash
cd rootfs/
find . | cpio --create --format='newc' | \
    gzip > ../initrd_busybox.cpio.gz
cd ..
abootimg --create ../new_boot.img -f bootimgBig.cfg \
    -k zImage -r initrd_busybox.cpio.gz >log 2>&1
if grep 'updated is too big' log ; then
    SIZE=$(grep 'updated is too big' log | awk '{print substr($10,2,7);}')
    HEX=$(echo -e "obase=16\n$SIZE\n" | bc -l)
    cat bootimg.cfg | sed "s/DEAD/$HEX/" > bootimgBig.cfg
    rm -f log
    abootimg --create ../new_boot.img -f bootimgBig.cfg \
         -k zImage -r initrd_busybox.cpio.gz | tee log
    if grep 'updated is too big' log ; then
        echo Failure...
        exit 1
    fi
fi
echo Rebooting...
fastboot boot ../new_boot.img
exit 0

And there it was, the 1st step - the tablet was now "equipped" with software that I provided myself:

host$ ./autoboot.sh
...
host$ adb shell

shell@K01E_2:/ $ telnet
Usage: telnet [-a] [-l USER] HOST [PORT]

Connect to telnet server

        -a      Automatic login with $USER variable
        -l USER Automatic login as USER

Almost there! Right?

Nope, not quite

At that point, I noticed something.

I was able to boot my custom images just fine - issuing adb reboot bootloader afterwards, and once my tablet reached fastboot stage, preparing and booting my next image... Always trying to become root.

But I noticed that when I tried rebooting the tablet ON ITS OWN, it ended up in the bootloader menu.

I believe the Android community calls this "stuck in fastboot".

Oh-oh - the tablet was no longer bootable on its own! It needed to be tethered to a PC, where a fastboot boot whatever.img would solve the issue.

Not that I take my tablet on long trips or anything, but I found it rather annoying. Why did this happen? I didn't do anything wrong, did I?

I needed a way to see what the tablet was saying during it's boot process. But... there's no way to do that in the Android world. No standard way, that is - every OEM has some method of its own.

Why Google didn't mandate logging the boot messages via some form of the fastboot protocol (e.g. fastboot logcat), I will never know.

What I do know, is that after Googling some more, I found out that Nexus devices have a serial port in their headphone jack.

And Asus manufactured some Nexus devices. Just as it manufactures ME103Ks (which are NOT Nexus, mind you).

Say, maybe they re-used the same hardware in my tablet...

Diving into hardware

In Nexuses, if you feed 3.3V in the microphone input of your headphone's TRRS plug, the left and right channels switch to carrying the serial port's TX and RX channels.

Sending 3.3V to the MIC input

I used my Raspberry PI TRRS-to-TV cable to get access to the 4 places I needed: GND, MIC, L and R. The cable was not supposed to be doing anything else except exposing the 3 signals (MIC,L,R - each one paired with GND) in the three corresponding cables (red, white, yellow).

I then used my recently acquired BitScope to probe between the TX (tip of white cable in picture) and the common GND (brown probe at the bottom of picture). I also used two probes (red and blue one) to "feed" 3.3V from my USB/TTL chip (a PL2303HX plugged in my laptop) to the MIC (red) tip.

Upon rebooting the tablet, I indeed saw what was unmistakably a serial signal at 115200 (peak-to-peak of 8 to 9us), but with lots of capacitance:

There you are! Come out, little serial...

BitScope also told me that the signal was at 1.8V - so my PL2303HX (a 3.3V USB/TTL) could not possibly decode it. I needed a shifter.

The signal also looked indecipherable, to my "amateur at electronics" eyes; the edges were surely too slow for any UART to pick up. I asked the Electronics Stack Exchange guys what they thought, and they recommended probing on the jack itself - so I stripped the tablet down to its PCB, and probed all over... Nope, same capacitance-riddled signal, and I couldn't find it anywhere else on the PCB.

But if after booting, I used stty to lower the speed to 9600 baud, then all was crystal clear - nice square pulses:

shell@K01E_2:/ $ stty -F /dev/ttyHSL0 9600
shell@K01E_2:/ $ while true ; do echo UUUUUUU > /dev/ttyHSL0 ; sleep 0.1 ; done

Much better at low frequencies

I considered giving up, but Chris Stratton, in one of the few Stack Exchange comments I got, claimed that "...a good shifter can cope even with this kind of capacitance-riddled signal".

Might as well try!

As good an opportunity as ever to start rebuilding my electronics "war chest"... In the meantime, I had left Malta and gotten a job at the European Space Agency in the Netherlands, so I made my first on-line order from Dutch electronics' shops.

3 days later, I got a bunch of cables, a breadboard, a soldering iron and a level shifter: a BSS138.

After a bit of lousy soldering and jungle-cable-breadboarding, with my heart beating unusually fast, I tried spawning minicom...

Adding a shifter

...and to my utter amazement, I saw this:

Data at last

Unbelievable - after BSS138 "lifted" the signal from 1.8 up to 3.3V, that miserable, capacitance-riddled signal was actually decoded! I could finally see why my tablet was stuck at fastboot.

Update, 2 weeks later: I ordered a TRRS breakout (to connect a male-to-male headphone cable from the tablet to the breadboard) and cleaned up the tiny design - it is, after all, a useful circuit to keep. Besides connecting the TIP and RING1 to the shifter, I also used a voltage divider to create 1.8V out of the PL2303HX's 3.3V output - and fed it in the LV input of the BSS138.

Cleaned-up breadboard

Back to software hacking

This is the complete transcript of what came through the serial port (if you are using a slow device, you may want to use your desktop/laptop to watch this):

Now after watching this ASCII cast (thanks, asciinema!), I am sure you are wondering where the heck I found those "magic" oem-specific commands (in this case, ME103K-specific):

host$ sudo fastboot fastboot oem device-info
...
(bootloader) Device tampered: true
...
host$ sudo fastboot oem reset-dev_info
host$ sudo fastboot fastboot oem device-info
...
(bootloader) Device tampered: false
...

Well...

"Normal" firmware (for various values of "normal") can be asked to actually list the oem commands it supports - but...

So I went back to that Over-The-Air update...

host$ ls -l
total 803592
drwxr-xr-x 2 ttsiod ttsiod      4096 Oct 21 01:22 ./
drwxr-xr-x 6 ttsiod ttsiod      4096 Sep 25 23:34 ../
-rw-rw-r-- 1 ttsiod ttsiod 822861864 Aug  7  2015 UL-K01E-WW-12.16.1.12-user.zip

host$ unzip -l UL-K01E-WW-12.16.1.12-user.zip | grep aboot
  1471540  2011-03-22 11:21   bootloader.aboot.img
 
host$ unzip UL-K01E-WW-12.16.1.12-user.zip bootloader.aboot.img
Archive:  UL-K01E-WW-12.16.1.12-user.zip
signed by SignApk
  inflating: bootloader.aboot.img 

host$ strings bootloader.aboot.img | grep ^oem
oem reboot-recovery is called!
oem unlock
oem device-info
oem lock
oem gpt-info
oem fuse_blow
oem check-fuse
oem reset-dev_info
oem grant
oem off-mode-charge 1
oem off-mode-charge 0
oem uart-on
oem uart-off

I wouldn't touch those 'fuse' things with a ten-foot pole; I don't want to brick my tablet.

But reset-dev_info?

Come on, I just had to try that :-)

No longer in a fastboot-loop, time to become root

So, what did we learn, Palmer?

If I can only temporarily become root with a custom boot image, so be it - I will do so, and then try to exploit something on the /system partition; and finally "re-brand" myself as "an ASUS-compliant, good guy".

Flashing a boot image and then resetting dev-info?

But just in case... could one, say, flash a boot image... and then pretend it never happened?...

host$ sudo fastboot flash boot new_boot.img
host$ sudo fastboot oem reset-dev_info
host$ sudo fastboot reboot

Nope - same 'stuck in fastboot' behaviour.

NOTE: I already had a backup of the boot partition when I tried this - I did it AFTER I became root and was able to dd a pristine copy of the full boot partition for restoring. It was also interesting to note that the dd copy of boot did NOT match the boot.img in the Over-The-Air update form - maybe there are signatures at the end of that partition that are checked for correctness. With closed-source systems, one never knows...

Spawning telnetd and also trying su

So, back to becoming root via a custom boot image.

Some of my research told me that if you are in control of the boot image, all you need to do is spawn telnetd from within init.rc - it would be spawned with root privileges, so anyone telnet-ing inside it is the equivalent of a Digital God.

I tried various forms of this, but to no avail...

host$ cat init.rc
...
service telnetd /sbin/telnetd -l /sbin/bash
    oneshot

(Remember that I had placed busybox and all its symlinks (including bash) under /sbin)

Nothing - there was no telnetd in the ps output after the boot.

And no log of this failure anywhere... Nothing in dmesg, no logs under /var/log/.

Silence.

(sigh)

I tried another form of spawning telnetd, suggested by an awesome fireman - kudos, Kris!

But that one failed as well.

Hmm...

Maybe I can try one of the classics, and place an su under /sbin? Cross-compiled it, and placed it (with the proper setuid bit set) under /sbin...

host$ adb shell

shell@K01E_2:/ $ /sbin/su
Permission denied.

Oh?

Denied by whom?

The 800-pound gorilla

SELinux - the 800 pound gorilla

Reading some more, I realized that SELinux was standing in my way:

host$ adb shell

shell@K01E_2:/ $ getenforce
Enforcing

shell@K01E_2:/ $ setenforce 0                                                  
setenforce:  Could not set enforcing status:  Permission denied

I clearly couldn't disable it - not in the default state of my tablet.

But since I control the filesystem bundled in my boot image, maybe there was a way to tell SELinux to get off my lawn? That is, to allow my shell user to control its state?

In the root folder of my boot image's root filesystem, I eventually found this:

host$ ls -l sepolicy
-rw-r--r-- 1 ttsiod ttsiod  177701 Oct 23 08:30 sepolicy

And - this took me ages to find - Joshua Brindle provides the source code of sepolicy-inject; a tool that allowed me to tell Android that the SELinux policy I want is one where my shell user has the power to kill the gorilla:

host$ ls -l
drwxr-xr-x 2 ttsiod ttsiod   4096 Oct 22 16:05 ./
drwxr-xr-x 4 ttsiod ttsiod   4096 Oct  4  2015 ../
-rw-r--r-- 1 ttsiod ttsiod    349 Oct 22 15:42 Makefile
-rw-r--r-- 1 ttsiod ttsiod   1088 Jul 17  2013 README.txt
-rw-r--r-- 1 ttsiod ttsiod   5851 Jul 17  2013 sepolicy-inject.c
-rwxr-xr-x 1 ttsiod ttsiod 293912 Oct 22 15:42 sepolicy-inject*

host$ ./sepolicy-inject -Z shell -P /path/to/original/sepolicy \
        -o /path/to/custom.rootfs/sepolicy.jean.let.me.in

I packaged this new sepolicy in my root filesystem, packed a new boot image, and after fastboot-ing with it, I saw this:

host$ adb shell

shell@K01E_2:/ $ /sbin/su

shell@K01E_2:/ # id

uid=0(root) gid=0(root) groups=1004(input),1007(log),1011(adb),
    1015(sdcard_rw),1028(sdcard_r),3001(net_bt_admin),3002(net_bt),
    3003(inet),3006(net_bw_stats) context=u:r:shell:s0

YES! I am (G)ROOT!

And I can chroot - right?

shell@K01E_2:/ # chroot /data/local/tmp

chroot: can't change root directory to '/data/local/tmp': Operation not permitted
1|shell@K01E_2:/ # ls -l /data/local/tmp
opendir failed, Permission denied

What?!?!

What kind of a root user can't list a folder?!

I asked the Gods again for inspiration - but as usual, the Gods don't respond to mortals.

The capabilities bounding set

FINE.

Remember this part?

host$ abootimg -x ../boot.img 

writing boot image config in bootimg.cfg
extracting kernel in zImage
extracting ramdisk in initrd.img

So far the only modifications were applied on the root filesystem contained inside the boot image (that is, in initrd.img) - the kernel (zImage) was left as-is.

To debug why the chroot system call (and the listing one!) fail, I decided to compile the kernel (ASUS thankfully provided the source code), and then sprinkle it with printk statements to see EXACTLY where the system calls fail.

After a LOT of work, I found out about the 'capabilities bounding set'.

To cut a long story short, the kernel allows you now to limit the kind of things ("the amount of damage") you can do from a process. The children spawned by it, can only limit things further - they can't get BACK what they (or their parents) lost.

Then again, I controlled the kernel code - so I went all in, guns blazing:

static long cap_prctl_drop(struct cred *new, unsigned long cap)
{
    if (!capable(CAP_SETPCAP))
        return -EPERM;
    if (!cap_valid(cap))
        return -EINVAL;

    // ttsiodras: come in, everyone, the water's fine!
    //cap_lower(new->cap_bset, cap);
    return 0;
}

(Yes, yes - I know, this is basically allowing everyone and their grandmother to do anything; capabilities are NEVER droped. But I don't care - remember, the plan is that I will use my temporary root powers to mess with my /system partition, and then revert back to a pristine boot image).

After building this kernel and packaging it in...

host$ adb shell

shell@K01E_2:/ $ /sbin/su

root@K01E_2:/ # chroot /data/debian/ /bin/bash

root@localhost:/# cat /etc/issue
Debian GNU/Linux 8 \n \l
Root at last

If you are wondering how the /data/debian came to be, I first created the relevant folder in my host machine (via Debian's instructions) and then used ADB's port forwarding powers - and netcat:

In the tablet:

host$ adb shell

shell@K01E_2:/ $ /sbin/su

root@K01E_2:/ # cd /data
root@K01E_2:/ # mkdir debian
root@K01E_2:/ # cd debian
root@K01E_2:/ # nc -l -p 9999 | tar xpvf -

In the host:

host$ adb forward tcp:9999 tcp:9999
host$ sudo -i
host# cd /opt/chroot-armie
host# tar cpf - ./* | nc localhost 9999

Cameras - and doing it properly

I enjoyed my newfound powers by setting up the chroot-ed Debian to do my bidding - and as it always does, Debian worked perfectly. I did some bind-mounting before chrooting to make sure it had access to everything in the tablet (and not just under the chroot-ed folder), which allowed me to e.g. process my recorded videos and images from within the tablet with ImageMagick and ffmpeg and mkvmerge, and...

Oh wait a moment.

The camera isn't working?!?!

What?! No camera?!?!

Googling about it... and look, I am not alone: tons of people all over the world are reporting a common thing about rooting their Android devices...

"I don't use rooting because afterwards the camera isn't working".

Now I know more than enough about this to be 100% sure: rooting has NOTHING to do with the camera! The simple thing is, the kernel sources that I just compiled obviously have no device drivers for the camera in them!

Why?

I am guessing the answer is closely related to patents and NDAs and similar (expletive).

(sigh)

BTW, this means there is no chance to compile a CyanogenMod distribution for my tablet; best-case scenario, I would succeed but lose the camera functionality in the process. And God knows what else - the GPS, etc :-(

I have to go back to my original plan - boot from the pristine boot image burned in the tablet, and become root ONLY through modifications to my /system partition.

Which I can modify now - but modify what?

Game over

Accomplishing rooting with the pristine boot image, means doing so with full SELinux and capability bounding sets in place.

Which can only mean one thing - I needed to find a way to run something as root BEFORE the parent process that spawns me ends up severely limited.

Where?

This took me even more time to find... but eventually, I noticed that the tablet upgrade (the Over-The-Air update from Asus that moved the tablet from KitKat (Android 4.4) to Lollipop (Android 5.1), left behind... a gift for me:

host$ adb shell

shell@K01E_2:/ $ /sbin/su
root@K01E_2:/ # cd /system/bin
root@K01E_2:/system/bin # cat install-recovery.sh 

#!/system/bin/sh
if ! applypatch -c EMMC:/dev/block/platform/msm_sdcc.1/by-name/\
        recovery:7847936:c8bd9025173cb3ab0f467f11291b10f517e95791; then
  applypatch -b /system/etc/recovery-resource.dat \
    EMMC:/dev/block/platform/msm_sdcc.1/by-name/boot:7368704:\
        9c5998b970de2449b4c73b890c44f8e68b2a84da \
    EMMC:/dev/block/platform/msm_sdcc.1/by-name/recovery \
        c8bd9025173cb3ab0f467f11291b10f517e95791 7847936 \
        9c5998b970de2449b4c73b890c44f8e68b2a84da:\
            /system/recovery-from-boot.p && \
            log -t recovery "Installing new recovery image: succeeded" \
                || log -t recovery "Installing new recovery image: failed"

else
  log -t recovery "Recovery image already installed"
fi

Hello - what's this?

This code clearly needs to run with god-like privileges - it is supposed to patch the recovery partition with the new recovery image contained in the update, and it obviously needs to do this rather early in the boot time.

Can I hook stuff in here?

root@K01E_2:/system/bin # vi install-recovery.sh 
...
root@K01E_2:/system/bin # cat install-recovery.sh 
...
  log -t recovery "Recovery image already installed"
fi
/system/xbin/telnetd -b 127.0.0.1:2323 -l /system/xbin/bash

(Notice that I copied all the busybox stuff from under /sbin - that is, from the modified boot image - to under /system/xbin ; since the plan is to boot from a normal boot image, that won't have anything under /sbin except what Asus placed in there).

And there it was - I added the proper music in the background and rebooted:

host$ adb reboot bootloader
...
host$ sudo fastboot oem reset-dev_info
host$ sudo fastboot reboot
...
host$ adb shell
shell@K01E_2:/ $ telnet 127.0.0.1 2323

Entering character mode
Escape character is '^]'.


/ # 

Finally!

I WIN
 
Oh God, no one will get the reference - I am too old... :-)
 

Finally, I streamlined the process with a fake 'su' that I placed under /system/xbin:

/ # cat /system/xbin/su
#!/system/bin/sh
echo "========================================================="
echo "Feel free to run 'r' to get to a full Debian."
echo ""
echo "Telneting into the tunnel..."
echo "========================================================="
/system/xbin/telnet 127.0.0.1 2323

So my tablet, in pristine boot condition, with no tethering requirements whatsoever, was finally rooted; in an open-source way, with no mysterious dependencies on external servers [2], and with the camera still working.

And is now proudly running a Debian chroot, of course :-)

Conclusion

There were many points during this journey that I felt really sad. It was as if Android creators absolutely HATED people like me, and did their best to make us suffer; because they group us together with malware authors; and add multiple layers of checks that don't distinguish between the owner of the machine and the developers of applications that run in the machine.

I am NOT a bad guy!... I just want to remain in full control of my OWN hardware...

What makes this sad is that "security" and "freedom" are not mutually exclusive - and Google already knows that this dichotomy is a false one. Their Chromebooks used to have hardware switches that the user had to set by hand, in order to enter "developer mode". This meant the owners of the machines had the freedom to root them, install chroots or native Linux distros, and generally do whatever they want with the hardware they purchased - if they chose to do so and accept the risks involved.

While malware authors still had to face pains like those I described above.

(Not that it stops them, BTW...)

I shudder to think what I will face the next time the HW of my Android tablet or phone dies. The way things are going in the Android ecosystem, freedom to "tinker" will be gone; sacrificed forever in the delusionary altar of "safety-uber-alles".

And if we are going that way... Android might as well be called iOS.


Discussions in

- Slashdot

- Reddit/programming

- Reddit/Linux

- Reddit/ReverseEngineering

- Hacker News

- Hackaday

Notes

  1. I wish the process was as smooth as I described it in the storyline above. In reality, as you can see in my two questions to Android Stack Exchange, and the one in Electronics Stack Exchange, the process is completely chaotic - there is no plan; you just try to make sense of all the complex factors that affect what is happening. As far as mental exercises go, this one was just as hard as the toughest ones I've ever met, and stretched my debugging muscles to their breaking point (which is good; that's how they get stronger). Huge thanks go out to the many people out there that work on this kind of thing and offer the resulting open-source tools and rooting instructions to mere mortals like me - probably without ever getting a "thank you" in return. I hope this page will act as my way of saying: "I now know what you guys go through - thank you so much!"

  2. Well, there are some closed-source "one-click roots" that seem to, er, communicate with servers in China; supposedly to obtain the "proper" kernel exploit. Much as I respect the hacking power behind this... I am sorry, but I can't trust that you guys won't install a keylogger, or steal my GMail credentials, or make my tiny tablet a part of the ARMies of Mordor that have been known to recently be used in DoS attacks.

  3. The details of Android rooting keep changing of course, as versions of Android constantly evolve. What I described above is most likely no longer applicable to any tablet or phone running Android 6.x.



profile for ttsiodras at Stack Overflow, Q&A for professional and enthusiast programmers
GitHub member ttsiodras
 
Index
 
 
CV
 
 
Updated: Sun Nov 19 23:06:10 2023