💻
crackcat @ studying
  • 🏠Home
  • 📑Infographics
  • 🛠️How Stuff Works
    • Buffer Overflow - Explained
    • Embedded Firmware Extraction
  • Proxmox+Packer+Terraform+Ansible
  • 📒TryHackMe Write-Ups
    • 📵Mr. Robot
    • 🔗Binex
    • *️CMesS
    • 🏛️Olympus
    • 🧑‍💻UltraTech
    • 🧠Brainpan
  • ⚙️CVE
    • CVE-2019-17571 RCE PoC
    • CVE-2022-45962 Postauth SQLI
  • 🏴CTF Challenges
    • BugBase - RaaS
  • 🏢AllEndEvent
    • Introduction
    • Chapter I
    • Chapter II
    • Chapter III
Powered by GitBook
On this page
  • What's All This Then?
  • How It Started
  • Simple Steps for Connecting to UART
  • Extracting an Unknown Filesystem from Flash Memory
  • Final Notes

Was this helpful?

  1. How Stuff Works

Embedded Firmware Extraction

A tale of practicing firmware extraction mixed with some file format reverse engineering.

PreviousBuffer Overflow - ExplainedNextProxmox+Packer+Terraform+Ansible

Last updated 3 months ago

Was this helpful?

What's All This Then?

Disclaimer: I am not an embedded expert. I love dabbling with electronics and taking things apart. This post is about my experience and learnings of disassembling an IoT device and having a look at its firmware.

Resources on getting started with hardware hacking are abundant. Great introductions like this video from Tony Gambacorta or this blog post about from Black Hills Information Security will teach you everything you need to start yourself.

So, why this post? Connecting via the Universal Asynchronous Receiver and Transmitter (UART) protocol will not always drop you into a root shell and dumping firmware will not always work with the tools shown in tutorials. Dealing with these scenarios can be challenging and time-consuming. Thus, I decided to document some challenges (read: fails) and learnings from my attempt at analyzing an IoT camera.

I will not cover the basics of UART or do theory on embedded storage devices. I will, however, show you my steps, methodology, and findings.

How It Started

Simple Steps for Connecting to UART

First things first, of course the most fun part of any hardware project is taking things apart. Luckily, this wasn't a problem at all and I managed to disassemble every component without breaking things. On the inside we got:

Now, the attentive reader will have noticed the small pads right next to the RISC-V processor:

The pads are kindly named RX and TX revealing a UART interface that is connected directly to the Hi3861 chip. That was easy but it gets better. When we turn the board around, we see:

Another UART interface, presumably for the video processor. Right next to it we can also find a flash chip that we'll take a closer look at later on.

However, soldering some small copper wire to the pads also works just fine. Once that was done, I used one of many available USB-to-UART bridges to hook up the RX and TX lines of one interface with my computer. Here, I used an AZDelivery CP2102.

As always with UART: RX (receiver) must be connected to TX (sender) and vice versa. The final setup looked like this (looks wonky but it works):

Using a plain Linux distribution, connecting to the UART interface can easily be achieved with:

sudo minicom -D /dev/ttyUSB0

/dev/ttyUSB0 is the equivalent of the COM port on Windows. If you are unsure which one's the correct one, there's multiple ways to find out. One simple way is to ls -la /dev/tty* before and after plugging in the USB-to-UART adapter.

Being lucky one more time, I didn't even have to change the baudrate or other parameters - 1152800 8N1 (default) worked fine. Connecting to the UART interface of the T31 chip and booting the device:

# minicom -D /dev/ttyUSB0
Ver:20210728-Turret
 uart ok
