Reverse Engineering a Pet Camera

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.

Component Image

STM32G030

STMicroelectronics

Microcontroller (MCU)

Datasheet
Component Image

GD25Q256D

GigaDevice

NOR Serial Flash

Datasheet
Component Image

T31

Ingenic

Smart Video Application Processor

Datasheet
Component Image

RTL8189FTV

Realtek

Network Interface Controller

Datasheet
Component Image

8733A

Awinic

Audio Amplifier

Datasheet

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.

Pinhole Connectors
5 Through-holes Pin Out 3 Through-holes Pin Out
  • VDD
  • SWCLK
  • SWDIO
  • NRST
  • GND
  • Unknown
  • Unknown
  • GND

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.

Downloads
Downloads

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.

Logic 2 - Saleae

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.

Baud rate Estimation Plugin

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).

115200 Baud rate

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.

MobaXterm serial baud rate configuration

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:

Flash Pin-out

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.

PCBite Probes connected to NOR Flash

Note: The SPI interface on the Pi first needed to be enabled

Enable SPI Interface on the Raspberry Pi - Raspberry Pi Spy
The Raspberry Pi SPI (Serial Peripheral Interface) bus can be enabled on Pins 19,21,23,24 & 26. It is a synchronous serial data link standard and is used for short distance single master communication between devices. This post shows how you can easily enable the SPI interface using a number of different methods.

Dumping NOR Flash

sudo flashrom -p linux_spi:dev=/dev/spidev0.0,spispeed=8000 -c "GD25Q256D/GD25Q256E" -r petcam_dump1.bin

Command to dump firmware over SPI

Dumping SPI Flash Memory of Embedded Devices | NSIDE ATTACK LOGIC GmbH
While auditing the security of embedded devices we often face situations where the firmware of the system under test is either not publicly available or the

Binwalk

/etc/shadow

root:vTY██████████:18591:0:99999:7:::
example_hashes [hashcat wiki]

Hashcat Hash Types List - 1500, DES (Unix)

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:

strings petcam_dump1.bin | grep "bootargs="

Command to find bootargs within firmware dump

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

  1. boot (256k)
  2. kernel (2048k)
  3. rootfs (6752k)
  4. app (6752k)
  5. cfg (512k)
  6. para (64k)
  7. cback (832k)
  8. kback (2048k)
  9. rback (6752k)
  10. 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:

Boot arg partitions separated into individual files

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":

import passlib.hash
print(passlib.hash.des_crypt.hash('password', salt='vT'))

Python script to generate new DES hash

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:

import os.path

original_length = os.path.getsize(r"C:\Users\User\Documents\Petcam\jz_sfc\rootfs.bin")
modified_length = os.path.getsize(r"C:\Users\User\Documents\Petcam\jz_sfc\rootfs_modified.bin")

with open(r"C:\Users\User\Documents\Petcam\jz_sfc\rootfs_modified.bin", 'ab') as modified:
    # Append the difference in bytes to the end of the file
    modified.write(b'\x00' * (original_length - modified_length))

Python code to pad the end of the SquashFS file system to fill partition size

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

Successful login using modified password
Filesystem readable via UART shell
Running processes

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
{{! Initialize Tocbot after you load the script }}