Notes from a Nexus 9

Jonathan Levin, http://NewAndroidBook.com, 12/05/14

As part of the devices I researched for my book on Android Internals, I got myself a Nexus 9. Although Google made the filesystem image available, there's no 64-Bit emulator (at least, none that I know of), and most of the interesting stuff is found during runtime. Plus, the device is unlockable, getting root is an easy matter, and the full internals of the system would be interesting to explore.

The version the Nexus 9 presently runs (it updates automatically as you first boot it) is LRX21R - technically 5.0.0._r7, and though the 5.0.1 image has been released, it's surprisingly unavailable for the Nexus 9 as an OTA (having checked for system updates 12/4/14, 20:19PM). One would think Google wouldn't have to do so many silent bugfixes after having delayed for so long.

A detailed teardown of the Nexus 9 and a hardware-perspective was already published in several places around the 'Net, so that's not my intent. Rather, this summarizes findings from snooping around the file system I did to kill time on a long trans-pacific with no better distraction the in-flight entertainment... Plus, this is somewhat of a complement to the article I wrote about disassembling aboot - which was SnapDragon (msm) oriented - whereas here we're dealing with Tegra.

Partitions

As with all Android devices (as discussed in Chapter 2 of the book), the MMC is partitioned into far more than the mountable partitions of /data, /system and /cache. The Nexus contains no less than 33 partitions, and unlike the msm eMMC (wherein the partitions are given sensible names!) the sdhci-tegra gives the partitions the same (annoying) three letter names found in the NVidia Shield (and, I therefore presume, all Tegra based controllers). These can be seen in the following output, which I've annotated for better readability. The greyed partitions are empty:

root@flounder: /#  ls -Ll /dev/block/platform/sdhci-tegra.3/by-name/            
brw-------    1 root     root      259,  13 Nov 30 23:26 APP -> ..29 /system 
brw-------    1 root     root      259,  14 Nov 30 23:26 CAC -> ..30 /cache
brw-rw----    1 system   system    259,   7 Nov 30 23:26 CDR -> ..23
brw-------    1 root     root      259,   4 Nov 30 23:26 DIA -> ..20
brw-------    1 root     root      179,   5 Nov 30 23:26 DTB -> ..5  (normally) Device Tree (but empty)
brw-rw----    1 system   system    259,   5 Nov 30 23:26 EF1 -> ..21
brw-rw----    1 system   system    259,   6 Nov 30 23:26 EF2 -> ..22
brw-------    1 root     root      179,   3 Nov 30 23:26 EKS -> ..3
brw-------    1 root     root      179,  11 Nov 30 23:26 EXT -> ..11
brw-------    1 root     root      179,  12 Nov 30 23:26 FST -> ..12
brw-------    1 root     root      259,  17 Nov 30 23:26 GPT -> ..33 GUID Partition Table (backup)
brw-------    1 root     root      179,   1 Nov 30 23:26 KEY -> ..1
brw-------    1 root     root      259,   0 Nov 30 23:26 LNX -> ..16  boot.img (with HTC wrap)
brw-------    1 root     root      259,   9 Dec  1 01:25 MD1 -> ..25
brw-------    1 root     root      259,  10 Nov 30 23:26 MD2 -> ..26
brw-------    1 root     root      259,   2 Nov 30 23:26 MFG -> ..18  Manufacturing Data
brw-------    1 root     root      259,   1 Nov 30 23:26 MSC -> ..17  Misc
brw-------    1 root     root      179,  10 Nov 30 23:26 NCT -> ..10  
brw-------    1 root     root      259,  12 Nov 30 23:26 OTA -> ..28  OTA Updates
brw-------    1 root     root      179,  14 Nov 30 23:26 PG1 -> ..14 
brw-------    1 system   system    259,  11 Dec  5 01:04 PST -> ..27 Persistent
brw-rw----    1 system   system    179,   8 Nov 30 23:26 RCA -> ..8
brw-------    1 root     root      179,   6 Nov 30 23:26 RV1 -> ..6  ?
brw-------    1 root     root      179,  13 Nov 30 23:26 RV2 -> ..13
brw-------    1 root     root      259,  16 Nov 30 23:26 RV3 -> ..32
brw-------    1 root     root      259,   3 Nov 30 23:26 SER -> ..19
brw-------    1 root     root      179,  15 Nov 30 23:26 SOS -> ..15 recovery.img (cute :-)
brw-------    1 root     root      179,   9 Nov 30 23:26 SP1 -> ..9
brw-------    1 root     root      179,   2 Nov 30 23:26 TOS -> ..2  ARM TrustZone? See below
brw-------    1 root     root      259,  15 Nov 30 23:26 UDA -> ..31 User data (i.e /data)
brw-------    1 root     root      259,   8 Nov 30 23:26 VNR -> ..24 /vendor
brw-------    1 root     root      179,   4 Nov 30 23:26 WB0 -> ..4
brw-------    1 root     root      179,   7 Nov 30 23:26 WDM ->7

