I bought this camera off eBay. It did not come with all the original accessories. This model only supports ethernet connectivity. I was able to restore it to factory settings by holding down reset button for 15 seconds. After that, the login is
admin. Next I set out to figure out how to get a remote shell on this device.
Initial investigation & firmware analysis
I port scanned the device and found only http open.
$ nmap -A -p1-65535 192.168.166.212 Starting Nmap 7.60 ( https://nmap.org ) at 2022-12-30 19:19 CST Nmap scan report for TV-IP110.home.hydrogen18.com (192.168.166.212) Host is up (0.0050s latency). Not shown: 65534 closed ports PORT STATE SERVICE VERSION 80/tcp open http TRENDnet TV-IP100 or TV-IP110 webcam display httpd | http-auth: | HTTP/1.1 401 Unauthorized\x0D |_ Basic realm=netcam |_http-title: Site doesn't have a title (text/html). Service Info: Device: webcam; CPE: cpe:/h:trendnet:tv-ip110
There are no obvious alternative methods of gaining access other than the HTTP server. I was able to download the firmware & ran binwalk on it. That identified a few important sections.
$ binwalk ~/trendnet_ip_110/FW_TV-IP110_1.1.2-73_20130521_r1283.pck DECIMAL HEXADECIMAL DESCRIPTION -------------------------------------------------------------------------------- 14920 0x3A48 CRC32 polynomial table, little endian 15948 0x3E4C CRC32 polynomial table, little endian 21088 0x5260 Linux kernel ARM boot executable zImage (little-endian) 32320 0x7E40 gzip compressed data, maximum compression, from Unix, last modified: 2013-05-21 03:52:56 679136 0xA5CE0 gzip compressed data, maximum compression, has original file name: "rootfs", from Unix, last modified: 2013-05-21 03:52:58
The last two entries appear to be GZIP decompressed data. After a bit of messing around I managed to decompress them. The firmware image seems to contain a huge amount of padding in it. So I used this python script to expedite the process of extracting the GZIP compressed data that
binwalk so helpfully found for me
import os import sys import gzip from io import BytesIO FILENAME = os.environ.get('FILENAME', 'FW_TV-IP110_1.1.2-73_20130521_r1283.pck') with open(FILENAME, 'rb') as fin: data = fin.read() data = memoryview(data) gzip_offsets = [0x7e40, 0xa5ce0] for i, gzip_offset in enumerate(gzip_offsets): if i == len(gzip_offsets) - 1: end = len(data) else: end = gzip_offsets[i+1] j = end + 1 while j > gzip_offset: j -= 1 cut = BytesIO(data[gzip_offset:j]) failed = False try: output = gzip.open(cut).read() except gzip.BadGzipFile: failed = True except EOFError: failed = True if failed: failed_at = gzip_offset + cut.tell() print("failed at 0x%x" % (failed_at,)) if failed_at != j: j = failed_at continue continue print("GZIP range is 0x%x : 0x%x" % (gzip_offset,j,)) fname = "output_%d.bin" % (i,) with open(fname, 'wb') as fout: fout.write(output) break
All this does is start from the offsets and work its way backwards until it gets a valid decompression. GZIP is fairly robust and generally won't decompress garbage input into garbage output.
I'm relatively confident the first one is just a Linux 2.4.19 kernel image. I was expecting the second one to be some sort of common image format for a filesystem, but apparently it is not. However, I just asked the
mount command to mount the filesystem and it worked! Looking at my mounted filesystems I see this
/home/ericu/trendnet_ip_110/output_1.bin on /mnt/tmp type minix (rw,relatime)
So apprently it is a "minix" filesystem. This gave me an idea of all the various software running on the device. If you'd like to skip all those steps and just analyze the filesystem yourself, here is a tarball of the filesystem I extracted from the firmware
Attempt 1: the camera's configuration profile
Like many webcams, this camera supports saving and restoring a configuration profile. I assumed this would bea good place to start to try and get a remote shell
I found a file
server/cgi-bin/tools.asp that has the configuration save & restore functionality. It contains this HTML form element
form action="restore.cgi" method="post" enctype="multipart/form-data" name="formrestore">
restore.cgi is whatever is responsible for restoring this configuration. Unfortunately this is not just some script I can casually inspect. I ran
strings on the executable and found these strings
/tmp/config1.tgz writing /tmp/config1.tgz cd /tmp; tar zxvf config1.tgz 1>/dev/null 2>/dev/null rm -f /tmp/config1.tgz
There is also a file
/etc/rc.d/rcS.d/S06initcfg that contains this as part of its start procedure
/bin/bkcfg -r /tmp/config.tgz cd /tmp; tar zxvf config.tgz
So it seems obvious that the configuration profile is somehow a tar file that gets restored on each startup. Based on the presence of some symbol names like
cfgAesDecode I'm relatively certain that the profile is encrypted. Reverse engineering this would be excellent chance to develop my skills, but I am afraid I do not have time for that.
Attempt 2 - just enable the telnet server already there
In the filesystem there is a file
/etc/init.d/daemon.sh, it contains these commented out shell script lines
# echo "Starting telnetd ..." # telnetd -p 15566
The decompressed filesystem is a "minix" type image, which I have no idea how to create. So what I did was just used a hex editor to change those
# characters to spaces. I then recompressed the filesystem image with
gzip and reassembled the firmware image. I used the web interface to upload my customized image. About a minute later, the device apparently rebooted. At this time I was able to telnet to the device!
$ telnet 192.168.166.212 15566 Trying 192.168.166.212... Connected to 192.168.166.212. Escape character is '^]'. BusyBox v1.01 (2012.05.30-05:39+0000) Built-in shell (ash) Enter 'help' for a list of built-in commands. / # cat /proc/cpuinfo Processor : Faraday FA526id(wb) rev 1 (v4l) BogoMIPS : 147.56 Features : swp half Hardware : Prolific ARM9v4 - PL1029 Revision : 0000 Serial : 0000000000000000 / #
This is apparently an ARM v9 CPU with a whopping 16 megabytes of RAM. You do need to have the user name and pasword to perform this trick, so I can't really say this counts as a vulnerability.
If you would like this "enhanced" image to use on your device, here you go. Keep in mind the telnet server on this is completely wide open with not authorization whatsoever.
In the admin interface, the video view does not work at all.
Looking at the HTML it tries to embed a Java applet like this
<object classid="clsid:CAFEEFAC-0015-0000-0012-ABCDEFFEDCBA" codebase="http://java.sun.com/update/1.5.0/jinstall-1_5_0_12-windows-i586.cab#Version=5,0,120,4" width="320" height="240" name="ucx"> <param name="CODE" value="ultracam.class"> <param name="ARCHIVE" value="ultracam.jar"> <param name="NAME" value="ucx"> <param name="type" value="application/x-java-applet;jpi-version=1.5.0_12"> <param name="scriptable" value="false"> <param name="accountcode" value="YWRtaW46YWRtaW4="> <param name="codebase" value="http://192.168.166.212:80/"> <param name="mode" value="1"> <comment> <embed type="application/x-java-applet" \="" code="ultracam.class" archive="ultracam.jar" name="ucx" width="320" height="240" accountcode="YWRtaW46YWRtaW4=" codebase="http://192.168.166.212:80/" mode="1" scriptable="false" pluginspage="http://java.sun.com/products/plugin/index.html#download"> <noembed> </noembed> </comment> </object>
ultracam.jar in the filesystem and decompiled it from the class files. This apparently contains a single class. It isn't obfuscated in anyway that I can tell. It's just written as one class. I found this line in the source code
URL uRL = new URL("http", this.RemoteHost, Integer.parseInt(this.RemotePort), "/cgi/mjpg/mjpeg.cgi");
There is a bunch of other Java code that does HTTP Basic Authorization. If you run this curl command it will capture into a file forever
curl --header 'Authorization: Basic YWRtaW46YWRtaW4=' http://192.168.166.212/cgi/mjpg/mjpeg.cgi > mjpeg.capture
I picked out one of the frames manually and captured this fuzzy image of my backdoor (the camera has manual focus only)
I was expecting this device to support RTSP. There are various hints on the filesystem about it, but I can't find anyway to enable it through the web interface. If you want the source code of the "ultracam" applet, here it is
This camera is actually pretty good from a security perspective for a nearly decade old camera. It has similar features to other cameras on the market from that time period. While I was able to gain a remote shell, I would need to have the administrator username & password on a device to do this. The developers seemed to do a pretty good job of not leaving the front door wide open. I tried numerous other places in the software to do simple tricks like shell injection but was unable to get that to work.
I obviously don't recommend someone go buy a 10 year old device, but if you're using one of these it probably isn't a big deal at all. Just make sure you change the default username and password to something that cannot be easily guessed.
If I am able to dedicate more time to the analysis of this device, the next avenue of attack would be to see if any of the services running on the device have vulnerabilities. Since I have a shell on the device, I was able to run
netstat after enabling dynamic DNS & UPnP via the web administration interface.
/ # netstat -lntu Active Internet connections (only servers) Proto Recv-Q Send-Q Local Address Foreign Address State tcp 0 0 0.0.0.0:15566 0.0.0.0:* LISTEN tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN tcp 0 0 0.0.0.0:57146 0.0.0.0:* LISTEN udp 0 0 0.0.0.0:62720 0.0.0.0:* udp 0 0 192.168.166.212:1900 0.0.0.0:* udp 0 0 0.0.0.0:1900 0.0.0.0:* / # ps PID Uid VmSize Stat Command 1 root 344 S init 2 root SW [keventd] 3 root RWN [ksoftirqd_CPU0] 4 root SW [kswapd] 5 root SW [bdflush] 6 root SW [kupdated] 48 root 408 S /sbin/hwmon 53 root 408 S /sbin/hwmon 54 root 408 S /sbin/hwmon 55 root 408 S /sbin/hwmon 58 root 408 S /sbin/hwmon 141 root 348 S /sbin/udhcpc -i eth0 -H TV-IP110 -p /var/run/udhcpc.pid 183 root 356 S /bin/sh /etc/rc.d/init.d/ntpc.sh 10 184 root 356 S /bin/sh /etc/rc.d/init.d/upnp_igdd.sh startdelay 195 root 332 S /sbin/syslogd -C -m 0 207 root 1064 S ./camsvr 210 root 1064 S ./camsvr 211 root 1064 S ./camsvr 232 root 452 S ./httpd 80 239 root 440 S /sbin/upnp 244 root 308 S /sbin/ipfind 246 root 284 S telnetd -p 15566 251 root 208 S /sbin/wdogmon -keepchk=6 253 root 396 S -ash 310 root 408 S /bin/sh 349 root 360 S /bin/sh /etc/rc.d/init.d/updatedd.sh restart eurodyndns user:foo somedomain.com 367 root 228 S sleep 120 517 root 228 S sleep 60 557 root 228 S sleep 60
Due to the extremely low RAM of this device, most of the processes appear to be completely custom implementations. The UPnP server and the HTTP server both have listening TCP sockets. It's possible the HTTP parsing implementation has a weakness in it that can be exploited.