Dumping a Bricked CP Plus E31A Camera and Pulling Its Firmware Apart
A practical walkthrough of dumping my own bricked CP Plus E31A IP camera with a CH341A, extracting its SquashFS and JFFS2 partitions, and mapping the boot flow, Tuya storage, and sensitive config areas.
- Device
- CP Plus E31A / EzyKam+ Wi-Fi IP camera
- Flash
- Winbond 8 MB SPI NOR
- Programmer
- CH341A with SOIC clip
- Firmware
- MIPS32 little-endian Linux, uClibc
- Tuya SDK
- TuyaOS 2.3.2 / SDK 5.3.37
- Status
- Dump verified, filesystems mapped, secrets redacted
This started with a camera that was already gone.
My CP Plus E31A, also sold around the EzyKam+ ecosystem, was permanently bricked. It was not booting, it was not joining Wi-Fi, and there was no normal web UI or app path left to recover it. At that point the interesting question changed from “can I fix it?” to “what is actually inside this thing?”
So I took the direct route: clip onto the SPI flash, dump the firmware, and pull the image apart locally.
This post is the cleaned-up version of that process. It is not a remote exploit story, and it is not a dump of someone else’s device. It is a hardware-level look at my own bricked camera, with private credentials and device values redacted.
Getting a trustworthy flash dump
The camera uses an 8 MB Winbond SPI NOR flash chip. I used a CH341A USB SPI programmer and a SOIC clip to read the chip in-circuit.
The basic setup was:
- CP Plus E31A camera board;
- Winbond 8 MB SPI NOR flash;
- CH341A USB SPI programmer;
- SOIC clip on the flash chip;
- Linux host running
flashrom.
I did not want to build the whole analysis on a flaky clip read, so I read the chip more than once and compared the dumps.
flashrom -p ch341a_spi -r backup1.bin
flashrom -p ch341a_spi -r backup2.bin
sha256sum backup1.bin backup2.bin
Both images were exactly 8 MB and produced the same SHA-256:
142c6f614b63aa8bc5f0b1731c5b1aff2a91511628ba0044fc01fc97b2555655
That matching hash mattered more than the tool output. A successful-looking
flashrom run can still be a bad read if the clip shifts or the board is being
powered in a messy way. Two matching full reads gave me a stable starting point.
Pulling apart the image
With a clean dump in hand, I used binwalk to inspect and extract the firmware:
binwalk backup1.bin
binwalk -e backup1.bin
The image separated into three areas that explained most of the device layout:
| Area | Offset | Type | Runtime mount |
|---|---|---|---|
| Root filesystem | 0x200000 |
SquashFS | / |
| Application partition | 0x2f0000 |
SquashFS | /app |
| Writable configuration | 0x7a0000 |
JFFS2 | /conf |
0x000000 ── earlier firmware regions
0x200000 ── SquashFS ── /
0x2f0000 ── SquashFS ── /app
0x7a0000 ── JFFS2 ── /conf
├── tuya.json
├── tuya_user.db
└── tuya_enckey.db
The first SquashFS is the embedded Linux root filesystem. The second SquashFS is the camera application partition. The JFFS2 area is where the device keeps writable configuration.
In short: the extracted root filesystem was SquashFS, the extracted application filesystem was SquashFS, and the writable configuration area was JFFS2.
That split is common in small embedded devices: keep the OS and application read-only, then put user/device state in a writable flash partition.
The root filesystem was small and familiar
The root filesystem looked like a compact BusyBox-based Linux system:
/
├── bin
├── etc
├── lib
├── sbin
├── usr
└── var
The first files I checked were the usual suspects:
/etc/inittab
/etc/init.d/rcS
/etc/passwd
/etc/shadow
/etc/passwd showed a root account:
root:x:0:0:root:/:/bin/sh
/etc/shadow contained a root password hash. I am not publishing the hash, but
its presence is important because the firmware also starts a serial login prompt:
console::respawn:/sbin/getty -L console 115200 vt100
That tells us the camera is configured to bring up a login prompt on the UART console. It does not mean there is an unauthenticated shell. It does mean that the UART path is part of the device’s recovery/debug surface.
The boot chain
The boot path is simple once the scripts are laid out:
- BusyBox init starts.
/etc/inittabruns/etc/init.d/rcS.rcSsetsPATHandLD_LIBRARY_PATH.rcSbrings loopback up and reads the hardware clock.rcSmounts/dev/mtdblock3as/app.rcSmounts/dev/mtdblock4as/conf.rcSstarts/app/init/app_init.sh.app_init.shruns/app/jz/load.sh.- Drivers and platform setup scripts run.
/app/bin/daemonis copied to/tmp/daemon./tmp/daemon @ipcstarts the main application path.
The key lines from rcS are:
mount -t squashfs /dev/mtdblock3 /app
mount -t jffs2 -o sync /dev/mtdblock4 /conf
/app/init/app_init.sh &
And the important part of app_init.sh:
cp /app/bin/daemon /tmp/
chmod +x /tmp/daemon
insmod /app/drivers/gv-gpio.ko
/tmp/daemon @ipc
There is also a commented telnet line in the startup scripts:
# telnetd &
That is just an observation. I did not find the boot scripts enabling telnet by default.
The application partition
The second SquashFS becomes /app at runtime. This is where the camera-specific
pieces live:
/app
├── bin
├── drivers
├── init
├── jz
├── lib
├── music
├── osd
├── sound
└── web
The two binaries that matter most are:
/app/bin/daemon
/app/bin/ipc
daemon is launched first. It imports process-control and watchdog-flavored
functions like fork, execvp, waitpid, kill, signal, setsid,
gv_swdg_check, and gv_swdg_rmpid. That makes it look like a supervisor for
the main camera process.
ipc is the big one. It is the main camera application and contains references
to Tuya startup, MQTT, ONVIF, RTSP, P2P, WebRTC, BLE, Wi-Fi provisioning, OTA,
cloud activation, and device identity handling.
Both are stripped MIPS32 little-endian ELF binaries using uClibc:
ipc: ELF 32-bit LSB executable, MIPS, MIPS32 rel2, dynamically linked, stripped
daemon: ELF 32-bit LSB executable, MIPS, MIPS32 rel2, dynamically linked, stripped
The app libraries also tell a lot about the design:
libgv_core.so
libgv_ipc.so
libgv_rtsp.so
libgv_onvif_rtsp.so
libgv_device_id_decryption.so
That last one, libgv_device_id_decryption.so, immediately went onto the list
for deeper analysis because the name points toward device identity handling
somewhere in the stack.
Tuya SDK fingerprints
Strings in ipc made the Tuya side visible:
Firmware version: 2.132.982
Tuya SDK version: 5.3.37
The longer SDK string was:
TuyaOS2.3.2_ty_ipc_wr_wl_linux_sdk_5.3.37-beta.16_mips-gcc472-uclibc216-64bit-r233_wifi_wire_ble_no_qrcode_0.0.1-beta.3
Some useful function anchors from the stripped binary:
0x004d2720 tuya_ipc_init_sdk
0x004d29dc tuya_ipc_start_sdk_generic
0x004d2bf4 tuya_ipc_start_sdk
0x004d8a48 tuya_ipc_mqtt_register_cb_init
0x0055e788 create_mqtt_hand
0x0055cbe4 mqtt_client_start
0x0059c2d4 mqtt_connect
0x00439ed8 gv_onvif_handler_init
0x0043a088 gv_onvif_handler_start
0x004eaa14 tuya_ipc_get_p2p_auth
0x004ea218 tuya_ipc_p2p_get_auth_param
0x004ea65c tuya_ipc_p2p_get_pw
0x004ea8f4 tuya_ipc_p2p_get_lk
0x0056ac9c GetEncryptorInstance
Those names were enough to map the broad architecture before fully decompiling
everything: daemon starts/supervises, ipc runs the camera, and Tuya SDK code
handles cloud, MQTT, P2P, and device configuration.
The writable configuration partition
The JFFS2 partition mounted as /conf was the most sensitive part of the dump.
It contained:
/conf/factory.json
/conf/gv.json
/conf/static_ip.conf
/conf/tuya.json
/conf/tuya/tuya_user.db
/conf/tuya/tuya_user.db_bak
/conf/tuya/tuya_enckey.db
This area included device configuration and private values. I recovered my own old Wi-Fi configuration, ONVIF configuration, MAC addresses, static network settings, and Tuya-related data. Those values are not included here.
The categories looked like this:
previous Wi-Fi credentials: ********
ONVIF password: ********
root password hash: ********
MAC addresses: ********
static network config: ********
Tuya cloud/P2P/BLE records: ********
local key-related data: ********
No plaintext email addresses showed up in the recovered files.
The Tuya database was not a normal database
tuya_user.db was not SQLite, JSON, or XML. It was a custom binary key-value
store.
The same directory also contained tuya_user.db_bak, the same size as the main
database file. I treated it as a backup copy, not a separate format.
The file started with recognizable little-endian headers:
file_header_u32_le:
0x10bc562d, 0x2, 0x1f, 0x701c
db_header_u32_le:
0x55aa55aa, 0x200, 0x13, 0x1
The directory contained entries such as:
gw_bi
gw_di
cloud_enc_key
p2p_auth_info
p2p_pwd
ble_network_key
ble_beaconkey
The companion file tuya_enckey.db was exactly 16 bytes. I am not publishing
the value, but its size and location make it interesting for the storage
encryption path.
tuya_enckey.db: ********************************
At this stage I am comfortable saying the database stores sensitive Tuya configuration. I am not claiming a parser vulnerability or broken cryptography from the file names alone.
Following the config read path
The most useful early reverse-engineering anchor was GetEncryptorInstance().
Tracing around it showed this path:
GetEncryptorInstance()
-> iot_wd_common_read()
-> wd_common_read()
-> kvs_read()
-> simplekv_read()
-> internal decode/read helper
GetEncryptorInstance() reads cloud_enc_key, checks that it has at least 16
bytes, copies 16 bytes into an internal buffer, and wires up AES-CBC-related
functions. It also calls MD5 helper routines.
That was the point where cloud_enc_key became more than a string hit. It was
part of a traced code path in the application.
What I can say from this dump
The strongest finding from this pass is simple:
physical access to the SPI flash can recover sensitive local configuration and Tuya/P2P/BLE-related records from this camera.
The evidence I used was:
/conf/tuya.jsoncontains ONVIF password configuration;/etc/shadowcontains a root password hash;/conf/tuya/tuya_user.dbcontains cloud, P2P, BLE, and identity-related records;/conf/tuya/tuya_enckey.dbis a 16-byte file associated with the device’s local Tuya storage and warrants further analysis;ipccontains the Tuya SDK code paths that read and use these values.
This is a physical-access finding, not a remote exploitation claim. It matters for resale, repair, disposal, and anyone doing hardware-level threat modeling.
Ending where the evidence ends
The dump gave me a solid map of the camera:
- how it boots;
- where
/appand/confcome from; - how
daemonandipcfit together; - which Tuya SDK version is present;
- where the writable configuration lives;
- which local files contain sensitive device state.
The most valuable part was not one magic string or one scary-looking filename. It was the chain: physical flash dump, verified hash, extracted filesystems, startup scripts, binaries, config database, and code paths that read those values.
That is the kind of firmware work I trust. Slow, a little messy, and backed by evidence instead of vibes.