Hacking Cheap eBay IP Camera

I bought a €40 “Anbere” brand IP camera from eBay to find that it can only be used to feed the supplied Microsoft Windows application. I need it to perform the simple task of serving a JPG through its web interface. This left me with two options: bin it or reverse engineer this shonky POS. I chose option 2.

Let’s Open This Bad Boy Up

It’s pretty ironic that just about every embedded device on the planet runs Linux yet, in this case, only Microsoft Windows is supported by the manufacturer for the client. I took the IP camera apart.

I opened the enclosure up by unscrewing the front cap with the glass in it, teasing out the IR LED assembly, unscrewing the main board (one screw missing – cost reduction!), undoing the strain relief and then teasing out the guts. The WiFi is implemented by a USB board Ty-Rap’ed into the cables at the back. So far I’m not too wowed by this whole thing!
D71_2261
The camera is run by a HiSilicon HI3158 System-On-Chip for IP cameras. Also you’ll see a HST-0041 SAR transformer for the wired Ethernet interface, a 25L6406E 64Mbit CMOS serial Flash and a IP101GR Ethernet transceiver. On the other side there’s the camera itself – you can unscrew the mechanicals to reveal the sensor which is mounted onto the PCB. That’s it for teardown!

Side note: apparently there’s an API for the HI3518 and Googling Hi3518_SDK_V1.0.7.0.tgz will get you an SDK.

Connecting To the Development Serial Port

D71_2257
As you can see at the bottom left I found a serial port running a 3.3V levels (checked with my oscilloscope) to which I soldered the yellow-orange-red wires and then connected to a cheap serial-to-USB converter module. Of course, my USB-serial converters are not FTDI these days, CH340G in this case.

Side note: I’m running this from my Rigol DP832 (full-feature-hacked, of course) which reports the IP camera drawing about 150mA. The IR LED board isn’t connected for this measurement. The main camera board runs at about 50° C. Toasty.

When I booted the camera again with my workstation’s serial console at 115200-8-N-1 I got:

U-Boot 2010.06-svn (Jan 04 2015 - 13:37:41)

DRAM: 256 MiB
Check spi flash controller v350... Found
Spi(cs1) ID: 0xC2 0x20 0x17 0xC2 0x20 0x17
Spi(cs1): Block:64KB Chip:8MB Name:"MX25L6406E"
envcrc 0x3991c61b
ENV_SIZE = 0xfffc
In: serial
Out: serial
Err: serial
Press Ctrl+C to stop autoboot
CFG_BOOT_ADDR:0x58040000
8192 KiB hi_sfc at 0:0 is now current device

### boot load complete: 1884960 bytes loaded to 0x82000000
### SAVE TO 80008000 !
## Booting kernel from Legacy Image at 82000000 ...
Image Name: linux
Image Type: ARM Linux Kernel Image (uncompressed)
Data Size: 1884896 Bytes = 1.8 MiB
Load Address: 80008000
Entry Point: 80008000

load=0x80008000,_bss_end=80829838,image_end=801d42e0,boot_sp=807c7168
Loading Kernel Image ... OK
OK

Starting kernel ...

Uncompressing Linux... done, booting the kernel.

The bad news is that there was no shell prompt. Boo.

Trying To Get Something Useful From the Serial Console

I got a lot of useful tips from Greg – the printenv U-Boot command reveals some secrets. Also, examining the /etc/init.d/dnode reveals that telnetd is running (as nmap would have told me, too 😉 ). Here’s the printenv output:

