
Offenlegung von Schwachstellen in Accfly-Kameras: CVE-2020-25782, CVE-2020-25783, CVE-2020-25784, CVE-2020-25785.
Anfang 2020 hatte ich an meinem damaligen Arbeitsplatz die Gelegenheit, an einem internen pwn2own-ähnlichen Event teilzunehmen. Es standen mehrere Ziele zur Verfügung, aber am meisten interessierte mich die Accfly Wireless Security Camera. Leider konnte ich meine Recherche für das eigentliche Event nicht abschließen, aber da es keine anderen Versuche an diesem Gerät gab, habe ich sie fortgesetzt.
Der Schwerpunkt der Forschung lag auf Schwachstellen, die zu Remote Code Execution (RCE) führen könnten. Diese Art von Schwachstelle ermöglicht es einem Angreifer, die vollständige Kontrolle über das Gerät zu übernehmen und kann bei einer Videokamera zu einem vollständigen Verlust der Privatsphäre des Besitzers führen. Leider stellte sich heraus, dass die Geräte-Firmware mit solchen Problemen übersät ist.
Zunächst bietet das Gerät keinerlei Authentifizierung. Daher kann ein Angreifer, der eine Verbindung herstellen kann, frei darauf zugreifen und es neu konfigurieren. In der einfachsten Form ist es möglich, das Gerät kontinuierlich neu zu starten, wodurch es für den legitimen Benutzer völlig unbrauchbar wird. Der Umfang dieses Angriffs ist etwas eingeschränkt, da das Gerät für die Nutzung in einem WiFi-Netzwerk, in der Regel hinter NAT, ausgelegt ist und daher nicht direkt aus dem Internet erreichbar ist. Das Fehlen einer Verschlüsselung zwischen dem Gerät und der Smartphone-App des Besitzers, zusammen mit der Nutzung des Servers des Herstellers als Proxy für die Kommunikation, bietet jedoch eine Möglichkeit für MitM- oder DNS-Manipulationsangriffe, die die WiFi-NAT-Einschränkung umgehen können.
Darüber hinaus verwendet die Anwendung ein proprietäres binäres Protokoll zur Kommunikation. Es wurde in einer Mischung aus C und C++ implementiert und erwies sich als voll von unsicheren String-Verarbeitungsfunktionen. Die Hauptausführungsdatei enthält eine große Menge ungenutzten Codes, was darauf hindeutet, dass sie auf anderen Geräten wiederverwendet wird. Dies erschwert die Wartung und vergrößert die Angriffsfläche. Die Anwendung aktiviert keine modernen Sicherheitsmechanismen, die sie gegen viele gängige Exploitation-Techniken schützen würden. Darüber hinaus schränkt sie nicht einmal die Benutzerrechte ein und läuft als root – mit den höchsten verfügbaren Privilegien.
Als Ergebnis dieser Forschung wurden die folgenden vier Schwachstellen dokumentiert.
CNetClientManage::ServerIP_Proto_Set bei der Verarbeitung eingehender NachrichtenCNetClientTalk::OprMsg bei der Verarbeitung eingehender NachrichtenCNetClientGuard::SubOprMsg bei der Verarbeitung eingehender NachrichtenCFtpProtocol::FtpLogin während des Update-VorgangsFür drei davon wurden RCE-Exploits entwickelt, die es einem Angreifer ermöglichen, die vollständige Kontrolle über das Gerät zu erlangen. Aufgrund des fehlenden Herstellerfeedbacks auf die Meldung der Schwachstellen enthält dieses Repository jedoch nur begrenzte PoC-Exploits, die lediglich die Anwendung zum Absturz bringen.
Die Probleme wurden in der Softwareversion V3.10.73 gefunden und in der Softwareversion V4.15.77 verifiziert, der zum Zeitpunkt dieser Veröffentlichung (26. Januar 2021) aktuellsten verfügbaren Version.
Bei Fragen zögern Sie nicht, mich per E-Mail (siehe Git-Commit) oder über GitHub Issues zu kontaktieren. Wenn Sie ein IoT-Gerät haben, das Sie für interessant zum Hacken halten, auf der Suche nach einem Security Researcher sind oder einfach nur Hallo sagen möchten, freue ich mich von Ihnen zu hören. Sie können mir auch einen Kaffee kaufen!
Das Zielgerät ist eine Videokamera, die über die zugehörige Mobile-App gesteuert wird. Meine Analyse begann mit dem Netzwerkverkehr der Kamera und setzte sich in der Kamera-Firmware fort. Für die gesamte Kommunikation wird ein benutzerdefiniertes binäres Protokoll verwendet. Befehle werden entweder direkt an das Mobilgerät gesendet, wenn sie sich im selben Netzwerk befinden, oder durch den Server des Geräteherstellers weitergeleitet. Die Kamerasoftware selbst hört auf mehreren Ports TCP (23456,34567) und UDP (34568,34569). Es gibt weder Verschlüsselung noch Authentifizierung für den Netzwerkverkehr, was MitM-Angriffe oder direkten Zugriff ermöglicht, wenn die Kamera über das Netzwerk exponiert ist. Es erscheint wahrscheinlich, dass auch der Zugriff auf den Videostream ohne Authentifizierung möglich ist, aber ich habe das proprietäre Protokoll nicht ausreichend reverse-engineered, um dies auszuprobieren.
Nach einer kurzen Übersicht über die Kommunikation bestand der nächste Schritt darin, Zugriff auf die Geräte-Firmware zu erhalten. Mein erster Versuch war, sie direkt durch Hijacking des Geräte-Update-Prozesses herunterzuladen, aber im Netzwerkverkehr passierte nichts dergleichen. Ich wäre in dieser Phase stecken geblieben, wenn nicht die dringend benötigte Hilfe eines Kollegen gewesen wäre, der die Firmware aus dem Flash-Speicher extrahierte, was es mir ermöglichte, mit dieser Forschung fortzufahren.
Die Firmware erwies sich als Linux auf einem MIPS-Little-Endian-Prozessor. Es gibt genau einen interessanten Prozess namens Alloca, der für die Videoaufnahme zuständig ist und auch die gesamte Netzwerkkommunikation abwickelt. Die Anwendung ist in C++ erstellt und enthält viel Code, der auf diesem Gerät ungenutzt ist. Dies deutet darauf hin, dass dieselbe Software auch auf anderen Geräten verwendet wird.
Obwohl dieses Problem als letztes gefunden wurde, ist es entscheidend für die tatsächliche Ausnutzung der meisten anderen, da sie auf der Verwendung unsicherer C-String-Funktionen beruhen. Obwohl es verschiedene Techniken gibt, die für eine erfolgreiche Codeausführung in ähnlichen Szenarien eingesetzt werden können, ist die Anwendung so aufgebaut, dass diese weitgehend nutzlos sind. Das Hauptproblem ist, dass Alloca's Code und Daten statisch auf niedrigen Adressen (< 0x01000000) alloziert sind. Daher sind Versuche, vorhandenen Code wiederzuverwenden (z.B. ROP und ähnliches), nicht sinnvoll, da sie die Fähigkeit erfordern, Adressen in den Speicher des Programms zu schreiben. Da C-Strings \x00 als Abschlusszeichen verwenden und String-Funktionen die Verarbeitung beim ersten solchen Byte beenden, ist es nicht möglich, mehr als ein einzelnes NULL-Byte zu verwenden. Zudem ist die Stack-Position randomisiert und die Anwendung stark multithreaded, was andere Techniken viel unzuverlässiger macht.
Diese Schwachstelle ist das Ergebnis der gemeinsamen Nutzung von Daten zwischen mehreren Threads und der unsicheren Verwendung von strcpy. Obwohl ich diese spezielle Problematik lange analysiert habe, habe ich die Möglichkeit, sie als Datenleck-Vektor zu nutzen, erst wenige Wochen vor dieser Veröffentlichung entdeckt. Interessanterweise ermöglicht diese Schwachstelle dank des Lecks der Heap-Adresse eines C++-Objekts auch eine Remote-Code-Ausführung. Dieser Angriff ist jedoch nicht in diesem Bericht enthalten.
Die Alloca-Anwendung kann sich über FTP aktualisieren. Dieser Vorgang kann von einem Server angefordert werden, der auch den erforderlichen Benutzernamen, das Passwort und den Dateinamen bereitstellt. Die Funktion, die das Update initiiert, ist hier dargestellt:

Drei Aufrufe von strcpy sind offensichtlich unsicher und führen zu einem Heap-Überlauf, da das ftpUpgrade-Objekt dynamisch alloziert ist. Leider machen die Reihenfolge, in der die Kopien durchgeführt werden, und das Layout der ftpUpgrade-Struktur es unmöglich, tatsächlich einen Thread zu starten, der Daten leakt. Ein genauerer Blick auf das eingehende Paket zeigt die folgende Struktur:
struct ftp_upgrade_pkt {
struct pktHeader;
char username[16];
char password[16];
char filename[128];
}
während das ftpUpgrade-Objekt etwa so aussieht:
struct CNetClientFtpUpgrade {
// ... etwas hier
char filename[128];
char unknown[6];
char username[16];
char password[16];
CFtpDownlad *;
CNetClientConnect*;
int something[5];
bool threadRunning;
// ... und mehr
}
Der Leak kann auftreten, nachdem einer der inneren Zeiger (CFtpDownload*, CNetClientConnect*) von der Anwendung ausgefüllt wurde. Zusätzlich werden der Benutzername und das Passwort (einmal mehr, aber diesmal sicher) in das neu erstellte Objekt kopiert, bevor sein Zeiger an der leckbaren Stelle gespeichert wird, sodass der Leak nur mit filename auftreten kann. Daher muss der Dateiname sehr lang sein, aber aufgrund der Reihenfolge und des Terminierungsverhaltens von strcpy führt ein ausreichend langer Dateiname zu einem noch längeren Benutzernamen und Passwort, was wiederum threadRunning überschreibt und keinen Thread startet.
Wenn dieser Code single-threaded wäre, könnte man nicht viel tun. Aber da der neue FtpDownload-Thread erzeugt wird und die Funktion DownloadFile ausführt, ergibt sich eine interessante Gelegenheit, da er das CNetClientFtpUpgrade-Objekt mit dem Thread teilt, der die eingehenden Pakete verarbeitet. Er hat nicht nur mehrere extern steuerbare IO-Operationen (DNS-Anfragen, FTP-Verbindungsaufbau), sondern versucht auch bis zu 10 Mal, eine Verbindung zum FTP-Server herzustellen (dies geschieht im Aufrufer von DownloadFile). Dies ermöglicht es, die Ausführung des FtpDownload-Threads zu steuern (indem man ihn bei IO-Operationen blockiert) und so dem Nachrichtenverarbeitungsthread Zeit zu geben, andere Anfragen zu bearbeiten.

