Poking around the Trendnet TV-IP110 and adding a remote shell

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/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

Starting Nmap 7.60 ( https://nmap.org ) at 2022-12-30 19:19 CST
Nmap scan report for TV-IP110.home.hydrogen18.com (
Host is up (0.0050s latency).
Not shown: 65534 closed ports
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 

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)
        end = gzip_offsets[i+1]

    j = end + 1
    while j > gzip_offset:
        j -= 1

        cut = BytesIO(data[gzip_offset:j])
        failed = False
            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


        print("GZIP range is 0x%x : 0x%x" % (gzip_offset,j,))
        fname = "output_%d.bin" % (i,)
        with open(fname, 'wb') as fout:

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

 trendnet_tv_ip110_firmware_filesystem.tar.gz 1.6 MB

The device filesystem 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">

So 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

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 15566
Connected to
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.

 FW_TV-IP110_1.1.2-73_20130521_r1283_telnet.pck 2.7 MB

The stock firmware with telnet enabled


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="">
    <param name="mode" value="1">

    <embed type="application/x-java-applet" \="" code="ultracam.class" archive="ultracam.jar" name="ucx" width="320" height="240" accountcode="YWRtaW46YWRtaW4=" codebase="" mode="1" scriptable="false" pluginspage="http://java.sun.com/products/plugin/index.html#download">



I found 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=' > 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

 ultracam.jar.src.zip 8.5 kB

Decompiled source code of the ultracam applet


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 ps and 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 *               LISTEN      
tcp        0      0    *               LISTEN      
tcp        0      0 *               LISTEN      
udp        0      0 *                           
udp        0      0*                           
udp        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.

Copyright Eric Urban 2022, or the respective entity where indicated