Your Security Cameras are Snitching on You…

... and if you’re a Hacker then here’s what you can do about it.

Late last year, we moved from suburban Orange County CA to rural TX. One of the things we immediately worked on was establishing security cameras around our property. Granted we’re many miles out in the country, and you can see the Milky Way on a good night, but there is still property crime out here.

The 4K camera system we picked up is very good. The cameras have high fidelity, and a bunch of configurable features like sub-streams, alerts, and motion detection, but there was one glaring problem…. they are constantly snitching on us by uploading data to the cloud.

When setting up the system online I explicitly checked boxes not to backup to “the cloud” or use any cloud-based services, however we did intend to use the associated viewer app to watch the video remotely. I naturally assumed that the uploading of data would only happen when we requested it from our phones…. well, I was wrong.

One while running tcpdump on the camera network I saw a litany of data uploading to Amazons S3 and EC2 services when the app was closed. Upon further inspection, it was camera and meta data. No point in attaching a pic of that data, imagine billions of bytes of data going outbound to a set of specific hosts (seen below).

This is problematic for a number of reasons. Privacy aside, it was tanking our upstream bandwidth on our already struggling rural radio-based Internet service.

Step 1: JAIL

So this is where the Linux Kernel netfilter extensions are your best friend. By adding another NIC, and establishing an additional network on a linux-based router box, you can segregate the traffic on the video network the Internet.

With a proper networking script (see Appendix), you can ensure that the router acts as a “one way diode”, aka connections can be initiated inwards from your home LAN (and later described a web service on a VPN), but all traffic initiated from the “cam-net” side is dropped.

When done right, that TCP dump (below) looks like just a bunch of failed DNS requests to the naughty services it used to talk to:

Great, so now my cams are secure, but with the DVR jailed, how do we view the videos? The next section will describe some options on how to view these videos.

Step 2: Custom Viewers

Before we get into the camera options, let’s understand the protocol these cameras use: RTSP. From my research, many/most cameras use this protocol. It’s good at what it does; stream high quality video to a DVR/NVR device.

The problem is that it’s a huge pain in the ass to work with for web and mobile apps.

I’m going to detail two options I built, one is for your LAN at home, and the other for mobile devices and remove viewing.

LAN Viewing

In this configuration, you can view the cameras in a higher resolution, and with better quality. In this case a sub-stream is used at 720p at 15fps and (5/6) compression quality. Something that saved me a ton of time was to use VLC as a testing client. This is good for viewing a camera-at-a-time, and making sure the quality is sufficient, even carrying around the laptop around the house to precision aim the devices.

To do this, goto: File > Open Network

The downside of just using VLC was that it does not cleanly support a grid view. I spent a few hours trying to get the “4-up” VLC viewer working, but it was incredibly kludgy, as it was combining streams into a single stream and it certainly could not do 16 channels.

After almost 2 dozen hours of research, testing all kinds of RTSP libraries, and even considering paying for some non-open-source solutions, I realized the best solution was to use the VLC Plugin for Firefox. With a few dozen lines of HTML code (and the old CSS “display: inline-block” hack) + the VLC plugin, I was able to build a highly capable high speed viewer for the house.

It’s important to note that Chrome, and Firefox have disabled support for plugins such as the VLC plugin due to security concerns. I agree with this, however the use of this specific viewer browser is for only one thing; VLC viewing on a secured LAN. I would never surf the broader web with this browser.

The only downside to this technique is that Firefox 46 wants to auto-upgrade to the latest (most secure) version, so you have to dig into the settings and disable that.

Goto about:config, and set app.auto.update to false

Remote Viewing

No, I’m not talking about ESP, I’m referring to viewing your cams while you are on the beach in the Bahamas, or in line at Disneyland. Is there a package on my porch, a weird car in my driveway, a tweaker on my back porch, or a herd of deer foraging on the back 40?

There were two distinct pieces: 
1) a web-based service in node.js which converts the RTSP stream from a given camera into a websocket stream, and 
2) a thin client-side library to convert this stream to a <canvas> based viewer.

This portion of the project took the most time. Because I was trying all angles, mobile apps, perhaps a Cordova plugin, or ideally something that can run lightweight in the browser. Additionally, the server-side tweaks to get ffmpeg to deliver the right stuff too many tries and many hours. (see Appendix)

After testing half a dozen projects, I found many of them were incomplete, and/or bit-rotted. Then I found several projects, which when plugged together, worked for me perfectly. Namely node-rtsp-stream-es6 which converts RTSP to websocket frames, and jsmpeg which “consists of an MPEG-TS demuxer, MPEG1 video & MP2 audio decoders, WebGL & Canvas2D renderers and WebAudio sound output”.

