Hacking einer China PTZ Webcam Part 1

Ich habe mir zwei Billigkameras aus China schicken lassen. Beim Blick auf den Preis von rund 8 Euro pro Stück inklusive Versand wird mir klar: Wenn man das Geschäftsmodell nicht erkennt, ist man selbst das Produkt. 🙂

Dies wird spätestens deutlich, als ich verstehe, dass diese Kameras ausschließlich per chinesischer App über deren Cloud-Server steuerbar sind. Das wäre doch eine interessante Herausforderung, dies zu ändern?

Gliederung

  • Informationsbeschaffung
  • Hardware identifizieren
  • UART finden
  • SPI Chip lesen zur Firmware Extraktion
  • Firmware reverse engineeren
  • Backdoor einbauen
  • China-Kontakte blocken
  • PTZ Mechanismus reverse engineeren

Informationsbeschaffung

Die Beschriftung lautet: Shenzhen XY-3820 SN 202405

Grundsätzlich gilt: Wenn etwas funkt, gibt es mit hoher Wahrscheinlichkeit auch Informationen von der FCC: https://fcc.report/FCC-ID/2BDQVXY-3820

Daraus lässt sich einiges lernen – z.B. technisch identische Modelle laut FCC:

XY-3820, XY-3820S, XY-3120SZ, XY-3120SZ-H, XY-3120, XY-3120S-DP, XY-3120S-DP-H, XY-3920, XY-5020, XY-5220, XY-5320, XY-6020, XY-1920, XY-A120, XY-6220, XY-7220, XY-3320S-DP, HM-3120S-DP-WIFI, HM-3120SZ-WIFI, HM-3120SZ-H-WIFI, SQ-3120S-DP-WIFI, SQ-3120SZ-WIFI, HM-6220-WIFI, HM-7320L-WIFI

Portscan 

Host is up (0.0079s latency). 
Not shown: 998 closed tcp ports (reset) 
PORT     STATE SERVICE        VERSION 
21/tcp   open  ftp            BusyBox ftpd (D-Link DCS-932L IP-Cam camera) 
6789/tcp open  ibm-db2-admin? 
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service : 
SF-Port6789-TCP:V=7.95%I=7%D=9/25%Time=66F42BF1%P=i686-pc-windows-windows% 
SF:r(JavaRMI,B,"\x0b\0\x02\x01\0\0\x01\x0004\0"); 
Service Info: Device: webcam; CPE: cpe:/h:dlink:dcs-932l 

Es gibt einen FTP? Das teste ich gleich – leider mit Passwort. Brute-Force-Versuche mit den Standardpasswörtern lassen wir erstmal sein.

Status: Verbinde mit 192.168.66.193:21... 
Status: Verbindung hergestellt, warte auf Willkommensnachricht... 
Status: Unsicherer Server; er unterstützt kein FTP über TLS. 
Befehl: USER anonymous 
Antwort: 331 Please specify password 
Befehl: PASS ********************* 
Antwort: 530 Login failed 
Fehler: Kritischer Fehler: Herstellen der Verbindung zum Server fehlgeschlagen 

Man-in-the-middle Proxy

Die Kamera wird ausschließlich über die Handy-App gesteuert – es gibt weder eine GUI noch ein Webinterface an der Kamera selbst. Mittels einer MITM-Attacke wurde per Wireshark der Traffic analysiert. Was soll man sagen – da wird alles Mögliche – und vor allem viel zu viel! – nach China übermittelt. Auch Daten vom Handy selbst!

Die Details lasse ich hier weg – aber sagen wir so: Gehostet wird scheinbar auf der Alibaba-Cloud (quasi als Gegenstück zu Amazon AWS) – und neben den angesprochenen personenbezogenen Daten werden auch die Kamera-UID, eine Update-URL und eine entsprechende API enthüllt:

https://plt-api-de.xiaoyi.com/vmanager/ipc/firmware/upgrade/app?sname=AKOpen&version=6.0.24.10_202401091113&did=A207N004J12P8924XXXX 
Antwort: 
{"code":1,"msg":"success","result":{"needUpdate":"false","downloadPath":null,"downloadTime":null,"version":null,"sdFlag":null,"message":null,"md5Code":null,"fileName":null,"forceUpdate":null}} 

