UART, uBoot, u root

23. Aug 2022, #hardware 

All the cheap devices that I’ve gotten my hands on since I started getting into hardware hacking had basically no protection. So after getting my hands on the D-Link DCS-5222L, playing with it, pwning it, and breaking it, I wanted to write this blog post to reflex on what I learned. This is written in chronological order of what I did first to last.

Checking Out the Firmware #

After ordering this camera from a secondhand platform I wanted to take a look at the firmware even before it arrived. The firmware is usually the first thing I look at. For D-Link products, I generally use files.dlink.com.au/products↗ or ftp.dlink.de↗.

After downloading the firmware I checked out what type of file it is by running file firmware.bin.

[~/dlink]$ file update_DCS-5211L_DCS-5222L_1.14_5601.bin 
update_DCS-5211L_DCS-5222L_1.14_5601.bin: POSIX shell script executable (binary data)

This gave me slight hope that the firmware is not encrypted, but a shell script is also rather weird as firmware for an IoT device. So I ran the trusty binwalk.

[~/dlink]$ binwalk update_DCS-5211L_DCS-5222L_1.14_5601.bin 

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             Executable script, shebang: "/bin/sh"
1863          0x747           Unix path: /etc/rc.d/init.d/services.sh stop > /dev/null 2> /dev/null
1941          0x795           Unix path: /etc/rc.d/init.d/services.sh start > /dev/null 2> /dev/null && exit 1; }
28820         0x7094          Linux kernel ARM boot executable zImage (big-endian)
12587800      0xC01318        CramFS filesystem, little endian, size: 192512, version 2, sorted_dirs, CRC 0x092E2C10, edition 0, 95 blocks, 19 files

This looked like a not encrypted firmware for an ARM device, so I unpacked it with binwalk -e. It found a CramFS filesystem that contained the following files:

[~/dlink/extracted/cramfs-root]$ find . -type f
./._dcp
./mydlink-watch-dog.sh  
./._mydlink-watch-dog.sh
./opt.local
./._signalc
./._upgradefw
./._upnpc-ddns
./._version
./._httpd_check
./signalc
./upnpc-ddns
./dcp
./httpd_check
./tsa
./version
./._tsa
./upgradefw
./._opt.local

All those files together don’t seem like a complete firmware. There does not seem to be a Linux file system present. I then took a look at the bash script portion of the firmware. The script was extracted by viewing the firmware in a hex editor and determining that the script finishes at line 118.

[~/dlink]$ head -n 118 update_DCS-5211L_DCS-5222L_1.14_5601.bin > update.sh

The script itself did not look that interesting. But right after the script (lines 121-534) was some base64 encoded data. I extracted it and based on line 120 it appears to be the dd utility.

[~/dlink]$ file b64decoded
b64decoded: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.3, for GNU/Linux 2.6.28, stripped

I was hoping for an easy win with the firmware. But at this point, the camera arrived, so I looked at the hardware instead.

Screwing Around #

As soon as the device arrived, I opened it up and started looking for serial interface pins. Lucky for me D-Link nicely labeled the four pins with a UART branding.

UART pins

Almost every time if the pins are not labeled, the top and bottom ones will be VSS and GND and the middle ones will be TX and RX. I simply enumerated GND by using a multimeter in “continuity testing” aka. “beep” mode. The multimeter beeps, if there is an electrical connection between the two probes. So you hold one probe onto the top or bottom UART pin and the other one to a known ground contact. This can be the ground of a USB port, the metal shielding on components, or the “golden ring” around screws. Sometimes it can also be the ground pin of the power supply. But this depends on the type of power supply and is generally not a safe bet.

With two of the four pins identified, soldering was next. In this case, soldering was my only option since I did not have enough space for pins and I did not have the correct pin size in the first place. For soldering, I used some 26 AWG wire.

After soldering, I did some tests to make sure that the camera was still functional. I then removed all parts from the camera that were not needed for it to operate. This was done to save space on my desk. Thru trial and error, I was able to remove everything but the main PCB as well as the camera PCB with its attached motor. The other two motors, their sensors, the speaker, and the WI-FI antennas were removed.

