Reverse Engineering a Pet Camera
Introduction
I wanted to dive into the world of "hardware hacking" with a hands-on approach, steering clear of predefined educational kits or well-documented beginner targets. My aim was to repurpose gadgets I already owned and document my journey for personal growth and future reference.
My first target was a non-functional pet camera and treat dispenser gathering dust in my closet. This project was my first introduction into hardware hacking, with my sole aim being to learn, fully expecting the device might not survive my experiments. Throughout this journey, I gathered all necessary equipment and spent considerable time on research and watching YouTube tutorials.
The Device Under Test (DUT)
The pet camera features capabilities for remote viewing of your pets, treat dispensing, and audio communication through a built-in microphone. It housed several motors for treat dispensing, multiple photoelectric "slot sensors" monitoring the rotation of the treat intake, and the circuit board itself.
After removing all external peripherals, I was left with the main board and the camera, connected by a Flex Flat Cable (FFC). The next step involved identifying each chip on the board.
Hardware Reconnaissance
My primary goal for chip identification was to identify the main SoC and it's data storage, hoping to eventually get a look at the application code that controls the camera, treat dispenser, and connection to the server.
At the time I did not have a microscope so I just took pictures with my phone. I took a picture of each chip that had text on it, googled the text, and found the component online with their datasheets. I got lucky in that all of these components were fairly easy to identify and their datasheets public. Based on the datasheet I could guess as to the function and purpose of each chip.
Finding Debug Interfaces
From the datasheets I could see pin-outs for UART on the main SoC and MCU as well as some JTAG. I could of spent time finding the pins on the chips then attempting to trace them out to a test point but these through-hole connectors stood out as a pretty good guess.
Debug Through-hole Connectors
The right three pins only have ground (GND) labeled while the left five pins are fully labeled. Based on the labeling and the number of through-holes, my assumption was the 5 holes were SWD JTAG while the 3 holes were UART.
By just briefly looking at the wiring coming out from the through-holes it looked as though the JTAG was going to the STM32 (MCU) while the UART was going to the Ingenic T31 (SoC). These were just initial guesses which I intended to confirm using my Saleae Logic Analyzer.
5 Through-holes | Pin Out | 3 Through-holes | Pin Out |
---|---|---|---|
|
|
UART Connection
To determine if the pins were actually UART, find which pin is TX and which is RX, and to find the correct baud rate, the Saleae logic analyzer was used. Connecting to all three pins, powering on the board, and launching Logic 2.
Logic 2
Logic 2 is the software created by Saleae to analyze the signals connected to the logic analyzer's channels. The software can be downloaded from the Saleae website.
Inside of the software, once the logic analyzer is detected, you will have the option to enable specific channels. The channel numbers are printed on the bottom of the logic analyzer, but it's just left to right starting from zero. For this, I enabled channels 0 and 1. GND was just connected to one of the ground pins on the logic analyzer (bottom row) and does not require a channel.
With the channels selected, clicking on the blue play button will begin to capture traffic.
As these through-hole connectors appeared to be for debugging purposes only, I did not expect to see any UART traffic on the TX side and only RX traffic from the SoC.
Determining Baud Rate
After capturing some traffic, I wanted to determine the communication baud rate so I could start decoding some actual data. I used a Logic extension called "Baud rate estimate" which is installable through the Logic extensions tab. With this extension I can just select a chunk of the communication and it will estimate the baud rate.
The baud rate in this case was estimated at 115.607 kHz, using this baud rate is probably close enough but since it's close enough to a more standard baud rate I used that instead (115200).
Now that I know the baud rate, I can create a new "Async Serial" analyzer within logic, configure the bitrate and the input channel, and begin to see data, in this case human readable data.
At this point I decided to switch over to a USB to UART adapter based on the FTDI chip or CP210. I just found a really cheap one on Amazon. Using MobaXTerm (or Putty) I configured the COM port to match the adapter and the baud rate to the pre-discovered 115200.
I was now seeing boot logs, applications logs, and a Ingenic login shell. I tried to halt the U-Boot in attempt to get to a U-Boot shell but had no success. My new goal was to determine the correct login credentials so that I could gain access to the shell.
SPI Connection
I figured the best bet was to dump the firmware from the flash storage, locate the user passwords (/etc/shadow), and login to the UART Shell. The flash storage communicates over a SPI connection. Based on the documentation, the following pinout should include the SPI pins needed for communication:
In order to communicate with the device, I would use a Raspberry Pi 5. The Pi has support for SPI communication with the following pinout.
I decided that rather than try to remove the Flash chip, I would connect to SPI directly. I used these PC Bite probe arms to create connections to each of the Flash's pins. I then wired them to their corresponding pins on the Pi.
Note: The SPI interface on the Pi first needed to be enabled
Dumping NOR Flash
Binwalk
/etc/shadow
root:vTY██████████:18591:0:99999:7:::
Firmware Patching
Once I had extracted the device's firmware and identified the location of the root password hash, the next step was to modify the password to a known hash.
Challenges: The main challenge with modifying this firmware dump is that the hash data I want to modify is part of a compressed Squashfs file system. From the binwalk output, we also know that there is not only one file system contained in this flash dump but several.
Solution: First step was to identify the correct filesystem that needed to be modified and carve it out from the flash dump. I did this by using the U-Boot arguments which define the "root" filesystem as well as the offsets of each partition.
Locating U-Boot arguments
The boot arguments can be found inside of /proc/cmdline
from the Linux shell or they are found within the boot partition of the flash dump. Rather than trying to carve out the whole boot partition I just located the boot arguments by running the following:
The boot arguments looked like this, clearly defining mtdparts as the offsets of all of the partitions. I noticed the 6752k(rootfs) as the filesystem that needed to be altered.
bootargs=console=ttyS1,115200n8 mem=80M@0x0 rmem=48M@0x5000000 init=/linuxrc rootfstype=squashfs root=/dev/mtdblock2 rw mtdparts=jz_sfc:256k(boot),2048k(kernel),6752k(rootfs),6752k(app),512k(cfg),64k(para),832k(cback),2048k(kback),6752k(rback),6752k(aback)
Partitions
- boot (256k)
- kernel (2048k)
- rootfs (6752k)
- app (6752k)
- cfg (512k)
- para (64k)
- cback (832k)
- kback (2048k)
- rback (6752k)
- aback (6752k)
Based on the partitions listed above, I carved out each partition just based on their sizes in order. That resulted in the following data:
The defined partitions made up the entirety of the data extracted from the flash (32 MB).
The next step was to decompress ("unsquash") the "rootfs" filesystem. For this I used the Linux utility "squashfs-tools" and the following command:
$ unsquashfs rootfs.bin
Similar to binwalk, this extracted the file system. From there I located the /etc/shadow file and replaced the hash. In order to generate a known hash, I decided to keep the algorithm used consistent and used the following Python script to generate a des_crypt hash of the string "password":
Once the hash was modified, I needed to re-compress ("mksquashfs") the filesystem back into the original format. I did that by using the following command matching the compression algorithm used (xz):
$ mksquashfs squashfs-root rootfs_modified.bin -comp xz -mkfs-time "Tue Sep 19 06:06:21 2023"
Lastly, I wanted to ensure that the size of the partition matched what was shown in the boot arguments. This meant padding the end of the partitions until the size matched. I used the following Python script to match the size of the modified file system with the original:
Once I had a modified "rootfs" SquashFS file system, I needed to then combine all the partitions back together so they could be flashed back onto the device. I simply used the cat
command to concatenate the partitions:
cat boot.bin kernel.bin rootfs_modified.bin app.bin cfg.bin para.bin cback.bin kback.bin rback.bin aback.bin > patched_firmware.bin
Root Access Achieved
Equipment
Item | URL |
---|---|
USB to UART | https://www.amazon.com/HiLetgo-CP2102-Converter-Adapter-Downloader/dp/B00LODGRV8 |
Saleae Logic Analyzer | https://www.saleae.com/products/saleae-logic-8 |
PCBite Kit | https://www.saleae.com/products/pcbite-kit-with-4x-sq10-probes-and-test-wires |
Raspberry Pi | https://www.adafruit.com/product/5813 |