ver:200a0604
riscv_param_t->ev:500
get tag section:SENS
riscv set sensor:sc200ai
tag::i2c_addr:0x30
tag::reg_addr_size:2
tag::reg_value_size:1
Set ev:500 intt:500 again:1024 dgain:1024
stream on in riscv
ae cu ver:2.10
exp_parameter[2]:2
tiziano_init done
awb cu ver:2.10
_awb_ct:7984
_awb_ct_last:8678
log2_max:377103
max_ag:55268
num:1 intt:1121 again:4571 dgain:1030 luma:2 ev:5033
...
(none) login: [ZRT_CLD_LoadOrderInfo:1130]:cred->device_id = 0
[ZRT_CLD_LoadOrderInfo:1131]:cred->user_id = 0
[ZRT_CLD_LoadOrderInfo:1132]:cred->cloud_provider_id = 0
[ZRT_CLD_LoadOrderInfo:1133]:cred->storage_mode = 0
[ZRT_CLD_LoadOrderInfo:1134]:cred->order_start_time = 0
[ZRT_CLD_LoadOrderInfo:1135]:cred->order_end_time = 0
[ZRT_CLD_LoadOrderInfo:1136]:cred->config =
[ZRT_CLD_LoadOrderInfo:1137]:cred->host =
[ZRT_CLD_LoadOrderInfo:1138]:cred->region =
[ZRT_CLD_LoadOrderInfo:1139]:cred->server =
[ZRT_CLD_LoadOrderInfo:1140]:cred->aws_file_path =
[ZRT_CLD_LoadOrderInfo:1141]:cred->access_key_id =
[ZRT_CLD_LoadOrderInfo:1142]:cred->secret_access_key =
[ZRT_CLD_LoadOrderInfo:1143]:cred->credential_lose_time = 0
[ZRT_CLD_LoadOrderInfo:1144]:cred->session_token =
...

There's some version information, the camera sensor, and a bunch of log output. That's it - no bootloader screen, no shell. Interestingly, the output contains this line:

[ZRT_CLD_LoadOrderInfo:1142]:cred->secret_access_key =

Now, I didn't bother to buy a premium account and connect this camera to the cloud but I could imagine the credentials showing up here if I did.

Regardless, apart from some log information, this interface doesn't give me much. So let's switch to the other interface:

# minicom -D /dev/ttyUSB0
ready to OS start
sdk ver: Hi3861V100R001C00SPC032 2022-06-17 10:00:00
fw ver: 5.20.32.t10, compiled in Dec 29 2023 10:13:40
power down soc
FileSystem mount ok.
wifi init success!
[func:PW_Main_DeviceInit, line:4650] device_info length : [792]
[func:PW_FS_ReadBackUpArea, line:189] crc32_f:222798945 crc32_b:-1
read device_info.time(0) abnormal, clean DEVICE_INFO_UPDATE_BIT_MASK_TIME
...
[func:PW_Main_MacInit, line:4732] mac_info.szModel : []
[func:PW_Main_MacInit, line:4733] mac_info.prefixname : []
[func:PW_Main_MacInit, line:4734] mac_info.connector : []
[func:PW_Main_FactoryInit, line:4870] cameraSensor.rotateAttrA=[0]
[func:PW_Main_FactoryInit, line:4871] cameraSensor.rotateAttrB=[0]
[func:PW_Main_FactoryInit, line:4898] The dev is general camera.
success now value : [3900], lowpower_adc_list[59] : [3892], lowpower_adc_list[60] : [3901]
[func:PW_Power_ElecUpdate, line:582] The new elec_value(first) : [60]
[func:PW_Power_Elec_Alarm, line:263] getmgr->mgr_tcp fd is NULL.
power up soc
[func:PW_FS_Read_File, line:51] read index_data failed! errno 0x80000405
...
master has power off at line 340 in pw_sdio_send_data
[func:PW_FS_Read_File, line:51] read pw_bindinfo failed! errno 0x80000405
[func:PW_Main_WorkInit, line:4939] g_Reboot_Flag = 0
[func:PW_Main_WorkInit, line:4945] g_WiFi_Abnormal_Flag = 0
[func:PW_Main_WorkInit, line:4955] pw_upgrade_st.flag = 0, pw_upgrade_st.state = 8
[func:PW_FS_Read_File, line:51] read alarm_snooze failed! errno 0x80000405
...
write motion cfg 0x001A2905
[func:PW_Info_GetNetConfig, line:143] get ssid: XXXXXXXXXXX
pw_main::start sdio init
master has power off at line 340 in pw_sdio_send_data
[func:intercomm_check_poweroff, line:837] time start = 240, interval = 90s
[func:intercomm_init, line:914] hi_sdio_init start : 250
wait card ready

 Received eint: 7 !