The cake is a lie

Connecting via UART #

To connect via UART I used a CP210x USB to UART adapter as well as one of those cheap ‘24M 8CH’ logic analyzers. I first connected both soldered cables to channels on the logic analyzer. Upon starting the Saleae Logic 2↗ software as well as powering the camera on I got some gibberish text on one channel inside Logic 2. With that, the TX pin was identified.

As a next step, I needed to identify the baud rate. I did this by simply guessing and trying from a know list of values. The correct value was 115200. On devices that do not use a common baud rate, you can use Logic 2 and measure the timing of the incoming gibberish text.

Upon connecting the right cables to the right pins on the CP210x I started the connection putty. The boot log showed up perfectly in the terminal.

I used putty to connect to the USB serial adapter:

[~/dlink]$ putty -serial /dev/ttyUSB0 -sercfg 115200 -log uart.log

U-Boot 2010.06 (Oct 08 2013 - 14:12:52)

DRAM:  128 MiB
Check spi flash controller v350... Found
Spi(cs1) ID: 0xC2 0x20 0x19 0xC2 0x20 0x19
Spi(cs1): Block:64KB Chip:32MB Name:"MX25L25635E/735E/635F"
In:    serial
Out:   serial
Err:   serial
Hit any key to stop autoboot:  0 
32768 KiB hi_sfc at 0:0 is now current device

## Booting kernel from Legacy Image at 82000000 ...
   Image Name:   Linux-3.0.8
   Image Type:   ARM Linux Kernel Image (uncompressed)
   Data Size:    1488100 Bytes = 1.4 MiB
   Load Address: 80008000
   Entry Point:  80008000
   Loading Kernel Image ... OK
OK

...

Welcome to ApproRootFileSystem
Show full boot log
U-Boot 2010.06 (Oct 08 2013 - 14:12:52)

DRAM:  128 MiB
Check spi flash controller v350... Found
Spi(cs1) ID: 0xC2 0x20 0x19 0xC2 0x20 0x19
Spi(cs1): Block:64KB Chip:32MB Name:"MX25L25635E/735E/635F"
In:    serial
Out:   serial
Err:   serial
Hit any key to stop autoboot:  0 
32768 KiB hi_sfc at 0:0 is now current device

## Booting kernel from Legacy Image at 82000000 ...
   Image Name:   Linux-3.0.8
   Image Type:   ARM Linux Kernel Image (uncompressed)
   Data Size:    1488100 Bytes = 1.4 MiB
   Load Address: 80008000
   Entry Point:  80008000
   Loading Kernel Image ... OK
OK

Starting kernel ...

Uncompressing Linux... done, booting the kernel.
Linux version 3.0.8 (jiahung@bullhead.approtech.com) (gcc version 4.4.1 (Hisilicon_v100(gcc4.4-290+uclibc_0.9.32.1+eabi+linuxpthread)) ) #126 Wed Feb 17 19:19:12 CST 2016
CPU: ARM926EJ-S [41069265] revision 5 (ARMv5TEJ), cr=00053177
CPU: VIVT data cache, VIVT instruction cache
Machine: hi3518
Memory policy: ECC disabled, Data cache writeback
AXI bus clock 200000000.
Built 1 zonelists in Zone order, mobility grouping on.  Total pages: 32512
Kernel command line: mem=128M console=ttyAMA0,115200 root=/dev/mtdblock4 ro rootfstype=jffs2
PID hash table entries: 512 (order: -1, 2048 bytes)
Dentry cache hash table entries: 16384 (order: 4, 65536 bytes)
Inode-cache hash table entries: 8192 (order: 3, 32768 bytes)
Memory: 128MB = 128MB total
Memory: 125840k/125840k available, 5232k reserved, 0K highmem
Virtual kernel memory layout:
    vector  : 0xffff0000 - 0xffff1000   (   4 kB)
    fixmap  : 0xfff00000 - 0xfffe0000   ( 896 kB)
    DMA     : 0xffc00000 - 0xffe00000   (   2 MB)
    vmalloc : 0xc8800000 - 0xfe000000   ( 856 MB)
    lowmem  : 0xc0000000 - 0xc8000000   ( 128 MB)
    modules : 0xbf000000 - 0xc0000000   (  16 MB)
      .init : 0xc0008000 - 0xc0025000   ( 116 kB)
      .text : 0xc0025000 - 0xc03c9000   (3728 kB)
      .data : 0xc03ca000 - 0xc03e7140   ( 117 kB)
       .bss : 0xc03e7164 - 0xc03f9228   (  73 kB)
