Matthew Jones

Ezviz DVR Rooting

I achieved root on my Chinese ezviz X5-8 DVR. I did this for a couple of reasons, the main being that the DVR menus were entirely in Chinese with no option to change it. This post provides a brief overview of the steps I took to get root access, but it’s been a while so some photos and details might be lacking.

Serial Console

The first logical step was to try and gain serial access. From what I’ve read online, many devices seem to provide the root shell via UART, giving relatively easy access. To do this, I took apart the casing of the DVR, revealing the main board and a JST connector of some sort.

Connector has a wire coming out of it

This connector implemented the UART protocol, with the middle pins being RX/TX and one of the outer pins being ground. This seems to be a common pinout, but to do a rudimentary check, I took a multimeter to each pin whilst the DVR was on and measured the voltage (TX oscillates as data is sent). I also checked for continuity to ground. Once I’d worked this out, it was simply a matter of connecting it to my USB TTL adaptor using jumpers and accessing the console via screen.

The boot logs weren’t too helpful, and typing commands in this Linux environment greeted me with password error, pls try again. I tried pressing ctrl-u on boot to try and access U-Boot’s CLI, however only managed to get an update screen. I was blocked on this for a while, but after some research on forums I found that on Hikvision (parent company of ezviz) devices pressing b at this screen brings up the CLI. This worked.

On some devices, bypassing the password protection in the Linux environment is a simple matter of setting the init process. I tried this by entering setenv bootargs mem=180M console=ttyS1,115200 init=/bin/sh, saveenv, and reset, but it didn’t work. Running printenv in the U-Boot environment gave:

bootdelay=1
baudrate=115200
gatewayip=192.0.0.1
netmask=255.255.255.0
bootfile=uImage
bootargs_end=255.255.255.0:Hik-eth:eth0:none
bootcmd=tftp 0x80400000 $(bootfile);bootm 0x80400000;
sec=tftp 0x80400000 uImage_sec;tftp 0x80800000 ramdisk.gz;bootm 0x80400000 0x80800000;
default=fsload 0x80400000 uImage;fsload 0x80800000 ramdisk.gz;bootm 0x80400000 0x80800000;
phyaddr0=9
rgmii_enable=n
serverip=192.0.0.128
update_source=net
ipaddr=192.168.0.61
stdin=serial
stdout=serial
stderr=serial
verify=y
ethaddr=64:db:8b:c5:e1:3a
bootargs=mem=180M console=ttyS1,115200 init=/bin/sh
ver=U-Boot 2010.06-svn (May 06 2017 - 15:30:45)

Environment size: 624/4092 bytes

I was again stuck - I tried various things that seem to work with other Hikvision devices, but ezviz devices seem to be securer. On some hikvision devices telnet is just left open, or a simple bootarg change gives root.

Dumping Flash

This step was probably made much harder than it should’ve been due to my lack of experience. Skip to the last paragraph for what I think is the more streamlined approach.

First, I identified that the DVR has 16MB of flash from the chip on the board (below the SATA port in the image below). Specifically, it’s a 25q128jvsq.

Flash chip

Based off this, I read the flash with:

sf probe 0
sf read 0x82000000 0x0 0x1000000
#setenv for the ip stuff
tftp 0x82000000 flash.bin 0x1000000

This initialises the flash, reads the start of flash which is at location 0x0 with the length 0x1000000 to RAM at 0x82000000. This is because the flash and RAM share the same address space, but I wasn’t sure where RAM starts, so chose something that seemed far enough away. I then set the IP I wanted tftp to dump to, then ran tftp to dump from what I’d copied into RAM into flash.bin on the TFTP server.

Unfortunately, this didn’t work as tftp would crash with the message TFTP error: 'Expected block 10000, got DATA for block 999' - I’m not sure if I ever worked out why but it might have been due to a memory-related constraint sending that much at once. Instead, I ran tftp 0x82000000 flash_part1.bin 0x800000 and tftp 0x82800000 flash_part2.bin 0x800000 on the device then cat flash_part1.bin flash_part2.bin > flash_parts.bin on the TFTP server. This simply splits the data into two parts to send, then joins them back up on the server end.

