US Home

(Last updated: Sunday January 03, 2016)

Hacking a Funlux IP Camera

Funlux Mini 720p wireless camera During the Christmas rush I mistakenly ordered an IP, Wireless, HD camera without too much investigation. When it arrived I suddenly realized that I hadn't checked if I could pull down the images with any of my HA software. What was I thinking?! Anyway, long story short. I can't get at the images directly. These camera's are meant to be used with a cell phone (not a tablet) or the web service. So I used their app, setup the camera, broke out nmap and later started searching the net for any details I could. Nmap found ports 23, 80, 8000 and 9000. My search for the Funlux Mini WiFi 720P HD (or CH-S1A-WA or ZH-IXY1D) camera brought me to a bunch of information such as telnet using root and no password (and no direct way to change that). The web page login is admin/111111. It needs IE because it uses a cab file (wouldn't work with Firefox). I used Wireshark to figure out what was going on and noticed that my WiFi SSID and the access password were being sent in plain text. And that the camera was sharing information (probably video and audio) with a web site on the internet. WOW! I don't know what to say other than I am not happy with this in so many different ways. The picture is great but other than that I can't say anything good.

For those looking to hack the camera, take a look on Hackaday, the link is called Zmodo - Local Controller. A bunch of us posted the various information we've found there. I'm also posting my results below.

Now despite this I'm going to keep the 2 cameras I have. I've already blocked them from internet access and I'm working on securing the telnet access. I'm also going to write some software so I can pull back the images to my HA server. While I can't trust the vendor and their software I should be able to still make use of the cameras in the way I intend.

One last note, I tried the camera on a south facing window and found that ths sun bleaches the image so bad nothing can fix it. So it looks like these cameras are solely for indoor use. I might grab on eof there outdoor models and see how well that works.

What I know so far

It seems that the hardware is a base design and a lot of cameras use this design.

  • Branded as ZModo or Funlux
  • CH-S1A-WA or ZH-IXY1D or ZM-SH75D001-WA based on the Hi3518 design (CH-S1A-WACS, smartlink, ZH-IXD1D-WAC, ZH-IZV15-WAC)
  • Processor: ARM926EJ-S rev 5 (v5l)
  • RAM: 32M
  • Flash: 16M
  • Linux: Linux funluxa 3.0.8 #2 Thu Apr 16 10:08:52 CST 2015 armv5tejl GNU/Linux
  • No support for rstp or ONVIF


Missing the ntp address 192.241.x.y
Audio: g711a (64Kbps), A-Law (64Kbps), or g726 (16Kbps) (???)
I noted UDP ports 5844, 39770 and 8080 open earlier but they aren't open below(???)

Unique commands(?):
From my trace

Source Port: 59894 (59894)
Destination Port: 8000 (8000)
Data: 5555aaaa0000000000000050 Stream video
Data: 5555aaaa0400000000000050

Source Port: 59893 (59893)
Destination Port: 8000 (8000)
Data: 5555aaaa0000000000000091 
Data: 5555aaaafccc000000000091 Contains info about the alarm and the wifi info
                               (in plaintext, grrrr)

Src Port: 59892 (59892)
Dst Port: 8000 (8000)
Data: 5555aaaa0000000000000098 Device info
Data: 5555aaaac400000000000098 (returns 208 bytes of data)

Source Port: 59895 (59895)
Destination Port: 8000 (8000)
Data: 5555aaaa0000000000000198
Data: 5555aaaa0100000000000198

Source Port: 59898 (59898)
Destination Port: 8000 (8000)
Data: 5555aaaa0000000000000690
Data: 5555aaaa0c00000000000690

Source Port: 59898 (59898)
Destination Port: 8000 (8000)
Data: 5555aaaa0000000000000790

Source Port: 59896 (59896)
Destination Port: 8000 (8000)
Data: 5555aaaa00000000000011a1
Data: 5555aaaa14000000000011a1

Source Port: 59897 (59897)
Destination Port: 8000 (8000)
Data: 5555aaaa0000000000006090
Data: 5555aaaa0800000000006090