SLUB: Genslabs=13, HWalign=32, Order=0-3, MinObjects=0, CPUs=1, Nodes=1
NR_IRQS:32 nr_irqs:32 32
sched_clock: 32 bits at 100MHz, resolution 10ns, wraps every 42949ms
Console: colour dummy device 80x30
Calibrating delay loop... 218.72 BogoMIPS (lpj=1093632)
pid_max: default: 32768 minimum: 301
Mount-cache hash table entries: 512
CPU: Testing write buffer coherency: ok
NET: Registered protocol family 16
Serial: AMBA PL011 UART driver
uart:0: ttyAMA0 at MMIO 0x20080000 (irq = 5) is a PL011 rev2
console [ttyAMA0] enabled
uart:1: ttyAMA1 at MMIO 0x20090000 (irq = 5) is a PL011 rev2
bio: create slab <bio-0> at 0
usbcore: registered new interface driver usbfs
usbcore: registered new interface driver hub
usbcore: registered new device driver usb
cfg80211: Calling CRDA to update world regulatory domain
Switching to clocksource timer1
NET: Registered protocol family 2
IP route cache hash table entries: 1024 (order: 0, 4096 bytes)
TCP established hash table entries: 4096 (order: 3, 32768 bytes)
TCP bind hash table entries: 4096 (order: 2, 16384 bytes)
TCP: Hash tables configured (established 4096 bind 4096)
TCP reno registered
UDP hash table entries: 256 (order: 0, 4096 bytes)
UDP-Lite hash table entries: 256 (order: 0, 4096 bytes)
NET: Registered protocol family 1
RPC: Registered named UNIX socket transport module.
RPC: Registered udp transport module.
RPC: Registered tcp transport module.
RPC: Registered tcp NFSv4.1 backchannel transport module.
NetWinder Floating Point Emulator V0.97 (double precision)
JFFS2 version 2.2. (NAND) 2001-2006 Red Hat, Inc.
msgmni has been set to 245
io scheduler noop registered
io scheduler deadline registered (default)
io scheduler cfq registered
brd: module loaded
loop: module loaded
Spi id table Version 1.22
Spi(cs1) ID: 0xC2 0x20 0x19 0xC2 0x20 0x19
SPI FLASH start_up_mode is 3 Bytes
Spi(cs1): 
Block:64KB 
Chip:32MB 
Name:"MX25L25635E/735E/635F"
spi size: 32MB
chip num: 1
8 cmdlinepart partitions found on MTD device hi_sfc
Creating 8 MTD partitions on "hi_sfc":
0x000000000000-0x000000080000 : "System"
0x000000080000-0x000000100000 : "MyDlink"
0x000000100000-0x000000200000 : "Emergency Kernel"
0x000000200000-0x000000400000 : "Kernel"
0x000000400000-0x000000b00000 : "Root Filesystem"
0x000000b00000-0x000000c00000 : "Config Data"
0x000000c00000-0x000001600000 : "WEB"
0x000001600000-0x000002000000 : "APPs"
Fixed MDIO Bus: probed
himii: probed
PPP generic driver version 2.4.2
PPP Deflate Compression module registered
PPP BSD Compression module registered
NET: Registered protocol family 24
tun: Universal TUN/TAP device driver, 1.6
tun: (C) 1999-2004 Max Krasnyansky <maxk@qualcomm.com>
ehci_hcd: USB 2.0 'Enhanced' Host Controller (EHCI) Driver
hiusb-ehci hiusb-ehci.0: HIUSB EHCI
hiusb-ehci hiusb-ehci.0: new USB bus registered, assigned bus number 1
hiusb-ehci hiusb-ehci.0: irq 15, io mem 0x100b0000
hiusb-ehci hiusb-ehci.0: USB 0.0 started, EHCI 1.00
hub 1-0:1.0: USB hub found
hub 1-0:1.0: 1 port detected
ohci_hcd: USB 1.1 'Open' Host Controller (OHCI) Driver
hiusb-ohci hiusb-ohci.0: HIUSB OHCI
hiusb-ohci hiusb-ohci.0: new USB bus registered, assigned bus number 2
hiusb-ohci hiusb-ohci.0: irq 16, io mem 0x100a0000
hub 2-0:1.0: USB hub found
hub 2-0:1.0: 1 port detected
using rtc device, hi_rtc, for alarms
hi_rtc hi_rtc: rtc core: registered hi_rtc as rtc0
Hisilicon Watchdog Timer: 0.01 initialized. default_margin=60 sec (nowayout=1)
TCP cubic registered
TCP highspeed registered
NET: Registered protocol family 10
IPv6 over IPv4 tunneling driver
NET: Registered protocol family 17
802.1Q VLAN Support v1.8
Registering the dns_resolver key type
hi_rtc hi_rtc: setting system clock to 2016-01-06 13:37:27 UTC (1452087447)
usb 1-1: new high speed USB device number 2 using hiusb-ehci
VFS: Mounted root (jffs2 filesystem) readonly on device 31:4.
Freeing init memory: 116K