[func:PW_Main_EintTask, line:3310] g_user_center.work_step = 5, device_info.update = 0xc0
wait ok
[func:intercomm_init, line:916] hi_sdio_init end : 1190
update = 0x7fdaf8, time = 0, wakeup_mod = 1
finish intercomm init.
[func:PW_Main_NetworkTimer_Delete, line:2218] g_Network_Timer = 0
auth type is WPA/WPA2-PSK MIX.
expected_bss.ssid = XXXXXXXXXXX.
expected_bss.key = XXXXXXXXXXX.
expected_bss.bssid = .
[func:PW_Network_StartStaMode, line:1163] ifname: [wlan0]
recv cmd to power on T31
[intercomm_power_on_msg]g_DevType: [0]
...

No shell here either. The output looks slightly more interesting but it's mostly just versions and configuration output. Note that this camera was setup normally once before being disassembled. During this setup, the camera had to be connected to a WLAN.

It was a fun surprise to see the stored SSID and password of my wireless lab access point being shown in cleartext without any further interaction.

auth type is WPA/WPA2-PSK MIX.
expected_bss.ssid = XXXXXXXXXXX.
expected_bss.key = XXXXXXXXXXX.

While showing cleartext credentials on a serial console (especially on an outdoor camera) may not be a great idea, it's not what I am after. In order to understand how the device operates, I want access to some sort of shell or the filesystem.

Just because we didn't get a shell though, does not mean that the UART interface was completely useless. For example, there was some output that could be analyzed more in-depth, like this snippet from the Hi3861 chip starting to communicate with sg.ipc365.com.

----------------LoginServerName : [sg.ipc365.com] ----------------
puwell PW_NET_Init succeed!!
--------puwell_do_device_login--------device_sn:[XXXXXXX]-    sign:[XXXXXXX]---------------
-------------------------global_login_ip_string = XXX.XXX.XXX.XXX
custom_id = [XXXXXXX]  device_id = [XXXXXXX]   challenge = [XXXXXXX]
[device_login response]--device_id[XXXXXXX]---custom_id[XXXXXXX]
rec from app user_id = [XXXXXXX], rec from login server user_id = [XXXXXXX]
[camera_login response]--mgr port[19001]---mgr ip[XXXXXXX]---custom_id[XXXXXXX]  session_id [XXXXXXX]
process_task start !
----------PW_NET_UdpPipeInit---process_udp_pipe = [0]---main_udp_pipe = [0]----------
local porcess udp pipe ip:127.0.0.1, port:10000
[process udp info] bind socket_fd = 0, bind address[127.0.0.1:10000]
create process udp pipe success!-----udp_pipe->process_udp_pipe : [0]-----------
pipe->process_udp_pipe fd = [0]
local main udp pipe ip:127.0.0.1, port:10200
[main udp info] bind socket_fd = 1, bind address[127.0.0.1:10200]
pipe->main_udp_pipe fd = [1]
-----------------puwell_do_device_signin----------------
mgrid:XXX.XXX.XXX.XXX, port:19001

Or several hints that the sd card slot was being checked for a bootloader and a filesystem, which could potentially be abused to boot from a "malicious" image.

But all that is out of scope for this post. Instead, I focused on getting the contents from that flash memory we saw in the beginning.

Extracting an Unknown Filesystem from Flash Memory

The XM25QH64C (note that the C from XMC is not actually part of the model number) is a flash memory chip with 8 MB of storage that can be interfaced via the serial peripheral interface (SPI) bus. Because connecting to that chip manually (i.e. using a Pi and SPI programming) would mean more effort, I decided to use another gadget for reading flash chips easily - the CH341A Flash Programmer.

Although the testclip that often comes together with the CH341A can hook directly onto the chip while it's on the board, surrounding circuitry might prevent you from getting an actual reading. Thus, I usually desolder the chip and place it into the clamp like this:

Before plugging in the USB adapter, there are two things to watch out for:

  1. Make sure that the chip is placed the right way (pin 1 of any IC is usually marked with a small dot) and that every pin has a solid connection to the adapter (as shown in the picture above).

  2. Use the markers on the side of the CH341A board to confirm that you placed the adapter into the socket the right way:

Getting step 2 wrong will short the board and you will notice a distinct smell and heat coming from the board. After I tried this accidentally for educational purposes, I quickly disconnected the board, placed the adapter the right way, turned it back on, and it worked flawlessly. Note that this could potentially damage (i.e. burn) your board or the flash chip permanently.