(Ich habe ein paar Infos per XXX verändert)

Also zusammenfassend werden einige IDs generiert und über diesen Cloud-Dienst kommunizieren Handy-App und Kamera miteinander. Auch die Push-Benachrichtigungen der Bewegungserkennung sind gut sichtbar. Mittels der generierten (normalerweise verschlüsselten und nicht sichtbaren) Zugangsdaten könnte man sich in der China-Cloud einloggen und weiter spionieren. Aber auch das lassen wir erstmal.

userid: 5908196 
seq:    1 
hmac:   0pWU1C1sTmO4WAQwXYIYxXXXXXX 
Und Response: 
{"data":[{"nickname":"peter ustinvo","uid":"TNPXGBA-781405-TYLSE","name":"Cam1","password":"A9FDAD4647667C788ECAECE8EXXXXX","message":"","isNew":false,"flag":true,"share":false,"online":true,"state":1,"type":2,"hasPincode":false,"ipcParam":{"p2p_encrypt":"true","ssid":"Death by SnuSnu","mac":"c0:4b:24:e5:XXXX","ip":"192.168.66.193","signal_quality":"70","powerstate":"0"},"appParam":"","interVersion":"6.0.24.10_202401091113","sdState":"10000","category":0,"count":0,"webPairStatus":3,"onlineTime":{"date":25,"day":3,"hours":15,"minutes":40,"month":8,"nanos":0,"seconds":26,"time":1727278826000,"timezoneOffset":0,"year":124},"activeTime":1727277097,"did":"A207N004J12P89XXXX","model":"10000","relayFlag":0}],"code":"20000"} 

Ein Weg zum Ziel wäre es nun, die Update-URL am Router umzuleiten und eine manipulierte Firmware anzubieten. Aber hierfür brauchen wir zuerst die Original-Firmware, und das Experimentieren mit der API hat auf die Schnelle kein Ergebnis gebracht. Es gäbe aber spezielle API-Scanner, die mit hoher Wahrscheinlichkeit hier eine Firmware zutage fördern würden.

Hardware identifizieren

OK, dann schrauben wir das Ding mal auf.

  • 3 verdächtige Pins gefunden die möglicherweise UART sind. 
  • CPU/SOC gefunden: Marke Anyka V200: AK3918EN080
  • SPI Chip gefunden: XM25QH64C
  • Kamerasensor: GC1084
  • Wifi Modul SSV6355

UART finden

Ein UART-Port ist recht schnell identifiziert: meist ein 4-poliger Stecker (bzw. dessen Löcher in der Platine in der Produktionsversion) oder 4 Testpunkte auf der Platine. Meist in der Nähe der CPU. Mit dem Multimeter messen – da findet sich dann meist GND, VIN und RX sowie TX. Letztere 2 sind Floating, VIN selbst wird nicht genutzt. Daher sind die 3 Pins/Holes zwischen der CPU und dem SD-Kartenleser SEHR verdächtig!

Nach dem Anlöten und Anschluss eines UART-USB-Adapters bekommen wir mittels picocom eine Konsole beim Booten:

U-Boot 2013.10.0-AK_V2.0.04 (Jun 05 2023 - 14:05:38) 
DRAM:  64 MiB 
8 MiB 
Create flash partition table init OK! 
ANYKA SDHC/MMC4.0: 0 
Load Env CRC OK! 
In:    serial 
Out:   serial 
Err:   serial 
Hit any key to stop autoboot:  0  
SF: 1417096 bytes @ 0x31000 Read: OK 
## Booting kernel from Legacy Image at 81808000 ... 
   Image Name:   Linux-3.4.35 
   Image Type:   ARM Linux Kernel Image (uncompressed) 
   Data Size:    1416712 Bytes = 1.4 MiB 
   Load Address: 81808000 
   Entry Point:  81808040 
   Verifying Checksum ... OK 
   XIP Kernel Image ... OK 
  
Starting kernel ... 
  
Uncompressing Linux... done, booting the kernel. 
Anyka Linux Kernel Version: 2.5.04 
Booting Linux on physical CPU 0 
[....cut]
anyka login:

Perfekt! Ein wahrer Informationsschatz!

Wir lernen, dass es sich offenbar um einen U-Boot-Bootloader handelt, kennen nun den EntryPoint, den Linux-Kernel, die Speicheradressen, die Partitionierung des Speichers und vieles mehr.

Lediglich der Login hilft uns nicht weiter, da wir logischerweise das Passwort nicht kennen. Und wie oben: Brute-Force ist erstmal kein Thema.

SPI Chip lesen zur Firmware Extraktion

Jetzt geht’s ans Eingemachte! Der eingangs identifizierte SPI-Chip ist ein klassisches 8-Pin-Package. Statt mit Heißluft auszulöten und in den Chip-Leser zu stecken, versuche ich es erstmal mit einer 8-Pin-Clamp und habe Erfolg: der komplette Speicher des SPI-Chips wird byte für byte ausgelesen.

Firmware reverse engineering

Firmware Reverse Engineering:
Nun nehmen wir uns die Binärdatei vor und schauen, was wir herauskitzeln können. Als erstes schauen wir, um was für eine Datei es sich handelt – leider wird nur „data“ angezeigt. Dann suchen wir nach Strings darin – und entdecken schon den Hash des Root-Passworts!

strings blob_cam2.bin | grep root
bootargs=console=ttySAK0,115200n8 root=/dev/mtdblock4 rootfstype=squashfs init=/sbin/init mem=64M memsize=64M
mtd_root=/dev/mtdblock3
rootfstype=squashfs
setcmd=setenv bootargs console=${console} root=${mtd_root} rootfstype=${rootfstype} init=${init} mem=${memsize}
mtd_root
format search root mtdblock failed!
root.sqsh4
tfdownrootfs
load root.sqsh4TF
    - write root.sqsh4 to flash
load root.sqsh4 tftp
bootargs=console=ttySAK0,115200n8 root=/dev/mtdblock4 rootfstype=squashfs init=/sbin/init mem=64M memsize=64M
mtd_root=/dev/mtdblock4 
rootfstype=squashfs
setcmd=setenv bootargs console=${console} root=${mtd_root} rootfstype=${rootfstype} init=${init} mem=${memsize}
root:B3IcMJenDTfLc:0:0:root:/:/bin/sh
root:$1$6AHjBnTn$LvoexcPTiWwZP5fLfCGdv1:0:0:99999:7:::

Das ist schon mal ein großer Erfolg. Man sieht an $1, dass es sich wohl um einen gesalzenen MD5-Hash handelt. Diesen könnte man nun z.B. per Hashcat oder John the Ripper analysieren, um zu sehen, ob man mit Wortlisten und entsprechenden Masken zu einem Ergebnis kommt. Aber wie schon mehrfach erwähnt: Brute-Force erst als letztes Mittel. 🙂

Auch sieht man gleich zwei Root-Hashes – offenbar ist der erste in der passwd-Datei ein Überbleibsel eines veralteten Unix Crypt(3?)-Hashes und der MD5 in der shadow-Datei. Wenn beides vorhanden ist, dann verwendet Linux den sichereren.

Nun schauen wir mittels binwalk, ob wir irgendwelche „Magic Bytes“ finden.

Perfekt! Wir sehen, dass die binäre Firmware-Datei aus einem Header und dem U-Boot-Image des Bootloaders sowie aus zwei SquashFS und einem JFFS-Filesystem besteht. Zusätzlich lernen wir noch die genauen Speicheradressen im SPI-Chip bzw. der Datei und einige Parameterdaten wie Big/Little Endian, Kompression, Blocksize etc. Das wird später noch alles wichtig.

Die Reise geht weiter – damit könnte man sich nun diese drei Filesysteme extrahieren und mit unsquashfs bzw. jefferson entpacken. Aber das mache ich nur, wenn es mit der praktischeren Variante mit binwalk Probleme gibt. In diesem Fall entpackt binwalk -e FILE wunderbar die drei Filesysteme und die komplette Firmware liegt somit offen!!

Nun geht es an die Analyse und die Auffrischung, wie ein eingebettetes Linux normalerweise bootet.