init started: BusyBox v1.20.2 (2013-12-04 14:21:43 CST)

starting pid 550, tty '': '/etc/rcS'
0
udev starting...
udevd (582): /proc/582/oom_adj is deprecated, please use /proc/582/oom_score_adj instead.
udev end!
Starting internet superserver: inetd.
Mounting local filesystems: mount mount: according to /proc/mounts, /dev/root is already mounted on /
mount: according to /proc/mounts, tmpfs is already mounted on /tmp
ADDRCONF(NETDEV_UP): eth0: link is not ready
rfkill: Cannot open RFKILL control device
ioctl[SIOCSIWAP]: Operation not permitted
insmod: can't insert 'hi3518_tde.ko': Operation not permitted
insmod: can't insert 'hi3518_dsu.ko': unknown symbol in module, or unknown parameter
insmod: can't insert 'hifb.ko': unknown symbol in module, or unknown parameter
insert audio
==== Your input Sensor type is ov9712 ====
cp: can't stat '/opt/ipnc/etc/esmtprc': No such file or directory
cp: can't stat '/opt/ipnc/etc/quftprc': No such file or directory

starting pid 883, tty '/dev/ttyAMA0': '/sbin/getty -L ttyS000 115200 vt100'

Welcome to ApproRootFileSystem

IPNetCam login: config_init:3623 machinesubcode = 0 
set_brand_data:BRAND_ID_DLINK__2554
config_init:3699 fd = 3 
config_init:3712 ptr = 0x156d440 
config_init:3722 ret = 45007 
Head OK!
revise_flag=0
[file_msg_drv.c] InitFileMsgDrv: Semaphore Addr: 0x1578418 
[file_msg_drv.c] InitFileMsgDrv: Qid: 98307 
[Servermain] 7274: Share memory is initialize 

[IR Brightness] dcpower_init org_level=100 
[HISYS] Hi_SYS_Init(44) HI_MPI_VB_Init failed!
[HILIB] HiAPI_Start(525) system init failed with -1!
[dn_ctrl_init] 145: ipcam[DNC_SENSITIVITY] = 15 
[dn_ctrl_init] 148: DNC_MODE:0 ,light_sensor_state = 0 
Disable IRLED 8ms
[IR Brightness] set_irled_brightness : start = 0 level = 0
[AvAPI_sysConfig] SetCamSaturation  CamSaturation = (49)
TZ = CST-8
time = Wed Jan  6 21:37:37 2016