Source Port: 59894 (59894)
Destination Port: 8000 (8000)
Data: 5555aaaa0400000000006690 (???)

Source Port: 59898 (59898)
Destination Port: 8000 (8000)
Data: 5555aaaaa400000000000890 (???)

Data: 5555aaaa200000000000009a
A post from Mark Alex on

... So far I have also captured the network traffic from the ZViewer. The applications
sends to commands via TCP to port 8000: First: 55 55 aa aa 00 00 00 00 00 00 02 90
This is slightly different than the one from Peter.
    31:31:31:31:31:31:00:00:00:00:00:00:00:00:00:00: ...
This includes the default login / password: admin (61:64:6d:69:6e)/ 111111 (31:31:31:31:31:31)
Audio to and from devices seems to be via TCP port 900o

The value 20 following the initial 55:55:aa:aa is interesting as it might suggest
the data length the follow (32 bytes).

Seems that the protocol is send hex values 55 55 AA AA 00 00 ... (12 bytes) and the camera will respond. I'll drag more information out of the trace at a later date.

Getting a screenshot

Well at least one method, that works for me. Not sure if it's the best idea. For now I'm using bash and the command line. Later I'll create some kind of binary to speed up the process.

Old way

$ time ( echo -e '\x55\x55\xaa\xaa\x00\x00\x00\x00\x00\x00\x00\x50' | \
    netcat -w 2 8000 | \
    avconv -i pipe:0 -q:v 1 -vframes 1 screenshot-a-%03d.png &>/dev/null )

real	0m52.769s
user	0m0.324s
sys	0m0.148s
$ ls
$ file screenshot-a-001.png
screenshot-a-001.png: PNG image data, 1280 x 720, 8-bit/color RGB, non-interlaced

Note it took about 1 minute to process that image. The image is roughly from 600K to 1.5 M in size.

New way

$ -h funluxa.uucp -t 2  > video.h264
$ avconv -strict experimental -r 25 -y -c:v h264  -i video.h264 -c:v h264 &>video.conv && \
    avconv -y -strict experimental -i -vsync 1 -r 25 -an -qscale 1 -vframes 1 screenshot_%03d.jpg &>result.conv

First, this command is much faster. I think it took a few seconds. Next, here's my Perl script to replace the echo and netcat, called I've had trouble getting netcat to be reliable with the amount of time it waited before exiting. I'm not sure that is much better but it seems to be working. The script above runs much faster than the old way. Getting the images is still pretty flakey but that's the camera itself. I don't think it likes being pounded on for stills. This could be a problem with the string information we're sending.

Update: Not sure this next part is true any longer. I could be something to do with the video processing and the fact that the time stamps are a bit out of whack. Another thing I've noted is that the image appears to be something stored on the camera and not the most recent image. This may be related to some kind of information that needs to be sent to the camera. I'm guessing some kind of timestamp. I'm working on that also I see this error is I have any kind of motion when grabbing the snapshot:

$ echo -e '\x55\x55\xaa\xaa\x00\x00\x00\x00\x00\x00\x00\x50' | netcat -w 2 8000 | avconv -y -loglevel debug -i pipe:0 -q:v 9 -s 1280x720 -vframes 1 screenshot-a-%03d.png )
avconv version 11.4-6:11.4-1~deb8u1, Copyright (c) 2000-2014 the Libav developers
  built on Jun  4 2015 19:39:02 with gcc 4.9.2 (Debian 4.9.2-10)
  configuration: --arch=amd64 --enable-pthreads --enable-runtime-cpudetect --extra-version='6:11.4-1~deb8u1' --libdir=/usr/lib/x86_64-linux-gnu --prefix=/usr --disable-avserver --enable-bzlib --enable-libdc1394 --enable-libfreetype --enable-frei0r --enable-gnutls --enable-libgsm --enable-libmp3lame --enable-librtmp --enable-libopencv --enable-libopenjpeg --enable-libopus --enable-libpulse --enable-libschroedinger --enable-libspeex --enable-libtheora --enable-vaapi --enable-vdpau --enable-libvorbis --enable-libvpx --enable-zlib --enable-gpl --enable-swscale --enable-libcdio --enable-x11grab --enable-libx264 --enable-libxvid --shlibdir=/usr/lib/x86_64-linux-gnu --enable-shared --disable-static
  libavutil     54.  3. 0 / 54.  3. 0
  libavcodec    56.  1. 0 / 56.  1. 0
  libavformat   56.  1. 0 / 56.  1. 0
  libavdevice   55.  0. 0 / 55.  0. 0
  libavfilter    5.  0. 0 /  5.  0. 0
  libavresample  2.  1. 0 /  2.  1. 0
  libswscale     3.  0. 0 /  3.  0. 0