I also tried attaching an IC test clip and reading via SPI with a raspberry pi using flashrom. To do this, I looked at the datasheet for the flash and wired it up to the Pi’s GPIO accordingly. This worked but was very slow. The results were the same as the above method and had the added risk of frying something by running the flash chip while it’s still wired to the rest of the system. I think the best practice is to desolder the chip first.

A simple auto-extract with binwalk of the dumped flash revealed a couple of files, the most notable being ramdisk.gz.

Although I did all this, if I had known what the ramdisk was (and that I wanted it) from the start it could’ve been much easier. In the above printenv, you can see default=fsload 0x80400000 uImage;fsload 0x80800000 ramdisk.gz;bootm 0x80400000 0x80800000;. This is loading the binaries from the JFFS2 flash filesystem into that memory location. I’m not completely sure, as I couldn’t find that much info on the syntax of the fsload command, but bootm would imply that I’m correct (it loads the kernel and the ramdisk from those locations). Based on this, while testing, I ran fsload 0x80400000 ramdisk.gz and tftp 0x80800000 ramdisk.gz to dump the ramdisk directly onto my TFTP server.

Ramdisk

The ramdisk is the filesystem that is loaded by the kernel on boot. If I could extract it, edit it, then repackage it and load it onto the DVR, I could run anything I wanted.

Running binwalk on ramdisk.gz gave:

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             uImage header, header size: 64 bytes, header CRC: 0x787850F7, created: 2017-08-16 08:58:58, image size: 745084 bytes, Data Address: 0x0, Entry Point: 0x0, data CRC: 0x7217C4D7, OS: Linux, CPU: ARM, image type: RAMDisk Image, compression type: lzma, image name: "ramdisk_1.1_EZVIZ"
64            0x40            LZMA compressed data, properties: 0x5D, dictionary size: 8388608 bytes, uncompressed size: 4194304 bytes
722714        0xB071A         MySQL ISAM index file Version 10

I then ran dd ibs=1 skip=64 if=ramdisk.gz of=ramdisk.lzma and xz --format=lzma --decompress -k -f ramdisk.lzma. This extracts only the compressed ramdisk and decompresses it. That left me with a ramdisk file that, when ran through binwalk, gave:

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             Linux EXT filesystem, blocks count: 4096, image size: 4194304, rev 1.0, ext2 filesystem data, UUID=ed0e4295-4e3c-4e77-a19e-d74d92549254

From here, it was just a case of mount ramdisk /mnt, and I had the filesystem mounted to be edited.

To repackage the ramdisk I unmounted it, ran xz -k -9 ramdisk --format=lzma to compress it, and ran mkimage -A ARM -O Linux -T ramdisk -C lzma -n ramdisk_1.1_EZVIZ -d ramdisk.lzma ramdisk.gz to make it into a uImage. I then loaded this onto my TFTP server. I then booted this by running: fsload 0x80400000 uImage;tftp 0x80800000 ramdisk.gz;bootm 0x80400000 0x80800000; in the U-Boot CLI. This loads the original kernel from flash and the new ramdisk from TFTP, then boots them together. This is only for a single boot but allows changes to be tested. You could possibly load the TFTP file to overwrite the local flash one, but I wasn’t too sure how in the U-Boot environment and found it much easier to do later.

SSH and Persisting Custom Ramdisks

When I first started, I set out to enable telnet. However, it turns out that telnet simply is not bundled with the cut-down busybox provided. I wanted to do this since connecting to UART and loading the modified ramdisk via TFTP from the U-Boot environment was quite painful. Since telnet wasn’t available, I decided to compile my own dropbear. It’s a relatively lightweight SSH server which can be compiled with utilities such as scp to enable file transfers. SSH seems like the preferable option anyway.