hisilicon # printenv
bootcmd=setenv setargs setenv bootargs ${bootargs};run setargs;fload;bootm 0x82000000
bootdelay=1
baudrate=115200
bootfile="uImage"
da=mw.b 0x82000000 ff 1000000;tftp 0x82000000 u-boot.bin.img;sf probe 0;flwrite
du=mw.b 0x82000000 ff 1000000;tftp 0x82000000 user-x.cramfs.img;sf probe 0;flwrite
dr=mw.b 0x82000000 ff 1000000;tftp 0x82000000 romfs-x.cramfs.img;sf probe 0;flwrite
dw=mw.b 0x82000000 ff 1000000;tftp 0x82000000 web-x.cramfs.img;sf probe 0;flwrite
dc=mw.b 0x82000000 ff 1000000;tftp 0x82000000 custom-x.cramfs.img;sf probe 0;flwrite
up=mw.b 0x82000000 ff 1000000;tftp 0x82000000 update.img;sf probe 0;flwrite
ua=mw.b 0x82000000 ff 1000000;tftp 0x82000000 upall_verify.img;sf probe 0;flwrite
tk=mw.b 0x82000000 ff 1000000;tftp 0x82000000 uImage; bootm 0x82000000
dd=mw.b 0x82000000 ff 1000000;tftp 0x82000000 mtd-x.jffs2.img;sf probe 0;flwrite
ipaddr=192.168.1.10
serverip=192.168.1.107
netmask=255.255.255.0
bootargs=mem=${osmem} console=ttyAMA0,115200 root=/dev/mtdblock1 rootfstype=cramfs mtdparts=hi_sfc:256K(boot),3520K(romfs),2560K(user),1280K(web),256K(custom),320K(mtd)
osmem=43M
ethaddr=00:12:12:f6:a6:8a
HWID=8043420004048425
NID=0x0005
muxctl0=
muxval0=
gpio0=
gpioval0=
appVideoStandard=PAL
appSystemLanguage=English
stdin=serial
stdout=serial
stderr=serial
verify=n
ver=U-Boot 2010.06-svn (Jan 04 2015 - 13:37:41)

Environment size: 1339/65532 bytes

I did try adding single, S, debug and / or verbose to the bootargs to no avail. It looks like the kernel simply won’t do anything with the serial port. I even tried changing the name of the serial device to ttyAMA1 and ttyAMA2. No dice.

Extract /boot From Flash Using U-Boot

The U-Boot build for shonkycam contains the sf command for managing the serial / SPI Flash. Whilst the help command on the IP camera’s U-Boot build doesn’t give any clues as to how to use this, a little Googling reveals:

Usage:
sf probe [[bus:]cs] [hz] [mode] - init flash device on given SPI bus and chip select
sf read addr offset len - read 'len' bytes starting at 'offset' to memory at 'addr'
sf write addr offset len - write 'len' bytes from memory at 'addr' to flash at 'offset'
sf erase offset [+]len - erase 'len' bytes from 'offset'; '+len' round up 'len' to block size
sf update addr offset len - erase and write 'len'bytes from memory at 'addr' to flash at 'offset'

I needed to install a TFTP server on my workstation (Ubuntu 14.04) to receive data from the IP camera’s U-Boot boot loader. I did:

sudo apt-get install tftpd-hpa

I edited /etc/default/tftpd-hpa (as root, natch), to set TFTP_OPTIONS="--secure --create". Then I set some permissions on the directory to receive the file from the IP camera:

sudo chown tftp:nogroup /var/lib/tftpboot/
sudo chmod a+w /var/lib/tftpboot/
sudo service tftpd-hpa restart

TFTP server now up, running and ready to accept incoming uploads from the IP camera to my workstation’s /var/lib/tpftboot directory.

I connected IP camera’s the wired Ethernet to my switch – clearly U-Boot isn’t going to do anything with the WiFi adaptor hanging off the USB interface.

I could then power cycle the IP camera ready with the Ctrl-C salute to halt the Linux boot and leave me at the U-Boot bootloader command prompt. We know that there’s RAM we can use at 0x82000000 from the U-Boot console output. . The following (with IP details x-ed out) got me the image onto my workstation – yay!

sf probe 0;sf read 0x82000000 0x40000 0x370000
setenv ipaddr x.x.x.81
setenv gatewayip x.x.x.253
setenv serverip x.x.x.43
tftp 0x82000000 romfs.cramfs 0x370000

Then on my workstation:

sudo mkdir /media/cramfs
sudo mount -t cramfs -o loop /var/lib/tftpboot/romfs.cramfs /media/cramfs