Splitting the commandline.
Reading option '-y' ... matched as option 'y' (overwrite output files) with argument '1'.
Reading option '-loglevel' ... matched as option 'loglevel' (set libav* logging level) with argument 'debug'.
Reading option '-i' ... matched as input file with argument 'pipe:0'.
Reading option '-q:v' ... matched as option 'q' (use fixed quality scale (VBR)) with argument '9'.
Reading option '-s' ... matched as option 's' (set frame size (WxH or abbreviation)) with argument '1280x720'.
Reading option '-vframes' ... matched as option 'vframes' (set the number of video frames to record) with argument '1'.
Reading option 'tmp-screenshot-%03d.png' ... matched as output file.
Finished splitting the commandline.
Parsing a group of options: global .
Applying option y (overwrite output files) with argument 1.
Applying option loglevel (set libav* logging level) with argument debug.
Successfully parsed a group of options.
Parsing a group of options: input file pipe:0.
Successfully parsed a group of options.
Opening an input file: pipe:0.
pipe:0: Invalid data found when processing input

Watching the live video

$ echo -ne '\x55\x55\xaa\xaa\x00\x00\x00\x00\x00\x00\x00\x50' | netcat -w 600 8000 | mplayer -fps 25 -demuxer h264es -

I haven't gotten any further than that with the live video. This should run for roughly 10 minutes (at least that's the theory, doesn't seem to be 10 minutes actually). But I am working on some ideas so I can watch the video in any browser. I just need a way to take the stream I'm piping to mplayer and send it to the browser.

Explanation: Yes that is a bit of magic. This is quick and dirty programming via the bash
command line on my Linux server . :-) I'll later write a program to do this but
it will be much more complex but a lot faster. I try to use the command line when
I need to toss together a proof of concept (and I stole parts of the commands
from @Peter Jerde below, but I upvoted him too).

Short answer:

The echo command sends the magic string into the stdin of netcat, netcat sends
it to the camera, the camera begins to stream the video and mplayer displays it
on the console of the Linux computer.

Long answer:

The magic string ('\x55\x55\xaa\xaa\x00\x00\x00\x00\x00\x00\x00\x50') is what
tells the camera to start sending video. There are other magic strings also and
I haven't figured them all out yet. The \x55 means translate that to the byte
0x55 in hex. That's a 12 byte string.

The camera is on IP and listening on port 8000 for a command (that
string above).

Netcat (or nc on some systems) sort of opens a 2 way pipe between the command
line and the camera. It hides the network magic (watch with wireshark to see
the magic). The -w 600 tells netcat to keep the connection open for 600 seconds
(10 minutes).

The mplayer will play the video. You need to be running X under Linux or Mac.

The fps is the frame per second (25 should work, I was messing around). I'm
not sure what the ' -demux h264es - ' options mean exactly yet (other than
treat the input as h264).

I'm not sure what to do on Windows.

If you wanted to save the video to a file you can use this command:

$ echo -e '\x55\x55\xaa\xaa\x00\x00\x00\x00\x00\x00\x00\x50' | netcat -w 600 8000 > video.h264

That should give you 10 minutes of video from the camera. Then you can use other tools to play the video. I plan on getting the video and images into a browser at some point.