Quite a few of the partitions are empty, at least on the WiFi-only model. I'm assuming that the situation might be different on the cellular-enabled version, since the baseband likely has need for some partitions.

DTB

The DTB partition was supposed to hold the compiled device tree - which instead is appended to the kernel image. To get the device tree you can use imgtool (though you'd need to strip the 0x100 byte hboot header first).

Misc (MSC)

The MSC partition is the classic Android misc, which is used for the Bootloader Control Block (BCB) - which is updated when you use adb reboot with recovery or bootloader (personal note: I'm surprised PST wasn't merged into this - see below).

Persist (PST)

The Nexus 9 L image contains the persistent_data_block service (android.service.persistentdata.IPersistentDataBlockService) which is not present in the Emulator (another reason why I needed a real device - you can't write a book about Android based on just the emulator images). This service (defined in the AOSP's frameworks/base/services/core/java/com/android/server/PersistentDataBlockService.java) controls whether or not the fastboot oem unlock command will be allowed by the boot loader. The Settings app calls on the service's setOemUnlockEnabled(boolean enabled); to disable or enable the fastboot oem unlock functionality.

The setting naturally has to be read by hboot/aboot - which means it has to reside on a raw partition. Indeed, it does - on the Persistent partition, labeled PST. The service gets the partition's name from the ro.frp.pst property, which is set in the "ADDITIONAL_BUILD_PROPERTIES" of the /system/build.prop.

The partition is entirely empty if the OEM unlock is disallowed (as it is by default), but if the setting is toggled, a single bit magically appears in the last word:

# Before
root@flounder:/ #  od -A x -t x4 /dev/block/platform/sdhci-tegra.3/by-name/PST
000000 00000000 00000000 00000000 00000000  # No oem unlock - nada
*
080000
# After
root@flounder:/ #  od -A x -t x4 /dev/block/platform/sdhci-tegra.3/by-name/PST
000000 00000000 00000000 00000000 00000000  # Still entirely empty... 
*                                           # ...
07fff0 00000000 00000000 00000000 01000000  # ... save 1 bit
080000

The boot loader's fastboot oem get_unlock_ability can be used to test the bit (as can fastboot oem unlock, of course).

MFG

MFG contains various manufacturing data pertaining to the device. The beginning of the partition has a(n as yet unknown) header, followed by variable=value , padded by a lot of null bytes. Somewhere deep in the partition you can find the device manufacturing date (201410xx or 201411xx), MB and S/N (as per the sticker on the back) but surprisingly not the IMEI or the P/N (unless I missed it).

Mounted File Systems

The /data/ filesystem is mounted with F2FS, which is all the rage in the world of Android nowadays. The mounting is over the UDA partition (..p31), but over the device mapper (i.e. /dev/block/dm-0, and not /dev/block/mmcblk0p31) so as to support the filesystem encryption (discussed in the book and the Andevcon lecture on security).

L finally uses the function file system, a successor to the gadgetFS/ConfigFS which serve the functions of the USB gadget driver, which it can adopt as part of the 3.10 kernel offerings. Another notable addition is pstore, which takes over the deprecated ram console - if the kernel ever panics, the log can be found in this directory. As with the experiment in chapter 2:

# This will crash your system. Make sure you close apps first..
root@flounder:/ # echo c > /proc/sysrq-trigger 
# System will automatically reboot. Reconnect adb, and voila:
root@flounder:/ # ls -l /sys/fs/pstore
# Permissions were set by /init.rc
-r--r----- system   log        106835 2014-12-11 07:21 console-ramoops

I also cover most of the filesystems in the book (Chapter 2), but the following output shows the new ones:

root@flounder: /# mount
rootfs / rootfs ro,seclabel,relatime 0 0
tmpfs /dev tmpfs rw,seclabel,nosuid,relatime,mode=755 0 0
devpts /dev/pts devpts rw,seclabel,relatime,mode=600 0 0
none /dev/cpuctl cgroup rw,relatime,cpu 0 0
adb /dev/usb-ffs/adb functionfs rw,relatime 0 0
proc /proc proc rw,relatime 0 0
sysfs /sys sysfs rw,seclabel,relatime 0 0
selinuxfs /sys/fs/selinux selinuxfs rw,relatime 0 0
none /sys/fs/cgroup tmpfs rw,seclabel,relatime,mode=750,gid=1000 0 0
pstore /sys/fs/pstore pstore rw,relatime 0 0
/sys/kernel/debug /sys/kernel/debug debugfs rw,relatime,mode=755 0 0
none /acct cgroup rw,relatime,cpuacct 0 0
tmpfs /mnt/asec tmpfs rw,seclabel,relatime,mode=755,gid=1000 0 0
tmpfs /mnt/obb tmpfs rw,seclabel,relatime,mode=755,gid=1000 0 0
# The actual mounted partitions are here: 
/dev/block/platform/sdhci-tegra.3/by-name/APP /system ext4 ro,seclabel,relatime,data=ordered 0 0
/dev/block/platform/sdhci-tegra.3/by-name/VNR /vendor ext4 ro,seclabel,relatime,data=ordered 0 0
/dev/block/platform/sdhci-tegra.3/by-name/CAC /cache ext4 rw,seclabel,nosuid,nodev,noatime,errors=panic,data=ordered 0 0
# /data: Note A) mounting over device mapper (encrypted) and B) f2fs
/dev/block/dm-0 /data f2fs rw,seclabel,nosuid,nodev,noatime,background_gc=on,user_xattr,acl,errors=panic,active_logs=6 0 0
# SDCard..
/dev/fuse /mnt/shell/emulated fuse rw,nosuid,nodev,relatime,user_id=1023,group_id=1023,default_permissions,allow_other 0 0
tmpfs /storage/emulated tmpfs rw,seclabel,nosuid,nodev,relatime,mode=050,gid=1028 0 0
/dev/fuse /storage/emulated/0 fuse rw,nosuid,nodev,relatime,user_id=1023,group_id=1023,default_permissions,allow_other 0 0
/dev/fuse /storage/emulated/0/Android/obb fuse rw,nosuid,nodev,relatime,user_id=1023,group_id=1023,default_permissions,allow_other 0 0
/dev/fuse /storage/emulated/legacy fuse rw,nosuid,nodev,relatime,user_id=1023,group_id=1023,default_permissions,allow_other 0 0
/dev/fuse /storage/emulated/legacy/Android/obb fuse rw,nosuid,nodev,relatime,user_id=1023,group_id=1023,default_permissions,allow_other 0 0

The Boot Loader

Unlike MSM devices, which reads images like aboot directly (in the format specified in the previous article), HTC uses their own loader - HBOOT - with a small custom header on images, which can be found prepended to both the aboot images (LNX and SOS), as well as the boot loader itself (bootloader-flounder-3.43.0.0114.img in the images supplied by Google). The header can be seen in the following output:

morpheus@Forge (~)$ od -A x -t x4 boot.img  | more 
0000000          12345678        00000000        007a6300        00000100
                 MAGIC           ?? MBZ?         File Size       Hdr Size?
0000010          00000100        007a6100        007a6200        00000100
                 Hdr Size?       PS - 0x100      Payload size    ???
0000020          00000000        00000000        00000000        00000000
*
0000100          52444e41        2144494f        006ef842        10008000
# ANDROID! ... etc.. 

It's not entirely clear to the author why there appears to be so much redundancy in the header (e.g. why is 0x100 repeated three times? Header Size? Data Start? What else?). But that's a non issue. Once you figure out the actual data starts at 0x100, the custom header can be stripped, leaving you (in the case of LNX/SOS) with an aboot image, or (for bootloader-flounder-xxxx.img) - a zip file! This becomes evident by the "PK" signature, which is correctly identified by file(1):

morpheus@Forge (~/.../volantis-lrx21l) % dd if=bootloader-flounder-3.43.0.0114.img bs=0x100 \
                                         skip=1 of=out
11879+1 records in
11879+1 records out
3041194 bytes transferred in 0.026451 secs (114974151 bytes/sec)
morpheus@Forge (~/...//volantis-lrx21l) % file out
out: Zip archive data, at least v2.0 to extract
morpheus@Forge (~/.../volantis-lrx21l) % unzip -l out 
Archive:  out
  Length     Date   Time    Name
 --------    ----   ----    ----
      253  10-10-14 17:05   _android-info.txt
    25744  08-25-14 09:51   mts_preboot_prod
  1588112  08-25-14 09:51   mts_prod
     8704  10-16-14 10:46   rawbct.img
  1046032  10-16-14 10:46   hboot.img
   125072  10-16-14 10:46   nvtboot.img
     2048  08-04-14 10:29   nvtbootwb0.bin
  2650880  10-16-14 11:38   tos.img
  6291456  08-04-14 10:29   sp1.nb0
  1048576  08-20-14 10:09   gp1.img
   524288  08-20-14 10:09   pt_16g.img
   524288  08-20-14 10:09   pt_32g.img
   524288  08-20-14 10:09   pt_64g.img
 --------                   -------
 14359741                   13 files

And voilà! All the boot loader components. I haven't fully researched the exact order of these, but TOS precedes hboot, and nvtboot.img (which is loaded at 0x40000000, before either) precedes them both (i.e. nvtboot → (+wb0) → tos → hboot). Given time and the NVidia docs I'll try and figure out the odds and ends here.

nvtboot.img

The nvtboot.img (NVidia Tegra Boot) is directly loaded onto the processor (likely by the ROM?). The image is based at 0x40000000 with variables at 0x60000000, and disassembles pretty easily thanks to verbose assertions (@0x4000C104) and printf()s (@0x4000c014) with strings starting at 0x40019978. It is an ARM32 (no Thumb-2 here) binary, and it doesn't bother with setting all the ARM exception vectors - only _reset, which it uses to jump +4 bytes (over where _undef would be) right into the TegraBoot code - the interesting portion of NvtBootMain starts around 0x40000254. It does the usual stuff one would expect (initialize CPU, board, battery, determine charger mode..), and calls 0x400054EC (a.k.a NvTbootTosInit) to load the "Secure Os" (q.v 0x4000531C) - which is the tos.img.

tos.img

The tos (Trusted Operating System?) image gets written to the TOS partition. I'm assuming this is for the ARM trust zone. You can pull the same dd trick from aboot, this time with bs=0x0100200 skip=1, to get the exception vectors and image (this time, rebase at 0x4800000). The resulting file is none other than lk (as can be evidenced by strings), although with some modifications.

The lk can load "trusty_apps", which are ELF binaries. Indeed, the ELF header magic (\177ELF) can be found at several offsets:

od -t c -t x4 -A x Partitions/TOS  | grep "E" | grep "L" | grep "F" | grep 177
010bdf0    A   G   E   _   M   A   S   K   )  \0 177   E   L   F  \0   t  # as a string
010e200  177   E   L   F 001 001 001  \0  \0  \0  \0  \0  \0  \0  \0  \0
0115500   \0  \0  \0  \0 001  \0  \0  \0  \0  \0  \0  \0 177   E   L   F
017e810  001  \0  \0  \0  \0  \0  \0  \0 177   E   L   F 001 001 001  \0
01e3b20   \0  \0  \0  \0 177   E   L   F 001 001 001  \0  \0  \0  \0  \0
01eae30  177   E   L   F 001 001 001  \0  \0  \0  \0  \0  \0  \0  \0  \0
0256130   \0  \0  \0  \0 001  \0  \0  \0  \0  \0  \0  \0 177   E   L   F

Again, with the same trick, we have:

dd if=Partitions/TOS bs=0x010e200 skip=1 of=toself
1+1 records in
1+1 records out
2039296 bytes transferred in 0.002057 secs (991471818 bytes/sec)
morpheus@Forge (~/Documents/Android/Images/volantis-lrx21l) %file toself                                              7:58
toself: ELF 32-bit LSB executable, ARM, version 1 (SYSV), statically linked, stripped

readelf, objdump and their ilk will usually ignore any ELF headers but the first one, though you'd need to use dd several times to extract the ELF binaries, as there is clearly more than one. Examining the binaries further corroborates they're meant for TrustZone (heck, one is actually called "trusty"!) but is left outside the scope of this writeup.

hboot.img

HBoot is closed source (as far as I know), but who needs source when you have disassembly? Looking at the file it's clearly evident this is an ARM (32) binary. Yup. Even though it's AArch64:

morpheus@Forge (~/.../volantis-lrx21l) % od -t x4 xx/hboot.img| head -5    
0000000          ea000007        ea016f8c        ea016f8c        ea016f8a
0000020          ea016f89        ea016f88        ea016f78        ea016f86
0000040          00000000        e32ff0df        e3a00207        e590c804

The "eaXXXXXX" words are recognizable as ARM 32 instructions - "e" is a condition (specifying 'always') and 'a' denotes a branch - which makes sense, because these are the instructions to override the ARM exception vector (as discussed in the aboot article). Loading into a disassembler (e.g. IDA), we have:

ROM:00000000
ROM:00000000 loc_0                                   ; DATA XREF: sub_472A0+BAw
ROM:00000000                                         ; sub_472A0+C2uw ...
ROM:00000000                 B               loc_24
ROM:00000004 ; ---------------------------------------------------------------------------
ROM:00000004                 B               loc_5BE3C
ROM:00000008 ; ---------------------------------------------------------------------------
ROM:00000008                 B               locret_5BE40
ROM:0000000C ; ---------------------------------------------------------------------------
ROM:0000000C                 B               loc_5BE3C
ROM:00000010 ; ---------------------------------------------------------------------------
ROM:00000010                 B               loc_5BE3C
ROM:00000014 ; ---------------------------------------------------------------------------
ROM:00000014                 B               loc_5BE3C
ROM:00000018 ; ---------------------------------------------------------------------------
ROM:00000018                 B               loc_5BE00
ROM:0000001C ; ---------------------------------------------------------------------------
ROM:0000001C                 B               loc_5BE3C
ROM:00000020 ; ---------------------------------------------------------------------------
ROM:00000020                 ANDEQ           R0, R0, R0
ROM:00000024
ROM:00000024 loc_24                                  ; CODE XREF: ROM:loc_0j
ROM:00000024                 MSR             CPSR_cxsf, #0xDF ; '¯'
ROM:00000028                 MOV             R0, #0x70000000
ROM:0000002C                 LDR             R12, [R0,#0x804]

And note that "0x70000000", which tells us where we need to rebase the image (also q.v. TheIphoneWiki's iBoot discussion - all ARM bootloaders are essentially similar). All the bootloader strings one would expect (e.g. those used in fastboot, and the unlock warning) are towards the end of the image, so disassembly isn't too difficult (though IDA, alas, leaves much to be desired). If anyone's interested in tips for the actual disassembly and (partial) symbolication, feel free to drop me a line.

On the flash

Although the /fstab.flounder contains a specific entry for "/bootloader", and alleges it's in the EBT labeled partition, the partition appears to be hidden from the by-name representation (at least on the device I got, there's no ..../by-name/EBT). Looking through the raw disk image (mmcblk0), though, the boot loader code can be found at 0x80000 (compare with previous output):

morpheus@Forge (~/.../Nexus9/Partitions) %  od -A x -t x4 blk0 | grep ea000007
0080000          ea000007        ea016f8c        ea016f8c        ea016f8a
0081220          e59f11e8        e3a02ec2        ea000007        e59f11e0
00cd0a0          ea000007        e3a00003        e28d1008        e3a02010
00cdd20          908ff100        ea0000c3        ea000007        ea000060
...

Indeed, this is corroborated by looking through the GPT (also at the beginning of mmcblk0), which clearly contains the EBT label.

fastboot commands

  • hboot, not lk, implements fastboot commands
  • fastboot oem verify will put the boot loader into a "veified state" (hey - that's HTC, not me :-), which has the side effect of A) wiping data and B) relocking the device..

    ...
    (bootloader) Start Verify: 0
    (bootloader) [hboot query] query 24 is not implemented
    (bootloader) [hboot query] query 24 is not implemented
    (bootloader) Start Verify: 0
    (bootloader) [hboot query] query 24 is not implemented
    (bootloader) [hboot query] query 24 is not implemented
    (bootloader) Device is in veified state!
    
    @TODO

    Swap

    The Nexus 9 uses zram - compressed memory as swap - a feature surprisingly similar to iOS 7+'s memory compressor. The compressed RAM is visible as a block device (/dev/block/zram0, with a size of 512MB. The swap is activated by init with the directive of swapon_all /fstab.flounder in the /init.flounder.rc. The /fstab.flounder contains the file system entry:

    /dev/block/zram0    none     swap      defaults     zramsize=533413200
    
    

    The kernel

    Unsurprisingly, Volantis runs a 3.10.x kernel. This was to be expected - It was high time for Android to catch up with Linux, and a post 3.8 kernel was needed to support AArch64 anyway. I'm guessing Google at least patched the well known futex bug here (i.e. TowelRoot).

    root@flounder:/ # cat /proc/cpuinfo
    Processor	: NVIDIA Denver 1.0 rev 0 (aarch64)
    processor	: 0
    processor	: 1
    Features	: fp asimd aes pmull sha1 sha2 crc32 
    CPU implementer	: 0x4e
    CPU architecture: AArch64
    CPU variant	: 0x0
    CPU part	: 0x000
    CPU revision	: 0
    
    Hardware	: Flounder
    Revision	: 0000
    Serial		: 0000000000000000
    MTS version	: 33410787
    root@flounder:/ # cat /proc/version
    Linux version 3.10.40-ga3846f1 (android-build@vpbs1.mtv.corp.google.com) 
    (gcc version 4.9.x-google 20140827 (prerelease) (GCC) ) #1 SMP PREEMPT 
    Thu Nov 6 01:39:45 UTC 2014
    

    And.. The kernel is open sourced, (and we just started our descent! It worked :-) so any further discussion here is moot.

    Comments/Requests