These are random notes descibing how I changed my D-Link DCS-8000LH
from a cloud camera to a locally managed IP camera, streaming H.264
MPEG-TS over HTTP and HTTPS. Some of the tools and ideas might work
for other cameras too, given some model specific adaptation.
Complete defogging requires modifying one of the file systems in the
camera. This implies a slight risk of ending up with a brick. You
have now been warned…
This is tested and developed on firmware versions v2.01.03 and
v2.02.02 only. The final complete procedure has only been tested with
v2.02.02. It should work fine with v2.01.03 and other versions, in
theory, but could fail like anything untested. Please let me know if
you have an original v2.01.03 firmware update from D-Link, or any
other version for that matter, or know where firmware updates can be
downloaded.
The v2.02.02 update is available from
https://mydlinkmpfw.auto.mydlink.com/DCS-8000LH/DCS-8000LH_Ax_v2.02.02_3014.bin
at the time of writing. But I assume this link stops working as soon
as there is a newer version available.
Changelog
- v0.01 (20190515) – initial published version
- v0.02 (20190515) – added RTSP support and information
Problem
My D-Link DCS-8000LH came with firmware version 2.01.03 from factory.
This firmware is locked to the mydlink
app/cloud service. It does not provide a local NIPCA compatible HTTP
API or similar, and it does not stream video over HTTP, HTTPS or RTSP.
Additionally, there is no way to downgrade the firmware. In fact,
there is no documented way to install any firmware image at all,
except trusting the “mydlink” cloud service to do it for you.
Solution
Primary goals achieved:
- configuration of network and admin password via Bluetooth LE, without
registering with D-Link or using the mydlink app at all - streaming MPEG-TS directly from camera over HTTP and HTTPS
- direct RTSP streaming
- NIPCA API configuration over HTTP and HTTPS, supporting settings
like LED, nightmode, etc
And some extra goodies which came for free
- Firmware upgrades and downgrades via HTTP
- telnet server with a root account (admin/PIN Code)
- easy access to serial console, using the same root account
- running arbitrary commands on the camera using Bluetooth
Read on for all the gory details…
Requirements
- a Linux PC with a Bluetooth controller
- python3 with @IanHarvey’s
bluepy
library - WiFi network with WPA2-PSK and a known password
- mksquashfs from the squashfs-tools package
- a tftp server or web server accepting file uploads (for backups)
- guts :-)
Most recent Linux distros will probably do. The bluepy library can be
installed using pip if it is not available as a distro package. Other
types of WiFi networks might work, but has not been tested with the
provided tools. The squashfs-tools are only necessary if you want to
rebuild the “mydlink” alternative file system. I assume you can even
run the tools without installing Linux, by using a Linux “Live”
CD/DVD/USB stick.
This was developed and tested on Debian Buster.
Camera configuration using the Bluetooth LE GATT API
The “mydlink” app uses Bluetooth LE for camera setup, authenticated by
the camera pincode. This repo includes an alternative python script
with a few extra goodies, but needing a better name:
dcs8000lh-configure.py
(Why not an Android app? Because it would take me much more time to
write. Should be fairly easy to do though, for anyone with enough
interest. You can find all the necessary protocol details here and in
the python code. Please let me know if you are interested)
The script does not support scanning for the simple reason that this
would require root access for not real gain. You have to provide the
PIN Code from the camera label
anyway. Reading the MAC ID as well is simple enough
The PIN Code and MAC is also printed on the code card that
came with the camera:
Note that the command line address paramenter must be formatted as
01:23:45:67:89:AB instead of the 0123456789AB format printed
on the label.
Current script help text at the time of writing shows what the script
can do:
$ ./dcs8000lh-configure.py -h
usage: dcs8000lh-configure.py [-h] [--essid ESSID] [--wifipw WIFIPW]
[--survey] [--netconf] [--sysinfo]
[--command COMMAND] [--telnetd] [--lighttpd]
[--rtsp] [--unsignedfw] [--attrs] [-V]
address pincode
IPCam Bluetooth configuration tool.
positional arguments:
address IPCam Bluetooth MAC address (01:23:45:67:89:AB)
pincode IPCam PIN Code (6 digits)
optional arguments:
-h, --help show this help message and exit
--essid ESSID Connect to this WiFi network
--wifipw WIFIPW Password for ESSID
--survey List WiFi networks seen by the IPCam
--netconf Print current network configuration
--sysinfo Dump system configuration
--command COMMAND Run command on IPCam
--telnetd Start telnet server on IPCam
--lighttpd Start web server on IPCam
--rtsp Enable access to RTSP server on IPCam
--unsignedfw Allow unsigned firmware
--attrs Dump IPCam GATT characteristics
-V, --version show program's version number and exit
Real session excample after a clean upgrade to firmware v2.02.02, followed by factory reset
- Start by making sure the camera can see our WiFi network. This
also verifies that we can connect and authenticate against the
Bluetooth LE IPCam service, without making any changes to any
camera settings:
$ ./dcs8000lh-configure.py B0:C5:54:AA:BB:CC 123456 --survey
Connecting to B0:C5:54:AA:BB:CC...
Verifying IPCam service
Connected to 'DCS-8000LH-BBCC'
DCS-8000LH-BBCC is scanning for WiFi networks...
{'I': 'AirLink126FD4', 'M': '0', 'C': '11', 'S': '4', 'E': '2', 'P': '47'}
{'I': 'Antiboks', 'M': '0', 'C': '11', 'S': '4', 'E': '2', 'P': '73'}
{'I': 'ASV17', 'M': '0', 'C': '11', 'S': '4', 'E': '2', 'P': '47'}
{'I': 'ASV17-dlink', 'M': '0', 'C': '6', 'S': '4', 'E': '2', 'P': '57'}
{'I': 'DIRECT-33-HP%20ENVY%205000%20series', 'M': '0', 'C': '1', 'S': '4', 'E': '2', 'P': '46'}
{'I': 'fjorde123', 'M': '0', 'C': '1', 'S': '4', 'E': '2', 'P': '55'}
{'I': 'JOJ', 'M': '0', 'C': '11', 'S': '4', 'E': '2', 'P': '48'}
{'I': 'Kjellerbod', 'M': '0', 'C': '11', 'S': '4', 'E': '2', 'P': '75'}
{'I': 'Landskap_24', 'M': '0', 'C': '11', 'S': '4', 'E': '2', 'P': '46'}
{'I': 'mgmt', 'M': '0', 'C': '1', 'S': '4', 'E': '2', 'P': '72'}
{'I': 'Rindedal', 'M': '0', 'C': '11', 'S': '4', 'E': '2', 'P': '68'}
{'I': 'risikovirus', 'M': '0', 'C': '1', 'S': '4', 'E': '2', 'P': '45'}
{'I': 'risikovirus%20WIFI', 'M': '0', 'C': '11', 'S': '4', 'E': '2', 'P': '45'}
{'I': 'Stavik2014', 'M': '0', 'C': '6', 'S': '4', 'E': '2', 'P': '47'}
{'I': 'TomterNett1', 'M': '0', 'C': '6', 'S': '4', 'E': '2', 'P': '44'}
{'I': 'VIF', 'M': '0', 'C': '11', 'S': '4', 'E': '2', 'P': '47'}
Done.
- We’re going to use the ‘Kjellerbod’ network, so that looks good.
Select it and give the associated WiFi password to the camera:
$ ./dcs8000lh-configure.py B0:C5:54:AA:BB:CC 123456 --essid Kjellerbod --wifipw redacted
Connecting to B0:C5:54:AA:BB:CC...
Verifying IPCam service
Connected to 'DCS-8000LH-BBCC'
DCS-8000LH-BBCC is scanning for WiFi networks...
Will configure: M=0;I=Kjellerbod;S=4;E=2;K=redacted
Done.
- Verify that the camera connected to the Wifi network and got an
address. If not, go back and try again, making sure you are using
the correct WiFi password:
$ ./dcs8000lh-configure.py B0:C5:54:AA:BB:CC 123456 --netconf
Connecting to B0:C5:54:AA:BB:CC...
Verifying IPCam service
Connected to 'DCS-8000LH-BBCC'
wifi link is Up
wifi config: {'M': '0', 'I': 'Kjellerbod', 'S': '4', 'E': '2'}
ip config: {'I': '192.168.2.37', 'N': '255.255.255.0', 'G': '192.168.2.1', 'D': '148.122.16.253'}
Done.
WARNING: You must make a backup of your device at this point if
you haven’t done so already. See the Backup section
below. I only skipped it in this example because I already had a
complete backup of my camera.
- We need HTTP NIPCA API for the remaining tasks, so temporarily
start lighttpd on the camera:
$ ./dcs8000lh-configure.py B0:C5:54:AA:BB:CC 123456 --lighttpd
Connecting to B0:C5:54:AA:BB:CC...
Verifying IPCam service
Connected to 'DCS-8000LH-BBCC'
Attempting to run '[ $(tdb get HTTPServer Enable_byte) -eq 1 ] || tdb set HTTPServer Enable_byte=1' on DCS-8000LH-BBCC by abusing the 'set admin password' request
Attempting to run '/etc/rc.d/init.d/extra_lighttpd.sh start' on DCS-8000LH-BBCC by abusing the 'set admin password' request
Done.
Note that this implicitly changes a couple of settings which are
stored in the “db” NVRAM partition, and therefore will persist until
the next factory reset:
- extra_lighttpd.sh will exit without doing anything unless
HTTPServer Enable is set - the admin password is set both because we’re abusing that BLE
request, and because we need it for the HTTP API access. The
script only supports setting the password to the PIN Code.
This password restriction is because I’m lazy – there is nothing in
the camera or protocol preventing the password from being set to
something else. But the script would then need the new password as
an additional input parameter for most commands
- Disable firmware signature verification. Only firmwares signed by
D-Link are accepted by default. This feature can be disabled by
changing a variable in the “db” NVRAM partition:
$ ./dcs8000lh-configure.py B0:C5:54:AA:BB:CC 123456 --unsignedfw
Connecting to B0:C5:54:AA:BB:CC...
Verifying IPCam service
Connected to 'DCS-8000LH-BBCC'
Attempting to run 'tdb set SecureFW _TrustLevel_byte=0' on DCS-8000LH-BBCC by abusing the 'set admin password' request
Done.
- The final step is the dangerous one. It replaces the file system
on the userdata partition with our home cooked one. The D-Link
firmware uses this partition exclusively for the “mydlink” cloud
tools, which we don’t need. The rest of the system is not touched
by our firmware update. The camera will therefore run exactly the
same kernel and rootfs as before the update, whatever version they
were. I.e., the firmware version does not change – only the
“mydlink” version.
NOTE; You need to build a fw.tar firmware
update image first.
$ curl --http1.0 -u admin:123456 --form upload=@fw.tar http://192.168.2.37/config/firmwareupgrade.cgi
upgrade=ok
See the section on error handling if the upgrade request
returned anything else.
The camera will reboot automatically at this point, assuming the
update was successful. From now both with telnetd and lighttpd
running, and with external access to the RTSP server. All services
will use the same admin:PIN Code account for authentication.
So we now have access to direct streaming over HTTP,
HTTPS and RTSP without ever having been in contact with the
mydlink service!
Streaming video locally
Which was the whole point of all this… We can now stream directly
from the camera using for example:
HTTP or HTTPS
vlc https://192.168.2.37/video/mpegts.cgi
vlc https://192.168.2.37/video/flv.cgi
Authenticate using the admin user with PIN Code as password
AFAICS, this camera does not support MJPEG encoding. But you can
always use ffmpeg to transcode the H.264 anyway. Looking closer at a
stream sample:
Direct RTSP access is also supported, using the same admin user.
The RTSP URLs are configurable, so the proper way to use RTSP is to
first check the URL of the wanted profile using the NIPCA API:
$ curl -u admin:123456 --insecure 'https://192.168.2.37/config/rtspurl.cgi?profileid=1'
profileid=1
urlentry=live/profile.0
video_codec=H264
audio_codec=OPUS
and then connect to this RTSP URL:
$ vlc rtsp://192.168.2.37/live/profile.0
Note that persistent RTSP access can be enabled with original
unmodified D-Link firmware, using the Bluetooth –rtsp option.
This modifies the necessary settings. The rtspd service is
already started by default in the original firmware.
So there is no need to mess with the firmware at all if all you want
is RTSP.
Errors during firmware update via HTTP
The firmwareupgrade.cgi script running in the camera isn’t much
smarter than the rest of the system, so there are a few important
things keep in mind. These are found by trial-and-error:
- HTTP/1.1 might not work – the firmwareupgrade.cgi script does not support 100 Continue AFAICS
- The firmware update image should be provided as a file input field from a form
- The field name must be upload.
Use the exact curl command provided above, replacing only the PIN
Code, IP address and firmware filename. This should work. Anything
else might not.
The camera must be manually rebooted by removing power or pressing
reset if the firmware upgrade fails for any reason. The
firmwareupgrade.cgi script stops most processes, inluding the
Bluetooth handler, and fails to restart them on errors.
There will be no permanent harm if the upload fails. But note that
you have to repeat the –lighttpd step after rebooting the camera,
before you can retry. It does not start automatically until we’ve
installed our modified “mydlink” alternative.
The contents of the fw.tar file must obviously be a valid, encrypted,
firmware update intended for the specified hardware. It must also be
signed. But the signing key can be unknown to the camera provided the
previous –unsignedfw request above was successful.
The Makefile provided here shows how to build a valid firmware
update, but for the DCS-8000LH only! It does not support any other
model. It will create a new throwaway signing key if it canæt find a
real one, and include the associated public key in the archive in case
you want to verify the signature manually.
Note that the encryption key might be model specific. I do not know
this as I have no other model to look at. Please let me know if you
have any information on this topic.
The encryption key is part ot the pib partition, and can be
read from a shell using
Or you can simply look at your partition backup. The key is stored as
a plain text RSA PRIVATE KEY PEM blob, so it is easy to spot. This
repo includes a copy of my key as I see
no point in attempting to keep a well known shared key like this one
“secret”
Backup
Create a backup of everything before you mess up. Restoring will be
hard anyway, so don’t rely on that. But you can forget about
restoring at all unless you have a backup, so make it anyway.
Note that the pib partition contains data which are
specific to your camera, and cannot be restored from any other
source! This includes
- model number
- hardware revision
- mac address
- feature bits
- private keys, pincode and passwords
Well, OK, we can restore most of the pib using information from
the camera label, but
it’s better to avoid having to do that…
A backup is also useful for analyzing the file systems offline.
Making a backup without networking is inconvenient, so setup
networking first. In theory, you could dump the flash to the serial
console. But this would be very time consuming and tiresome.
The D-Link firmware provides a selection of network file transfer
tools. Pick anyone you like:
- tftp
- wget
- curl
- …and probably more
I’ve been using tftp for my backups because it is simple. You’ll
obviously need a tftp server for this. Google for instructions on
setting that up. You could alternatively set up a web server and use
wget or curl to post the files there, but this is more complx to set
up IMHO.
Here is one example of how to enable temporary telnet access and
copying all camera flash partitions to a tftp server:
the address of your tftp server. Note that most tftp servers require
existing and writable destination files. Refer to your tftp server docs
for details.
All the gory details
Restoring original D-Link firmware
The D-Link firmware, including the mydlink tools in the
userdata partition, can be restored by doing a
manual firmware upgrade providing a firmware update from D-Link. Real
example, going back to v2.02.02:
$ curl --http1.0 -u admin:123456 --form upload=@DCS-8000LH_Ax_v2.02.02_3014.bin http://192.168.2.37/config/firmwareupgrade.cgi
curl: (52) Empty reply from server
I don’t know why I got that Empty reply warning instead of the
expected upgrade=ok, but update went fine so I guess it can safely
be ignored. Might be a side effect of rewriting the root file system,
which the firmwareupgrade.cgi script is running from.
Serial console
Useful for fw greater than v2.02.02. The serial console is used to temporally
enable the webservice of the camera. Then, the fw can be downloaded using defogging procedure
and further flash the custom fw.tar firmware.
There is a 4 hole female header with 2 mm spacing in the bottom of the
camera. This header is easily accessible without opening the case at
all. But you will need to remove the bottom label to find it:
Take a picure of the lable or save the information somewhere else
first, in case you make the it unreadable in the process.
Mate with a 3 (or 4) pin male 2 mm connector, or use sufficiently
solid wires. The pins need to be 6-10 mm long. The pins will mess up the QR code, but the rest of the label can be left intact if you’re careful:
The pinout seen from egde to center of camera is:
and the serial port parameters are 57600 8N1.
You obviously need a 3.3V TTL adapter for this, Look at for example
at the generic OpenWrt console instructions if you need guidance.
Do not connect the 3.3V pin. All USB TTL adapters are powered by the
USB bus.
Opening the case
Remove the top and bottom parts of the sylinder. I assume the two
remaning halves of the sylinder are simple held together by clips, but
I did not verify this after discovering the easily accessible console
header.
The bottom cover is held in place by two screws under the label:
Removing the bottom cover reveals the reset button and the console header:
U-Boot
My DCS-8000LH came with this boot loader:
U-Boot 2014.01-rc2-V1.1 (Jun 06 2018 - 03:44:37)
But it is patched/configured to require a password for access to the
U-Boot prompt. Fortunately, D-Link makes the password readily
available in their GPL package :-) It is found in the file
DCS-8000LH-GPL/configs/gpl_defconfig
:
ALPHA_FEATURES_UBOOT_LOGIN_PASSWORD="alpha168"
Enter alpha168 password when you see
Press ESC to abort autoboot in 3 seconds
and you’ll get a rlxboot#
prompt, with access to these U-Boot commands :
rlxboot# ?
? - alias for 'help'
base - print or set address offset
bootm - boot application image from memory
bootp - boot image via network using BOOTP/TFTP protocol
cmp - memory compare
coninfo - print console devices and information
cp - memory copy
crc32 - checksum calculation
echo - echo args to console
editenv - edit environment variable
efuse - efuse readall | read addr
env - environment handling commands
fephy - fephy read/write
go - start application at address 'addr'
help - print command description/usage
imxtract- extract a part of a multi-image
loadb - load binary file over serial line (kermit mode)
loadx - load binary file over serial line (xmodem mode)
loady - load binary file over serial line (ymodem mode)
loop - infinite loop on address range
md - memory display
mm - memory modify (auto-incrementing address)
mw - memory write (fill)
nm - memory modify (constant address)
ping - send ICMP ECHO_REQUEST to network host
printenv- print environment variables
reset - Perform RESET of the CPU
setenv - set environment variables
setethaddr- set eth address
setipaddr- set ip address
sf - SPI flash sub-system
source - run script from memory
tftpboot- boot image via network using TFTP protocol
tftpput - TFTP put command, for uploading files to a server
tftpsrv - act as a TFTP server and boot the first received file
update - update image
version - print monitor, compiler and linker version
Using the boot loader for image manipulation will be hard though,
since the camera has no ethernet, USB or removable flash and the boot
loader has no WiFi driver. It is probably possible to load an image
over serial, but I don’t have the patience for that…
The environment is fixed and pretty clean:
rlxboot# printenv
=3
addmisc=setenv bootargs ${bootargs}console=ttyS0,${baudrate}panic=1
baudrate=57600
bootaddr=(0xBC000000 + 0x1e0000)
bootargs=console=ttyS1,57600 root=/dev/mtdblock8 rts_hconf.hconf_mtd_idx=0 mtdparts=m25p80:256k(boot),128k(pib),1024k(userdata),128k(db),128k(log),128k(dbbackup),128k(logbackup),3072k(kernel),11264k(rootfs)
bootcmd=bootm 0xbc1e0000
bootfile=/vmlinux.img
ethact=r8168#0
ethaddr=00:00:00:00:00:00
load=tftp 80500000 ${u-boot}
loadaddr=0x82000000
stderr=serial
stdin=serial
stdout=serial
Environment size: 533/131068 bytes
So we can get ourselves a root shell:
rlxboot# setenv bootargs ${bootargs} init=/bin/sh
rlxboot# ${bootcmd}
Nothing is mounted or started since /sbin/init is skipped altogether
in this case. Not even /sys and /proc. We can emulate a semi-normal
system by running
/etc/rc.d/rcS
as the first command. And then run for example
telnetd -l /bin/sh
to enable temporary passwordless telnet into the camera instead of/in
addition to the serial console. This is futile unless you have
networking of course. Use the much simpler Bluetooth procedure described
above. Or the “mydlink” app if you prefer to establish a network connection
to your camera.
Then run the following commands:
grep -Eq ^admin: /etc/passwd || echo admin:x:0:0::/:/bin/sh >>/etc/passwd
grep -Eq ^admin:x: /etc/passwd && echo "admin:$(pibinfo Pincode)" | chpasswd
tdb set HTTPServer Enable_byte=1"
tdb set HTTPAccount AdminPasswd_ss="$(pibinfo Pincode)"
/etc/rc.d/init.d/extra_lighttpd.sh start
on the local machine, run
$ curl --http1.0 -u admin:CAMPIN --form upload=@DCS-8000LH_Ax_v2.02.02_3014.bin http://CAM.IP/config/firmwareupgrade.cgi curl: (52) Empty reply from server
this will downgrade the firmware to 2.02.02.
Repeat the bluetooth hack or using serial to re-enable lighttpd server on your camera and run
the following command on your local machine.
$ curl --http1.0 -u admin:CAMPIN --form upload=@fw.tar http://CAM.IP/config/firmwareupgrade.cgi
About 1 min later, the camera will reboot.
After the reboot process, you will have a cam with all the goodies mentioned above with fw 2.02.02.