Bingo – I can see the IP camera’s basic filesystem on my workstation. Here’s the image if you want to have a look yourself. Now to find out how to get into it to enable me to get that snapshot-over-HTTP feature. Alternatively binwalk -Me could be used to expand the dump from Flash.

Looking at /etc/init.d/rcS we see some interesting stuff:

...
mount -t squashfs /dev/mtdblock2 /usr
mount -t squashfs /dev/mtdblock3 /mnt/web
mount -t squashfs /dev/mtdblock4 /mnt/custom
mount -t jffs2 /dev/mtdblock5 /mnt/mtd
...

This is where extra bits of Flash get mapped into the filesystem. I think that /dev/mtdblock* is defined in the kernel. Source to the kernel is… …where? (GPL, anybody?). Since we don’t have a console, we can’t see the kernel boot log or check /proc/mtd.

/etc/passwd Cracked – Got root

Plan B. John the Ripper was applied to /etc/passwd (cheers, Greg) to reveal:

username root
password xmhdipc

Which works on the telnet service. Ace! Googling the password (xmhdipc) reveals other cameras (Escam QD300, IPCX-MC41672, TOP-201, ANRAN AR-AP2GA-WIFI-IP2, Foscam FI9821W) with the same root. Super-shonky! Side note: oclHashcat is supposed to be a lot faster than John.

# cat /proc/cpuinfo
Processor : ARM926EJ-S rev 5 (v5l)
BogoMIPS : 218.72
Features : swp half thumb fastmult edsp java
CPU implementer : 0x41
CPU architecture: 5TEJ
CPU variant : 0x0
CPU part : 0x926
CPU revision : 5

Hardware : hi3518
Revision : 0000
Serial : 0000000000000000
# ps
PID USER VSZ STAT COMMAND
1 root 1240 S init
2 root 0 SW [kthreadd]
3 root 0 SW [ksoftirqd/0]
4 root 0 SW [kworker/0:0]
5 root 0 SW [kworker/u:0]
6 root 0 SW [rcu_kthread]
7 root 0 SW< [khelper] 8 root 0 SW [kworker/u:1] 119 root 0 SW [sync_supers] 121 root 0 SW [bdi-default] 122 root 0 SW< [kintegrityd] 124 root 0 SW< [kblockd] 137 root 0 SW [khubd] 148 root 0 SW< [cfg80211] 149 root 0 SW [kworker/0:1] 231 root 0 SW< [rpciod] 234 root 0 SW [kswapd0] 288 root 0 SW [fsnotify_mark] 291 root 0 SW< [nfsiod] 302 root 0 SW< [crypto] 372 root 0 SW [mtdblock0] 377 root 0 SW [mtdblock1] 382 root 0 SW [mtdblock2] 387 root 0 SW [mtdblock3] 392 root 0 SW [mtdblock4] 397 root 0 SW [mtdblock5] 431 root 0 SW< [wusbd] 440 root 0 SW< [kpsmoused] 468 root 872 S < udevd --daemon 474 root 0 SWN [jffs2_gcd_mtd5] 730 root 1564 S /utils/upgraded 733 root 2452 S searchIp 734 root 2440 S wlandaemon 735 root 1352 S route_switch 737 root 9548 S dvrHelper /lib/modules /usr/bin/Sofia 127.0.0.1 9578 739 root 1240 S telnetd 750 root 525m S /usr/bin/Sofia 775 root 0 DW [RtmpTimerTask] 776 root 0 SW [RtmpMlmeTask] 777 root 0 SW [RtmpCmdQTask] 847 root 1252 S -sh 852 root 1240 R ps # free total used free shared buffers Mem: 38948 37888 1060 0 3020 Swap: 0 0 0 Total: 38948 37888 1060 # netstat -lnp Active Internet connections (only servers) Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name tcp 0 0 0.0.0.0:34561 0.0.0.0:* LISTEN 730/upgraded tcp 0 0 0.0.0.0:8899 0.0.0.0:* LISTEN 750/Sofia tcp 0 0 0.0.0.0:34567 0.0.0.0:* LISTEN 750/Sofia tcp 0 0 0.0.0.0:554 0.0.0.0:* LISTEN 750/Sofia tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 750/Sofia tcp 0 0 0.0.0.0:9527 0.0.0.0:* LISTEN 737/dvrHelper tcp 0 0 0.0.0.0:23 0.0.0.0:* LISTEN 739/telnetd netstat: /proc/net/tcp6: No such file or directory udp 0 0 0.0.0.0:34568 0.0.0.0:* 750/Sofia udp 0 0 255.255.255.255:34569 0.0.0.0:* 733/searchIp udp 0 0 0.0.0.0:58899 0.0.0.0:* 750/Sofia udp 0 0 0.0.0.0:3702 0.0.0.0:* 750/Sofia udp 0 0 0.0.0.0:40327 0.0.0.0:* 750/Sofia udp 0 0 0.0.0.0:55222 0.0.0.0:* 750/Sofia udp 0 0 0.0.0.0:46522 0.0.0.0:* 750/Sofia udp 0 0 0.0.0.0:37105 0.0.0.0:* 750/Sofia netstat: /proc/net/udp6: No such file or directory netstat: /proc/net/raw6: No such file or directory Active UNIX domain sockets (only servers) Proto RefCnt Flags Type State I-Node PID/Program name Path

