In this post I'll describe how I used hardware hacking techniques to get more information about the device and dump its internal storage. If you missed the introductory post you can find it here Man-in-the-conference room - Part I (Introduction). Let's start right away !
If we remove the two enclosure screws and open it up, we immediately identify two pinout slots:
- a slot with four pins (red)
- a second slot with six pins (purple)
Slot 1 - UART
The four pins slot highlighted in red is a good candidate for UART serial port. I won't cover the details on how to identify GND, Vcc, Rx, and Tx pins here, but I recommend you read @barbieauglend Hardware 101 recent post if you want to learn how to do that with a simple multimeter.
I'll be using a bus pirate in UART transparent bridge mode here. The diagram below will give you some information about the required connections.
Once our target is connected to the Bus Pirate, it's time to configure it to act as a UART transparent bridge. The first steps involve switching it to UART mode and setting serial parameters (baud rate, parity bits, stop bits, polarity):
$ screen /dev/ttyUSB0 115200 HiZ>m 1. HiZ 2. UART 3. I2C 4. SPI x. exit(without change) (1)>2 Set serial port speed: (bps) 1. 300 2. 1200 3. 2400 4. 4800 5. 9600 6. 19200 7. 38400 8. 57600 9. 115200 10. BRG raw value (1)>9 Data bits and parity: 1. 8, NONE *default 2. 8, EVEN 3. 8, ODD 4. 9, NONE (1)>1 Stop bits: 1. 1 *default 2. 2 (1)>1 Receive polarity: 1. Idle 1 *default 2. Idle 0 (1)>1 Select output type: 1. Open drain (H=Hi-Z, L=GND) 2. Normal (H=3.3V, L=GND) (1)>2 Clutch disengaged!!! To finish setup, start up the power supplies with command 'W' Ready UART>W POWER SUPPLIES ON Clutch engaged!!!
Once in UART mode, we select the mode of operation:
UART>(0) 0.Macro menu 1.Transparent bridge 2.Live monitor 3.Bridge with flow control 4.Auto Baud Detection UART>(1) UART bridge Reset to exit Are you sure? y
Everything is up and running, let's boot up the device and see what it has to say !
Serial output gives us already a good amount of information. The WonderMedia board is an ARMv7, bootloader is U-Boot and it starts Linux with kernel version 126.96.36.199. We also get interesting information about the different services running on the device and even the PIN code used to associate with it (1235). At the end of the init process, we are not dropped into a shell or presented with a login prompt.
At this point, I usually check if we can drop to a bootloader shell by sending characters over the serial line. This device being a good candidate given that it prints out this line:
Hit any key to stop autoboot: 0
I rebooted the device and pressed <Enter> multiple times during the bootloader stage. We get dropped to a U-Boot shell, wonderful. Let's check the environment variables to see how the bootloader boots the operating system:
Abort WMT Display Logo Function WMT # WMT # printenv ipaddr=192.168.0.2 serverip=192.168.0.1 gatewayip=192.168.0.1 netmask=255.255.255.0 wmt.gpo.lcd=0:1:0:d8110040:d8110080:d81100c0 wmt.i2c.param=0:1,1:0 wmt.eth.param=0x11 wmt.ui.storage=7 wmt.vd.debug=0 wmt.camera.param=0:0:1 wmt.gpo.cmos=1:0:3:D8110040:D8110080:D81100C0 wmt.webview.param=11 wmt.pwbn.param=100:100:100 wmt.l2c.param=1:0e420000 wmt.display.hdmi.vmode=auto wmt.sd0.param=1:0 wmt.sd1.param=1:0 wmt.sd2.param=0:0 wmt.plla.param=1:800:1,2:5,2:3 wmt.audio.i2s=hwdac wmt.audio.rate=all wmt.display.hdmi_audio_inf=i2s wmt.display.logoaddr=0x500000 wmt.mali.param=0:-1:-1:-1 memtotal=232M mbsize=64M wmt.display.param=7:0:0:1280:720:60 wmt.display.param2=5:6:1:1280:720:60 ethaddr=00:12:5F:16:30:9F gmac=00:12:5F:16:30:9F bootargs=mem=232M root=/dev/mtdblock0 noinitrd console=ttyS0,115200n8 mbtotal=64M wmt.ge.param=1:24:0:0 awgpio=mw 0xd81100c0 0x202; mw 0xd8110080 0x332 awdisplay=display init;mw 0x0e900000 0x0 0x1fa400 spiboot=run spiargs; bootm 0xffd00000 mmcboot=mmcinit 1; fatload mmc 1:3 0x500000 ulogo.bmp; display show;fatload mmc 1:3 0x1400000 spi.img; if iminfo 0x1900000; then run emmcargs; bootm 0x1900000; fi bootcmd=run awgpio; run awdisplay; run mmcboot; run spiboot emmcargs=setenv bootargs mem=232M root=/dev/ram0 ro initrd=0x1400000,16M console=ttyS0,115200n8 mbtotal=64M spiargs=setenv bootargs mem=232M root=/dev/mtdblock0 noinitrd console=ttyS0,115200n8 mbtotal=64M bootdelay=0 stdin=serial stdout=serial stderr=serial ver=U-Boot 1.1.4 (Sep 18 2013 - 17:32:14) Environment size: 1493/65531 bytes
We learn from spiboot, spiargs, emmcargs and mmcboot variables that the device uses two kinds of storage medium: an SPI flash and an MMC flash.
Let's check SPI flash banks first with flinfo:
WMT # flinfo Bank # 1: SST SPI Flash(25P64A-8MB) Sector Start Addresses: [ 0]FF800000 [ 1]FF810000 [ 2]FF820000 [ 3]FF830000 [ 4]FF840000 [ 5]FF850000 [ 6]FF860000 [ 7]FF870000 [ 8]FF880000 [ 9]FF890000 [ 10]FF8A0000 [ 11]FF8B0000 [ 12]FF8C0000 [ 13]FF8D0000 [ 14]FF8E0000 [ 15]FF8F0000 [ 16]FF900000 [ 17]FF910000 --snip-- Bank # 2: SST SPI Flash(25P64A-8MB) Sector Start Addresses: [ 0]FF000000 [ 1]FF010000 [ 2]FF020000 [ 3]FF030000 [ 4]FF040000 [ 5]FF050000 [ 6]FF060000 [ 7]FF070000 [ 8]FF080000 [ 9]FF090000 [ 10]FF0A0000 [ 11]FF0B0000 [ 12]FF0C0000 [ 13]FF0D0000 [ 14]FF0E0000 [ 15]FF0F0000 [ 16]FF100000 [ 17]FF110000 --snip--
So, we got two banks of 8MB each. Let's check MMC storage now. We initialize it first with mmcinit:
WMT # mmcinit 1 Initial SD/MMC Card OK! SD/MMC clock is 44Mhz register mmc device part_offset : 10, cur_part : 1
From the boot arguments it seems some partitions of MMC storage are FAT filesystems so let's double-check that with fatinfo and fatls:
WMT # fatinfo mmc 1:1 part_offset : 10, cur_part : 1 Interface: MMC Device 1: Vendor: Prod.: Rev: Type: Hard Disk Capacity: 3776.0 MB = 3.6 GB (7733248 x 512) No valid FAT fs found WMT # fatinfo mmc 1:2 part_offset : 7a4f0, cur_part : 2 Interface: MMC Device 1: Vendor: Prod.: Rev: Type: Hard Disk Capacity: 3776.0 MB = 3.6 GB (7733248 x 512) No valid FAT fs found WMT # fatinfo mmc 1:3 part_offset : f49e0, cur_part : 3 Interface: MMC Device 1: Vendor: Prod.: Rev: Type: Hard Disk Capacity: 3776.0 MB = 3.6 GB (7733248 x 512) Partition 3: Filesystem: FAT32 " " WMT # fatinfo mmc 1:4 part_offset : 2dd1d0, cur_part : 4 Interface: MMC Device 1: Vendor: Prod.: Rev: Type: Hard Disk Capacity: 3776.0 MB = 3.6 GB (7733248 x 512) Partition 4: Filesystem: FAT32 "InternalMem" WMT # fatls mmc 1:4 / part_offset : 2dd1d0, cur_part : 4 0 file(s), 0 dir(s) WMT # fatls mmc 1:3 / part_offset : f49e0, cur_part : 3 8388608 spi.img 921654 ulogo.bmp 2 file(s), 0 dir(s)
Partitions 3 and 4 are FAT filesystems, partition 3 holds two files: spi.img (a copy of SPI content, holding kernel and CRAMFS), and ulogo.bmp (a bitmap file holding the logo displayed during the early boot stage).
My understanding at this point is that U-Boot load SPI flash memory content to partition 3 of MMC storage as spi.img. It then loads the image to memory and boots the Linux kernel. The kernel will then take care of mounting MMC storage during the init process.
Ghetto Memory Acquisition
Our objective being dumping the MMC storage content, let's look for an easy way to do that from the bootloader. One of the easiest way from a U-Boot shell is to use the USB subsystem to mount a USB key and dump data to USB so it is fast and efficient. Sadly, the USB subsystem cannot be initialized because of some Wondermedia weirdness. spi commands are not available in this U-Boot shell and we are left with mmc commands to dump memory.
After a few hours of research, I came up with the following strategy:
- use mmcread command to read a portion of MMC storage to a known safe address in RAM (0x1400000 for example)
- use md.b command to dump memory from RAM
- convert hexadecimal dump received from md.b to binary and save it to a file
A quick demonstration of that strategy: we load 512 (0x400) bytes from block number 20736 (0x5100) to address 0x1400000 and then print loaded memory content:
WMT # mmcread 1 0x1400000 0x5100 0x400 Read Data Success WMT # md.b 0x1400000 0x400 01400000: 00 00 00 07 73 73 68 2d 64 73 73 00 00 00 81 00 ....ssh-dss..... 01400010: c0 ca 70 94 a9 c6 14 38 b6 61 ad be 43 b4 cb ff ..p....8.a..C... 01400020: b2 52 07 59 0f 01 3e e0 82 b0 d9 4c 8a f7 1d 1e .R.Y..>....L.... 01400030: 16 c7 ce 10 c4 b2 ff bb c2 b3 8e 6b 8c c8 3e 8c ...........k..>. 01400040: 5c eb 70 1e 13 02 1b fe e9 33 71 a4 91 7c eb 7a \.p......3q..|.z 01400050: ce e3 96 13 3f e4 e1 b0 d3 58 cd 47 d0 0e 97 a0 ....?....X.G.... 01400060: ca bb fc 3d cb 4f 5e a4 c3 72 31 ae 1e 55 af 23 ...=.O^..r1..U.# 01400070: ff 52 cf 70 1d ac 92 bb 8e 3d e8 61 c9 f1 c7 4f .R.p.....=.a...O 01400080: 02 0e 84 ad d4 a3 fa 8d 82 e5 9d 20 63 28 d2 59 ........... c(.Y 01400090: 00 00 00 15 00 ba 2d 5d 7f 86 8c b3 00 49 13 a5 ......-].....I.. 014000a0: b6 00 8e d7 fd a4 28 72 cd 00 00 00 80 6b 4a 2f ......(r.....kJ/ --snip--
From the information provided by fatinfo, we know the MMC storage is composed of 7733248 blocks of 512 bytes. To dump the full MMC we can read each block sequentially. We can automate that process with a bit of Python by using pexpect over a serial line opened by picocom :)
The complete proof-of-concept for this firmware dump over serial is presented below.
The problem with this method is that it is insanely slow. Transfer rate is something around 2kB/sec so it takes approximately 20 days to dump the full MMC. Let's launch the script and grab a coffee or, you know, a thousand.
$ ./mmc_dump.py /dev/ttyUSB0 mmc_dump.bin [+] Setting up serial line. [+] Initializing MMC ... [+] Starting dumping process ... [o] 128/7733248 blocks read
Days have passed and we now have a complete dump of MMC storage. We can check that with the file command that indicates it is an MBR boot sector with 4 partitions. The fdisk command provides some more information, mainly about the filesystem type used by each partition.
$ file mmc_dump.bin mmc_dump.bin: DOS/MBR boot sector; partition 1 : ID=0x83, start-CHS (0x0,1,1), end-CHS (0x1f0,62,16), startsector 16, 500960 sectors; partition 2 : ID=0x83, start-CHS (0x1f1,0,1), end-CHS (0x3e1,62,16), startsector 500976, 500976 sectors; partition 3 : ID=0xc, start-CHS (0x3e2,0,1), end-CHS (0x3ff,62,16), startsector 1001952, 2000880 sectors; partition 4 : ID=0xc, start-CHS (0x3ff,62,16), end-CHS (0x3ff,62,16), startsector 3002832, 3907008 sectors $ fdisk -l mmc_dump.bin Disk mmc_dump.bin: 3,7 GiB, 3959422976 bytes, 7733248 sectors Units: sectors of 1 * 512 = 512 bytes Sector size (logical/physical): 512 bytes / 512 bytes I/O size (minimum/optimal): 512 bytes / 512 bytes Disklabel type: dos Disk identifier: 0x00000000 Device Boot Start End Sectors Size Id Type mmc_dump.bin1 16 500975 500960 244,6M 83 Linux mmc_dump.bin2 500976 1001951 500976 244,6M 83 Linux mmc_dump.bin3 1001952 3002831 2000880 977M c W95 FAT32 (LBA) mmc_dump.bin4 3002832 6909839 3907008 1,9G c W95 FAT32 (LBA)
I read this excellent resource on how to mount the different partitions of a disk image and mounted the four of them as described below:
$ sudo losetup -v -f mmc_dump.bin $ sudo losetup -a /dev/loop0: :9755872 (/home/quentin/research/airmedia/hardware/dumps/uboot/mmc_dump.bin) $ sudo partx --show /dev/loop0 NR START END SECTORS SIZE NAME UUID 1 16 500975 500960 244,6M 2 500976 1001951 500976 244,6M 3 1001952 3002831 2000880 977M 4 3002832 6909839 3907008 1,9G $ sudo partx -v --add /dev/loop0 partition: none, disk: /dev/loop0, lower: 0, upper: 0 /dev/loop0: partition table type 'dos' detected /dev/loop0: partition #1 added /dev/loop0: partition #2 added /dev/loop0: partition #3 added /dev/loop0: partition #4 added $ sudo blkid /dev/loop0* /dev/loop0: PTTYPE="dos" /dev/loop0p1: UUID="6e4f610d-796f-4b15-8b21-f3da4538f09b" TYPE="ext2" /dev/loop0p2: UUID="1ce0eb6b-4733-44e8-9b4d-761597dd4a36" TYPE="ext2" /dev/loop0p3: UUID="7A5C-49D2" TYPE="vfat" /dev/loop0p4: LABEL="InternalMem" UUID="7A69-9C39" TYPE="vfat" $ sudo mount -o ro /dev/loop0p1 /mnt/tmp
We can get a better understanding of storage layout by looking into each partition to see what they hold. The diagram below provides a good overview of how the CPU interface with storage medium and what they contain.
20 days, really ??
Ok, I might have skipped something on purpose just to show what can be done from U-Boot only. There is an easier and faster way to dump MMC storage: editing boot parameters to get a shell.
This can be done using setenv to edit the emmcargs boot arguments, appending
init=/bin/sh so that we get dropped to a shell. Once the environment variable is edited, we run mmcboot to boot the kernel.
WMT # setenv emmcargs 'setenv bootargs mem=232M root=/dev/ram0 ro initrd=0x1400000,16M console=ttyS0,115200n8 mbtotal=64M init=/bin/sh' WMT # run mmcboot --device boots--
We get dropped to a shell, prior to Linux running init. The next step is to mount required elements suchs as /proc, /sys, and /dev:
/ # /bin/mount -t proc proc /proc / # /bin/mount -t sysfs sysfs /sys / # /bin/mount -t ramfs ramfs /tmp / # /bin/mount -t usbfs usbfs /proc/bus/usb / # mount -t tmpfs mdev /dev / # mkdir /dev/pts / # mount -t devpts devpts /dev/pts / # /etc/dev/MKDEV / # cp /etc/mdev.conf.base /tmp/mdev.conf / # sync / # mdev -s
Then we need to load kernel modules for USB storage devices so that we can dump the MMC to a USB key:
/ # insmod /lib/modules/kernel/usb/usb-storage.ko Initializing USB Mass Storage driver... scsi0 : SCSI emulation for USB Mass Storage devices usbcore: registered new interface driver usb-storage USB Mass Storage support registered. scsi 0:0:0:0: Direct-Access SanDisk Ultra 1.00 PQ: 0 ANSI: 6 sd 0:0:0:0: [sda] 60063744 512-byte logical blocks: (30.7 GB/28.6 GiB) sd 0:0:0:0: Attached scsi generic sg0 type 0 sd 0:0:0:0: [sda] Write Protect is off sd 0:0:0:0: [sda] Assuming drive cache: write through sd 0:0:0:0: [sda] Assuming drive cache: write through sda: sda1 sd 0:0:0:0: [sda] Assuming drive cache: write through sd 0:0:0:0: [sda] Attached SCSI removable disk FAT: utf8 is not a recommended IO charset for FAT filesystems, filesystem will be case sensitive!
We load the kernel module for MMC storage so that it detects the card:
/ # insmod /lib/modules/kernel/usb/mmc_atsmb1.ko wmt.sd1.param = 1:0 WMT ATSMB1 (AHB To SD/MMC1 Bus) controller registered! mmc0: new high speed MMC card at address 0001 mmcblk1: mmc0:0001 004G90 3.68 GiB mmcblk1: p1 p2 p3 p4 SD1 Host Clock 41666666Hz EXT3-fs: Unrecognized mount option "iocharset=utf8" or missing value FAT: utf8 is not a recommended IO charset for FAT filesystems, filesystem will be case sensitive! EXT3-fs: Unrecognized mount option "iocharset=utf8" or missing value FAT: utf8 is not a recommended IO charset for FAT filesystems, filesystem will be case sensitive! FAT: utf8 is not a recommended IO charset for FAT filesystems, filesystem will be case sensitive! FAT: utf8 is not a recommended IO charset for FAT filesystems, filesystem will be case sensitive!
Our USB key gets auto-mounted in read-only so we remount it in read-write mode:
/ # mount -o rw,remount /tmp/usb/sda1
After all these steps, we can simply use dd to copy the MMC to a file onto our USB key:
/ # dd if=/dev/mmcblk1 of=/tmp/usb/sda1/mmcblk1.dd.img bs=1M 3776+0 records in 3776+0 records out 3959422976 bytes (3.7GB) copied, 457.659017 seconds, 8.3MB/s
7 minutes ! Way better than our serial dump, right ? Note that it's not always possible to do that so the U-Boot only method is still relevant for some devices :)
Now it's time to move to the other slot, the one with six different pins.
Slot 2 - JTAG
This time, on top of differentiating GND from Vcc ports, I used a logic analyzer from Saleae to help me find what kind of debug port I was connecting to.
In the screenshot below you see the Logic user interface with the signal received by each connector. Those who've already played with it will immediately identify the protocol it is speaking: JTAG.
After a few mistakes, I finally identified the correct connections for JTAG. Here I present the connections to make to a Bus Pirate that we will connect to with OpenOCD:
It's now time to launch OpenOCD. I created an airmedia.cfg file with the following content:
source [find interface/buspirate.cfg] buspirate_vreg 0 # turn off the voltage regulator buspirate_mode normal buspirate_pullup 0 # turn pull up's down (no VTref) buspirate_port /dev/ttyUSB0
Sadly I wasn't able to properly interact with the JTAG port via OpenOCD and the bus pirate. It is definitely JTAG -that I'm sure- but it takes more skills or time to get it to work properly. Or maybe the port is just f*cked. I don't know. This is the kind of output I got when auto-probing the device, with IDCODE changing every time.
Given that I already got a memory dump and didn't want to perform debugging over JTAG I stopped my investigations there.
We successfully identified two debug ports: one speaking UART and another speaking JTAG.
We gathered information about applications, OS, and underlying CPU architecture by connecting to the device over UART. We then took advantage of a U-Boot misconfiguration to drop to the bootloader shell and dumped the content of MMC storage by using two different methods:
- serial transfer using bootloader commands only
- dumping to a USB key using dd by dropping to a shell using boot commands editing
We gained more insight about storage layout and boot process by analyzing the image dump. On top of that, we now have access to the root filesystem holding all the files and binaries. This means it's time to move onto the next step: network assessment.
This will be covered in the third part of this blog series, you can find it at Man-in-the-Conference Room - Part III (Network Assessment).