The outcome is “pretty good fidelity”. The codecs are ancient, and presumably are not the best when losing frames and there can beoccasional artifacts on “bad Internet weather” days, however it’s a very lightweight solution and just what the Doctor ordered.

I’m not going to post the code online (for now) but will reveal the few hacks I had to do to get it done.

  1. It was necessary to fork the node rtsp stream library, because it had hard-coded magic variables used during the callout to ffmpeg which were not optimal for my camera setup. Since all cameras presumably have varied optimal settings, it’s ideal to just put your own in there. What I did was to tune it to fit my cams.
  2. The second major hack was to get the “channel changing” call from the UI to block until there was actually data going down the web socket. This enabled the UI to show a “spinning hamster wheel” icon while it loads, because there is an unavoidable 5–7 second lag to spin up the stream. Below is the snippet for doing so. Basically after looking at the events the library emitted, we had to capture the on camdata event. (see Appendix)
  3. On your public web-service, make sure it has a vpn connection to our linux router. On the Linux router, run the below iptables script which will deal with the port-forwarding and

Appendix — Key Hacks

//-= For the channel change event, wait for data before responding
app.get(“/channel/:number”, (req, res) => {
stream.url = “rtsp://user:pass@192.168.1.”+ +req.params.number +”:554/cam/realmonitor?channel=1&subtype=1";
stream.start();
    //-= Using camdata event, on the first frame send our 204
const watcher = (data) => {
res.status(204).end();
stream.removeListener(“camdata”, watcher);
};
    stream.on(“camdata”, watcher);
});

Change the ffmpeg settings:

//-= in the node rtsp lib, slight tunings required
- this.stream = child_process.spawn(“ffmpeg”, [“-rtsp_transport”, “tcp”, “-i”, this.url, ‘-f’, ‘mpeg1video’, ‘-b:v’, ‘180k’, ‘-r’, ‘30’, ‘-’], {
+ let args = (“-rtsp_transport tcp -i “+ this.url +” -f mpegts -codec:v mpeg1video -bf 0 -codec:a mp2 -r 24 -”).split(/ /);
+ this.stream = child_process.spawn(“ffmpeg”, args, {

For the firewall rules:

# a netfilter script which employs stateful connection tracking where:
# I’ll call you, and you don’t call ANYONE
#!/bin/bash
echo 1 > /proc/sys/net/ipv4/ip_forward
/sbin/iptables -t nat -F
/sbin/iptables -t mangle -F
/sbin/iptables -F
/sbin/iptables -X
/sbin/iptables -P INPUT ACCEPT
/sbin/iptables -P FORWARD DROP
/sbin/iptables -P OUTPUT ACCEPT
# Regular LAN access (enp1s0) via enp4s0 which is on the public Internet
/sbin/iptables -t nat -A POSTROUTING -o enp4s0 -j MASQUERADE
/sbin/iptables -A FORWARD -i enp4s0 -o enp1s0 -m state — state RELATED,ESTABLISHED -j ACCEPT
/sbin/iptables -A FORWARD -i enp1s0 -o enp4s0 -j ACCEPT
# Allow LAN to access “cam-net” enp2s0
/sbin/iptables -t nat -A POSTROUTING -o enp1s0 -j MASQUERADE
/sbin/iptables -A FORWARD -i enp1s0 -o enp2s0 -j ACCEPT
/sbin/iptables -A FORWARD -i enp2s0 -o enp1s0 -m state — state RELATED,ESTABLISHED -j ACCEPT
# Allow you VPN connection tun0 to initiate RTSP streams to cam-net
/sbin/iptables -t nat -A POSTROUTING -o tun0 -j MASQUERADE
/sbin/iptables -A FORWARD -i tun0 -o enp2s0 -j ACCEPT
/sbin/iptables -A FORWARD -i enp2s0 -o tun0 -m state — state RELATED,ESTABLISHED -j ACCEPT
# port-forward camera RTSP ports via our single tun0 ip address
/sbin/iptables -t nat -A PREROUTING -i tun0 -p tcp — dport 55410 -j DNAT — to-destination 10.0.0.1:554
/sbin/iptables -t nat -A PREROUTING -i tun0 -p tcp — dport 55420 -j DNAT — to-destination 10.0.0.2:554
/sbin/iptables -t nat -A PREROUTING -i tun0 -p tcp — dport 55430 -j DNAT — to-destination 10.0.0.3:554
# ... up to "n" cameras
Like what you read? Give Dave Gullo a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.