Various information about what's running on the camera

You can telnet directly into the camera (once you have it setup with the meshare app). The only user is root and it has no password.

$ telnet funluxa.uucp
Connected to funluxa.uucp.
Escape character is '^]'.

funluxa login: root
Welcome to HiLinux.
None of nfsroot found in cmdline.
# ps w 
    1 root      1708 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]
  105 root         0 SW   [sync_supers]
  107 root         0 SW   [bdi-default]
  108 root         0 SW<  [kintegrityd]
  110 root         0 SW<  [kblockd]
  123 root         0 SW   [khubd]
  216 root         0 SW   [kswapd0]
  269 root         0 SW   [fsnotify_mark]
  281 root         0 SW<  [crypto]
  355 root         0 SW   [mtdblock0]
  360 root         0 SW   [mtdblock1]
  365 root         0 SW   [mtdblock2]
  370 root         0 SW   [mtdblock3]
  375 root         0 SW   [romblock0]
  378 root         0 SW   [romblock1]
  381 root         0 SW   [romblock2]
  384 root         0 SW   [romblock3]
  513 root         0 SW   [kworker/0:1]
  557 root       872 S <  udevd --daemon
  563 root         0 SWN  [jffs2_gcd_mtd3]
  565 root         0 SWN  [jffs2_gcd_mtd1]
  568 root      1716 S    -sh
  570 root      1712 S    telnetd
  583 root         0 SW<  [cfg80211]
  738 root         0 SW   [hidog]
  794 root      1068 S    ./message
  795 root      217m S    ./App3518
  807 root         0 SW   [RTW_CMD_THREAD]
  832 root      2108 S    /app/wifi/tools/wpa_supplicant -B -Dwext -iwlan0 -c /tmp/wpa.conf
  847 root      1720 S    udhcpc -i wlan0 -b -R -s /usr/share/udhcpc/default.script
 1005 root      1716 S    -sh
 1008 root      1708 R    ps w
# cd /app
# ls
App3518          dvr              foo              message          upgrade          wifi           fontfile         hi3518           softwareversion  voice
# netstat -nat
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       
tcp        0      0  *               LISTEN      
tcp        0      0  *               LISTEN      
tcp        0      0    *               LISTEN      
tcp        0      1       SYN_SENT    
tcp        0      0 :::23                   :::*                    LISTEN      
tcp        0      0 ::ffff: ::ffff: ESTABLISHED 
tcp        0      0 ::ffff: ::ffff: ESTABLISHED 
udp        0      0  *                           
Active UNIX domain sockets (servers and established)
Proto RefCnt Flags       Type       State         I-Node Path
unix  3      [ ]         DGRAM                      1019 /config/run/wpa_supplicant/wlan0
unix  2      [ ]         DGRAM                      1020 /config/wlan0
unix  2      [ ]         DGRAM                       266 @/org/kernel/udev/udevd
# route
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
default         UG    0      0        0 wlan0     *        U     0      0        0 wlan0

The connection attempt to (note the SYN, start of a connection) is just the camera attempting to connect to I've blocked it's route so it won't connect.

# route del default
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface     *        U     0      0        0 wlan0

I removed the default route so the router can't route to the internet. It still may be possible for the camera to get back it's routes as it does an SSDP (UP-n-P) to find a router to configure itself. But I don't have any UPnP turned on, in my network (security risk).

I've actually gone one step further and hard coded a DHCP entry into my dhcpd server so that it never gets a default route that points to the internet.

# ntpd -q -p ntp.uucp
# date
Sun Jan  3 17:37:14 UTC 2016

Note that the time is UTC. I'll look at that later. For now that's okay.

# ntpd -q -p ntp.uucp
# date
Sun Jan 17 13:39:23 EST 2016

The camera doesn't have the timezone pkg installed so I used the TZ string for the US East Coast.

More Notes and experiments

To reset the time to something a bit closer to my TZ. After setting the TZ and running the ntp command, I decided to see what would happen is I killed the App3518 program. Results:

# kill -s SIGKILL 795
# cd /app/
# ./App3518
cur process id : 1136  
open gpio_motor erro!
 parameter size is : 52476 
LoadParameterFrom /var/
/home/harvey/src/app/3518/3518e/appe8188/appeCloud/parameter/parametermanage.cpp 2283 CheckExtendApSettings......
/home/harvey/src/app/3518/3518e/appe8188/appeCloud/parameter/parametermanage.cpp 2301 CheckWebSettings......
upnp port =10540     10541      10542 
6 7c:c7:9:15:3f:25
deviceid = ZMD20IF01204513
InitDeviceConfigInfo :3
---------read configinfo--------
open rtc  ds1307dev device failed!
open rtc pcf8563dev device failed!
Open misc rtc file failure!
get time failed!
loadTm ---------- year = 0,mon = 1075095968,date = 0,hour = 0,min = 203404,sec = 268435456 week 0 
realtime---------- = 0,mon = 160,date = 0,hour = 0,min = 140,sec =  0 
set sys date success
MPP Version  HI_VERSION=Hi3518_MPP_V1.0.8.1   
sensor list num 4
ov9712 idl = 0x22 idh = 0xa0
sensor_list id = 0xa022 , ID = 0x9711
JXH22 idl = 0x22 idh = 0xa0
sensor_list id = 0xa022 , ID = 0xa022
sensor id ############################:1
 au32CompMask1 00000000
 @@@@@@@@@@@@stAEAttr.u16ExpTimeMax :: ffff
@@@@@@@@@@@@stAEAttr.u16ExpTimeMin :: 2
@@@@@@@@@@@@stAEAttr.u16AGainMax :: 3
@@@@@@@@@@@@stAEAttr.u8ExpStep :: 80
@@@@@@@@@@@@stAEAttr.s16ExpTolerance :: 28
@@@@@@@@@@@@stAEAttr.enFrameEndUpdateMode :: 2
@@@@@@@@@@@@stAEAttr.u8ExpCompensation  :: 70
=============before HI_MPI_ISP_GetAEAttrEx:1000
=============after HI_MPI_ISP_GetAEAttrEx:0800
stGammaAttr.enGammaCurve = 0
=====================AWBAttr.u8Speed :80
=====================AWBAttr.u8Speed :c8
###########Antiflicker.bEnable :: 0
###########Antiflicker.u8Frequency :: 0
###########Antiflicker.enMode :: 0
####2######Antiflicker.bEnable :: 1
####2######Antiflicker.u8Frequency :: 60
####2######Antiflicker.enMode :: 0
########################## u32LumaVal:50 ,u32ContrVal:50,u32SatuVal:50
-------------------------AudioOutPutOnOff flag:0
InitAudio: InitAudioInitAudio,/dev/acodec
=============== m_initAbnormalVoice:1
SetInPutVolume :28
SetOutPutVolume :31
Ai(0,0) bind to AencChn:0 ok!
function: PlayFileThead threadid 1120027840  ,line:28
function: AudioEncodecThread threadid 1111049408  ,line:36
StartAudioDecode Success
File size = 261702
StartVideoEncoder 0 thread success!
function: MDThreadEntry threadid 1137820864  ,line:245
 MDThreadBody TID:1137820864 !!!!
function: VideoEncoderThreadEntry threadid 1150436544  ,line:81

GetAVStreamInOneChn Start!! venc:0

StartVideoEncoder 2 thread success!
================ CreateSnapChn:65 start!!! 
================ CreateSnapChn:120 success 
=============stop osd show !
function: VideoEncoderThreadEntry threadid 1158825152  ,line:81

GetAVStreamInOneChn Start!! venc:2

    *      IPC  zmdnetlib build version: [V1.6.1]  
    *      IPC  zmdnetlib build time: [10:38:22] [Jul 14 2015]   

    * wifi for AR9271,MT7601,RTL8188,Internal Version:v1.0  Build Time:18:04:36,Jul 31 2015  *