time = Wed Jan  6 21:37:37 2016

Selected interface 'wlan0'
OK
[StartStream] 2568:StartStream is starting 
/opt/ipnc/av_server.out   AUDIOIN20DB PAL AEWB FREE H_1280x720 4000000 VBR 25 25 J_640x360 60 VBR 25 1 H_320x176 512000 CBR 25 25 J_640x360 60 VBR 3 1 J_1280x720 60 1 1  J_320x176 60 1 1  G.711   PROFILENUM_4 16:9 NONE_AF   OSD_ON LENS_IDLE MENUOFF &

[StartStream] 3054:=====================print default config================================
NVIDEOMODE 		= 1 {PAL:1 , NTSC:0}
profile[0]type		= 1
profile[0]format	= 2
profile[0]buf_size	= 8388608
profile[0]height	= 720
profile[0]width 	= 1280
profile[0]bit_rate	= 4000
profile[0]f_rate	= 25
profile[0]rc_mde	= 1
profile[0]eptz		=(0,0,720,1280)
profile[0]osd		= 1
next profile --------------------------------------------
profile[1]type		= 1
profile[1]format	= 0
profile[1]buf_size	= 8388608
profile[1]height	= 360
profile[1]width 	= 640
profile[1]bit_rate	= 60
profile[1]f_rate	= 25
profile[1]rc_mde	= 2
profile[1]eptz		=(0,0,360,640)
profile[1]osd		= 1
next profile --------------------------------------------
profile[2]type		= 1
profile[2]format	= 2
profile[2]buf_size	= 8388608
profile[2]height	= 176
profile[2]width 	= 320
profile[2]bit_rate	= 512
profile[2]f_rate	= 25
profile[2]rc_mde	= 0
profile[2]eptz		=(0,0,176,320)
profile[2]osd		= 1
next profile --------------------------------------------
profile[3]type		= 2
profile[3]pinx		= 0
profile[3]buf_size	= 2097152
next profile --------------------------------------------
profile[4]type		= 2
profile[4]pinx		= 1
profile[4]buf_size	= 2097152
next profile --------------------------------------------
profile[5]type		= 2
profile[5]pinx		= 2
profile[5]buf_size	= 2097152
next profile --------------------------------------------
profile[6]type		= 1
profile[6]format	= 0
profile[6]buf_size	= 2097152
profile[6]height	= 360
profile[6]width 	= 640
profile[6]bit_rate	= 512
profile[6]f_rate	= 3
profile[6]rc_mde	= 1
profile[6]eptz		=(0,0,360,640)
profile[6]osd		= 1
next profile --------------------------------------------
profile[7]type		= 3
profile[7]format	= 257
profile[7]buf_size	= 524288
profile[8]type		= 3
profile[8]format	= 259
profile[8]buf_size	= 524288
next profile --------------------------------------------
 =============================print default config==================================
[AVMEM] AvMem_create(130) [0] fail to call shmget( ), key=FA3CB417, size=8388608
[AVMEM] AvMem_create(130) [1] fail to call shmget( ), key=FA3CB418, size=8388608
[AVMEM] AvMem_create(130) [2] fail to call shmget( ), key=FA3CB419, size=8388608
[AVMEM] AvMem_create(130) [3] fail to call shmget( ), key=FA3CB41A, size=2097152
[AVMEM] AvMem_create(130) [4] fail to call shmget( ), key=FA3CB41B, size=2097152
[AVMEM] AvMem_create(130) [5] fail to call shmget( ), key=FA3CB41C, size=2097152
[HISYS] Hi_SYS_Init(44) HI_MPI_VB_Init failed!
[HILIB] HiAPI_Start(525) system init failed with -1!
Audio in enable(1),valume = 117
Audio out enable(1),valume = 6
Audio out enable(0),valume = 80
[AvAPI_sysConfig] SetSysLDCEnable  SetSysLDCEnable = (0)
[HIVI] Hi_VI_SetLDC(770) HI_MPI_VI_GetLDCAttr FAIL
[AVENC] AvEnc_setLDC(1798) [NG] HiAPI_SetLDC fail
 @@@@@@@@@@@@@@@@@@@@@   AvAPI profileConfig is setup   @@@@@@@@@@@@@@@@@@@@@@@@@@@@