Mit dem Wissen, dass SquashFS grundsätzlich als Read-only-Filesysteme eingesetzt werden und JFFS2 die änderbare Komponente darstellt, leiten wir ab, dass alle Standardfunktionen in den ersten beiden Filesystemen liegen werden und alles „Individuelle“ wie Seriennummer, Passwörter, Konfigurationen etc. im änderbaren Filesystem.

Damit und mit der Art, wie gebootet wird, sehen wir: 0x1B3000 wird als root / gemountet, das zweite SquashFS 0x2B3000 wäre der Mountpoint /usr und das änderbare JFFS2 landet in /etc/jffs2, während wichtige Dinge wie /etc/passwd per Symlink in das änderbare /etc/jffs2 verlinkt werden.

Backdoor einbauen

Nun wäre eine Shell im laufenden Linux praktisch.

Nach intensiver Analyse, steiler Lernkurve und vor allem dem Verstehen, wie dieses Linux-System funktioniert, habe ich entdeckt, dass tatsächlich Telnet gestartet wird:

grep -r telnet .
...
./1B3000/unmodified/blob_cam2.bin.extracted/1B3000/squashfs-root/etc/init.d/rcS:echo "start telnet……"
./1B3000/unmodified/blob_cam2.bin.extracted/1B3000/squashfs-root/etc/init.d/rcS:telnetd &
grep: ./1B3000/unmodified/blob_cam2.bin.extracted/1B3000/squashfs-root/bin/busybox: Übereinstimmungen in Binärdatei
./1B3000/squashfs-root/etc/services:telnet 23/tcp
./2B3000/squashfs-root/sbin/service.sh: killall telnetd
...

Spannend. Scheinbar wird so die lokale UART-Shell ermöglicht und wohl aus „Sicherheitsgründen“ in dieser service.sh gegen Ende des Startens aller Dienste ein killall gemacht, damit ja kein Telnet offen ist.

Tja – was wäre, wenn man nun z.B. auf Port 2323 NACH dem killall einfach wieder einen Telnet-Server starten würde?

Schnell eingebaut – nun muss die Firmware wieder zusammengebaut werden.

Dazu habe ich mir ein paar Shell-Skripte erstellt, die im Grunde das jeweilige Filesystem wieder in eine binäre Datei verpacken – und zuletzt diese drei Teildateien per dd wieder byteweise in die originale Firmware einfügen. Eine Signierung des Headers etc. ist in diesem Fall nicht notwendig.

ksquashfs squashfs-root 1B3000_mod.squashfs -comp xz -b 131072
bzw. 
mkfs.jffs2 -l -v --pagesize=0x10000 -e 0x10000 -d jffs2-root 5A805c -o 5A805C_mod.jffs2 --pad=2334628
------------------------
rm neue_firmware.bin 
cp blob_cam2.bin neue_firmware.bin 
dd if=./1B3000/1B3000_mod.squashfs of=neue_firmware.bin bs=1 seek=1781760 conv=notrunc 
dd if=./2B3000/2B3000_mod.squashfs of=neue_firmware.bin bs=1 seek=2830336 conv=notrunc
#dd if=./5A805C/5A805C_mod.jffs2 of=neue_firmware.bin bs=1 seek=5931100 conv=notrunc

Danach wird die neue, geänderte Firmware wieder auf den SPI Chip geflasht.

flashrom -p ch341a_spi -w neue_firmware.bin

Bäm – wir haben eine rootshell via Telnet 🙂

Update: eine weitere Schwachstelle wäre das Shellscript im änderbaren JFFS2 Filesystem: den Telnetbefehl kann man auch im time_zone.sh anhängen.

Die nächsten 2 Challenges werden in den nächsten Abenden erfolgen: einmal die Kommunikation mit China blocken – und die PTZ Funktionalität (evtl. auch onvif) lokal bereitstellen.

China-Kontakte blocken

coming

PTZ Mechanismus reverse engineeren

coming

Weitere Ideen:

  • eine eigene, lokale custom Firmware-Update Möglichkeit schaffen.
  • eine Backdoor / Reverseshell einbauen (nein, danach nicht die Cam verscherbeln 🙂 )