With buildroot, I cross-compiled a statically-linked dropbear binary. The DVR uses uClibc, which made things a bit more overcomplicated, so I went this route mostly out of convenience.

I added it to /bin and modified /etc/init.d/rcS to make it start on boot. Whilst doing this I also used John the Ripper to crack the /etc/shadow - the root user had the password 12345. I did try entering this into the password-protected serial login but it didn’t work. Instead of using this user for SSH I decided to add my own user with root permissions.

Once I had SSH access, I could read the flash directly. The output of mount showed:

rootfs on / type rootfs (rw)
/dev/root on / type ext2 (rw,relatime,errors=continue)
proc on /proc type proc (rw,relatime)
sysfs on /sys type sysfs (rw,relatime)
udev on /dev type tmpfs (rw,relatime)
devpts on /dev/pts type devpts (rw,relatime,mode=600,ptmxmode=000)
/dev/mtdblock1 on /home/hik type jffs2 (rw,relatime)
tmpfs on /home/app type tmpfs (rw,relatime)

We can see here that /home/hik is the flash filesystem and /home/app is a tmpfs (it’s populated by extracting files in /home/hik on boot). Inside /home/hik we can see the ramdisk.gz and so we can use scp to directly write our custom ramdisks into flash. I haven’t run into any problems but I would be slightly wary about writing to this partition too often, as I’m not sure how many writes the flash can endure. We aren’t just limited to overwriting existing files here; any files in this directory will persist.

Switching from the UI from Chinese to English

The UI is provided via HDMI/VGA, and you can connect a mouse and keyboard to access menus to change display settings, disk writing behaviour, etc. Unfortunately, as this is a Chinese (and presumably region-locked) DVR, we cannot change the display language.

For CCTV cameras that want to run on the cloud this is an issue as the ezviz network requires a Chinese phone number to use the Chinese cloud. Thankfully, since this is just a DVR, we do not need to worry about the cloud capabilities, making things a bit easier. There may be a way to change the region via a config, but I had no luck finding an obvious setting. One possible route was a protected /dev/mtdblock0 that contained bytes that looked like the usual Hikvision language config format, but I had no luck with this.

Instead, I took a more rudimentary approach of simply replacing all Chinese strings with the English translations. In /home/hik/ there’s a file called app.tar.lzma that contains the config settings for the UI, as well as images/fonts/etc. I extracted this and found conf/1024x768.cfg. Here it had various config settings, as well as locale strings following the format FEATURE_NAME_cap0 for English and FEATURE_NAME_cap1 for Chinese. I made a script to copy the cap0 values into the equivalent cap1 setting. I then recompressed this (in the same format) and set the permissions to 777, and ownership to nobody:nobody to match the original file. Once I’d replaced this in flash and rebooted, my UI was all in English! Success!

Extras

In no particular order, here are some takeaways from this (and also to remind myself in the future):

  • Although Hikvision are often scrutinised for their device’s security, this ezviz device seemed to be relatively secure. I couldn’t find any obvious security flaws (although I’m no expert) that didn’t require physical access.
  • I never worked out the password protection on UART. I think it is probably done in the kernel somehow. Maybe you could request the changes they made to the kernel via GPL, but it might be difficult getting a response.
  • Everything is handled by a hicore binary. I think hikvision have multiple versions of these, as some devices have full management options available via a web interface. It also writes directly to the framebuffer for the HDMI/VGA UI and handles camera-related logic.
  • The operation of the DVR is surprisingly simple. It seems to use standard Linux utilities for its access point, and all the management, logic, and UI is handled by the monolithic hicore binary.
  • The flash memory isn’t too large, but it was large enough for me to drop my own busybox into it. I’m not booting with it, but it’s useful if I want extra commands.
  • It writes to raw storage via /dev/sdX (doesn’t mount it) with Hikvision’s own filesystem. There’s some research on it, and I’m on-and-off writing a reader that can run on-device.
  • I think it has a dedicated chip for video processing, but I’m not sure how it’s interfaced with in software.