- Mitglied seit
- 10 Mai 2006
- Beiträge
- 15,267
- Punkte für Reaktionen
- 1,750
- Punkte
- 113
Nachdem nun bei der 7390 der Plugin-Mechanismus wiederauferstanden ist (das war er bei der internationalen Version ja schon für die 06.20, weil die Language-Datenbanken ausgelagert wurden) und auch die (sicheren) Updates der DECT-Komponenten immer wichtiger werden, weil sie meist ohne Interaktion mit dem Benutzer ablaufen, wird es Zeit, den AVM-Mechanismus zum Signieren der Image-Dateien mal etwas genauer zu untersuchen (bzw. eher zu erläutern). Auch die Tatsache, daß neue Firmware nur noch korrekt signierte Firmware-Images installieren will, trägt natürlich dazu bei, daß dieses Thema einmal mehr in den Mittelpunkt des Interesses (zumindest des meinigen) gerückt ist.
Das ist hier wieder eher "als Story" erzählt ... wer es kürzer und "als Referenz" will, muß es eben selbst erkunden.
Nicht zuletzt spielt die Verwendung einer wirklich originalen Firmware auch dann ein Rolle, wenn man mein modfs-Skript verwenden will, um seine NAND-basierte FRITZ!Box an die eigenen Wünsche anzupassen. Dieses Skript kann ja auf Wunsch auch eine aktuelle Firmware direkt vom FTP-Server von AVM laden ... bisher wird dabei allerdings nicht getestet, ob es sich wirklich um eine unveränderte Version von AVM handelt oder ob da schon jemand manipuliert hat.
Nun kann so eine Image-Datei ja auf verschiedenen Wegen in eine FRITZ!Box gelangen, wer schon einmal einen Blick in das Shell-Skript /sbin/start_dect_update.sh einer aktuellen Firmware-Version gewagt hat, dem wird früher oder später unweigerlich die Zeile
ins Auge springen und nach etwas Analyse des Shell-Codes in dieser Datei stellt man dann schnell fest, daß damit offenbar ein solches Image (das ja nur ein TAR-Archiv mit einem "komischen Namen" ist) sowohl geladen (je nach URL) als auch geprüft und entpackt werden kann, denn nach dem Aufruf dieses Programms liegen die einzelnen Bestandteile so einer Image-Datei in /var vor, sofern die Datei korrekt signiert ist. Das gilt auch für die Datei /sbin/plugin_start.sh in der neuesten Version für die 7390, dort wird ebenfalls tr069fwupdate zur Prüfung benutzt:
... offenbar sind wir also bei diesem Programm schon vor der richtigen Schmiede.
Für eine genauere Analyse der Vorgänge dort, müssen wir uns also erst einmal eine passende Datei besorgen, die wir als Eingabe für unsere Tests benutzen können. Ein komplettes Firmware-Image paßt hier schon deshalb nicht so richtig, weil es eine erkleckliche Größe hat und wir absehbar mit ein paar Kommandos wie hexdump, dd und auch strings umgehen müssen, die bei einer größeren Datei dann auch größere Ausgaben produzieren und in der Regel auch länger brauchen bei der Abarbeitung.
Daher böte es sich an, für diese Untersuchungen ein Update-Image für ein DECT-Gerät von AVM zu verwenden, denn dieses ist normalerweise etwas kleiner - allerdings eignet sich seit dem Erscheinen der 84.06.51 das dort enthaltene plugins.update (auch ein verkapptes TAR-Archiv) noch viel besser für die ersten Schritte, da im Gegensatz zu den für andere Images verwendeten öffentlichen Schlüsseln:
es für das Signieren der Plugins offenbar ein anderes Schlüsselpaar gibt, dessen öffentlicher Teil in einer weiteren Datei enthalten ist:
Das hat also gleichzeitig den Vorteil, daß wir nicht erst raten müssen, welcher öffentliche Schlüssel denn nun zu einer Signatur gehören könnte ... die Annahme, daß es sich hier um den oben gezeigten handelt, ist ja naheliegend.
Also nehmen wir mal diese Datei plugins.update auf einer 7390 genauer unter die Lupe (sie wird ja beim Update auf die 06.51 automatisch an einer Stelle im Dateisystem abgelegt, auf die man notfalls auch mit den NAS-Funktionen zugreifen könnte), zuerst sehen wir uns einmal an, was dort an Dateien enthalten ist:
So eine Datei mit dem Namen signature in der Image-Datei legt ja einen Zusammenhang mit unserem Thema nahe, auf den ersten Blick wirkt es natürlich etwas seltsam, wenn die Signatur-Datei selbst Bestandteil der signierten Datei ist. Das kann normalerweise ja nicht funktionieren ... beim Berechnen der Signatur (die i.d.R. ja eine mit einem privaten (RSA-)Schlüssel verschlüsselte DER-Struktur ist, die einen kryptographisch erzeugten Hash-Wert über die signierte Datei enthält) kann ja das endgültige Ergebnis (also der Inhalt dieser Signatur) noch nicht in deren Berechnung einfließen; da muß AVM offenbar irgendwelche "Kunstgriffe" verwenden, damit das am Ende funktioniert.
Nun steht zu vermuten, daß AVM auch nicht jedesmal das Rad neu erfindet und daher wird die Berechnung so einer Signatur sicherlich in irgendeiner Bibliothek enthalten sein, da bietet sich eine solche mit dem Namen libfwsign.so ja geradezu als "Verdächtige" an. Schauen wir dort also einmal nach, was diese Bibliothek einem Aufrufer so zu bieten hätte (das erfolgt jetzt auf einer 7490 und in dem dort installierten Labor-Image (33361 - mit modfs angepaßt), weil ich die Toolchain nur auf der 7490 verwende und sie daher auch nur für die 7490 erstellt habe, die 7390 ist dafür dann doch zu schwachbrüstig - daher können die Offsets in der Bibliothek von denen in der 7390 abweichen):
Diese Bibliothek stellt also einige Funktionen zum Umgang mit einer Signatur zur Verfügung (die roten) und verwendet dazu offenbar kryptographische Standardfunktionen: MD5 - sicherlich für den Hash-Wert und RSA - womit man schon mal Daten bis zur maximalen Länge des Schlüsselpaars direkt signieren kann.
AVM verwendet 1024-Bit-RSA-Schlüssel, wie man oben anhand des Inhalts der /etc/plugin_global_key.pem ja sehen kann, die erste Zeile ist der Modulus und die zweite der Exponent des öffentlichen Schlüssel, das sehen wir später auch noch einmal in den Protokoll-Dateien von AVM.
Auf der anderen Seite paßt das auch hervorragend zu der Größe der Datei signature aus unserem Image, denn diese 1024 Bit sind ja genau die 128 Byte, die diese Datei groß ist und auch die von der libfwsign.so importierte Funktion BN_hex2bn paßt zu unserem Szenario, denn aus der Zeile mit den beiden Faktoren in der Datei mit dem öffentlichen Schlüssel müssen ja irgendwann wieder "big numbers" in der internen Darstellung werden, damit die Kryptographie-Funktionen damit umgehen können.
Auch die ebenfalls von der Bibliothek importierte Funktion RSA_verify paßt nahtlos zu den bisherigen Feststellungen (wobei man für das grundlegende Verständnis des AVM-Mechanismus nun nicht unbedingt auch mit den Kryptographie-Funktionen von OpenSSL programmieren können muß, das ist nur als "Nachweis" verlinkt).
Damit können wir (als Arbeitsthese) mal zwei Punkte festhalten:
1. Die Datei signature in so einer Image-Datei ist eine mit einem privaten RSA-Schlüssel erzeugte Signatur-Datei, der signierte Hash-Wert dürfte ein MD5-Hash über die Eingabedatei sein.
2. Dieses Verhalten müßte man auch mit "normalen Tools" nachbilden können, denn für das Signieren so einer Datei gibt es im OpenSSL-Paket auch entsprechende Programme.
Das läßt sich ja nun recht einfach mittels OpenSSL überprüfen, die dazu notwendigen Binaries kann man z.B. mit dem Freetz-Projekt erstellen.
Wenn es sich bei der signature um das vermutete Format handeln sollte, dann müßte diese Datei ja einfach zu "entschlüsseln" sein.
Dazu brauchen wir - neben einem möglichst kompletten OpenSSL-Programm, das eigentlich nur ein CLI für die libcrypto.so und libssl.so ist - noch die Signatur-Datei und den öffentlichen Schlüssel zu ihrer Überprüfung.
Erstere ist schnell extrahiert:
Nun ist die Datei /etc/plugin_global_key.pem in der AVM-Firmware ja trotz ihres Namens keine Datei in PEM-Kodierung, wie man sie gewöhnlich im Umgang mit Kryptographie-Tools findet, dort handelt es sich beim PEM-Format um die Base64-kodierte DER-Struktur - also ASN.1-kodierte einzelne Werte in binärer Darstellung.
Für das Umwandeln einer Datei aus dem von AVM verwendeten Format in eine "richtige" PEM-Datei habe ich schon vor langer Zeit ein Shell-Skript erstellt und dieses (auch schon eine Weile her) in meinem GitHub-Repository veröffentlicht: https://github.com/PeterPawn/YourFritz/blob/master/signimage/avm_pubkey_to_pkcs8
Mit diesem Skript ist dann der öffentliche Schlüssel von AVM schnell in das passende Format konvertiert und einem Test der oben stehenden Hypothese zum Inhalt der Datei signature steht nichts mehr im Wege:
Nach der Umwandlung des öffentlichen Schlüssels noch einmal schnell den korrekten Aufbau verifiziert (wenn OpenSSL den akzeptiert, kann da nichts falsch sein, außerdem stimmt der angezeigte Modulus mit dem Hexdump im AVM-File überein) und dann erst einmal mit diesem öffentlichen Schlüssel den Inhalt der signature geprüft ... das hat noch nichts mit der Prüfung der Image-Datei zu tun, es stellt nur sicher, daß die Datei signature mit dem zugehörigen privaten Schlüssel erzeugt wurde und die oben zu sehende ASN.1-Struktur "enthüllt" auch, daß die Annahme eines MD5-Hashes richtig war - der rote Teil ist der mit der Signatur gesicherte Hash-Wert der Eingabedatei. Die Gegenprobe mit einem anderen Schlüssel ergibt dann den erwarteten Fehler - bis hier sollte also alles halbwegs stimmig sein.
Schauen wir uns jetzt das Ergebnis so eines Aufrufs von tr069fwupdate mal etwas genauer an:
Neben dem Return-Code erzeugt so ein Aufruf ja noch einiges an Dateien, schauen wir einmal, welche das sind:
In unseren umgeleiteten Ausgabedateien steht offensichtlich nichts (die haben die Länge 0) - da werden tatsächlich nur Fehler beim Aufruf (z.B. ungültige Dateinamen) protokolliert. Das Ergebnis der Signaturprüfung wird wohl am ehesten in den Dateien in /var/tmp stehen, die Liste wurde oben nach Datum sortiert und nur die relevanten Einträge werden dargestellt. Sehen wir uns zunächst die (vermutlich) relevantesten Dateien an:
Hier finden wir also unseren MD5-Hash in den Protokolldateien wieder ... die fwsign.log enthält darüber hinaus noch weitere Einträge zu anderen öffentlichen Schlüsseln (erst der grüne ist der hier verwendete), wenn man die Daten vergleicht, stellt man schnell fest, daß es sich hier um den Inhalt der Dateien für das Signieren von anderen Images handelt:
Anscheinend wird also bei der Verwendung der libfwsign.so (oder auch von tr069fwupdate selbst) nach mehr als einem Kandidaten für den öffentlichen Schlüssel geschaut ... wenn keiner gefunden wird, gibt tr069fwupdate allerdings einen Return-Code ungleich 0 aus, hier wurde also (s.o.) offenbar ein passender Schlüssel zum Prüfen der Signatur gefunden.
(Wenn man das später weiter testet, stellt man fest, daß da die Dateien /etc/avm_firmware_public_key[1-9] der Reihe nach probiert werden (so sie denn vorhanden sind) und dann erst kommt die /etc/plugin_global_key.pem dran - das merken wir uns mal für später als interessantes Detail.)
Nun ist offenbar beim Erzeugen der fwsign.log die Prüfung schon erfolgt bzw. in vollem Gange, das wirft dann die Frage auf, was da in der /var/tmp/firmware_stream_result stehen mag.
Dem Augenschein nach handelt es sich bei total=1239040 ja um die Größe der Image-Datei (viel weiter oben mal markiert in einem CODE-Kasten), ret=0 ist sicherlich irgendein Return-Code und die Angabe nach sigcrc ist ja ohne Zweifel der MD5-Hash, der uns hier immer wieder über den Weg läuft.
Bleibt die Frage, wer diese Datei eigentlich erzeugt ... da tr069fwupdate bereits selbst die Signaturen überprüft, macht so eine "intermediate"-Datei für dieses Programm ja nur begrenzten Sinn, es ist also anzunehmen, daß sie von einer anderen durch tr069fwupdate aufgerufenen Komponente erzeugt wurde als Mittel der Kommunikation zwischen Prozessen. Stimmt diese Annahme, wird der Dateiname irgendwo in dieser Komponente auftauchen und somit auch per grep zu finden sein.
Das machen wir jetzt wieder an einer entpackten Dateisystemstruktur für die 7490 (und auf der 7490), weil so eine Suche über das procfs auf der FRITZ!Box nicht funktioniert und das dann eine ziemlich nervige Zeile für den Aufruf von grep würde. Also verwende ich dafür die auf der Box entpackte Struktur der 113.06.55-33361 - irgendwo habe ich mal das Skript extract_7490_new_filesystem hier veröffentlicht, mit dem man so eine Struktur auf einer 7490 selbst auspacken kann (wenn man kein Freetz nutzen will) - allerdings muß man auf der FRITZ!Box dann sicherstellen, daß sich keine "directory loops" ergeben, wenn man rekursiv suchen läßt.
Das Ergebnis dürfte nicht allzuviel Interpretationsspielraum lassen ... ganz offensichtlich kommunizieren nur diese beiden Komponenten über diese Datei.
Also liegt die Vermutung nahe, daß tr069fwupdate (welches wir ja aufgerufen haben) seinerseits firmwarecfg aufruft. firmwarecfg ist nun ein CGI-Binary, das auch an diversen anderen Stellen im GUI für den Upload von Dateien verwendet wird, insofern wäre auch die Annahme, daß dort ebenfalls Funktionen zur Prüfung von Images enthalten sind (z.B. für das manuelle Update über eine Image-Datei), auch nicht so abwegig. Das war lange Zeit so etwas wie die eierlegende Wollmilchsau bei den binären Komponenten für das GUI - im Prinzip ist das sogar heute noch so, daß da alle möglichen Funktionen aus unterschiedlichen "Funktionsblöcken" enthalten sind.
Sehen wir jetzt mit strings mal in das Programm tr069fwupdate hinein, findet sich auch schnell der Aufruf von firmwarecfg:
Das hatten wir zwar früher schon festgestellt (bei der TR069-Geschichte) ... aber man sieht auch deutlich, daß offenbar irgendwie die Image-Datei auf verschiedenen Wegen bereitgestellt werden kann, aber am Ende landet sie immer in stdin für firmwarecfg, während die Ausgabe dann gleich per Pipe an das tar-Applet weitergeleitet und von diesem entpackt wird.
Spätestens seit der Version 06.30 der Firmware wissen wir auch, daß AVM da den Mechanismus beim Auspacken von Firmware-Images in firmwarecfg noch einmal geändert hat (bei der Verwendung von tr069fwupdate war da schon länger ein anderes Verzeichnis beim Aufruf eines der o.a. Kommandos aktiv, nämlich /var/packet), damit das Auspacken nicht mehr direkt nach /var erfolgt und dabei dann alle möglichen Dateien (im einzigen regulär beschreibbaren Verzeichnis unterhalb von /) überschrieben werden können.
Zwar wurde auch vorher schon sichergestellt, daß nur relativ zu ./var/ eingepackte Dateien überhaupt ausgepackt werden können (andere Pfade werden stur in /var/tmp/ignored_tar_content geändert von firmwarecfg stream, ebenso alle Dateien, die kein normales Verzeichnis/keine normale Datei sind oder das /../ für das übergeordnete Verzeichnis im Namen haben - obwohl das schon vom tar-Applet (inzwischen) verhindert würde), aber es gab immer wieder neue Ideen, wie man diesen Mechanismus zum Überschreiben von FRITZ!OS-Dateien verwenden könnte - schon früher wurde nach jedem der oben stehenden tar-Aufrufe die Datei /var/post_install aus dem SquashFS-Image erneuert, weil sie einfach durch so eine Image-Datei überschrieben werden konnte und dann beim Neustart des Routers Kommandos ausgeführt werden konnten. Seit der Verschärfung der "Sicherheitsbestimmungen" ist da jedenfalls (fast) alles wirklich sicherer geworden, weil nicht mehr mit dem Wurzelverzeichnis als Basis entpackt wird und die entpackten Dateien erst nach erfolgreicher Signaturprüfung nach / (bzw. /var) verschoben werden. Für die 06.30 hat dann auch AVM irgendwann mal diese beseitigte Schwachstelle beschrieben, nachdem der Finder sie auf der Full Disclosure-Mailingliste veröffentlicht hatte (bzw. parallel dazu, denn das war wohl abgestimmtes Vorgehen).
Ausgehend davon, daß der Aufruf von firmwarecfg das einzige Programm ist, was in den oben gefundenen Zeilen zwischen dem Download einer Datei und dem Auspacken steht, kann eigentlich nur innerhalb von firmwarecfg diese Umorganisation des Firmware-Images stattfinden und in der Tat stellt man bei direktem Aufruf von firmwarecfg fest, daß dort am TAR-Archiv "herumgepfuscht" wird:
Wie man sehen kann, fügt firmwarecfg dem Pfad so einer Datei einfach eine weitere Komponente unpack hinzu, dieses Verzeichnis wird auch vorher ordentlich rekursiv gelöscht. Damit ist das Schreiben außerhalb von /var/unpack durch das tar-Applet recht schwierig, auch wenn das Wurzelverzeichnis das aktuelle wäre für den tar-Aufruf - bei tr069fwupdate ist das ohnehin nicht der Fall, dort wird ja /var/packet beim Aufruf der Kommandokette verwendet.
Auf der anderen Seite kann man auch sehen, daß die Datei /var/tmp/firmware_stream_result tatsächlich von firmwarecfg (oder einer dort aufgerufenen Komponente) erzeugt wird, denn sie ist nach vorherigem Löschen und Ausführung von firmwarecfg ja wieder da.
Wenn wir jetzt etwas weiter oben noch einmal nach den Dateien schauen, die so ein Aufruf von tr069fwupdate offensichtlich erzeugt, stechen einem die Dateien /var/tmp/install_out.log und /var/tmp/install_error.log ins Auge, die waren vorher ebenfalls nicht vorhanden.
Hier handelt es sich augenscheinlich um die Ausgabedateien bei der Abarbeitung der im Image enthaltenen Skript-Datei ./var/install ... wenn die Signatur paßt, ruft tr069fwupdate offenbar auch gleich noch die im Image enthaltene Skript-Datei auf. Das disqualifiziert dann tr069fwupdate irgendwie als Mittel der Wahl für eine eigene Prüfung eines AVM-Images - es ist sicherlich nicht im Sinne des Erfinders, wenn ein Firmware-Image mit gültiger Signatur im Rahmen von modfs auch gleich noch installiert wird. Selbst wenn das in die inaktive Partition erfolgen würde und sich durch das Umsetzen von linux_fs_start auf den ursprünglichen Wert wieder rückgängig machen ließe, ist das eine unnötige Belastung des Flash-Speichers, denn sicherlich soll eher eine modifizierte Firmware nach modfs verwendet werden.
Also müssen wir uns vielleicht selbst um die Auswertung des Ergebnisses von firmwarecfg stream in der /var/tmp/firmware_stream_result kümmern, wenn wir nur rein die gültige Signatur eines AVM-Images überprüfen wollen. Probieren wir doch einfach einmal aus, ob bereits der Aufruf von firmwarecfg stream zur Ausführung der enthaltenen ./var/install führt:
Der Vergleich der Zeitstempel der erzeugten Protokolldateien macht schnell deutlich, daß von firmwarecfg tatsächlich nur die /var/tmp/firmware_stream_result geschrieben wird (neben der Ausgabe des modifizierten Archivs auf stdout). Für weitere Tests können wir uns also auch gleich auf den Aufruf von firmwarecfg stream beschränken.
Nun kommen wir wieder zu der Thematik zurück, wie eine Signatur gleichzeitig Bestandteil der signierten Datei sein könnte ... ohne besondere Vorkehrungen ist das schlicht nicht möglich, wie ich weiter oben schon mal angedeutet habe. Auch offenbart die Verwendung eines stinknormalen Programms zur Berechnung des MD5-Hashes für die plugins.update schnell, daß da offenbar der MD5-Hash nicht 1:1 über die Datei - so wie sie im Dateisystem liegt - gebildet wird, weder vor noch nach der Modifikation mit dem zusätzlichen unpack-Verzeichnis im Pfad:
Die Änderung an den Pfaden bewirkt zwar auch eine Änderung des Hashes, aber der ist immer noch meilenweit von dem entfernt, was da firmwarecfg für die Datei berechnet hat.
Ausgehend davon, daß es eben nicht möglich ist, die Signatur selbst in die Berechnung der Signatur einzubeziehen, liegt ja die Vermutung nahe, daß da von firmwarecfg auch einfach bei der Berechnung des Hashes diese Datei ausgeblendet wird ... wenn diese Annahme stimmt, müßte ja auch die Berechnung des Hashes durch firmwarecfg dasselbe Ergebnis liefern, wenn man diese Datei im "Ausgangsmaterial" ändert - eine solche Änderung fällt dann logischerweise nicht auf, wenn der Hash ohne diese Datei berechnet wird. Das läßt sich ja schnell überprüfen:
Die Datei ./var/signature ist die letzte im Archiv, der Header mit den Metadaten der Datei beginnt an Offset 0x12da00 und der eigentliche Inhalt der Datei beginnt am Offset 0x12dc00. Wenn der Inhalt der Datei tatsächlich nicht berücksichtigt wird, dürfte sich eine Änderung an diesem Inhalt nicht auf den Hash-Wert auswirken, den firmwarecfg berechnet/berechnen läßt (denn das macht sicherlich wieder die libfwsign.so). Also überschreiben wir den Inhalt einfach mit etwas anderem, ausgehend von der Angabe im total-Wert in der firmware_stream_result sollte man erst einmal nicht annehmen, daß die Datei einfach "ausgeschnitten" wird - solche abweichenden Längen machen das am Ende nur komplizierter als nötig:
Die Änderung des Inhalts hat also keine Auswirkung auf den durch firmwarecfg berechneten MD5-Hash, aber der Hash über die geänderte Datei stimmt immer noch nicht mit dem berechneten Wert überein, also ist da offensichtlich noch etwas anders im Archiv.
Nun kann man den Inhalt der Metadaten für die ./var/signature zwar wohl tatsächlich "vorhersagen" und könnte ihn vermutlich auch in die Signatur mit einbeziehen, aber genauso gut kann man die natürlich auch auslassen ... wir suchen ja jetzt nach der maximal möglichen Änderung am Eintrag für die ./var/signature, die noch keine Auswirkung auf den von firmwarecfg berechneten Hash hat. Die Hälfte der denkbaren Änderungen haben wir schon durchgeführt (so eine Datei in einem TAR-Archiv hat immer eine Länge, die ein Vielfaches von 512 ist und die Metadaten stecken ebenfalls in einem Block dieser Länge), also ändern wir jetzt einfach die andere Hälfte auch noch und sollte das nicht zum Erfolg führen (weil sich der Hash-Wert von firmwarecfg dann doch ändert), liegt die Wahrheit irgendwo in der Mitte zwischen den beiden Änderungen:
Wir können nun natürlich nicht länger nach dem Namen im Hexdump suchen, aber man sieht oben, daß die kompletten Metadaten und der Inhalt (2x 512 Byte) mit binären Nullen überschrieben wurden. Was sagt denn firmwarecfg zu dieser Datei?
Na also, für firmwarecfg sind die Dateien noch identisch und sogar das "Standardprogramm" zum Berechnen eines MD5-Hashes auf der Linux-Kommandozeile kommt zu demselben Ergebnis. Damit ist also nachgewiesen, daß von firmwarecfg bei der Berechnung des MD5-Hashes über ein Firmware-Image (bzw. über irgendeine TAR-Datei, die durch firmwarecfg stream gejagt wird) der Archiv-Member mit dem Namen ./var/signature nicht berücksichtigt wird ... man darf sicherlich auch getrost annehmen, daß das in der tatsächlichen Größe dieser Datei anhand der Metadaten erfolgt und nicht einfach 1024 Byte da blind überschrieben werden.
Damit wissen wir nun aber auch, wie wir für eine Signaturprüfung ohne die Verwendung von tr069fwupdate oder firmwarecfg stream vorgehen müssen.
Der zweite Aufruf ist nur die Gegenprobe.
Nun gilt es also, die angeführten Aktionen so in einem Shell-Skript zu kombinieren, daß damit ein vom FTP-Server geladenes AVM-Image auf seine Echtheit geprüft werden kann (für modfs) und auf der anderen Seite ist es nun auch möglich, seine eigene Firmware zu signieren, wenn man sich selbst einen passenden RSA-Schlüssel (die 1024 Bit sind nun mal "Pflicht") erzeugt und den zugehörigen öffentlichen Schlüssel im richtigen Format (das ist ja auch alles andere als kompliziert) in das erste eigene Firmware-Image erst einmal eingebunden hat.
Hier rufen wir uns dann wieder in Erinnerung, daß tr069fwupdate (bzw. eher libfwsign.so) da zuerst die Dateien /etc/avm_firmware_public_key[1-9] versucht zu lesen und daß - in den Firmware-Versionen, die ich bisher gesehen habe - dort bisher nur bis zur "3" diese Dateien in Benutzung sind. Da es auch nichts schadet, wenn es eine Lücke zwischen den vorhandenen Dateien und der eigenen gibt, kann man den eigenen öffentlichen Schlüssel problemlos als /etc/avm_firmware_public_key9 in ein eigenes Image einbinden lassen.
Die entsprechenden Skript-Dateien für das Signieren eines eigenen Images (immer daran denken, daß die Verwendung einer solchen Signatur dann voraussetzt, daß man seinen öffentlichen Schlüssel irgendwie auf die FRITZ!Box gebracht hat und zwar auch noch unter dem richtigen Namen) und für die Prüfung so einer Signatur kommen in Kürze ins GitHub-Repository... aber das kann noch ein wenig dauern. bzw. sie stehen inzwischen unter https://github.com/PeterPawn/YourFritz/tree/master/signimage im Repository. Der Frage, wie der Aufruf da auszusehen hat und wie man eine AVM-Signatur prüft bzw. seine eigene erzeugt, die dann von einer Firmware mit dem zusätzlichen öffentlichen Schlüssel auch mit Bordmitteln getestet werden kann, widmet sich der nächste Beitrag.
Sollte jemand Fragen haben, wäre es nett, wenn die erst einmal in einem gesonderten Thread gestellt werden könnten - event. kann dann ja ein Moderator diesen zusätzlichen Thread später hier anhängen, wenn ich mit der Beschreibung fertig bin.
EDIT 2020-02-02: CODE-Blöcke als "rich BBCode" markiert, damit funktionieren die Farben wieder.
Das ist hier wieder eher "als Story" erzählt ... wer es kürzer und "als Referenz" will, muß es eben selbst erkunden.
Nicht zuletzt spielt die Verwendung einer wirklich originalen Firmware auch dann ein Rolle, wenn man mein modfs-Skript verwenden will, um seine NAND-basierte FRITZ!Box an die eigenen Wünsche anzupassen. Dieses Skript kann ja auf Wunsch auch eine aktuelle Firmware direkt vom FTP-Server von AVM laden ... bisher wird dabei allerdings nicht getestet, ob es sich wirklich um eine unveränderte Version von AVM handelt oder ob da schon jemand manipuliert hat.
Nun kann so eine Image-Datei ja auf verschiedenen Wegen in eine FRITZ!Box gelangen, wer schon einmal einen Blick in das Shell-Skript /sbin/start_dect_update.sh einer aktuellen Firmware-Version gewagt hat, dem wird früher oder später unweigerlich die Zeile
Rich (BBCode):
tr069fwupdate packet ${url} 2>>/var/tmp/dect_update_error.log >>/var/tmp/dect_update_out.log
Rich (BBCode):
install_plugins() {
[...]
echo "$0[$$]: Extract SOFTWARE-PLUGIN ${imountdir}/${plugin_updatefile}"
if [ -n "${isavefile}" ]; then
echo "$0[$$]: ... option savefile '${isavefile}' found"
tr069fwupdate packet ${imountdir}/${plugin_updatefile} ${isavefile}
else
tr069fwupdate packet ${imountdir}/${plugin_updatefile}
fi
if ! fw_error_text $? ; then
echo "$0[$$]: ACHTUNG: es ist ein Fehler aufgetreten."
rm -f ${isavefile}
return
fi
[...]
}
Für eine genauere Analyse der Vorgänge dort, müssen wir uns also erst einmal eine passende Datei besorgen, die wir als Eingabe für unsere Tests benutzen können. Ein komplettes Firmware-Image paßt hier schon deshalb nicht so richtig, weil es eine erkleckliche Größe hat und wir absehbar mit ein paar Kommandos wie hexdump, dd und auch strings umgehen müssen, die bei einer größeren Datei dann auch größere Ausgaben produzieren und in der Regel auch länger brauchen bei der Abarbeitung.
Daher böte es sich an, für diese Untersuchungen ein Update-Image für ein DECT-Gerät von AVM zu verwenden, denn dieses ist normalerweise etwas kleiner - allerdings eignet sich seit dem Erscheinen der 84.06.51 das dort enthaltene plugins.update (auch ein verkapptes TAR-Archiv) noch viel besser für die ersten Schritte, da im Gegensatz zu den für andere Images verwendeten öffentlichen Schlüsseln:
Rich (BBCode):
# ls -l /etc/avm_firmware_public_key*
-rw-r----- 1 root root 266 May 8 22:44 /etc/avm_firmware_public_key1
-rw-r----- 1 root root 266 May 8 22:44 /etc/avm_firmware_public_key2
-rwxr-xr-x 1 root root 266 May 8 22:44 /etc/avm_firmware_public_key3
Rich (BBCode):
# cat /etc/plugin_global_key.pem
00bed5268d38b33fe9876f4ae22a5970657c3501adcb84879654def6fc83c1303667b12a031025782cb6490fed946ec81c3968ebc5d50697af9a2475339692eb5c84240cac09b2b3ca2a419efb6ae206e782209fc5a405054630634d4a4bb0f3c053c72547f2fb95add232929a7f722db94d873e02cbb2985106d6dd66dfa5592f
010001
Also nehmen wir mal diese Datei plugins.update auf einer 7390 genauer unter die Lupe (sie wird ja beim Update auf die 06.51 automatisch an einer Stelle im Dateisystem abgelegt, auf die man notfalls auch mit den NAS-Funktionen zugreifen könnte), zuerst sehen wir uns einmal an, was dort an Dateien enthalten ist:
Rich (BBCode):
# ls -l /var/media/ftp/FRITZ/plugins/
-rwx--xr-x 1 root root 1239040 Jan 1 1970 plugins.update
# tar -t -v -f /var/media/ftp/FRITZ/plugins/plugins.update
drwxr-x--- 0/0 0 2016-04-25 12:45:48 ./var/
-rwxrwxrwx 0/0 179 2016-04-25 12:45:48 ./var/install
-rwx------ 0/0 1228800 2016-04-25 12:45:48 ./var/plugin-wlan.image
-rwx------ 0/0 4096 2016-04-25 12:45:48 ./var/plugin-webcm_interpreter.image
-rw-r----- 0/0 128 2016-04-25 12:45:48 ./var/signature
Nun steht zu vermuten, daß AVM auch nicht jedesmal das Rad neu erfindet und daher wird die Berechnung so einer Signatur sicherlich in irgendeiner Bibliothek enthalten sein, da bietet sich eine solche mit dem Namen libfwsign.so ja geradezu als "Verdächtige" an. Schauen wir dort also einmal nach, was diese Bibliothek einem Aufrufer so zu bieten hätte (das erfolgt jetzt auf einer 7490 und in dem dort installierten Labor-Image (33361 - mit modfs angepaßt), weil ich die Toolchain nur auf der 7490 verwende und sie daher auch nur für die 7490 erstellt habe, die 7390 ist dafür dann doch zu schwachbrüstig - daher können die Offsets in der Bibliothek von denen in der 7390 abweichen):
Rich (BBCode):
root@FB7490:~ $ /var/bintools/usr/bin/nm -D /lib/libfwsign.so
U BN_hex2bn
U ERR_get_error
U MD5Final
U MD5Init
U MD5Update
U RSA_free
U RSA_new
U RSA_verify
[...]
000013b0 T hexdump
[...]
000021f4 T my_BN_hex2bn
0000215c T my_RSA_free
00002174 T my_RSA_md5_verify
00002100 T my_RSA_new
00001f60 T signature_add_dirty_marker
00001d28 T signature_check
00001bdc T signature_get_crc
00001c68 T signature_set_crc
00001b10 T signature_stream_exit
00001438 T signature_stream_init
00001528 T signature_stream_write
AVM verwendet 1024-Bit-RSA-Schlüssel, wie man oben anhand des Inhalts der /etc/plugin_global_key.pem ja sehen kann, die erste Zeile ist der Modulus und die zweite der Exponent des öffentlichen Schlüssel, das sehen wir später auch noch einmal in den Protokoll-Dateien von AVM.
Auf der anderen Seite paßt das auch hervorragend zu der Größe der Datei signature aus unserem Image, denn diese 1024 Bit sind ja genau die 128 Byte, die diese Datei groß ist und auch die von der libfwsign.so importierte Funktion BN_hex2bn paßt zu unserem Szenario, denn aus der Zeile mit den beiden Faktoren in der Datei mit dem öffentlichen Schlüssel müssen ja irgendwann wieder "big numbers" in der internen Darstellung werden, damit die Kryptographie-Funktionen damit umgehen können.
Auch die ebenfalls von der Bibliothek importierte Funktion RSA_verify paßt nahtlos zu den bisherigen Feststellungen (wobei man für das grundlegende Verständnis des AVM-Mechanismus nun nicht unbedingt auch mit den Kryptographie-Funktionen von OpenSSL programmieren können muß, das ist nur als "Nachweis" verlinkt).
Damit können wir (als Arbeitsthese) mal zwei Punkte festhalten:
1. Die Datei signature in so einer Image-Datei ist eine mit einem privaten RSA-Schlüssel erzeugte Signatur-Datei, der signierte Hash-Wert dürfte ein MD5-Hash über die Eingabedatei sein.
2. Dieses Verhalten müßte man auch mit "normalen Tools" nachbilden können, denn für das Signieren so einer Datei gibt es im OpenSSL-Paket auch entsprechende Programme.
Das läßt sich ja nun recht einfach mittels OpenSSL überprüfen, die dazu notwendigen Binaries kann man z.B. mit dem Freetz-Projekt erstellen.
Wenn es sich bei der signature um das vermutete Format handeln sollte, dann müßte diese Datei ja einfach zu "entschlüsseln" sein.
Dazu brauchen wir - neben einem möglichst kompletten OpenSSL-Programm, das eigentlich nur ein CLI für die libcrypto.so und libssl.so ist - noch die Signatur-Datei und den öffentlichen Schlüssel zu ihrer Überprüfung.
Erstere ist schnell extrahiert:
Rich (BBCode):
# mkdir /var/media/ftp/sigtest
# tar -x -f /var/media/ftp/FRITZ/plugins/plugins.update -O ./var/signature >/var/media/ftp/sigtest/signature
# cat /etc/plugin_global_key.pem >/var/media/ftp/sigtest/plugin_avm.key
# ls -l /var/media/ftp/sigtest
-rw-r--r-- 1 root root 266 Jun 6 17:22 plugin_avm.key
-rw-r--r-- 1 root root 128 Jun 6 17:20 signature
# cat /var/media/ftp/sigtest/plugin_avm.key
00bed5268d38b33fe9876f4ae22a5970657c3501adcb84879654def6fc83c1303667b12a031025782cb6490fed946ec81c3968ebc5d50697af9a2475339692eb5c84240cac09b2b3ca2a419efb6ae206e782209fc5a405054630634d4a4bb0f3c053c72547f2fb95add232929a7f722db94d873e02cbb2985106d6dd66dfa5592f
010001
Für das Umwandeln einer Datei aus dem von AVM verwendeten Format in eine "richtige" PEM-Datei habe ich schon vor langer Zeit ein Shell-Skript erstellt und dieses (auch schon eine Weile her) in meinem GitHub-Repository veröffentlicht: https://github.com/PeterPawn/YourFritz/blob/master/signimage/avm_pubkey_to_pkcs8
Mit diesem Skript ist dann der öffentliche Schlüssel von AVM schnell in das passende Format konvertiert und einem Test der oben stehenden Hypothese zum Inhalt der Datei signature steht nichts mehr im Wege:
Rich (BBCode):
# cd /var/media/ftp/sigtest
# ./avm_pubkey_to_pkcs8 <plugin_avm.key >plugin_avm.pem
# openssl rsa -pubin -in plugin_avm.pem -text -noout
Public-Key: (1024 bit)
Modulus:
00:be:d5:26:8d:38:b3:3f:e9:87:6f:4a:e2:2a:59:
70:65:7c:35:01:ad:cb:84:87:96:54:de:f6:fc:83:
c1:30:36:67:b1:2a:03:10:25:78:2c:b6:49:0f:ed:
94:6e:c8:1c:39:68:eb:c5:d5:06:97:af:9a:24:75:
33:96:92:eb:5c:84:24:0c:ac:09:b2:b3:ca:2a:41:
9e:fb:6a:e2:06:e7:82:20:9f:c5:a4:05:05:46:30:
63:4d:4a:4b:b0:f3:c0:53:c7:25:47:f2:fb:95:ad:
d2:32:92:9a:7f:72:2d:b9:4d:87:3e:02:cb:b2:98:
51:06:d6:dd:66:df:a5:59:2f
Exponent: 65537 (0x10001)
# openssl rsautl -in signature -verify -inkey plugin_avm.pem -pubin -asn1parse
0:d=0 hl=2 l= 32 cons: SEQUENCE
2:d=1 hl=2 l= 12 cons: SEQUENCE
4:d=2 hl=2 l= 8 prim: OBJECT :md5
14:d=2 hl=2 l= 0 prim: NULL
16:d=1 hl=2 l= 16 prim: OCTET STRING
0000 - ee 4e f7 45 2f 76 b8 b9-51 31 43 72 f5 78 3d 8b .N.E/v..Q1Cr.x=.
# ./avm_pubkey_to_pkcs8 </etc/avm_firmware_public_key1 >avm1.pem
# openssl rsautl -in signature -verify -inkey avm1.pem -pubin -asn1parse
RSA operation error
1996924008:error:0407006A:lib(4):func(112):reason(106):NA:0:
1996924008:error:04067072:lib(4):func(103):reason(114):NA:0:
Schauen wir uns jetzt das Ergebnis so eines Aufrufs von tr069fwupdate mal etwas genauer an:
Rich (BBCode):
# tr069fwupdate packet file:///var/media/ftp/FRITZ/plugins/plugins.update 2>/var/media/ftp/sigtest/check.err >/var/media/ftp/sigtest/check.out;echo "rc=$?"
rc=0
Rich (BBCode):
# ls -lrt /var/tmp
[...]
-rw-r--r-- 1 root root 60 Jun 6 18:05 firmware_stream_result
-rw-r--r-- 1 root root 109 Jun 6 18:05 install_out.log
-rw-r--r-- 1 root root 58 Jun 6 18:05 install_error.log
-rw-r--r-- 1 root root 1222 Jun 6 18:05 fwsign.log
# ls -l /var/media/ftp/sigtest/
-rw-r--r-- 1 root root 0 Jun 6 18:05 check.err
-rw-r--r-- 1 root root 0 Jun 6 18:05 check.out
-rw-r--r-- 1 root root 266 Jun 6 17:22 plugin_avm.key
-rw-r--r-- 1 root root 128 Jun 6 17:20 signature
Rich (BBCode):
# cat /var/tmp/firmware_stream_result
total=1239040 ret=0 sigcrc=ee4ef7452f76b8b951314372f5783d8b
# cat /var/tmp/fwsign.log
md5: ee 4e f7 45 2f 76 b8 b9 51 31 43 72 f5 78 3d 8b
public num='00ab54b73f000e9fc5bf3c0d229e56ae1644507877ca1eaf364708975de1e50236754fdc8577bd9e9ec4c94bd595c22195a9cfa2ac57840c507b483ccf1c5d4d1448c6d8c8bdab629df4e5bcd65a52695064819d1f5157afeb8fea43dedd9c7b091c344cfb42434f5f7bc77bbf2c0469400d10a29d04d6c1b2807fd3be68800eaf'
public exp='010001'
public num='00c923d6cde5ca1780e84b6383c6c24b03a56532149f0a210541f16b1698d5761dd90ffd77500ff5dd2c9269710dad5ebcb1f6fbf318993429fcb228c043cc0980ec09b85b8a393c96b3e52f647b898ddff37aa9f662771aa87cee8686d3e2e3970a38e25bdc13f591344a2f6a39647a6555696fca21423e90c987e990ad64ff81'
public exp='010001'
public num='00f2ee9ffd8556211f5644da48a252b107124b330d4c20dcf3b9bac892924cabaa4df4f53e1c62e3f2aa12a23eb1d770df1520a998078738407e6a71b077f73ba976363836b880b0dd88741bc3b83ab061691226e823404b7fc88ed278d8130fe5336eb925c78f2f8ad7cb87d9586286f768ab3236fa8fb51ae7c4bbe1e041d849'
public exp='010001'
public num='00bed5268d38b33fe9876f4ae22a5970657c3501adcb84879654def6fc83c1303667b12a031025782cb6490fed946ec81c3968ebc5d50697af9a2475339692eb5c84240cac09b2b3ca2a419efb6ae206e782209fc5a405054630634d4a4bb0f3c053c72547f2fb95add232929a7f722db94d873e02cbb2985106d6dd66dfa5592f'
public exp='010001'
Rich (BBCode):
# for d in /etc/avm_firmware_public_key*;do echo $d; cat $d; done
/etc/avm_firmware_public_key1
00ab54b73f000e9fc5bf3c0d229e56ae1644507877ca1eaf364708975de1e50236754fdc8577bd9e9ec4c94bd595c22195a9cfa2ac57840c507b483ccf1c5d4d1448c6d8c8bdab629df4e5bcd65a52695064819d1f5157afeb8fea43dedd9c7b091c344cfb42434f5f7bc77bbf2c0469400d10a29d04d6c1b2807fd3be68800eaf
010001
/etc/avm_firmware_public_key2
00c923d6cde5ca1780e84b6383c6c24b03a56532149f0a210541f16b1698d5761dd90ffd77500ff5dd2c9269710dad5ebcb1f6fbf318993429fcb228c043cc0980ec09b85b8a393c96b3e52f647b898ddff37aa9f662771aa87cee8686d3e2e3970a38e25bdc13f591344a2f6a39647a6555696fca21423e90c987e990ad64ff81
010001
/etc/avm_firmware_public_key3
00f2ee9ffd8556211f5644da48a252b107124b330d4c20dcf3b9bac892924cabaa4df4f53e1c62e3f2aa12a23eb1d770df1520a998078738407e6a71b077f73ba976363836b880b0dd88741bc3b83ab061691226e823404b7fc88ed278d8130fe5336eb925c78f2f8ad7cb87d9586286f768ab3236fa8fb51ae7c4bbe1e041d849
010001
(Wenn man das später weiter testet, stellt man fest, daß da die Dateien /etc/avm_firmware_public_key[1-9] der Reihe nach probiert werden (so sie denn vorhanden sind) und dann erst kommt die /etc/plugin_global_key.pem dran - das merken wir uns mal für später als interessantes Detail.)
Nun ist offenbar beim Erzeugen der fwsign.log die Prüfung schon erfolgt bzw. in vollem Gange, das wirft dann die Frage auf, was da in der /var/tmp/firmware_stream_result stehen mag.
Dem Augenschein nach handelt es sich bei total=1239040 ja um die Größe der Image-Datei (viel weiter oben mal markiert in einem CODE-Kasten), ret=0 ist sicherlich irgendein Return-Code und die Angabe nach sigcrc ist ja ohne Zweifel der MD5-Hash, der uns hier immer wieder über den Weg läuft.
Bleibt die Frage, wer diese Datei eigentlich erzeugt ... da tr069fwupdate bereits selbst die Signaturen überprüft, macht so eine "intermediate"-Datei für dieses Programm ja nur begrenzten Sinn, es ist also anzunehmen, daß sie von einer anderen durch tr069fwupdate aufgerufenen Komponente erzeugt wurde als Mittel der Kommunikation zwischen Prozessen. Stimmt diese Annahme, wird der Dateiname irgendwo in dieser Komponente auftauchen und somit auch per grep zu finden sein.
Das machen wir jetzt wieder an einer entpackten Dateisystemstruktur für die 7490 (und auf der 7490), weil so eine Suche über das procfs auf der FRITZ!Box nicht funktioniert und das dann eine ziemlich nervige Zeile für den Aufruf von grep würde. Also verwende ich dafür die auf der Box entpackte Struktur der 113.06.55-33361 - irgendwo habe ich mal das Skript extract_7490_new_filesystem hier veröffentlicht, mit dem man so eine Struktur auf einer 7490 selbst auspacken kann (wenn man kein Freetz nutzen will) - allerdings muß man auf der FRITZ!Box dann sicherstellen, daß sich keine "directory loops" ergeben, wenn man rekursiv suchen läßt.
Rich (BBCode):
root@fb7490:/var/media/ftp/system/FB7490/firmware/113.06.55-33361 $ grep -r firmware_stream_result *
Binary file usr/www/cgi-bin/firmwarecfg matches
Binary file usr/bin/tr069fwupdate matches
Also liegt die Vermutung nahe, daß tr069fwupdate (welches wir ja aufgerufen haben) seinerseits firmwarecfg aufruft. firmwarecfg ist nun ein CGI-Binary, das auch an diversen anderen Stellen im GUI für den Upload von Dateien verwendet wird, insofern wäre auch die Annahme, daß dort ebenfalls Funktionen zur Prüfung von Images enthalten sind (z.B. für das manuelle Update über eine Image-Datei), auch nicht so abwegig. Das war lange Zeit so etwas wie die eierlegende Wollmilchsau bei den binären Komponenten für das GUI - im Prinzip ist das sogar heute noch so, daß da alle möglichen Funktionen aus unterschiedlichen "Funktionsblöcken" enthalten sind.
Sehen wir jetzt mit strings mal in das Programm tr069fwupdate hinein, findet sich auch schnell der Aufruf von firmwarecfg:
Rich (BBCode):
# strings /usr/bin/tr069fwupdate | grep firmwarecfg
wget %s -O - "ftp://%s" 2>/var/dl_err | /usr/www/cgi-bin/firmwarecfg stream %s | tar xvf -
wget %s -O - "ftp://%s:%s@%s" 2>/var/dl_err | /usr/www/cgi-bin/firmwarecfg stream | tar xvf -
httpsdl%s -O - "%s" 2>/var/dl_err | /usr/www/cgi-bin/firmwarecfg stream %s | tar xvf -
httpsdl%s -O - "%s" "%s" "%s" 2>/var/dl_err | /usr/www/cgi-bin/firmwarecfg stream | tar xvf -
cat "%s" 2>/dev/null | /usr/www/cgi-bin/firmwarecfg stream %s | tar xvf -
Spätestens seit der Version 06.30 der Firmware wissen wir auch, daß AVM da den Mechanismus beim Auspacken von Firmware-Images in firmwarecfg noch einmal geändert hat (bei der Verwendung von tr069fwupdate war da schon länger ein anderes Verzeichnis beim Aufruf eines der o.a. Kommandos aktiv, nämlich /var/packet), damit das Auspacken nicht mehr direkt nach /var erfolgt und dabei dann alle möglichen Dateien (im einzigen regulär beschreibbaren Verzeichnis unterhalb von /) überschrieben werden können.
Zwar wurde auch vorher schon sichergestellt, daß nur relativ zu ./var/ eingepackte Dateien überhaupt ausgepackt werden können (andere Pfade werden stur in /var/tmp/ignored_tar_content geändert von firmwarecfg stream, ebenso alle Dateien, die kein normales Verzeichnis/keine normale Datei sind oder das /../ für das übergeordnete Verzeichnis im Namen haben - obwohl das schon vom tar-Applet (inzwischen) verhindert würde), aber es gab immer wieder neue Ideen, wie man diesen Mechanismus zum Überschreiben von FRITZ!OS-Dateien verwenden könnte - schon früher wurde nach jedem der oben stehenden tar-Aufrufe die Datei /var/post_install aus dem SquashFS-Image erneuert, weil sie einfach durch so eine Image-Datei überschrieben werden konnte und dann beim Neustart des Routers Kommandos ausgeführt werden konnten. Seit der Verschärfung der "Sicherheitsbestimmungen" ist da jedenfalls (fast) alles wirklich sicherer geworden, weil nicht mehr mit dem Wurzelverzeichnis als Basis entpackt wird und die entpackten Dateien erst nach erfolgreicher Signaturprüfung nach / (bzw. /var) verschoben werden. Für die 06.30 hat dann auch AVM irgendwann mal diese beseitigte Schwachstelle beschrieben, nachdem der Finder sie auf der Full Disclosure-Mailingliste veröffentlicht hatte (bzw. parallel dazu, denn das war wohl abgestimmtes Vorgehen).
Ausgehend davon, daß der Aufruf von firmwarecfg das einzige Programm ist, was in den oben gefundenen Zeilen zwischen dem Download einer Datei und dem Auspacken steht, kann eigentlich nur innerhalb von firmwarecfg diese Umorganisation des Firmware-Images stattfinden und in der Tat stellt man bei direktem Aufruf von firmwarecfg fest, daß dort am TAR-Archiv "herumgepfuscht" wird:
Rich (BBCode):
# tar -t -v -f /var/media/ftp/FRITZ/plugins/plugins.update
drwxr-x--- 0/0 0 2016-04-25 12:45:48 ./var/
-rwxrwxrwx 0/0 179 2016-04-25 12:45:48 ./var/install
-rwx------ 0/0 1228800 2016-04-25 12:45:48 ./var/plugin-wlan.image
-rwx------ 0/0 4096 2016-04-25 12:45:48 ./var/plugin-webcm_interpreter.image
-rw-r----- 0/0 128 2016-04-25 12:45:48 ./var/signature
# rm /var/tmp/firmware_stream_result
# cat /var/media/ftp/FRITZ/plugins/plugins.update | /usr/www/cgi-bin/firmwarecfg stream | tar -t -v
drwxr-x--- 0/0 0 2016-04-25 12:45:48 ./var/unpack/
-rwxrwxrwx 0/0 179 2016-04-25 12:45:48 ./var/unpack/install
-rwx------ 0/0 1228800 2016-04-25 12:45:48 ./var/unpack/plugin-wlan.image
-rwx------ 0/0 4096 2016-04-25 12:45:48 ./var/unpack/plugin-webcm_interpreter.image
-rw-r----- 0/0 128 2016-04-25 12:45:48 ./var/unpack/signature
# cat /var/tmp/firmware_stream_result
total=1239040 ret=0 sigcrc=ee4ef7452f76b8b951314372f5783d8b
Auf der anderen Seite kann man auch sehen, daß die Datei /var/tmp/firmware_stream_result tatsächlich von firmwarecfg (oder einer dort aufgerufenen Komponente) erzeugt wird, denn sie ist nach vorherigem Löschen und Ausführung von firmwarecfg ja wieder da.
Wenn wir jetzt etwas weiter oben noch einmal nach den Dateien schauen, die so ein Aufruf von tr069fwupdate offensichtlich erzeugt, stechen einem die Dateien /var/tmp/install_out.log und /var/tmp/install_error.log ins Auge, die waren vorher ebenfalls nicht vorhanden.
Rich (BBCode):
# cat /var/tmp/install_out.log
install plugins ...
PLUGINS=/var/packet/var/plugin-webcm_interpreter.image /var/packet/var/plugin-wlan.image
# cat /var/tmp/install_error.log
mkdir: can't create directory '/var/plugins': File exists
Also müssen wir uns vielleicht selbst um die Auswertung des Ergebnisses von firmwarecfg stream in der /var/tmp/firmware_stream_result kümmern, wenn wir nur rein die gültige Signatur eines AVM-Images überprüfen wollen. Probieren wir doch einfach einmal aus, ob bereits der Aufruf von firmwarecfg stream zur Ausführung der enthaltenen ./var/install führt:
Rich (BBCode):
# rm /var/tmp/install_* /var/install
rm: can't remove '/var/install': No such file or directory
# ls -lrt /var/tmp
[...]
-rw-r--r-- 1 root root 1222 Jun 6 18:05 fwsign.log
-rw-r--r-- 1 root root 60 Jun 6 19:16 firmware_stream_result
-rw-r--r-- 1 root root 28 Jun 6 19:26 dnsd_servers
-rw-r--r-- 1 root root 27 Jun 6 19:26 avm-resolv.conf
-rw-r--r-- 1 root root 42 Jun 6 19:31 chrony.drift
# cat /var/media/ftp/FRITZ/plugins/plugins.update | /usr/www/cgi-bin/firmwarecfg stream | tar -t -v
drwxr-x--- 0/0 0 2016-04-25 12:45:48 ./var/unpack/
-rwxrwxrwx 0/0 179 2016-04-25 12:45:48 ./var/unpack/install
-rwx------ 0/0 1228800 2016-04-25 12:45:48 ./var/unpack/plugin-wlan.image
-rwx------ 0/0 4096 2016-04-25 12:45:48 ./var/unpack/plugin-webcm_interpreter.image
-rw-r----- 0/0 128 2016-04-25 12:45:48 ./var/unpack/signature
# ls -lrt /var/tmp
[...]
-rw-r--r-- 1 root root 1222 Jun 6 18:05 fwsign.log
-rw-r--r-- 1 root root 28 Jun 6 19:26 dnsd_servers
-rw-r--r-- 1 root root 27 Jun 6 19:26 avm-resolv.conf
-rw-r--r-- 1 root root 42 Jun 6 19:31 chrony.drift
-rw-r--r-- 1 root root 60 Jun 6 19:55 firmware_stream_result
Nun kommen wir wieder zu der Thematik zurück, wie eine Signatur gleichzeitig Bestandteil der signierten Datei sein könnte ... ohne besondere Vorkehrungen ist das schlicht nicht möglich, wie ich weiter oben schon mal angedeutet habe. Auch offenbart die Verwendung eines stinknormalen Programms zur Berechnung des MD5-Hashes für die plugins.update schnell, daß da offenbar der MD5-Hash nicht 1:1 über die Datei - so wie sie im Dateisystem liegt - gebildet wird, weder vor noch nach der Modifikation mit dem zusätzlichen unpack-Verzeichnis im Pfad:
Rich (BBCode):
# md5sum /var/media/ftp/FRITZ/plugins/plugins.update
c8cc0c757610a86c7bd26db526c7ea88 /var/media/ftp/FRITZ/plugins/plugins.update
# cat /var/media/ftp/FRITZ/plugins/plugins.update | /usr/www/cgi-bin/firmwarecfg stream | md5sum;cat /var/tmp/firmware_stream_result
08f680b247fc60bb0b4f24d6cfedeb69 -
total=1239040 ret=0 sigcrc=ee4ef7452f76b8b951314372f5783d8b
Ausgehend davon, daß es eben nicht möglich ist, die Signatur selbst in die Berechnung der Signatur einzubeziehen, liegt ja die Vermutung nahe, daß da von firmwarecfg auch einfach bei der Berechnung des Hashes diese Datei ausgeblendet wird ... wenn diese Annahme stimmt, müßte ja auch die Berechnung des Hashes durch firmwarecfg dasselbe Ergebnis liefern, wenn man diese Datei im "Ausgangsmaterial" ändert - eine solche Änderung fällt dann logischerweise nicht auf, wenn der Hash ohne diese Datei berechnet wird. Das läßt sich ja schnell überprüfen:
Rich (BBCode):
# mkdir /var/tmp/sigtest
# cd /var/tmp/sigtest
# cp -a /var/media/ftp/FRITZ/plugins/plugins.update .
# hd plugins.update | grep -B 5 -A 40 "./var/signature"
0012ccf0 00 00 02 1d 00 00 00 00 00 00 00 00 00 00 02 e6 |................|
0012cd00 00 11 00 00 6a 06 7c f5 ac 51 6c 0f c2 1f ff fb |....j.|..Ql.....|
0012cd10 7c 40 00 00 00 00 00 00 00 03 00 00 00 00 00 00 ||@..............|
0012cd20 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
0012da00 2e 2f 76 61 72 2f 73 69 67 6e 61 74 75 72 65 00 |./var/signature.|
0012da10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
0012da60 00 00 00 00 30 31 30 30 36 34 30 00 30 30 30 30 |....0100640.0000|
0012da70 30 30 30 00 30 30 30 30 30 30 30 00 30 30 30 30 |000.0000000.0000|
0012da80 30 30 30 30 32 30 30 00 31 32 37 30 37 33 37 32 |0000200.12707372|
0012da90 35 33 34 00 30 31 32 30 35 34 00 20 30 00 00 00 |534.012054. 0...|
0012daa0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
0012db00 00 75 73 74 61 72 20 20 00 00 00 00 00 00 00 00 |.ustar ........|
0012db10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
0012db40 00 00 00 00 00 00 00 00 00 30 30 30 30 30 30 30 |.........0000000|
0012db50 00 30 30 30 30 30 30 30 00 00 00 00 00 00 00 00 |.0000000........|
0012db60 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
0012dc00 99 a2 19 46 60 79 f1 07 f1 a7 5e 07 27 3d 74 58 |...F`y....^.'=tX|
0012dc10 32 ff a7 fc be f0 84 4d 03 7d c4 7e b1 f7 0c 86 |2......M.}.~....|
0012dc20 1b 88 db 73 4a 4b 6f d6 e8 06 5d 30 bb 9a 7c f0 |...sJKo...]0..|.|
0012dc30 f0 f6 33 44 73 fe 7a c8 ad 4f 81 16 b7 21 cb c8 |..3Ds.z..O...!..|
0012dc40 02 0b ec 33 db e8 ae 92 d5 cf c4 25 06 43 e2 40 |...3.......%.C.@|
0012dc50 e6 ee d3 e0 7f f7 41 2c 96 96 f5 b5 8d 90 71 c3 |......A,......q.|
0012dc60 03 c0 7b 83 94 38 47 c6 b0 16 33 79 dd b2 64 3e |..{..8G...3y..d>|
0012dc70 8d 48 b5 ca 13 2e 99 97 6c 2b b0 8b 74 59 41 61 |.H......l+..tYAa|
0012dc80 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
0012e800
Rich (BBCode):
# cp plugins.update plugins_test.update
# dd if=/dev/zero of=plugins_test.update bs=256 seek=$(( 0x12dc )) count=2 conv=notrunc
2+0 records in
2+0 records out
512 bytes (512B) copied, 0.000353 seconds, 1.4MB/s
# hd plugins_test.update | grep -B 5 -A 40 "./var/signature"
0012ccf0 00 00 02 1d 00 00 00 00 00 00 00 00 00 00 02 e6 |................|
0012cd00 00 11 00 00 6a 06 7c f5 ac 51 6c 0f c2 1f ff fb |....j.|..Ql.....|
0012cd10 7c 40 00 00 00 00 00 00 00 03 00 00 00 00 00 00 ||@..............|
0012cd20 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
0012da00 2e 2f 76 61 72 2f 73 69 67 6e 61 74 75 72 65 00 |./var/signature.|
0012da10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
0012da60 00 00 00 00 30 31 30 30 36 34 30 00 30 30 30 30 |....0100640.0000|
0012da70 30 30 30 00 30 30 30 30 30 30 30 00 30 30 30 30 |000.0000000.0000|
0012da80 30 30 30 30 32 30 30 00 31 32 37 30 37 33 37 32 |0000200.12707372|
0012da90 35 33 34 00 30 31 32 30 35 34 00 20 30 00 00 00 |534.012054. 0...|
0012daa0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
0012db00 00 75 73 74 61 72 20 20 00 00 00 00 00 00 00 00 |.ustar ........|
0012db10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
0012db40 00 00 00 00 00 00 00 00 00 30 30 30 30 30 30 30 |.........0000000|
0012db50 00 30 30 30 30 30 30 30 00 00 00 00 00 00 00 00 |.0000000........|
0012db60 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
0012e800
# rm /var/tmp/firmware_stream_result;cat plugins.update | /usr/www/cgi-bin/firmwarecfg stream >/dev/null;cat /var/tmp/firmware_stream_result
total=1239040 ret=0 sigcrc=ee4ef7452f76b8b951314372f5783d8b
# rm /var/tmp/firmware_stream_result;cat plugins_test.update | /usr/www/cgi-bin/firmwarecfg stream >/dev/null;cat /var/tmp/firmware_stream_result
total=1239040 ret=0 sigcrc=ee4ef7452f76b8b951314372f5783d8b
# md5sum plugins_test.update
1862c662233643641f506eb9df43ad70 plugins_test.update
Nun kann man den Inhalt der Metadaten für die ./var/signature zwar wohl tatsächlich "vorhersagen" und könnte ihn vermutlich auch in die Signatur mit einbeziehen, aber genauso gut kann man die natürlich auch auslassen ... wir suchen ja jetzt nach der maximal möglichen Änderung am Eintrag für die ./var/signature, die noch keine Auswirkung auf den von firmwarecfg berechneten Hash hat. Die Hälfte der denkbaren Änderungen haben wir schon durchgeführt (so eine Datei in einem TAR-Archiv hat immer eine Länge, die ein Vielfaches von 512 ist und die Metadaten stecken ebenfalls in einem Block dieser Länge), also ändern wir jetzt einfach die andere Hälfte auch noch und sollte das nicht zum Erfolg führen (weil sich der Hash-Wert von firmwarecfg dann doch ändert), liegt die Wahrheit irgendwo in der Mitte zwischen den beiden Änderungen:
Rich (BBCode):
# dd if=/dev/zero of=plugins_test.update bs=256 seek=$(( 0x12da )) count=2 conv=notrunc
2+0 records in
2+0 records out
512 bytes (512B) copied, 0.000356 seconds, 1.4MB/s
# hd plugins_test.update | grep -A 45 "^0012ccf0"
0012ccf0 00 00 02 1d 00 00 00 00 00 00 00 00 00 00 02 e6 |................|
0012cd00 00 11 00 00 6a 06 7c f5 ac 51 6c 0f c2 1f ff fb |....j.|..Ql.....|
0012cd10 7c 40 00 00 00 00 00 00 00 03 00 00 00 00 00 00 ||@..............|
0012cd20 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
0012e800
Rich (BBCode):
# rm /var/tmp/firmware_stream_result;cat plugins.update | /usr/www/cgi-bin/firmwarecfg stream >/dev/null;cat /var/tmp/firmware_stream_result
total=1239040 ret=0 sigcrc=ee4ef7452f76b8b951314372f5783d8b
# rm /var/tmp/firmware_stream_result;cat plugins_test.update | /usr/www/cgi-bin/firmwarecfg stream >/dev/null;cat /var/tmp/firmware_stream_result
total=1239040 ret=0 sigcrc=ee4ef7452f76b8b951314372f5783d8b
# md5sum plugins_test.update
ee4ef7452f76b8b951314372f5783d8b plugins_test.update
Damit wissen wir nun aber auch, wie wir für eine Signaturprüfung ohne die Verwendung von tr069fwupdate oder firmwarecfg stream vorgehen müssen.
- Suchen des Offsets, an dem die Metadaten der ./var/signature in der Datei beginnen
- Parsen der Metadaten, um die Länge der Datei zu ermitteln
- Überschreiben der Metadaten und des Dateiinhalts (bis zur nächsten 512-Byte-Grenze) mit binären Nullen
Rich (BBCode):
# openssl dgst -md5 -verify plugin_avm.pem -signature signature plugins_test.update
Verified OK
# openssl dgst -md5 -verify plugin_avm.pem -signature signature plugins.update
Verification Failure
Nun gilt es also, die angeführten Aktionen so in einem Shell-Skript zu kombinieren, daß damit ein vom FTP-Server geladenes AVM-Image auf seine Echtheit geprüft werden kann (für modfs) und auf der anderen Seite ist es nun auch möglich, seine eigene Firmware zu signieren, wenn man sich selbst einen passenden RSA-Schlüssel (die 1024 Bit sind nun mal "Pflicht") erzeugt und den zugehörigen öffentlichen Schlüssel im richtigen Format (das ist ja auch alles andere als kompliziert) in das erste eigene Firmware-Image erst einmal eingebunden hat.
Hier rufen wir uns dann wieder in Erinnerung, daß tr069fwupdate (bzw. eher libfwsign.so) da zuerst die Dateien /etc/avm_firmware_public_key[1-9] versucht zu lesen und daß - in den Firmware-Versionen, die ich bisher gesehen habe - dort bisher nur bis zur "3" diese Dateien in Benutzung sind. Da es auch nichts schadet, wenn es eine Lücke zwischen den vorhandenen Dateien und der eigenen gibt, kann man den eigenen öffentlichen Schlüssel problemlos als /etc/avm_firmware_public_key9 in ein eigenes Image einbinden lassen.
Die entsprechenden Skript-Dateien für das Signieren eines eigenen Images (immer daran denken, daß die Verwendung einer solchen Signatur dann voraussetzt, daß man seinen öffentlichen Schlüssel irgendwie auf die FRITZ!Box gebracht hat und zwar auch noch unter dem richtigen Namen) und für die Prüfung so einer Signatur kommen in Kürze ins GitHub-Repository
Sollte jemand Fragen haben, wäre es nett, wenn die erst einmal in einem gesonderten Thread gestellt werden könnten - event. kann dann ja ein Moderator diesen zusätzlichen Thread später hier anhängen, wenn ich mit der Beschreibung fertig bin.
EDIT 2020-02-02: CODE-Blöcke als "rich BBCode" markiert, damit funktionieren die Farben wieder.
Zuletzt bearbeitet: