Rooting the Ezviz C1C-B

January 20, 2026

Ezviz recently launched a new public bug bounty program on yeswehack. Having never hacked on yeswehack before, and in need of a new hardware target anyway, I thought it might be a fun new opportunity. What follows is my first step whenever researching a hardware device: dumping the firmware and getting a root shell.

Discovering UART

The scope for Ezviz's bug bounty program includes any camera listed on ezviz's website. Naturally, I bought two of the cheapest ezviz cameras - the C1C-B. Once they arrived, I got to work disassembling the device, and identifying potential debugging interfaces.

Three pads in particular looked like UART. Using a multimeter I probed the pins during the boot sequence and confirmed that my suppositions were likely correct. One pin was ground, another a constant 3.3v (RX or receive) and the last fluctuated (TX or transmit).

Next, I soldered wires to the pads and connected them to my USB-TTL adapter to read the UART output and potentially get a shell. The C1C-B uses a baud rate of 115200. The output of the boot sequence is as follows:

U-Boot 2010.06-svn104177 (Dec 17 2020 - 19:59:01)
product name:C1C_2020_1080P
spic use FH_SPIC_DUAL_READ && 3bytes mode
Interface:  MMC
  Device 0: Vendor: Man 035344 Snr 452b87e3 Rev: 8.0 Prod: SC200
            Type: Removable Hard Disk
            Capacity: 187746.0 MB = 183.3 GB (384503808 x 512)
Partition 1: Filesystem: FAT32 "           "
reading ezviz.dav
MMC read file ezviz.dav failed,ret=-1!
load_update_file fail
Net:   USB0:   Core Release: 4.00a
scanning bus 0 for devices... 1 USB Device(s) found
       scanning usb for ethernet devices... 0 Ethernet Device(s) found
set to RMII
DM9051-MAC, FH EMAC
Hit Ctrl+u to stop autoboot: 0
load sys to 0xa1000000 ...
spic use FH_SPIC_DUAL_READ && 3bytes mode
sys type 0x00020001
spic use FH_SPIC_DUAL_READ && 3bytes mode
Verify [app0] img successfully...
spic use FH_SPIC_DUAL_READ && 3bytes mode
Verify [rootfs0] img successfully...
spic use FH_SPIC_DUAL_READ && 3bytes mode
Verify kernel successfully...
Done!
## Booting kernel from Legacy Image at a1000000 ...
   Image Name:   Linux-3.0.8
   Image Type:   ARM Linux Kernel Image (uncompressed)
   Data Size:    1382924 Bytes = 1.3 MiB
   Load Address: a0008000
   Entry Point:  a0008000
   Verifying Checksum ... OK
   Loading Kernel Image ... OK
OK
prepare atags

Starting kernel ...

Uncompressing Linux... done, booting the kernel.
starting pid 500, tty '': '/etc/app'
ifconfig: ioctl 0x8913 failed: No such device
ez_spp is lock

After this, the output just hangs and there is no way to send or receive data. I tried interrupting the boot sequence after u-boot had loaded, both with various key inputs and by shorting the CS pin of the SPI flash but was unable to drop into a shell. However, as the boot output suggested, I was able to interrupt u-boot by issuing Ctrl-u early in the boot sequence. Indeed, doing so drops us into a limit u-boot shell:

HKVS #

Issuing help listed a few interesting commands, including the ability to change u-boot environment variables (setenv and saveenv), list the partition table (ezmtdparts), modify memory (mm) and even a few devoted the updating different partitions on the flash. I tried changing the bootcmds and bootargs u-boot environment variables to run /bin/sh and drop a shell but, as is often the case, the environment variables were not actually modifiable.

Firmware dump and analysis

I played around in the u-boot shell for a bit to see if there was any straight forward way to dump the SPI flash, but gave up pretty quickly. The flash chip on the C1C-B is a typical SOP8 SPI flash and quite easy to remove and reattach. Rather than play around with u-boot I took the path of least resistance and simply removed the chip, dumped the firmware with my ch341a, and soldered it back on [1]. The output of the ezmtdparts u-boot command was helpful in determining the structure of the filesystem:

mtd     offset          size            name                    upname
mtd0:   0x00000000      0x00040000      bld                     u-boot_y14.bin
mtd1:   0x00040000      0x00010000      env                     env.bin
mtd2:   0x00050000      0x00010000      enc                     sec.bin
mtd3:   0x00060000      0x00180000      sys0                    uImage
mtd4:   0x001e0000      0x00160000      rootfs0                 rootfs.img
mtd5:   0x00340000      0x00310000      app0                    app.img
mtd6:   0x00650000      0x000b0000      cfg                     cfg.bin
mtd7:   0x00700000      0x00100000      recov                   rtthread.bin

I wrote a couple python scripts to split/rebuild the firmware dump to make modifications easier. The filesystem itself is split between the app.img and rootfs.img partitions.

Shelling

Now that I had the firmware, the next step is to get a root shell so that I can use a debugger/other dynamic analysis tools. Editing /etc/inittab seemed like a good choice. By default the file looks like this:

::sysinit:/etc/app
#::respawn:/sbin/inetd -f -e /etc/inetd.conf
#::wait:-/bin/sh
::respawn:-/usr/sbin/ez_login
::restart:/sbin/init

Simply uncommenting the #::wait:-/bin/sh line should be enough to get a UART shell. Things are, however, never so simple, and indeed ezviz has introduced a number of obstacles to make getting a root shell more difficult (though nothing actually robust like secure boot).

First, the u-boot output references filesystem verification in the boot logs: Verify [rootfs0] img successfully.... Unsurprisingly, the boot process only succeeds if the u-boot successfully verifies that the rootfs partition has not been modified. I don't really know how the verification happens, because it was easy enough to patch out. Using the tool rizin, which guesses a binary's base address using cross references to strings, I determined the base address for the u-boot_y14.bin binary to be at0xa07f3000. I loaded the binary in ghidra at this address and searched for references to the string "Verify [". The verification logic looks like this:

rc = verify_image(uVar1, iVar2, 0x100000);
if (rc != 0) {
    /* succeed */
} else {
    /* fail */
}

In the assembly, the if (rc != 0) line is a bne (branch not equal) instruction. By patching it to b (unconditional branch) instruction, the code will always run the succeed code block - regardless what verify_image returns.

After writing the patched u-boot and rootfs, the u-boot verification will always succeed, but we still don't have a shell. Indeed, there is still no output after the string "ez_spp is lock". I searched the filesystem for references to this string, and, to summarize, during the boot sequence the shell script initrun.sh calls /usr/sbin/ez_lock which locks the UART interface.

I commented out the call to ez_lock and, once again, removed the SPI flash, flashed the modified firmware, reattached the flash chip and rebooted the device.This time I got past the u-boot verification and had full UART output but the device was stuck in a boot loop. As it turned out, I had failed yet another firmware integrity check:

...
CKFL [initrun.sh]...
file name initrun.sh !
check sha256 not match!
...

Once again, I looked for references to the string "check sha256" and found it in libeztools.so. The verification code compares the sha256 hash of the initrun.sh file to an expected hash with memcmp. All I had to do was change a beq instruction to b and reflash the filesystem. This time, all the verification checks passed, UART was fully enabled and - finally - I had a shell!

Postscript: analyzing ezapp

Most of the device logic and interesting attack surface is implemented in the ezapp binary. As a final annoyance and attempt at security through obscurity, this binary is encrypted on the filesystem. During the boot sequence, the binary is decrypted, run, and then deleted. While there is probably many ways to extract it, I modified the firmware once again to explicitly call to /bin/sh after the binary is decrypted, but before it runs/is deleted. Then I was able to extract the binary using the UART shell.

At this point I now have full access to the firmware as well as functioning debugging and can finally begin actual vulnerability research on the device. Hopefully this post is helpful for other bug hunters looking to get started with ezviz!

  1. Using an SOP8 SPI clip its often possible to dump and even write firmware to the flash without removing the chip. However, the clip can power the board through the SPI connection which can cause interference during firmware reads/writes. To be safe I always just remove the chip