Who's Watching You? Security Analysis of the LSC 1080P IP Camera From Action

Surveillance cameras are becoming an increasingly common sight in public and private spaces, with IP-based cameras being particularly popular due to their ability to transmit and store video footage over the internet. While these cameras can provide a sense of security and peace of mind, they can also be vulnerable to hacking. In this blog post, we will try to hack the LSC 1080 a popular Wifi camera sold by Action here in Germany and other European countries. The work on this camera was a joint effort together with my friend Michael Baurschmid.

Specifications

1
2
3
4
5
6
7
8
9
Product:                   LSC Smart Connect Smart Video Indoor Camera 1080
Manufacturer:              Tuya
Vendor:                    Action
Price:                     ~20 €
Kernel:                    4.9.129
CPU:                       FH8626V100
Memory:                    XM25QH64A
Firmware Version:          7.6.32
Architecture:              ARM v7 32 Bit Little Endian

Open Ports

The first step was to register the camera in the WIFI using the LSC app and perform a port scan via Nmap. At first, we struggled a bit, as the camera seems to be quite finicky in terms of supported frequency (read it supports only 2.4 GHz). A good old Windows 10 mobile hotspot came to the rescue and allowed us to move forward with our testing.

A scan across all TCP ports turned up the following:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
alexander@roger:~/Desktop$ nmap -p- 192.168.137.80 -T 4
Starting Nmap 7.93 ( https://nmap.org ) at 2023-01-06 15:51 CET
Warning: 192.168.137.80 giving up on port because retransmission cap hit (6).
Nmap scan report for dgiot.mshome.net (192.168.137.80)
Host is up (0.030s latency).
Not shown: 65308 closed tcp ports (conn-refused), 162 filtered tcp ports (no-response), 60 filtered tcp ports (host-unreach)
PORT     STATE SERVICE
80/tcp   open  http
835/tcp  open  unknown
6668/tcp open  irc
8554/tcp open  rtsp-alt

Nmap done: 1 IP address (1 host up) scanned in 793.53 seconds

Dumping the Firmware

Our second step in getting a better understanding of the inner workings of the camera was dumping the firmware using a cheap CH341A programmer from Aliexpress. The camera uses an XMC XM25QH64AHIG as a persistent storage, which luckily is supported out of the box in the NeoProgrammer software we were using for reading the bytes from the memory chip. Reading the 32.768 memory pages took a few minutes and left us with a 1:1 copy of whatever was stored on the chip.

image

Dumping the firmware off the XM25QH64AHIG with a CH341A

To get to the guts of our LSC camera, we extracted the contents of the bin file using binwalk, which gave us the following output.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
alexander@roger:~/dev/hackathon/firmware$ binwalk -e filesystem_dump.bin 

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
479772        0x7521C         CRC32 polynomial table, little endian
589824        0x90000         uImage header, header size: 64 bytes, header CRC: 0xCF3D12EF, created: 2021-12-11 03:44:31, image size: 1899280 bytes, Data Address: 0xA0008000, Entry Point: 0xA0008000, data CRC: 0x67CB52B2, OS: Linux, CPU: ARM, image type: OS Kernel Image, compression type: none, image name: "Linux-4.9.129"
589888        0x90040         Linux kernel ARM boot executable zImage (little-endian)
604972        0x93B2C         xz compressed data
605204        0x93C14         xz compressed data
2490368       0x260000        Squashfs filesystem, little endian, version 4.0, compression:xz, size: 4671622 bytes, 222 inodes, blocksize: 131072 bytes, created: 2022-04-27 11:38:29
8061104       0x7B00B0        JFFS2 filesystem, little endian
8126464       0x7C0000        JFFS2 filesystem, little endian
8323180       0x7F006C        JFFS2 filesystem, little endian
8344152       0x7F5258        JFFS2 filesystem, little endian

As noticeable from the output, the camera uses both Squashfs and Jefferson filesystems.

A quick look around the file system revealed multiple files that will be of further interest for the remainder of the post, such as the /etc/passwd file, the dgiot application and the telnetd daemon.

The /etc/passwd

The analysis of the /etc/passwd file was a first for me, as it was the first time I actually saw a password hash that was stored within the passwd file and not the more common shadow file. Upon closer inspection, the password seemed to be encrypted using the DES algorithm. Due to the algorithm’s limited key lengths derived from 56 bits and susceptibility to brute-force attacks, the DES encryption should not be used anymore.

The entry discovered in the /etc/passwd file on the camera contained the following entry:

1
root:IsHLvr/zaeG2U:0:0:root:/:/bin/sh

Besides being encrypted with a weak algorithm, the camera is also sharing the same key to the kingdom with other popular IP cameras. A quick Google search showed that the password is indeed identical to the ones of the IOIODOG and La View cameras. A tip of the head goes to Mark, who documented the password dgiot010 as part of writing his thesis 1.

It is worth noticing that there were no other users present on the system

Enabling Telnet Remote Access

A quick look at the app_init.sh startup script unveiled that two primary applications get started during the boot process: the deamon and dgiot applications.

Based on our experience of working on similar IoT cameras we knew that often enough additional functionality, such as remote access can be enabled by creating a specific file on the SD card. Halfway through reverse engineering the two main system applications named deamon and dgiot and two fairly large .xz-files in Ghidra, we found the GitHub repo 2 of guino, who -like us- also scrutinized the camera in an effort to remove the cloud dependency and documented the steps for enabling remote access. In order to enable Telnet on the camera an ini-styled file named product.cof has to be created on a FAT32 formatted Micro SD with the following content:

1
2
[DEFAULT_SETTING]
telnet=1

Once rebooted, we could log in using telnet and the previously recovered credentials (user: root, password: dgiot010).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
telnet 192.168.137.170
Trying 192.168.137.170...
Connected to 192.168.137.170.
Escape character is '^]'.

(none) login: root
Password: 


BusyBox v1.26.2 (2021-10-25 16:05:04 CST) built-in shell (ash)
Enter 'help' for a list of built-in commands.

A Quick Look Around the System

Now that we had a shell, it was time to explore the device in action:

image

The cameras Linux kernel

The camera uses a quite dated Linux kernel from December 2016 last compiled in 2021.

Now let’s check active processes and open ports:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# ps 
  PID USER       VSZ STAT COMMAND 
    1 root      1484 S    init 
    2 root         0 SW   [kthreadd] 
    3 root         0 SW   [ksoftirqd/0] 
    4 root         0 SW   [kworker/0:0] 
    5 root         0 SW<  [kworker/0:0H] 
    7 root         0 SW   [rcu_preempt] 
    8 root         0 SW   [rcu_sched] 
    9 root         0 SW   [rcu_bh] 
   10 root         0 SW<  [lru-add-drain] 
   11 root         0 SW   [watchdog/0] 
   12 root         0 SW   [kdevtmpfs] 
   13 root         0 SW   [khungtaskd] 
   14 root         0 SW   [oom_reaper] 
   15 root         0 SW<  [writeback] 
   16 root         0 SW   [kcompactd0] 
   17 root         0 SW<  [crypto] 
   18 root         0 SW<  [bioset] 
   19 root         0 SW<  [kblockd] 
   20 root         0 SW   [kworker/0:1] 
   21 root         0 SW<  [cfg80211] 
   22 root         0 SW   [kswapd0] 
   36 root         0 SW   [spi0] 
   39 root         0 SW<  [bioset] 
   40 root         0 SW<  [bioset] 
   41 root         0 SW<  [bioset] 
   42 root         0 SW<  [bioset] 
   43 root         0 SW<  [bioset] 
   44 root         0 SW<  [bioset] 
   45 root         0 SW   [spi1] 
   47 root         0 SW<  [fh_aes.0] 
   62 root         0 SW<  [kworker/0:1H] 
   63 root         0 SW<  [bioset] 
   64 root         0 SW   [mmcqd/0] 
   79 root      1032 S <  /sbin/udevd --daemon 
   92 root      1016 S <  /sbin/udevd --daemon 
   93 root      1036 S <  /sbin/udevd --daemon 
   96 root         0 SWN  [jffs2_gcd_mtd5] 
  102 root         0 SW<  [ssv6xxx_sdio_wq] 
  103 root         0 SW   [ssv6xxx_hci_tx_] 
  104 root         0 SW<  [ssv6xxx_cong_wq] 
  105 root         0 SW   [ssv6xxx_rx_task] 
  106 root         0 SW<  [ssv6xxx_house_k] 
  107 root         0 SW<  [ssv6xxx_tx_done] 
  108 root         0 SW<  [ssv6xxx_scan_wq] 
  109 root         0 SW   [ksdioirqd/mmc1] 
  112 root         0 SW   [irq/48-VMM-BUS] 
  114 root         0 SW   [vbus_chn1_proc] 
  115 root         0 SW   [vbus_chn2_proc] 
  121 root         0 SW   [vbus_chn3_proc] 
  123 root      300m S    ./dgiot 
  125 root      2504 S    ./daemon 
  135 root      1484 S    telnetd 
  144 root         0 SW   [jpeg_kick] 
  145 root         0 SW   [vpu_task] 
  146 root         0 SW   [vbus_chn4_proc] 
  147 root         0 SW   [pae_proc] 
  175 root         0 SW   [bgm_algo] 
  176 root         0 SW   [bgm_data] 
  195 root      1232 S    wpa_supplicant -Dnl80211 -iwlan0 -c /tmp/wpa_supplic 
  197 root         0 SW   [kworker/u2:2] 
  289 root      1496 S    udhcpc -b -i wlan0 -h dgiot -s /usr/share/udhcpc/def 
 2083 root         0 SW   [kworker/u2:0] 
 2935 root         0 SW   [kworker/u2:1] 
 2989 root      1488 S    /bin/login
 3094 root      1492 S    -sh 
 3145 root      1484 R    ps

# netstat -tulpn 
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:835             0.0.0.0:*               LISTEN      123/dgiot 
tcp        0      0 0.0.0.0:8554            0.0.0.0:*               LISTEN      123/dgiot 
tcp        0      0 0.0.0.0:6668            0.0.0.0:*               LISTEN      123/dgiot 
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      123/dgiot 
tcp        0      0 0.0.0.0:23              0.0.0.0:*               LISTEN      135/telnetd 
netstat: /proc/net/tcp6: No such file or directory 
udp        0      0 0.0.0.0:3702            0.0.0.0:*                           123/dgiot 
udp        0      0 192.168.137.100:60085   0.0.0.0:*                           123/dgiot 
udp        0      0 127.0.0.1:48842         0.0.0.0:*                           123/dgiot 
netstat: /proc/net/udp6: No such file or directory 

We can see the applications running behind the open ports identified by the Nmap scans are the dgiot application and the in case of telnet, the telnetd daemon.

Reading Wifi PSKs through UART

We were able to connect to dev ports labeled RX and TX via the RS232 adapter, bought on AliExpress. At first glance, we want to try a script to figure out the correct baud rate but after a brief discussion, we tried the rates 9600 and 115200. The winner was 115200 and we were off to the moon.

image

Functional serial ports of the LSC 1080

If you are having trouble connecting to your device: Try troubleshooting it with sudo lshw. The RS232 should be shown as product: FT232R USB UART and after that, a quick demesg | grep tty can be used to find the device path. In our case, we could connect with screen /dev/ttyUSB0 115200.

After that, we monitored the device behavior via the dev port connection. To our big surprise, we discovered the SSID and password during the boot process as plain text.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
===========>Middleware WifiStationModeCreate 
TestNetz,MichaelHappyPW
killall: udhcpd: no process killed 
killall: hostapd: no process killed 
[   86.772633] AMPDU_TX_STOP FA:59:71:FC:A6:B1 0. 
[   86.779206] SSV WLAN driver SSV6020C: Set key VIF 0 VIF type 2 STA 0 algorithm = 4, key->keyidx = 0, cmd = 1 
[   86.806537] Clear key 0 VIF 1, STA 1 
[   86.816694] SET KEY 0 
[   86.873882] SSV WLAN driver SSV6020C: Set key VIF 0 VIF type 2 STA -1 algorithm = 4, key->keyidx = 2, cmd = 1 
[   86.894140] Clear key 2 VIF 1, STA 0 
[   86.901938] SET KEY 0 
[31m2023-01-06 13:48:51 [1673012931:188] DGIOT ../../Media/Record.cpp 0419 DoRecord(): [record]---do record... [39m

The SSID and password can be seen here:

1
2
===========>Middleware WifiStationModeCreate 
TestNetz,MichaelHappyPW

An unauthorized person could grep the device and read out the WPA2 password without having to perform a timely Wifi-based attack.

Unauthenticated RTSP Stream

After our initial port scan, we discovered the device had an open port on 8445, which is commonly used by the Real Time Streaming Protocol (RTSP). At this point, we refer to Wikipedia for a quick explanation.

The Real Time Streaming Protocol (RTSP) is an application-level network protocol designed for multiplexing and packetizing multimedia transport streams (such as interactive media, video and audio) over a suitable transport protocol. RTSP is used in entertainment and communications systems to control streaming media servers.

When we’d cross-checked the official RFC documentation, we discovered an interesting note in chapter 16 “Security Considerations” of the RFC2326.

Authentication: Servers SHOULD implement both basic and digest authentication. In environments requiring tighter security for the control messages, the RTSP control stream may be encrypted.

So as a minimum standard, the connection URL should look something like this:

1
rtsp://username:password@DeviceIP

With this knowledge and a sip from our coffee cups, we were off to the races. Guess what, this “security consideration” wasn’t anything we needed to consider. By inserting the URL in the trusty VLC Player, we could get access to the live stream - no credentials asked.

image

Configuring the RTSP stream in VLC and accessing the video stream

Plaintext Storage of Wifi credentials

When digging around the file system, we noticed that the current WIFI PSK is stored in plaintext in a file named wpa_supplicant.conf inside of the /tmp directory. It looks as follows:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# cat /tmp/wpa_supplicant.conf 
ctrl_interface=/var/run/wpa_supplicant
        
network={
        ssid="TestNet"
        proto=WPA RSN
        key_mgmt=WPA-PSK
        pairwise=TKIP CCMP
        group=TKIP CCMP
        psk="<redacted>"
        priority=1
}

E.T. Wants To Call Home

Last but not least we tried to connect via the LSC App over a cellular connection, to see if possible from a public network would be possible. We soon discovered it worked. At this point, our discovery spirit was waked and we fired up Wireshark on the “router” (our Windows goat 🐐) and inspected the traffic.

image

Connections as viewed in Wireshark

We found out the camera always wants to connect to the AWS Cloud and this can’t be disabled in the app. Of course, we ignore the existence of firewalls at this point for now. Through a quick whois we discovered the destination IP addresses belong to the AWS Cloud. On the positive side, the few IP addresses that we checked were all from the European Union.

Failed attempts

The above might sound bad, but we also have to give credit to Tuya where it’s due. Firstly, as explained above we were not able to log in as root via the serial connection or get root access directly through the network services.

Additionally, we tried to exploit the only user input the camera accepts: a QR code generated by the app during the registration of the camera. After decoding the code, we noticed that the app basically encodes the Wifi SSID, the Wifi password and an unknown identifier as a JSON dictionary (see below for an example). The data is then decoded by the camera and used to configure the WLAN interface using iwconfig.

image

This did not suffice to get code execution on the camera

Furthermore, we tried to fuzz the HTTP server using DirBuster. Unfortunately, this did not lead anywhere.

Even if our initial attempts to complete these tasks were not successful, the knowledge gained through the process will serve as an avenue for future research.

Summary

In this blog post, we explored the process of hacking the LSC 1080P camera sold by Action. We covered the tools and techniques that could be to gain unauthorized access and thoroughly enjoyed every bit. For us, this wee challenge proved to be a super interesting learning experience. In the future, we are planning to continue our disassembly efforts and intercept the boot process. Once we come around to doing that, we will update the post accordingly.

References


  1. Mark Stanislav Dissertation https://github.com/mstanislav/phd-dissertation/blob/main/Camera%20Credentials.md ↩︎

  2. GitHub Guino LSC1080P https://github.com/guino/LSC1080P/ ↩︎