Netgear Nighthawk R7800 : add USB camera support to create a security webcam

Posted on Wed 22 November 2017 in Article

This article explains how to customize Nighthawk X4S firmware to add a security camera feature to this always-online & almost-always-idle device. Alternative firmwares like OpenWRT or LEDE exist, but they don't fully support all stock features yet. So instead this approach is based on modified stock firmware.

Netgear Nighthawk X4S Serious webcam

Main steps are:

  • Customize kernel to add USB video support (uvc, v4l2)
  • Install additional software packages for motion detection
  • Configure motion detection alerts

#YOLO

There's always a risk of bricking the device if something goes wrong. However, a recovery procedure via TFTP exists.

Software downloads are performed over HTTP, due to client limitation on target side.

Hardware

Root that firmware

I first thought that this step would be a pain, but then reminded the device manufacturer name. By grepping 'telnet' in the firmware binary, we discover the existence of a debug page /debug.htm , with telnet option:

R7800 debug webpage

Telnet access is protected with the same password as WebUI, and gives a root shell.

Backup all the things

Thanks to telnet access, we backup the original kernel partition on an external USB drive :

$ telnet 192.168.1.1
Trying 192.168.1.1...
Connected to 192.168.1.1.
Escape character is '^]'.
 === LOGIN ===============================
  Please enter your password,It's the same
  with DUT login password
 ------------------------------------------
telnet password:JCVD4l1FE
=== IMPORTANT ============================
 Use 'passwd' to set your login password
 this will disable telnet and enable SSH
------------------------------------------


BusyBox v1.4.2 (2017-08-29 13:01:25 CST) Built-in shell (ash)
Enter 'help' for a list of built-in commands.

     MM           NM                    MMMMMMM          M       M
   $MMMMM        MMMMM                MMMMMMMMMMM      MMM     MMM
  MMMMMMMM     MM MMMMM.              MMMMM:MMMMMM:   MMMM   MMMMM
MMMM= MMMMMM  MMM   MMMM       MMMMM   MMMM  MMMMMM   MMMM  MMMMM'
MMMM=  MMMMM MMMM    MM       MMMMM    MMMM    MMMM   MMMMNMMMMM
MMMM=   MMMM  MMMMM          MMMMM     MMMM    MMMM   MMMMMMMM
MMMM=   MMMM   MMMMMM       MMMMM      MMMM    MMMM   MMMMMMMMM
MMMM=   MMMM     MMMMM,    NMMMMMMMM   MMMM    MMMM   MMMMMMMMMMM
MMMM=   MMMM      MMMMMM   MMMMMMMM    MMMM    MMMM   MMMM  MMMMMM
MMMM=   MMMM   MM    MMMM    MMMM      MMMM    MMMM   MMMM    MMMM
MMMM$ ,MMMMM  MMMMM  MMMM    MMM       MMMM   MMMMM   MMMM    MMMM
  MMMMMMM:      MMMMMMM     M         MMMMMMMMMMMM  MMMMMMM MMMMMMM
    MMMMMM       MMMMN     M           MMMMMMMMM      MMMM    MMMM
     MMMM          M                    MMMMMMM        M       M
       M
 ---------------------------------------------------------------
   For those about to rock... (%C, %R)
 ---------------------------------------------------------------
root@R7800:/# cat /proc/mtd | grep kernel
mtd5: 00220000 00020000 "kernel"

root@R7800:/# dd if=/dev/mtdblock5 of=/mnt/sda1/kernel.img
4352+0 records in
4352+0 records out

Kernel

Netgear has released GPL source code for the Linux kernel used in this device. This copy on GitHub integrates few fixes to compile with newer GCC and V4L2 headers, and also the original kernel configuration dumped from live device.

$ git clone https://github.com/frederic/netgear-R7800-GPL.git
$ cd netgear-R7800-GPL/

Build mkimage tool

The mkimage tool is used at the end of kernel building process to create the new kernel image partition for the device.

$ make tools/mkimage/install
 make[1] tools/mkimage/install
 make[2] -C tools/sed compile
 make[2] -C tools/sed install
 make[2] -C tools/mkimage compile
 make[2] -C tools/mkimage install

Add freshly built mkimage binary to our PATH environment:

$ export PATH=$PWD/build_dir/host/u-boot-2012.04.01/tools:$PATH

Build Linux kernel

To build a kernel for this ARM-based device, we need to add a cross-compilation toolchain to our build environment :