Kurz gesagt: Durch das Senden mehrerer Update-Anfragen ist es möglich, den filename (und andere Parameter) zu ändern, die vom bereits laufenden FtpDownload-Thread verwendet werden, und eine geleakte Heap-Adresse zu erhalten. Als Bonus verwendet die Funktion FtpSize (grün markiert) den Puffer innerhalb des Objekts, auf das die geleakte Adresse verweist, um den filename selbst zu speichern, was eine triviale Injektion der ersten Stufe des Shellcodes ermöglicht. Die einzigen Einschränkungen hier sind die Länge und das Fehlen von NULL-Bytes aufgrund der Verwendung von strcpy. Ein Beispiel-PoC wird bereitgestellt, das lediglich eine Heap-Adresse vom Gerät leakt.
nicht authentifizierter Stack-basierter BO in der Funktion CNetClientManage::ServerIP_Proto_Set
Das völlige Fehlen einer Authentifizierung bei der Verarbeitung eingehender Daten lenkte mich auf die Suche nach Pakethandlern. Eine interessante Funktion ist ServerIP_Proto_Set. Sie scheint verwendet zu werden, um eine statische Überschreibung für die DNS-Auflösung zu erstellen. Ich habe keinen Weg gefunden, den Datenverkehr auf diese Weise umzuleiten, aber es gibt hier einen weiteren Pufferüberlauf (orange markiert).