Alternatively, you can use flashrom on a Linux system:

# It's generally a good idea to check the list of supported chips first
flashrom -L
# If the programmer and chip are both supported, then you can use
sudo flashrom -p ch341a_spi -c XM25QH64C -r flash_dump.bin

After saving the output to a bin file, I started to analyze it right away. But I soon realized that unpacking the firmware was not as straight forward as I had hoped. Applying strings on the binary file resulted in some promising boot loader messages and configuration settings but all that binwalk was able to carve were some empty directories of a squashfs filesystem.

At this point I tried several tools without success until I turned back to flashrom and AsProgrammer.exe to dump the flash contents again. To my surprise, the checksum was a different one this time and I continued to read the chip several times to make sure that this time I actually had read the chip correctly.

Take an extra minute to realign the chip in the adapter and read it multiple times if you're not sure that the connection is flawless. Verifying the checksum can save you a lot of time.

Finally, equipped with an exact image of the memory chip, I felt confident that extracting the filesystem should be easy now.

$ binwalk XM25QH64C.bin                           

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
29772         0x744C          CRC32 polynomial table, little endian
31192         0x79D8          LZO compressed data
202000        0x31510         CRC32 polynomial table, little endian
206248        0x325A8         LZO compressed data
210172        0x334FC         Android bootimg, kernel size: 0 bytes, kernel addr: 0x70657250, ramdisk size: 543519329 bytes, ramdisk addr: 0x6E72656B, product name: "mem boot start"
622592        0x98000         uImage header, header size: 64 bytes, header CRC: 0xF56DF60, created: 2023-12-29 02:15:11, image size: 1931178 bytes, Data Address: 0x80010000, Entry Point: 0x802ECDC0, data CRC: 0x70CDA758, OS: Linux, CPU: MIPS, image type: OS Kernel Image, image name: "Linux-3.10.14-Archon"
622656        0x98040         LZO compressed data
2146057       0x20BF09        lzop compressed data,d52,
3047326       0x2E7F9E        ELF, 32-bit LSB processor-specific, ("")
3828261       0x3A6A25        Base64 standard index table
4432794       0x43A39A        SHA256 hash constants, little endian
4455856       0x43FDB0        Certificate in DER format (x509 v3), header length: 4, sequence length: 831
4456615       0x4400A7        Private key in DER format (PKCS header length: 4, sequence length: 1188
4461483       0x4413AB        Certificate in DER format (x509 v3), header length: 4, sequence length: 823
7340032       0x700000        Squashfs filesystem, little endian, version 4.0, compression:xz, size: 237530 bytes, 30 inodes, blocksize: 131072 bytes, created: 2023-12-29 02:15:17
7602176       0x740000        JFFS2 filesystem, little endian

What stands out immediately are the unexpected descriptions and the many odd hexadecimal offsets. Of course odd offsets can occur, but when looking at a firmware image I usually expect at least some reasonable numbers that may align with common memory sizes.

And indeed, apart from results such as "Base64 standard index table" and "SHA256 hash constants" we can spot a few lines that look like they could be worth investigating:

0x98000         uImage header, header size: 64 bytes, header CRC: 0xF56DF60, created: 2023-12-29 02:15:11, image size: 1931178 bytes, Data Address: 0x80010000, Entry Point: 0x802ECDC0, data CRC: 0x70CDA758, OS: Linux, CPU: MIPS, image type: OS Kernel Image, image name: "Linux-3.10.14-Archon"
0x700000        Squashfs filesystem, little endian, version 4.0, compression:xz, size: 237530 bytes, 30 inodes, blocksize: 131072 bytes, created: 2023-12-29 02:15:17
0x740000        JFFS2 filesystem, little endian

The first line appears to be an intact uImage header, indicating a MIPS architecture based Linux-3.10.14-Archon.

uImage is a kernel image with a prepended U-Boot header that provides some information such as where the image will be loaded to. U-Boot is a boot loader for embedded boards that is closely related to Linux. MIPS is a Reduced Instruction Set Computer (RISC) Instruction Set Architecture (ISA). Sounds a mouthful but describes the architecture that is compatible with the hardware implemented on the chip (the Hi3861 is a RISC-V processor while the T31 supports MIPS).

SquashFS is a compressed read-only filesystem while JFFS2 is a compressed but writeable filesystem. Their offset starts at 7MiB (out of the 8 MiB flash memory) so this can not be everything yet.

After using binwalk -e XM25QH64C.bin we can see that the SquashFS is almost empty:

$ ls -laR squashfs-root
squashfs-root:
total 12
drwxr-xr-x 3 kali kali 4096 Dec 28 21:15 .
drwxr-xr-x 4 kali kali 4096 May 14 14:58 ..
drwxr-xr-x 4 kali kali 4096 Dec 28 21:15 snd

squashfs-root/snd:
total 16
drwxr-xr-x 4 kali kali 4096 Dec 28 21:15 .
drwxr-xr-x 3 kali kali 4096 Dec 28 21:15 ..
drwxr-xr-x 2 kali kali 4096 Dec 28 21:15 VoiceCN
drwxr-xr-x 2 kali kali 4096 Dec 28 21:15 VoiceEN

squashfs-root/snd/VoiceCN:
total 308
drwxr-xr-x 2 kali kali  4096 Dec 28 21:15 .
drwxr-xr-x 4 kali kali  4096 Dec 28 21:15 ..
-rwxr-xr-x 1 kali kali 17763 Dec 28 21:15 ap_mode.g711a
-rwxr-xr-x 1 kali kali 25495 Dec 28 21:15 call.g711a
-rwxr-xr-x 1 kali kali 24868 Dec 28 21:15 dhcp_getip_failed.g711a
-rwxr-xr-x 1 kali kali 10490 Dec 28 21:15 dingdong.g711a
-rwxr-xr-x 1 kali kali 11703 Dec 28 21:15 input_ssid.g711a
-rwxr-xr-x 1 kali kali   308 Dec 28 21:15 linkVoice.sh
-rwxr-xr-x 1 kali kali 41424 Dec 28 21:15 motion.g711a
-rwxr-xr-x 1 kali kali 14419 Dec 28 21:15 net_connect.g711a
-rwxr-xr-x 1 kali kali 30510 Dec 28 21:15 passwd_err.g711a
-rwxr-xr-x 1 kali kali 24768 Dec 28 21:15 smoke.g711a
-rwxr-xr-x 1 kali kali 45556 Dec 28 21:15 upgrade_start.g711a
-rwxr-xr-x 1 kali kali 11912 Dec 28 21:15 video_close.g711a
-rwxr-xr-x 1 kali kali 13165 Dec 28 21:15 video_open.g711a

squashfs-root/snd/VoiceEN:
total 244
drwxr-xr-x 2 kali kali  4096 Dec 28 21:15 .
drwxr-xr-x 4 kali kali  4096 Dec 28 21:15 ..
-rwxr-xr-x 1 kali kali 17763 Dec 28 21:15 ap_mode.g711a
-rwxr-xr-x 1 kali kali 25495 Dec 28 21:15 call.g711a
-rwxr-xr-x 1 kali kali 13632 Dec 28 21:15 dhcp_getip_failed.g711a
-rwxr-xr-x 1 kali kali 10490 Dec 28 21:15 dingdong.g711a
-rwxr-xr-x 1 kali kali 11703 Dec 28 21:15 input_ssid.g711a
-rwxr-xr-x 1 kali kali   308 Dec 28 21:15 linkVoice.sh
-rwxr-xr-x 1 kali kali 41424 Dec 28 21:15 motion.g711a
-rwxr-xr-x 1 kali kali  9408 Dec 28 21:15 net_connect.g711a
-rwxr-xr-x 1 kali kali 10752 Dec 28 21:15 passwd_err.g711a
-rwxr-xr-x 1 kali kali 24768 Dec 28 21:15 smoke.g711a
-rwxr-xr-x 1 kali kali 23616 Dec 28 21:15 upgrade_start.g711a
-rwxr-xr-x 1 kali kali  9600 Dec 28 21:15 video_close.g711a
-rwxr-xr-x 1 kali kali 10752 Dec 28 21:15 video_open.g711a

The JFFS2 looks a bit more interesting but still does not include the root filesystem:

$ ls -laR jffs2-root 
jffs2-root:
total 156
drwxr-xr-x 3 kali kali   4096 May 14 14:58 .
drwxr-xr-x 4 kali kali   4096 May 14 14:58 ..
-rw-r--r-- 1 kali kali      4 May 14 14:58 cust_mode.cfg
lrwxrwxrwx 1 kali kali     19 May 14 14:58 device.log1 -> /system/snd/VoiceEN
-rw-r--r-- 1 kali kali 131074 May 14 14:58 device.log1_bak
lrwxrwxrwx 1 kali kali     19 May 14 14:58 host.log1 -> /system/snd/VoiceEN
lrwxrwxrwx 1 kali kali     19 May 14 14:58 host.log1_bak -> /system/snd/VoiceEN
-rw-r--r-- 1 kali kali      4 May 14 14:58 .index_status
-rw-r--r-- 1 kali kali   2396 May 14 14:58 .order
lrwxrwxrwx 1 kali kali     19 May 14 14:58 playtime.debuginfo -> /system/snd/VoiceEN
lrwxrwxrwx 1 kali kali     19 May 14 14:58 snd -> /system/snd/VoiceEN
drwxr-xr-x 2 kali kali   4096 May 14 14:58 .tag

jffs2-root/.tag:
total 16
drwxr-xr-x 2 kali kali 4096 May 14 14:58 .
drwxr-xr-x 3 kali kali 4096 May 14 14:58 ..
-rwxr-xr-x 1 kali kali  843 May 14 14:58 webrtc_profile.ini
-rwxr-xr-x 1 kali kali  124 May 14 14:58 ZRT_Profiles.ini

These look like log files and some configuration. The most interesting bit was probably:

But where's the root file system with all the interesting files, scripts, and binaries? At this point I was quite stuck for some time as I never had to deal with firmware extraction to this extent before.

Huge shoutout at this point to one of my colleagues and also @DigitalAndrew from the TCM (TheCyberMentor) discord for teaching me new stuff and enduring my endless questions. With their help and lots of trial and error I was finally able to make sense of the image.

Probably the best hint I received was to use entropy analysis to get a better understanding of the file structure. And that's easily done with binwalk -E:

While binwalk could be fine tuned with -H and -L to identify the most relevant edges, the defaults result in a pretty interesting graph.

When looking at the entropy graph of binwalk, it can be read as:

  • Entropy (close to) 0: no chaos, very structured data and/or reoccurring patterns

  • Low entropy: structured data (clear text will usually show some form of patterns)

  • High entropy: unstructured data, a constant high entropy may indicate compression

  • Entropy (close to) 1: fully random (pure chaos), probably encrypted data

Look out for steep falling and rising edges as they may indicate a change in the file structure. With the rough position of these edges, you can use a hexeditor to analyze the surrounding area of the binary file. Search for magic bytes, trailers, or other interestings bits and bytes.

Note that the scale is different here, so 1 is not the highest value, but the same principles apply.

In order to fully make sense of the single regions, it also helped to take a look at the strings output of the entire image. For examples, the strings of the boot loader segment can contain valuable information about memory regions, sizes, and meanings.

$ strings XM25QH64C.bin | grep boot
...
bootargs=console=ttyS1,115200n8 mem=42M@0x0 rmem=22M@0x2a00000 root=/dev/ram0 rw rdinit=/linuxrc mtdparts=jz_sfc:256K(boot),352K(tag),5M(kernel),3M(rootfs),2560K(recovery),4512K(system),512k(config),16M@0(all)
bootcmd=sf probe;sf read 0x80600000 0x898000 0x280000; bootm 0x80600000
bootdelay=1
...
console=ttyS1,115200n8 mem=42M@0x0 rmem=22M@0x2a00000 root=/dev/ram0 rw rdinit=/linuxrc mtdparts=jz_sfc:256K(boot),352K(tag),5M(kernel),3M(rootfs),2560K(recovery),4512K(system),512k(config),16M@0(all)
- boot Android system....
...
'mtdids' - linux kernel mtd device id <-> u-boot device id mapping
CMDLconsole=ttyS1,115200n8 mem=41M@0x0 rmem=23M@0x2900000 root=/dev/ram0 rw rdinit=/linuxrc mtdparts=jz_sfc:256K(boot),352K(tag),2M(kernel),4512k(rootfs),256k(system),256k(config),512k(log),8M@0(all) lpj=6955008 quiet
CMDLconsole=ttyS1,115200n8 mem=41M@0x0 rmem=23M@0x2900000 root=/dev/ram0 rw rdinit=/linuxrc mtdparts=jz_sfc:256K(boot),352K(tag),2M(kernel),4512k(rootfs),256k(system),256k(config),512k(log),8M@0(all) lpj=6955008 quiet
...

It took some time and experimenting to align the given information with the firmware image but, ultimately, this snippet from the CMDLconsole was a perfect fit:

mtdparts=jz_sfc:256K(boot),352K(tag),2M(kernel),4512k(rootfs),256k(system),256k(config),512k(log),8M@0(all)
  • 256 KiB: boot loader (256*1024 bytes)

  • 352 KiB: "tag" (this region contains an "Encryption Key" and also the "CMDLconsole" line but I am not entirely sure where that partition belongs to - I assume it's part of the boot loader)

  • 2 MiB: kernel image (including 64 byte uImage header)

  • 4512 KiB: root filesystem (that's what we're after)

  • 256 KiB: "system"

    Remember the SquashFS we saw ranging from 0x700000 to 0x74000? That's exactly 256 KiB and the boot loader, kernel image, and rootfs fill exactly 0x700000 bytes. So I am pretty confident that "system" is the named partition for the SquashFS we extracted earlier.

  • 256 KiB + 512 KiB: config & log -> matches the JFFS2 that we extracted previously (0x740000 to 0x800000) and does indeed contain some configurations and logs (with the cleartext WPA2 passphrase again)

Let's take a look at the entropy graph one last time:

Offset
End
Size
Region

0x000000

0x098000

0x098000

boot loader (incl. "tag")

0x098000

0x298000

0x200000

compressed kernel

0x298000

0x700000

0x468000

compressed rootfs

0x700000

0x740000

0x040000

squashfs ("system")

0x740000

0x800000

0x0c0000

JFFS2 ("config" & "log")

We know that binwalk's ruleset could not match the start of the root filesystem with any known type. But the screenshot above indicates a cpio archive (looking at the ASCII column), so there's at least some hope that this is not an encrypted blob but rather something that can be extracted.

First, I used dd to manually extract the rootfs partition from the raw image:

dd if=XM25QH64C.bin skip=2719744 bs=1 count=4620288 of=rootfs.bin
# 0x298000 = 2719744
# 0x468000 = 4620288

Expectedly, file rootfs.bin fails to identify a useful filetype. Next up, I ran strings against it and the first result was "rootfs_camera.cpio" - we saw that string right at the beginning of the rootfs in the hexeditor.

Whenever I encounter such strings (including a name, version, manufacturer, library, whatever), it sometimes pays off to do a quick google search. Maybe someone has already reverse engineered this file format or maybe we find some documentation.

Alright, let's try that:

$ lzop -d rootfs.bin               
lzop: rootfs.bin: not a lzop file

At this point I knew I had to be close, so I opened the file in hexeditor once again and started looking for anything noticeable.

The first few bytes don't match any known magic bytes. More interestingly though, they look like a 32 bit value stored in little endian order. Now, this is something I have only ever seen in CTFs (Capture-the-Flag events), but just for fun I converted this number: 0x39C598 or 3786136 in decimal.

When I saw that value, I had a very good hunch of where this was going. Remember the size of the rootfs? That was 0x468000 or 4620288 in decimal. Remember the entropy graph and how it had this large blob of nothing at the end of the rootfs?

The actual size of the rootfs is exactly 0x39C598 bytes, the rest is padded with zeroes. So for some reason, the first four bytes of the rootfs were overwritten with its size.

So what happens when we simply restore the original bytes of an lzop file (89 4C 5A 4F)?

$ file rootfs.bin 
rootfs.bin: lzop compressed data - version 1.030, LZO1X-999, os: Unix

Finally.

$ mv rootfs.bin rootfs.cpio.lzo
$ lzop -d rootfs.cpio.lzo
lzop: rootfs.cpio.lzo: warning: ignoring trailing garbage in lzop file
$ sudo cpio -i < rootfs.cpio  
16826 blocks
$ ls
bin         dev  linuxrc  proc         rootfs.cpio.lzo  sys     usr
config      etc  media    root         run              system  var
config_bak  lib  mnt      rootfs.cpio  sbin             tmp     XM25QH64C.bin
$ cat etc/shadow     
root:$6$GupOzpfi$0rjXgFOJjceyAExJ20VCsdD1gShcNtIf6OPSHmKyCMiMy9DPFgZ31TEcKqYYd/UfNxZKhoFhFXXeSAl1XzPDw/:10933:0:99999:7:::

At last, the root filesystem is extracted and we could start with a static analysis of etc/init.d/rcS, for example.

Final Notes

As this post has already become longer than anticipated, I will keep this section short.

There is one final important lesson to note. Obviously, as you may have guessed from the layout of the board, the chip description of the T31, or even the log outputs - this flash is used to store the firmware for the T31. It does not contain a single piece of application logic running on the Hi3861.

So, mission failed?

While it's not exactly the thing I originally intended to do, I believe it turned out to be a valuable experience. Extracting the T31 firmware was a great practice and I hope that this post succeeds in saving other people some time or trouble with similar situations.

Lastly, dumping the firmware of the Hi3861 would probably include desoldering the board with the processor on it, reading the manual, identifying the right pins, and then connecting to the chip manually (for example via UART, or whatever this chip offers for programming).

Thanks for reading!

When I was looking for devices to tinker with I did not put a huge effort into researching the model or brand first, which in hindsight I definitely recommend doing. Resources such as or directly (featured by Tony Gambacorta in his previously mentioned video) may help you select a device by giving you an idea of what's inside the product.

Basically, the Federal Communications Commission (FCC) of the United States assigns IDs for devices that meet certain regulatory standards for wireless communication. Applications include pictures of the internals and are publicly accessible. You can search for the device FCC ID using Google and then submit the FCC ID .

Instead of doing this, I opted for a random chinese outdoor camera by .

Two labelled UART interfaces with intact traces sounds promising, so what do we do with them? If you are lucky enough to own one of these fancy Sensepeek sets (), then it's as as simple as placing the probes on the pads. One of my colleagues actually demonstrated this and sure enough got a boot log within seconds.

The "AZDelivery CP2102 USB to TTL (Transistor-Transistor Logic) converter" is based on the CP2102 chip (common alternatives include the CH340 or FT232) and serves as an intermediary between the USB port of your computer and the UART interface of the embedded device. If you are on a Windows machine, you may need some additional drivers for the corresponding chipset. For example, in this case you would need the .

The CH341A Flash Programmer plugs into your computer as USB device and allows you to easily interface with different sorts of common memory chips. This way, we may be able to read or even write memory contents. Most offers include some adapters and a testclip for different sizes of chips, which I can definitely recommend. The CH341A works fine on Windows () and Linux (flashrom).

Failing to do step 1, however, may have you running around in circles for hours. Using the Windows utility AsProgrammer.exe (there are several locations where you can download it from, choose your poison, here's a ) I successfully read some flash contents:

Alternatively, we can use which also offers an entropy graph - amongst many other binary analysis things.

Grepping for boot turned out to yield the most interesting results such as the bootargs and bootcmd lines. You can read more about these U-Boot variables .

In this case, I found several s describing extraction of rootfs_camera.cpio contents.

No wonder that didn't work - binwalk has a rule for LZOP compressed files and couldn't recognize it from the beginning. So something is different from the guide.

https://fccid.io
https://apps.fcc.gov/oetcf/eas/reports/GenericSearch.cfm
here
Virtavo
https://sensepeek.com/
CP2102 drivers
AsProgrammer
GitHub repository
Detect-It-Easy (DIE)
here
blog post
here
🛠️
"Intro to Hardware Reversing"
"Dumping Firmware"
Cracked open chassis of an outdoor surveillance camera
Virtavo top board view with two processors
UART interface for the Hi3861 chip
Virtavo bottom board view with the mounted camera lense
Connecting one UART interface to the computer via the CP2012
XM25QH64C hooked up to a computer via the CH341A programmer
Placing the adapter into the CH341A socket
Output of AsProgrammer after dumping flash contents
Credentials stored in ZRT_Profiles.ini
Entropy analysis of the firmware image with binwalk
Entropy analysis with DIE
Merging the information from entropy analysis, strings and hexeditor to identify each firmware partition
One of the blog posts describing how to create and extract a rootfs using cpio and lzop
Beginning of the compressed rootfs in hexeditor
End of the rootfs at 0x39C598
Page cover image