$ git clone https://android.googlesource.com/platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.8
$ export PATH=$PWD/arm-eabi-4.8/bin:$PATH

Then we create a new kernel configuration based on the original one :

$ cd git_home/linux.git/sourcecode/
$ make r7800_defconfig ARCH=arm CROSS_COMPILE=arm-eabi-
$ make menuconfig ARCH=arm CROSS_COMPILE=arm-eabi-

In the kernel configuration menu, we enable the following options to get V4L2 & USB Video support :

Device Drivers --->
-> <*> Multimedia support --->
--> <*> Video For Linux
--> <*> Video capture adapters --->
---> <*> V4L USB devices --->
----> <*> USB Video Class (UVC)
----> <*> GSPCA based webcams

For info, that should correspond to these options :

CONFIG_USB_VIDEO_CLASS=y
CONFIG_USB_GSPCA=y
CONFIG_VIDEO_V4L2_COMMON=y
CONFIG_VIDEO_V4L2=y
CONFIG_V4L_USB_DRIVERS=y

Finally, we build the kernel image :

$ make -j8 uImage ARCH=arm CROSS_COMPILE=arm-eabi-
[...]
  Kernel: arch/arm/boot/Image is ready
  Kernel: arch/arm/boot/zImage is ready
  UIMAGE  arch/arm/boot/uImage
Image Name:   Linux-3.4.103
Created:      Mon Jan 29 23:58:38 2018
Image Type:   ARM Linux Kernel Image (uncompressed)
Data Size:    2210952 Bytes = 2159.13 kB = 2.11 MB
Load Address: 41508000
Entry Point:  41508000
  Image arch/arm/boot/uImage is ready

Output kernel image is arch/arm/boot/uImage

$ file arch/arm/boot/uImage
arch/arm/boot/uImage: u-boot legacy uImage, Linux-3.4.103, Linux/ARM, OS Kernel Image (Not compressed), 2210952 bytes, Tue Jan 30 07:58:38 2018, Load Address: 0x41508000, Entry Point: 0x41508000, Header CRC: 0x81013485, Data CRC: 0xDEA9B00E

In current firmware, kernel partition size is 2228224 bytes. So the new kernel image cannot be larger.

We copy that image to a USB drive and then, from the telnet shell, we overwrite original kernel with the new image :

root@R7800:/# dd if=/mnt/sda1/uImage of=/dev/mtdblock5
root@R7800:/# sync
root@R7800:/# reboot

Note: You have to enable telnet in WebUI after each reboot.

Now the router detects USB camera when plugged in :

usb 3-1: new high-speed USB device number 5 using xhci-hcd
usb 3-1: New USB device found, idVendor=1908, idProduct=2310
usb 3-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3
usb 3-1: Product: USB2.0 PC CAMERA
usb 3-1: Manufacturer: Generic
usb 3-1: SerialNumber: 20100331010203
INFO008C: Add device intf d39cc400, dev d39a5000
INFO0C15:  filter audio device
uvcvideo: Found UVC 1.00 device USB2.0 PC CAMERA (1908:2310)
input: USB2.0 PC CAMERA as /devices/platform/ipq-dwc3.1/dwc3.1/xhci-hcd.1/usb3/3-1/3-1:1.0/input/input3

Install software packages

"Motion is a program that monitors the video signal from cameras. It is able to detect if a significant part of the picture has changed; in other words, it can detect motion."

Fortunately, this package is available in OpenWrt repositories for our architecture. And stock firmware includes the OpenWrt package manager opkg.

However, opkg is outdated and needs to be patched first :

root@R7800:/# curl -k https://gist.githubusercontent.com/frederic/fcb7ddc14c46aa630143aaeafe2d706f/raw/8242c18a514f19b58c74387d4bfa0e5511bbb4e5/functions.sh -o '/lib/functions.sh'

Then, we can install motion & libjpeg packages :

root@R7800:~# curl -k https://raw.githubusercontent.com/frederic/netgear-R7800-GPL/master/ipk/libjpeg_9a-1_ipq806x.ipk -o /tmp/libjpeg_9a-1_ipq806x.ipk
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 92994  100 92994    0     0   259k      0 --:--:-- --:--:-- --:--:--  430k
root@R7800:~# opkg install /tmp/libjpeg_9a-1_ipq806x.ipk 
Installing libjpeg (9a-1) to root...
Configuring libjpeg.