Die Daten, die direkt aus dem Paket gelesen werden, werden in der sprintf-Funktion verwendet. In diesem Fall wird angenommen, dass die Daten aus dem Paket in einen 16-Byte-Puffer passen, aber die Verwendung von einfachem %s-Format erlaubt das Schreiben beliebig vieler Bytes, vorausgesetzt, sie enthalten kein NULL.
Diese Schwachstelle ist recht eingeschränkt. Obwohl es möglich ist, viele Daten auf den Stack zu schreiben, sodass eine NOP-Sledge funktionieren könnte, ist es nicht möglich, NULL-Bytes zu schreiben. Selbst der Versuch, ein einzelnes NULL-Byte zu schreiben, schlägt fehl, da die sprintf-Funktionsformatierung ein \n voranstellt. Ein weiteres Hindernis ist ein CMutex-Objekt, das nach dem Puffer gespeichert ist. Jeder Überlaufversuch muss diesen Mutex mit einem korrekten Wert (oder zumindest einem, der den CGuard-Destruktor zufriedenstellt – rot markiert) füllen. Dies ist problematisch, da der Destruktor die übergebene Variable zweimal dereferenziert und ihren Wert dann im Aufruf von pthread_mutex_unlock verwendet. Nach einigen Tests habe ich herausgefunden, dass ein mit NULLs gefüllter Puffer ausreicht, um korrekt von pthread_mutex_unlock zurückzukehren, aber es musste dennoch in eine geeignete Speicheradresse dereferenziert werden.
Der Leak kommt zur Rettung. Der Angriff ist etwas komplex, da wir eine Heap-Adresse benötigen, die keine NULL-Bytes enthält. Glücklicherweise wird die Suche erleichtert, da das Gerät die Möglichkeit bietet, einen Remote-Neustart ohne Authentifizierung durchzuführen. Jedes Mal wird eine andere Heap-Adressraum-Allokation bereitgestellt. So kann man das Gerät einfach zurücksetzen und eine Adresse leaken, bis eine geeignete gefunden wird. Praktischerweise erlaubt dies auch, eine kurze erste Stufe des Shellcodes zu speichern. Da wir das Mutex-Problem überwinden müssen (ein Zeiger auf einen Zeiger wird benötigt), leaken wir eine weitere Adresse, diesmal unter Verwendung einer zuvor geleakten Adresse als Dateinamen. Das folgende Diagramm zeigt das erwartete Speicherlayout:

Wenn alles nach Plan läuft, ist es möglich, eine 2. Adresse als Mutex und die erste als Rücksprungadresse zu übergeben. Dies ist jedoch nicht notwendig, um die Anwendung nur zum Absturz zu bringen, wie es im PoC getan wird.
nicht authentifizierter Heap-basierter BO in der Funktion CNetClientTalk::OprMsg
Das Gerät soll bidirektionale Sprachkommunikation ermöglichen. Ein weiterer Handler für eingehende Pakete scheint für den Empfang und die Wiedergabe von Audio verantwortlich zu sein. Das Netzwerkpaket audio_pkt_hdr wird durch die folgende Struktur beschrieben:
struct audio_pkt_hdr {
struct pktHeader field_0x0;
int field_0x14
int field_0x18
int field_0x1c
char audioBuff[0x140];
}
Eines der Felder der pktHeader-Struktur ist die Länge des Pakets (wie über das Netzwerk übertragen). Dieses Feld kann vom Sender frei gesetzt werden. Der anfällige Teil ist das Kopieren von Daten direkt aus dem eingehenden Paket unter Verwendung des nicht vertrauenswürdigen Längenwerts, der im Header des eingehenden Pakets bereitgestellt wird.

Wie zu sehen ist, wird das CNetClientTalk-Objekt mit dem folgenden Konstruktor erstellt:

so führt der obige Aufruf von memcpy zu einem Pufferüberlauf auf dem Heap. Die tatsächliche Ausnutzung dieses Problems ist jedoch ziemlich schwierig. Obwohl es möglich ist, den Heap wiederholt zu überschreiben, habe ich keinen Weg gefunden, zu kontrollieren, welche Daten nach dem überlaufenden Puffer auf dem Heap gespeichert werden. Da die Anwendung über 50 aktive Threads hat, von denen einige für die Verarbeitung von Audio und Video zuständig sind, wird ständig Speicher (de-)allokiert. Dies führt zu ständig wechselnden Heap-Daten, was es schwierig macht, vorherzusagen, was nach dem Puffer gespeichert ist, und es korrekt zu überschreiben.
nicht authentifizierter Stack-basierter BO in der Funktion CNetClientGuard::SubOprMsg
Hier kommt ein weiterer Handler für eingehende Pakete. Diesmal hat das Netzwerkpaket die folgende Struktur (der gemeinsame Paketheader wird weggelassen):
struct pkt_hdr_sub_cliGuard {
dword deviceId;
dword userId;
dword magic;
dword subCmd;
dword field_0x10;
dword field_0x14;
dword field_0x18;
dword field_0x1c;
dword guard_icommand;
dword moreThenRandomStackValue;
dword itemCnt;
char array_of_0x18[24];
};
Wieder ist das letzte Array der interessante Teil (da wir dieses Paket beliebig groß machen können), das eine interne Struktur der Größe 24 enthält. Die Schwachstelle rührt von der Annahme her, dass der empfangene itemCnt 6 nicht überschreiten wird, da der Zielpuffer für die Kopie die Größe 144 (=24*6) hat, wie im folgenden Listing sichtbar ist (orange Hervorhebung):

Diesmal wird die Kopie mit memcpy durchgeführt (grüne Hervorhebung), sodass es keine Begrenzung der zulässigen Zeichen gibt. Die Kopie erfolgt in Blöcken durch eine while-Schleife (blau markiert). Es ist erwähnenswert, dass der Zähler cnt_v0 innerhalb der Schleife dekrementiert wird, sodass die Blöcke in umgekehrter Reihenfolge kopiert werden. Einschließlich der Variablen, die dem anfälligen Puffer buf folgen, muss der Überlauf 256 Bytes haben, dann 4 Register ($s0-$s3) und $ra. Da wir keine Kenntnis des Speicherlayouts haben, verwendet der PoC-Code eine ROP-Technik. Ein einzelnes Gadget wird verwendet, das einen der integrierten Sounds des Geräts abspielt (und abstürzt).
nicht authentifizierter Stack-basierter Pufferüberlauf in der Funktion CFtpProtocol::FtpLogin
Eine der ersten Richtungen meiner Analyse war die Suche nach dem Update-Verfahren. Wie ich herausfand, verfügt das Gerät über eine FTP-Update-Funktionalität, die durch Senden einer Update-Anfrage ausgelöst werden kann und zum Herunterladen der Firmware von einer externen FTP-Site führt. Wie bei anderen Schwachstellen ist keine Authentifizierung erforderlich, bevor das Geräte-Update angefordert wird. Eine eingehende Analyse der FTP-Funktionalität deckte einen Stack-basierten Pufferüberlauf in der Funktion CFtpProtocol::FtpLogin auf. Wie im dekompilierten Listing unten zu sehen ist, übergibt die Funktion ein char-Array der Größe 256 an die Funktion FtpPwd.

FtpPwd wird verwendet, um das aktuelle Arbeitsverzeichnis vom FTP-Server zu erhalten. Es lädt seinen internen Puffer mit bis zu 1500 Bytes der Antwort und kopiert sie dann in den bereitgestellten Puffer. Diese Aufruffolge führt zu einem Überlauf von 1242 Bytes. In diesem Fall sind die zulässigen Zeichen sehr eingeschränkt, da die Verwendung von " (doppeltes Anführungszeichen) zur Verkürzung des Eingabestrings führen würde (strchr wird verwendet, um nach char in einem C-String zu suchen) und den Puffer nicht überlaufen würde. Glücklicherweise ist es nur notwendig, eine einzelne Adresse zu liefern, zu der die Codeausführung umgeleitet wird.

Um diese Schwachstelle auszunutzen, ist es notwendig, entweder DNS zu kontrollieren oder die Verbindung zum FTP-Server umzuleiten (oder einen MitM durchzuführen). Die Anwendung hat keine modernen Schutzmechanismen, daher ist es möglich, den Code direkt vom Stack aus auszuführen. Ohne das Leaken einer Adresse ist das Beste, was getan werden kann, entweder die Stack-Position zu erraten oder die Ausführung auf eine einzelne Funktion umzuleiten, die dann die Anwendung zum Absturz bringt. Meine ersten Versuche waren genau das, das Abspielen eines der eingebauten Sounds, was als PoC bereitgestellt wird. Mit Hilfe von the leak ist es möglich, die vollständige Kontrolle über das Gerät zu erlangen.