[StartStream] 3295:startstream is end! 
[image_init] 4943:image_setup_start--------------------------------
[AvAPI_sysConfig] image_init  brightness   = 50
[AvAPI_sysConfig] image_init  contrast     = 50
[AvAPI_sysConfig] image_init  saturation   = 49
[AvAPI_sysConfig] image_init  sharpness    = 50
[AvAPI_sysConfig] image_init  denoise      = 0
[AvAPI_sysConfig] image_init  antiflicker  = 0
[AvAPI_sysConfig] image_init  flip         = 0
[AvAPI_sysConfig] image_init  mirror       = 0
[AvAPI_sysConfig] image_init  whitebalance = Auto
[image_init] 4965:image_setup_end  --------------------------------

dn_ctrl_init(0)
Disable IRLED 8ms
[IR Brightness] set_irled_brightness : start = 0 level = 0
[IR Brightness] set_irled_brightness : start = 0 level = 0
[AvAPI_sysConfig] SetCamSaturation  CamSaturation = (49)
[AvAPI_sysConfig] setexposuremode  CamExposuremode = (Auto)
av_2A.aew.ae_max_gain = 24
[image_init] 5041:image_init_end! 
[SystemInit] 5914:SystemInit_end ! 

[Servermain] 7283: System is initialize!!! 
RTNETLINK answers: No such file or directory
RTNETLINK answers: No such file or directory
RTNETLINK answers: No such file or directory
udhcpc (v1.20.2) started
Sending discover...
Remainer in 50%=3840, Remainer in 75%=768
upnpc : miniupnpc library test client. (c) 2006-2008 Thomas Bernard
Go to http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/
for more information.
[file_msg_drv.c] InitFileMsgDrv: Semaphore Addr: 0x1c9d008 
[file_msg_drv.c] InitFileMsgDrv: Qid: 98307 
sendto: Network is unreachable
No IGD UPnP Device found on the network !
Initializing...
Unable to determine our source address: liveMedia0
Unable to determine our source address: liveMedia0
Unable to determine our source address: liveMedia2
Unable to determine our source address: liveMedia2
Unable to determine our source address: liveMedia4
Unable to determine our source address: liveMedia4
Unable to determine our source address: liveMedia6
Unable to determine our source address: liveMedia6
Unable to determine our source address: liveMedia8
Unable to determine our source address: liveMedia8
Unable to determine our source address: liveMedia10
Unable to determine our source address: liveMedia10
count 6 and stream->audio_type [0]
Play this stream using the URL:
	rtsp://0.0.0.0/live1.sdp
Play this stream using the URL:
	rtsp://0.0.0.0/live2.sdp
Play this stream using the URL:
	rtsp://0.0.0.0/live3.sdp
Play this stream using the URL:
	rtsp://0.0.0.0/video.sdp
Play this stream using the URL:
	rtsp://0.0.0.0/video2.sdp
Play this stream using the URL:
	rtsp://0.0.0.0/video3.sdp
Play this stream using the URL:
	rtsp://0.0.0.0/audio.sdp
Interface eth0 is down.
Recheck uShare's configuration and try again !
ioctl: Cannot assign requested address
[file_msg_drv.c] InitFileMsgDrv: Semaphore Addr: 0x1921498 
[file_msg_drv.c] InitFileMsgDrv: Qid: 98307 
[boa.c] main: http stream name 0 = /video1.mjpg
[boa.c] main: http stream name 1 = /video2.mjpg
[boa.c] main: http stream name 2 = /video3.mjpg
[boa.c] main: http stream name 3 = /video4.mjpg
HASH_TABLE_SIZE = 578
[boa.c] main: server_port=80