root@R7800:~# curl -k https://raw.githubusercontent.com/frederic/netgear-R7800-GPL/master/ipk/motion_3.4.0-20141018-9479d910f2149b5558788bb86f97f26522794212-1_ipq806x.ipk -o /tmp/motion_3.4.0-20141018-9479d910f2149b5558788bb86f97f26522794212-1_ipq806x.ipk
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 94802  100 94802    0     0   297k      0 --:--:-- --:--:-- --:--:--  387k
root@R7800:~# opkg install /tmp/motion_3.4.0-20141018-9479d910f2149b5558788bb86f97f26522794212-1_ipq806x.ipk
Installing motion (3.4.0-20141018-9479d910f2149b5558788bb86f97f26522794212-1) to root...
Configuring motion.

Note that motion software expects its default configuration file at different path than the one actually provided. So we move it back to default path :

root@R7800:/# mkdir /etc/motion
root@R7800:/# mv /overlay/etc/motion.conf /etc/motion/motion.conf

Configuration

To enable motion detection only when our smartphone (hence we) is not at home, we call our custom script in Hostapd script /lib/wifi/wps-hostapd-update-uci. It will only be triggered when a station connects or disconnects from the router.

diff --git a/lib/wifi/wps-hostapd-update-uci b/lib/wifi/wps-hostapd-update-uci
index f60abe3..dd6f3f2 100755
--- a/lib/wifi/wps-hostapd-update-uci
+++ b/lib/wifi/wps-hostapd-update-uci
@@ -157,6 +157,9 @@ check_ap_lock_down()
 }

 case "$CMD" in
+        AP-STA-CONNECTED|AP-STA-DISCONNECTED)
+                /etc/motion_cron.sh $CMD $3
+        ;;
        WPS-NEW-AP-SETTINGS|WPS-NEW-AP-SETTINGS-AP-PIN)
                local ssid=$(hostapd_cli -i$IFNAME -p/var/run/hostapd-$parent get_config | grep ^ssid= | cut -f2- -d =)
                local wpa=$(hostapd_cli -i$IFNAME -p/var/run/hostapd-$parent get_config | grep ^wpa= | cut -f2- -d=)

We create the following script in /etc/motion_cron.sh :

#!/bin/sh
# Start or stop motion service depending on wifi client status
# Usage: motion_cron.sh [<event_type> <mac addr>]

HWADDR=00:11:22:33:44:55 # Update with your smartphone MAC address

CONNECTED=0
CMD=motion
CMD_PID=/var/run/$CMD.pid
CMD_BIN=/usr/bin/$CMD
PID=`pidof $CMD`

if [ $# -eq 2 ]; then
 EVENT=$1
 EVENT_MAC=$2
 if [ "$EVENT_MAC" == "$HWADDR" ]; then
  case "$EVENT" in
    AP-STA-DISCONNECTED)
      CONNECTED=0
      ;;
    AP-STA-CONNECTED)
      CONNECTED=1
      ;;
    *)
      echo "Non-interesting event, ignore..."
      exit 0
  esac
  echo "$EVENT $EVENT_MAC"
 else
  echo "Not our MAC, ignore..."
  exit 0
 fi
else
 if grep "0x2\W\+$HWADDR" /proc/net/arp ; then
  echo "$HWADDR is connected in arp table"
  CONNECTED=1
 else
  echo "$HWADDR is disconnected in arp table"
  CONNECTED=0
 fi
fi

if [ $CONNECTED -eq 1 ]; then
 echo "$HWADDR is connected"
 if [ -n "$PID" ]
 then
      echo "$CMD is running, stopping it..."
      start-stop-daemon -K -x $CMD_BIN -p $CMD_PID
 fi
else
 echo "$HWADDR is disconnected"
 if [ -z "$PID" ]
 then
      echo "$CMD is not running, starting it..."
      start-stop-daemon -S -x $CMD_BIN -- -b -p $CMD_PID
 fi
fi

$HWADDR has to be set to MAC address of our smartphone.

As failsafe mechanism, we can also add this script to cron :

echo "  0 *  *   *   *     /etc/motion_cron.sh" >> /etc/crontabs/root

Finally, we edit the file /etc/motion/motion.conf to configure motion. A guide is available on official motion website. In addition of your own settings, I recommend to set the output storage path to an external USB drive :

target_dir /mnt/sda1