That /usr/bin/Sofia binary looks like it's doing all the work.

What's Running On the Network Interface?

Well, we get a good idea from /etc/init.d/rcS and the netstat output, but let's try nmap from the workstation:

nmap -p1-10000 192.169.1.10

Starting Nmap 6.40 ( http://nmap.org ) at 2015-07-04 20:18 CEST
Nmap scan report for shonkycam.lan (192.169.1.10)
Host is up (0.021s latency).
Not shown: 9995 closed ports
PORT STATE SERVICE
23/tcp open telnet
80/tcp open http
554/tcp open rtsp
8899/tcp open ospf-lite
9527/tcp open unknown

Nmap done: 1 IP address (1 host up) scanned in 1.11 seconds

RTSP is the video streaming service.

Another Tack - Get a Frame From the Video Stream

Whilst Googling with the root password, I found another suggestion to get a snapshot from the camera which is to capture a burst of video from the HD stream and then extract a frame from that. For this you'll need some extra tools on your workstation / monitoring machine:

sudo apt-get install livemedia-utils ffmpeg vlc

Something like this.

openRTSP -D 5 -B 10000000 -b 10000000 -F snapshot -d 5 rtsp://192.169.1.10:554/user=admin&password=&channel=1&stream=0.sdp?
avconv -i snapshotvideo-H264-1 -y './snapshot%d.jpg'

I tried this and the results weren't impressive - the snapshot frame was very pixelated. Close, but no cigar.

Next plan - when I get around to it - is to see if I can replace /usr/bin/Sofia:

cat /proc/750/status
Name: Sofia
State: S (sleeping)
Tgid: 750
Pid: 750
PPid: 738
TracerPid: 0
Uid: 0 0 0 0
Gid: 0 0 0 0
FDSize: 256
Groups:
VmPeak: 490720 kB
VmSize: 487268 kB
VmLck: 0 kB
VmHWM: 10496 kB
VmRSS: 10496 kB
VmData: 479456 kB
VmStk: 136 kB
VmExe: 5468 kB
VmLib: 1820 kB
VmPTE: 146 kB
VmSwap: 0 kB
Threads: 58
SigQ: 0/303
SigPnd: 0000000000000000
ShdPnd: 0000000000000000
SigBlk: 0000000000000000
SigIgn: 0000000000010006
SigCgt: 0000000180001080
CapInh: 0000000000000000
CapPrm: ffffffffffffffff
CapEff: ffffffffffffffff
CapBnd: ffffffffffffffff
Cpus_allowed: 1
Cpus_allowed_list: 0
voluntary_ctxt_switches: 445
nonvoluntary_ctxt_switches: 3753
# ls -l /usr/bin/Sofia
-rwxrwxr-x 1 556 556 5988932 Mar 17 01:44 /usr/bin/Sofia

It's in the /usr filesystem which was mounted from /dev/mtdblock2. Let's get the rest of the data out of the Flash...

Extracting All Flash Partitions

So, here's the process for understanding what's in the Flash partitions, the Flash memory map, the filesystem types, etc. Remember we saw the following from the U-Boot environment variables using the printenv command:

mtdparts=hi_sfc:256K(boot),3520K(romfs),2560K(user),1280K(web),256K(custom),320K(mtd)

This gives us the memory map of the Flash. Using mount when logged in over telnet, we have:

/dev/root on / type cramfs (ro,relatime)
/dev/mtdblock2 on /usr type squashfs (ro,relatime)
/dev/mtdblock3 on /mnt/web type squashfs (ro,relatime)
/dev/mtdblock4 on /mnt/custom type squashfs (ro,relatime)
/dev/mtdblock5 on /mnt/mtd type jffs2 (rw,relatime)

Just for giggles, I also used binwalk on the complete Flash image. Knowing that it's a 8MB Flash, we dump it from U-Boot like this:

sf probe 0;sf read 0x82000000 0x0 0x800000
tftp 0x82000000 flash.bin 0x800000

Binwalk gives us (cruft removed and results formatted):
[table]
DECIMAL, HEXADECIMAL,DESCRIPTION
262144,0x40000,"CramFS filesystem, little endian size 3502080 version #2 sorted_dirs CRC 0xd62fbca4, edition 0, 1297 blocks, 171 files"
3866624,0x3B0000,"Squashfs filesystem, little endian, version 4.0, compression:lzma (non-standard type definition), size: 2357490 bytes, 116 inodes, blocksize: 262144 bytes, created: Tue Mar 17 02:44:57 2015"
6488064,0x630000,"Squashfs filesystem, little endian, version 4.0, compression:lzma (non-standard type definition), size: 1255996 bytes, 139 inodes, blocksize: 262144 bytes, created: Tue Mar 17 02:44:55 2015"
7798784,0x770000,"Squashfs filesystem, little endian, version 4.0, compression:lzma (non-standard type definition), size: 10546 bytes, 38 inodes, blocksize: 262144 bytes, created: Tue Mar 17 02:44:54 2015"
8060928,0x7B0000,"JFFS2 filesystem, little endian"
[/table]
So we now also know the filesystem types and mount points. Let's stitch this all together:
[table]
Start address,End address,Size (Hex),Size (kB),Contents
000000,03FFFF,040000,256,U-Boot - the bootloader
040000,3AFFFF,370000,3520,/ squashfs read-only
3B0000,62FFFF,280000,2560,/usr squashfs read-only
630000,76FFFF,140000,1280,/mnt/web squashfs read-only
770000,7AFFFF,40000,256,/mnt/custom squashfs read-only
7B0000,7FFFFF,50000,320,/mnt/mtd jffs2 read-write
[/table]
This neatly accounts for all 64Mbit / 8MB of Flash.

Let's go back to U-Boot and extract images of the read-only file systems for analysis and possible modification on the workstation:

setenv ipaddr x.x.x.81
setenv gatewayip x.x.x.253
setenv serverip x.x.x.43
sf probe 0;sf read 0x82000000 0x3B0000 0x280000
tftp 0x82000000 usr.cramfs 0x280000
sf probe 0;sf read 0x82000000 0x630000 0x140000
tftp 0x82000000 web.cramfs 0x140000
sf probe 0;sf read 0x82000000 0x770000 0x40000
tftp 0x82000000 custom.cramfs 0x140000

For those playing along at home, here's usr.squashfs, web.squashfs and custom.squashfs. Remember - this is squashfs format, not cramfs. You'll need to adjust your mount command accordingly.

Running strings Sofia reveals that all the debug info is in there. It's going to take quite a while to go through all that stuff, but it certainly looks interesting at first glance. I may still need to resort to the SDK to get a snapshot image out of this beastie.