[boa.c] main: SSL server port: 443
[boa.c] InitSSLStuff: Enabling SSL security system

Restroe IRLED to disable
[IR Brightness] set_irled_brightness : start = 0 level = 0
[boa.c] InitSSLStuff: Loaded SSL certificate file: /mnt/data/ssl_cert.pem

[boa.c] InitSSLStuff: Opened private key file: /mnt/data/ssl_key.pem

[boa.c] InitSSLStuff: SSL security system enabled

[06/Jan/2016:13:37:39 +0000] boa: server version Boa/0.94.13
[06/Jan/2016:13:37:39 +0000] boa: server built Feb 18 2016 at 19:04:57.
[06/Jan/2016:13:37:39 +0000] boa: starting server pid=926, port 80
Sending discover...
opt.local stop ok.
opt.local start ok.
CA refresh fail cause ca-update app not exist
CA doesn't exist or overdue
Sending discover...
[Wed Jan  6 21:37:47 STD 2016] signalc is not running!
killall: signalc: no process killed
[Wed Jan  6 21:37:48 STD 2016] dcp is not running!
killall: dcp: no process killed

Welcome to ApproRootFileSystem

After the device finished booting, I noticed however that I was able to interact with the device. It took me a couple of minutes, but I noticed that the GND from the CP210x and the camera were not properly connected. Upon fixing that and pressing ENTER a couple of times I was presented with a login shell. By hand, I tried a bunch of classic combinations but had no luck. I also could not find any credentials online. For some reason, I thought that the best idea was to code a login brute-forcer via UART. I can confidently say that this was a stupid idea. After fighting two days with newlines, carriage returns, and text detection I gave up and put this project to the side for about a month.

Back to U-Boot #

I skipped U-Boot before but after the break I felt like looking into it was worth a shot. Based on the line Hit any key to stop autoboot: I smashed ENTER right after powering on the device and was dropped into the U-Boot shell: At this point, I needed to know what shell would be on the camera to set the boot arguments correctly. Most of the time /bin/bash is not available on IoT devices. I’ve encountered /bin/ash as well as /bin/sh in the past. They are usually symlinks to a busybox↗ binary that the manufacturer compiled themselves with only the minimum of modules needed. For the first try, I choose /bin/sh.

Using the commands printenv as well as fatinfo I got some information regarding the filesystem and memory layout. This gave me the following command chain which I entered into the U-Boot shell:

# sets the bootargs, saves them and then resets the camera
setenv bootargs mem=128M console=ttyAMA0,115200 root=/dev/mtdblock4 ro rootfstype=jffs2 init=/bin/sh; saveenv; reset

The camera booted and gave me a root shell.

Serial Connection

Hashcat to the Rescue #

The root shell itself kicked me out every 60s since some watchdog kicked in. The easiest path forward was getting the user accounts and cracking their passwords. At first, I tried the trusty rockyou.txt↗ as well as asking the internet, but I had no luck with both. Since the encryption (DES) on those passwords was not that strong I’d figured that my GPU probably could do up to 8 characters pretty quickly.

To brute force the root hash eRgTKCCvNAte. I used hashcat with the following command:

[~/dlink]$ hashcat FILE -m 1500 -a 3 -O -w 4 -i -1 abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 ?1?1?1?1?1?1?1?1

# -m 1500 = mode: crack descrypt, DES (Unix), Traditional DES 
# -a 3 = attack mode: Brute-force
# -O = Enable optimized kernels
# -w 4 = max speed
# -i = iterate (try 1 to max. chars definded by mask, instead of only max. chars)
# -1 xxx = defines charset 1 with possible chars a-zA-Z0-9
# ?1?1?1?1?1?1?1?1 = use charset 1 8 times

This gave me the root password of hi3518c in about 15 min. (This is actually the name of the processor used inside this camera)

Since the camera still boots to my custom set shell, I needed to put the default boot arguments back. Upon spamming ENTER again while the device powers on, I was able to enter the following command to get back to the default shell:

setenv bootargs mem=128M console=ttyAMA0,115200 root=/dev/mtdblock4 ro rootfstype=jffs2; saveenv; sf probe 0; sf read 0x82000000 $(loadbootaddr) $(loadbootsize);go 0x820000000

I got some of the information for this command chain from Synack’s DEF CON 23 talk↗. After waiting for the boot to finish, I was back at the default /bin/ash login prompt for which I now knew the password this time.

Welcome to ApproRootFileSystem

dlinkcamEEFE login: root
Password: hi3518c

root@ /root# 

Dumping the Filesystem #

My next goal was to dump the filesystem. For this, I needed a way to copy files off the device. The easiest way to do this would be scp. This requires an SSH server with a sFTP submodule running on the device. Upon finding /etc/sshd I started the daemon with /usr/sbin/sshd -Dd. Before connecting I took a look at the configuration in /etc/ssh/sshd_config and noticed that the SSH port was 8992.

I connected a computer with a static IP of 192.168.0.21 to the LAN port of the camera and tried logging in with ssh root@192.168.0.20 -p 8992 but got the error:

Unable to negotiate with 192.168.0.20 port 8992: no matching host key type found. Their offer: ssh-rsa,ssh-rsa

This was likely due to the age of the sshd binary. To get around this error, set the key type to something old on the connecting host with:

[~/dlink]$ ssh root@192.168.0.20 -p 8992 -o HostKeyAlgorithms=ssh-rsa

But upon entering the correct password the session would immediately terminate with Connection reset by 192.168.0.20 port 8992.

Since I started the daemon on the camera in debug mode, I saw what caused the error:

debug1: kex: algorithm: ecdh-sha2-nistp256
debug1: kex: host key algorithm: ssh-rsa
debug1: kex: server->client cipher: aes128-gcm@openssh.com MAC: <implicit> compression: none
debug1: kex: client->server cipher: aes128-gcm@openssh.com MAC: <implicit> compression: none
debug1: kex: ecdh-sha2-nistp256 need=16 dh_need=16
debug1: kex: ecdh-sha2-nistp256 need=16 dh_need=16
debug1: expecting SSH2_MSG_KEX_ECDH_REPLY

After a quick online search, I added the -o Ciphers=3des-cbc to fix the cipher to 3des in CBC mode. This would finally let me log in via SSH with:

[~/dlink]$ ssh root@192.168.0.20 -p 8992 -o HostKeyAlgorithms=ssh-rsa -o Ciphers=3des-cbc

Got sFTP? #

Upon trying to scp the whole camera I noticed that there was no sFTP submodule present on the device. This means that scp is not an option. After viewing the built-in commands for the busybox binary with list it seemed that there was also no FTP module present - busybox was compiled without FTP.

To get FTP connectivity to the device I uploaded a full busybox binary↗. The version on the device was 1.20.2 so the version chosen was the closest one with 1.20.0 (armv4l). Since this busybox binary had all modules in it, I started an FTP daemon with:

busybox tcpsvc -vE 0.0.0.0 21 ./busybox ftpd /

A bit of bash enabled me to then dump the filesystem:

[~/dlink]$ cat folders.txt
bin etc home lib linuxrc mnt modules mydlink opt root sbin tmp usr var

[~/dlink]$ for i in $(cat folders.txt); do wget -r ftp://ANONYMOUS@192.168.0.20/$i; done

Honey, I Broke the Camera #

With the filesystem dumped I looked at the firmware upgrade process to possibly smuggle a reverse shell into the upgrade. However, during this, I bricked the device, and it got stuck in a boot loop. With no “full image”, I could not restore the device from the U-Boot shell. And with that, this story ends.

I’m happy with what I achieved even if in the end I bricked the camera. I’ve gotten enough learning value out of it that I can say the 30 bucks were a pretty good investment. If you want to get started, pick something a bit older and probably outdated. Grab the low-hanging fruits first and don’t set a CVE as your main goal.