v380 IPcam: Firmware patching
I made some progress:
- Running code from a micro SD card as root
- Downloading camera firmware
- Writing custom firmware patches
- Finding the root password hash
- Changing the root password
To see what's going on, you'll need to pop open the camera casing and connect USB UART to screen
or similar as in #1. Also you need a micro SD card.
Prelude #
To actually get RTSP working on the camera, I had to patch the firmware and enable RTSP via the ceshi.ini
file. The patch file was provided on a forum from someone who emailed v380 tech support.
Structure of patch zip #
Reverse-engineering that patch AK3918E-V200_V.2.5.9.5 .zip
was my first step to understanding the camera.
The zip file looks like this:
$ als AK3918E-V200_V.2.5.9.5\ .zip
Archive: AK3918E-V200_V.2.5.9.5 .zip
Length Date Time Name
--------- ---------- ----- ----
50 2019-07-13 10:18 local_update.conf
0 2019-07-13 10:18 patch_reuse
0 2019-07-13 10:18 updatepatch/
3451562 2019-07-13 10:18 updatepatch/5d4315195544f84f54a52ac757ce200e.patch
where local_update.conf
contains the name of the patch file (its MD5 hash)
[PATCH]
patchmd5=5d4315195544f84f54a52ac757ce200e
To upgrade the camera, unzip to the root of an sd card, put the card in the camera and boot it up. You'll hear 固件更新開始 (
) and after a few minutes it should reboot itself.Binwalk patch #
The structure of the patch file wasn't clear (file
just said it was data), so I ran binwalk -e
on it.
The most interesting file it extracted was a squashfs file system containing some ARM32 ELFs.
squashfs-root/apps:
as9nvserver gpiotest log_server prerun
as9updatednsip gzip motor_test recorder
daemon hu_updater mqtt_test vsipbroadcast
eventhub_core hwwtd mvrtsp wpa_cli_lite
squashfs-root/lib:
libcurl.so.4 libmvs_ctk.so libmvs_tls.so
libHYWideAreaObjectTrack.so libmvs_lch_client.so libonvif.so
libmval_eventhub.so libmvs_lch.so libopencv_core.so
libmvs_assisttools.so libmvs_mdq.so libopencv_imgproc.so
libmvs_clog.so libmvs_memp.so
libmvs_core.so libmvs_pcc.so
squashfs-root/modules:
akcamera.ko aw_gpio_moto_driver.ko sensor_h42.ko sensor_sc1135.ko
akcamera_n1.ko i2c-gpio-soft.ko sensor_h62.ko sensor_sc1145.ko
akcamera_n2.ko mv_motor_driver.ko sensor_sc1035.ko sensor_sc1235.ko
ak_gpio_i2c.ko otg-hs.ko sensor_sc1037.ko sensor_sc1245.ko
ak_sar_adc_drv.ko sensor_gc1034.ko sensor_sc1045.ko
I then spent some time looking through strings with rabin2 -zzz
and grepping for things.
hu_updater / prerun #
I loaded hu_updater
into Ghidra and it did really well at disassembling ARM code. (I should really have looked at prerun
, but the relevant code is similar).
Patch file format #
From this I was able to reverse-engineer the patch file format. It uses little-endian 32-bit integers and null-terminated strings.
The header of size 0x80 needs the following parts:
- 0x00: 0x0a
- 0x04:
V380E2_C
/V380E2_CA
This is thehwname
defined in/mnt/mtd/mvconf/patchmanage.conf
which depends on the firmware version you have. If this is wrong, there will be a log message containing the expected value:patch not match <V380E2_CA>:<V380E2_C> failed.
. - 0x14:
0x1f4b59
a version - 0x18: Number of files in the patch
Each file contains a header of size 0x40 before the file contents:
- The filename
- 0x38: File size
As an example, the patch file from the zip file looks like:
00000000 0a 00 00 00 56 33 38 30 45 32 5f 43 00 00 00 00 |....V380E2_C....|
00000010 00 00 00 00 59 4b 1f 00 15 00 00 00 aa aa 34 00 |....YK........4.|
00000020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00000080 65 78 73 68 65 6c 6c 5f 61 66 75 2e 73 68 00 00 |exshell_afu.sh..|
00000090 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
000000b0 00 00 00 00 00 00 00 00 1b 05 00 00 87 7f 00 00 |................| # Length 0x051b
000000c0 23 21 2f 62 69 6e 2f 73 68 0a 0a 6e 65 65 64 5f |#!/bin/sh..need_|
... Contents of exshell_afu.sh ...
000005c0 6d 74 64 2f 6d 76 63 6f 6e 66 2f 70 61 74 63 68 |mtd/mvconf/patch|
000005d0 6d 61 6e 61 67 65 2e 63 6f 6e 66 65 78 73 68 65 |manage.confexshe| # EOF / next header
000005e0 6c 6c 5f 62 66 75 2e 73 68 00 00 00 00 00 00 00 |ll_bfu.sh.......|
...
I wrote a tool bcaller/v380-ipcam-firmware-patch so you can see the files in your patch.
Updater sequence #
- The patch MD5 and hwname are checked
- Files are extracted from the patch to subfolders of
/tmp/hu_files_tmpdir/
depending on their prefix - If it exists,
exshell_bfu.sh
is executed (bfu = before update) - Generic files and sound files are copied over to
/mnt/mtd/
and/mnt/mtd/mvsound/
respectively - Kernel and MTD images are flashed using
/sbin/updater
- If it exists,
exshell_afu.sh
is executed
Run code as root #
Let's have fun and make a patch containing exshell_bfu.sh
e.g.:
#!/bin/sh
set -x
echo HELLO
/mnt/mtd/audiofile_player /mnt/mtd/mvsound/sf_sysstarting_en.wav 0
whoami
ls /
ls /etc/
ls /mnt/
cat /etc/passwd
cat /etc/shadow
ps waxfu
sleep 30
Use patchv380, copy the patch to /sdcard/updatepatch/
and alter the patchmd5
in local_update.conf
. Then boot up the camera. The sleep 30
is there so that you have time to unplug the camera before it runs post-update code (probably not harmful either way).
IFCReadStringOnce Warning:no such file(/tmp/hu_files_tmpdir/exshells/patchrule.sh)
+ echo HELLO
HELLO
+ /mnt/mtd/audiofile_player /mnt/mtd/mvsound/sf_sysstarting_en.wav 0
=== play type : 0 ===
--AudioFilter Version V1.8.00_svn5047, type:8000--
## ERROR: CHIP(106552) unsupported
can't open the sd filter!
open sdfilter failed!!!!
Play Finished
+ whoami
root
+ ls /
bin dev etc ext init lib mnt mvs proc sbin sys tmp usr var
+ ls /etc/
bak hosts ld.so.conf profile sysconfig
fstab init.d mdev.conf resolv.conf
group inittab nsswitch.conf services
host.conf jffs2 passwd shadow
+ ls /mnt/
mtd nand sdcard
+ cat /etc/passwd
root:x:0:0:root:/:/bin/sh
daemon:x:1:1:daemon:/usr/sbin:/bin/sh
bin:x:2:2:bin:/bin:/bin/sh
nobody:x:99:99:nobody:/home:/bin/sh
+ cat /etc/shadow
root:$5$EvgtGUo1zRnZRW$Ge399ZNp3EYQP1NJt7MF1fbYjfnhtloG5m1N2KCp9l0:10933:0:99999:7:::
bin:*:10933:0:99999:7:::
daemon:*:10933:0:99999:7:::
nobody:*:10933:0:99999:7:::
+ps waxfu
PID USER TIME COMMAND
1 root 0:01 init
... # I removed the kernel threads in [square brackets] for brevity
362 root 0:00 syslogd -n -O /var/log/messages -s 200 -b 3
363 root 0:00 klogd -n
371 root 0:00 {vg_boot.sh} /bin/sh /mnt/mtd/vg_boot.sh
375 root 0:00 /tmp/prerun
388 root 0:00 sh -c /tmp/hu_files_tmpdir/exshells/exshell_bfu.sh
389 root 0:00 {exshell_bfu.sh} /bin/sh /tmp/hu_files_tmpdir/exshells/ex
402 root 0:00 /sbin/getty -L ttySAK0 115200 vt100
404 root 0:00 ps waxfu
That sha256crypt
password in the shadow file ($5$EvgtGUo1zRnZRW$Ge399ZNp3EYQP1NJt7MF1fbYjfnhtloG5m1N2KCp9l0
) looks tough, so I tried to change the password but failed.
Changing password for root
New password:
Retype password:
passwd: /etc/passwd: Read-only file system
passwd: can't update password file /etc/passwd
Not too upset as I have root anyway. We'll come back to it.
Firmware flasher #
I extracted the hardware flasher cp /sbin/updater /mnt/sdcard/updater
and loaded it into Ghidra to see how firmware updates work.
Updating the kernel, nand, serial number, LOGO (not sure what that is) and mac address use ioctl
magic, whereas updating the MTDs looks simpler.
There are 5 mtd parts:
MTD | Update file prefix | Type |
---|---|---|
1 | IMG_RFS | SquashFS |
2 | IMG_USR | SquashFS |
3 | IMG_MVS | SquashFS |
4 | IMG_EXT | SquashFS |
5 | IMG_JFS | jffs2 |
To update mtd1, the patch must contain a squashfs file with filename beginning with IMG_RFS
.
/dev/mtd1
is erased using an ioctl
, but then we just write the squashfs file system to /dev/mtd1
.
Grabbing the firmware #
+ ls /dev/
akfha_char mem mtdblock0 random
akgpio mmcblk0 mtdblock1 rfkill
akpcm_cdev0 mmcblk0p1 mtdblock2 root
akpcm_cdev1 mtd0 mtdblock3 rtc0
console mtd0ro mtdblock4 tty
cpu_dma_latency mtd1 mtdblock5 ttySAK0
full mtd1ro network_latency ttyp0
i2c-0 mtd2 network_throughput ttyp1
ion mtd2ro null ttyprintk
isp_char mtd3 ptmx uio0
kmsg mtd3ro pts urandom
log mtd4 ptyp0 watchdog
loop-control mtd4ro ptyp1 zero
loop0 mtd5 ram0
loop1 mtd5ro ram1
The "files" /dev/mtd?
are character devices, so function like byte streams.
We can make an exshell_bfu.sh patch e.g. cat /dev/mtd4ro > /mnt/sdcard/mtd4
to extract the contents of the MTD parts.
Changing the root password #
If I want to set the root password to Hello.123
, The shadow file hash can be changed to:
$ mkpasswd --method=sha256crypt --salt=EvgtGUo1zRnZRW Hello.123
$5$EvgtGUo1zRnZRW$2A2sE5yjjsR2K6QJH0Te2rKOUGaCRXiEIgdr9e5KlO0
Looking through the files in the MTDs downloaded to the sdcard (with unsquashfs -lls
), I found /etc/password
in mtd1.
Now we can make a cheeky update patch:
$ sudo unsquashfs -d mtdA mtd1
Parallel unsquashfs: Using 4 processors
206 inodes (230 blocks) to write
created 58 files
created 26 directories
created 148 symlinks
created 0 devices
created 0 fifos
$ sed -i 's/^root:[^:]*:/root:$5$EvgtGUo1zRnZRW$2A2sE5yjjsR2K6QJH0Te2rKOUGaCRXiEIgdr9e5KlO0:/' mtdA/etc/shadow
$ mksquashfs mtdA IMG_RFS_mtd1.squashfs -b 128K -comp xz
We used sudo
so that we unsquash files with the correct owners (e.g. uid=0
). Check that the output of unsquashfs -s
looks the same for the old mtd1
and patched IMG_RFS_mtd1.squashfs
.
$ patchv380 IMG_RFS_mtd1.squashfs -h V380E2_CA
Let's run it #
Patch copied over to the sdcard correctly (as explained in the zip section above). Camera booting up.
copy finished normally.
copy finished normally.
==== access [/dev/mtd1]
[updater]erase mtd start...........................erase success!
write mtd start.........................................write OK!
hardware update finished normally === has img:[1] , has spec:[2]
=== update success! ===
sdcard update finished!
...
Restarting system.
Now we've successfully flashed our system! Time for some fun...
V380E login: root
Password:
welcome to file system
[root@V380E ~]$
🎉
- Previous: v380 IPcam: Move with SOAP
- Next: SocketIO / EngineIO DoS