function: OsdShowEntry threadid 1167213760  ,line:14
 OsdProcess pid:1167213760 !!!!
ioctl[SIOCSIWAP]: Operation not permitted
ctrl_iface exists and seems to be in use - cannot override it
Delete '/config/run/wpa_supplicant/wlan0' manually if it is not used anymore
Failed to initialize control interface '/config/run/wpa_supplicant'.
You may have another wpa_supplicant process already running or the file was
left by an unclean termination of wpa_supplicant in which case you will need
to manually remove this file before starting wpa_supplicant again.

ioctl[SIOCSIWAP]: Operation not permitted
RetartWPA:/app/wifi/tools/wpa_supplicant -B -Dwext -iwlan0 -c /config/wpa_supplicant.conf
------wpa_ctrl_open 1526 jam want to know socket id =30----------
ZMD_Connect_Wifi=====FUNC:WifiProceess_Thread,LINE:2009 Encryp=6 ssid=SSID_HERE pwd=PASSWD_HERE

##### QR_Scan_Thread success
sendto: OSPQueueCreate=====udwTempID:0

 ========================= OSPQueueCreate Success m_QuenceMsgID:0
Network is unreachable
             ZMDNETLIB ERROR (handlePing:731):             handlePing() bind() failed !!!!
ioctl[SIOCSIWAP]: Operation not permitted
------wpa_ctrl_open 1526 jam want to know socket id =40----------
success connect wifi exit QRScan


------------------------- RunDHCP OSPQueueSendMessage
######### set dhcp success ########
getaddrinfo: Network is unreachable
[1]+  Stopped                    ./App3518
# bg
[1] ./App3518
# 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
# uname -a
Linux (none) armv5tejl

I've removed my SSID and password but the above is untouched otherwise. I find it interesting that it has the time_zone as America/New_York yet the date is still in UTC. I get the feeling that the App3518 application is doing some clean up as it runs. Possibly mounting and unmounting thing as needed. But that's just a guess at the moment.

Pulling the mtd back to another server for investigation

camera# for i in 0 1 2 3
    echo -n "$i "
    dd if=/dev/mtd${i} 2>/dev/null | uuencode -m -
server$ busybox uudecode mtd0.uue

Below are the commands I used to pull the contents of the camera's flash over to my server so I can take a peek at what is on it. This will take sime time.

$ mkdir -p t
$ expect funlux-cap.exp funluxa.uucp >encode-cap.txt
$ for i in 0 1 2 3 0ro 1ro 2ro 3ro block0 block1 block2 block3
$ do
$     echo "Working on mtd${i}"
$     cat encode-cap.txt | perl ~/dev/perl/ "mtd${i}\$?" | perl ~/dev/perl/ '^$' >t/mtd${i}.uue
$ done
$ pushd t
$ for i in *
$ do
$     busybox uudecode $i
$ done
$ ls -lh
$ popd


Jack Sparrow: You know, for all that pirates are clever-called, we are an unimaginative lot when it comes to naming things.
Gibbs: [nods] Aye.
Jack Sparrow: I once sailed with a geezer lost both his arms and part of his eye.
Gibbs: What did you call him?
Jack Sparrow: [pause] Larry.
Disney's - Pirates of the Caribbean: At World's End (2007)

... Same goes for programmers, scientists and engineers. ;-) In case anyone's wondering if the above is efficient. It is, it's an efficient use of my time rather than efficient use of resources. Though one day I will figure out a nice way of doing the needed parsing of the file just and outputting the results to the correct files. For now I just need to think about that. I also do a lot of line but line string comparison (that's what begin and are doing. Again that's fine for small files but I'm doing a lot more work with 20G+ files and that becomes very tedious.


All the software listed here is GPLv2 Licensed.


So where do we go from here?

Well I know I need to do a bit more digging but I am hoping that I can get something like a Raspberry Pi serving up the video and images. I'm pretty sure it wouldn't take much to get the Pi serving rstp streams. I don't know about the audio yet but I have a trace with audio being sent and received. The rstp streams will at least let me use Zoneminder.