Immer wieder stehen FRITZ!Box-Besitzer aus den verschiedendsten Gründen vor dem Problem, aus ihrem Gerät irgendwelche Daten extrahieren zu müssen, die von AVM dort (vorbildlicherweise, wenn es um das "ob" geht, zum "wie" und ein paar Nachlässigkeiten dabei kommen wir gleich) nur in verschlüsselter Form gespeichert werden und auch beim Export der Einstellungen sind diese Daten normalerweise verschlüsselt.
Es gibt aber durchaus gute und legitime Gründe für das Ansinnen, auch auf solche Daten Zugriff zu erhalten. Eine kleine Liste von Beispielen (ohne jeden Anspruch auf Vollständigkeit) wäre z.B. die folgende:
- Jemand möchte seine FRITZ!Box mit zusätzlichen Programmen erweitern, die Kenntnis von solchen, verschlüsselt gespeicherten, Einstellungen benötigen. In diesem Falle ist es sicherlich keine allzu gute Idee, dieselben Datei noch einmal irgendwoanders zu verwalten und das vielleicht noch unverschlüsselt ... es gibt ja i.d.R. einen Grund dafür, warum so eine Einstellung von AVM verschlüsselt wird und nur wenige dieser Werte vom FRITZ!OS dann wieder in der Klartext-Version bereitgestellt werden (zumindest auf Wegen, die mit überschaubarem Aufwand verbunden sind).
- Jemand hat nur eine ältere Sicherungsdatei auf einer früheren Box (mit oder ohne Kennwort für die Sicherungsdatei), braucht aber ganz dringend noch irgendeine uralte Einstellung oder irgendwelche Zugangsdaten, die nur in dieser einen Datei noch vorhanden sind und ein (teilweiser) Import kommt als Lösung nicht in Frage.
- Als aktuelles Problem: Jemand hat eine aktuelle Sicherungsdatei erstellt, weil er verdächtige Aktivitäten in seiner FRITZ!Box bemerkt hat und bevor er alle Spuren vernichtet durch das Laden der Werkseinstellungen samt Neukonfiguration, war er so schlau, noch eine Sicherung der aktuellen Einstellungen zu machen. Die nutzt ihm aber nichts, weil er die in dieser Sicherung enthaltenen Benutzerkonten gar nicht wirklich "untersuchen" kann, denn man sieht einem Account in einer Sicherungsdatei nicht mehr an, für welchen Benutzer der nun erzeugt wurde (es gibt ja auch eine Möglichkeit, Benutzer zu verstecken vor dem GUI) und welches Kennwort dort ggf. von einem Angreifer überschrieben wurde, so daß sich der Besitzer selbst nicht mehr anmelden konnte. Hier hilft es natürlich unheimlich, wenn man (bei Kenntnis des Export-Kennworts, es geht hier nicht um das Brechen der Verschlüsselung einer unbekannten Export-Datei) die Sicherungsdatei dann einfach als Klartext anzeigen lassen kann.
Es gibt also auch legitime Gründe für solche "Neugierde" und nachdem AVM mit Version 06.10 das früher existierende "allcfgconv -c" zum Anzeigen von Klartext-Daten eingestampft hat, gab es noch ein weiteres Programm (namens "webdavcfginfo"), mit dem man in der Version 06.20 immer noch diese Daten
entschlüsseln konnte. Das wurde aber nach dieser Version von AVM ebenfalls abgestellt. Trotzdem gibt es in den Händen der falschen Leute natürlich immer noch entsprechende alte Firmware (und AVM hat da praktisch nichts Entscheidendes geändert bei der internen Version, nur der Export wurde etwas modifiziert) und damit auch diese älteren Programme ... wer also genug Energie aufbringt (und das darf man einem (halbwegs engagierten) Angreifer unterstellen), der findet auch heute noch genug Möglichkeiten, an die geheimen Daten aus einer FRITZ!Box zu kommen, wenn er erst einmal so weit gekommen ist, daß er die AVM-Programme (allcfgconv, webdavcfginfo) überhaupt verwenden könnte. Dazu müßte er ja auch erst einmal in einer (fremden) Box sein und die dort aufrufen können ... dann läßt er sich eben die verschlüsselten Daten ausgeben (am besten gleich als TFFS-Dump) und dekodiert die hinterher in aller Ruhe auf einem eigenen System, notfalls einer FRITZ!Box mit Firmware vor 06.10.
Einem "bad guy" (unter "bad girl" versteht sicherlich jeder etwas anderes) hat man damit also nur ein paar Kiesel in den Weg gelegt ... aber der "Gelegenheitsnutzer" aus den o.a. Beispielen steht dabei (in aller Regel, solange er das nicht ständig machen muß) vor entsprechenden Problemen. Das ist in meinen Augen aber der Falsche, der hier von Sanktionen betroffen ist ... daher halte ich es für geboten, diesem legitimen Benutzer auch die passenden Werkzeuge in die Hand zu geben. Das habe ich lange und reiflich überlegt - ein entscheidender Punkt war auch der Wegfall des Routerzwangs und damit entfällt für mich zu einem großen Teil auch das "Schutzbedürfnis", das man früher für Einstellungen vom Provider in den FRITZ!Boxen noch unterstellen und geltenlassen konnte, die dem Provider gehören. Seitdem die Kunden aber die Zugangsdaten ohnehin vom Provider erhalten müssen, entfällt auch dieser Punkt der "contra"-Liste.
Das führt dann im Ergebnis zu der Einstellung, daß diese "security by obscurity" eigentlich nur einem wirklich schadet: dem berechtigten Benutzer. Alle anderen finden auch ohne mich und die folgenden Informationen immer wieder einen Weg, an diese Daten zu gelangen. AVM hilft also mit dieser Geheimniskrämerei eher den Bösen und ich sehe nicht einen einzigen Punkt, wo eine Beschreibung meinerseits die Sicherheit eines FRITZ!Box-Besitzers tatsächlich aushöhlen würde - ganz im Gegenteil ... wie im Beispiel oben angeführt, kann es dem Besitzer sogar helfen, über Merkwürdigkeiten in den Einstellungen auf einen Angriff zu schließen.
Das war's dann jetzt auch mit der Präambel und nun geht es "in medias res".
Schaut man sich die verschlüsselten Daten in irgendeiner AVM-Datei einmal an, stellt man ja schnell fest, daß diese immer mit vier Dollarzeichen beginnen und danach dann nur die Großbuchstaben A bis Z und Ziffern 1 bis 6 folgen. AVM muß also irgendwie die verschlüsselten Daten aus der binären Form (das Ergebnis der eigentlichen Verschlüsselung sind alle denkbaren Werte von 0 bis 255 in mehreren Bytes - das ist dann die "binäre Form", so wie die Daten im Speicher liegen) in eine andere transformieren, die mit insgesamt 32 Zeichen (26 Buchstaben, 6 Ziffern) auskommt.
Das kennt aber praktisch jeder aus der Mathematik und der Schule ... es ist am Ende nichts anderes als eine andere Zahlenbasis, die da zum Einsatz kommt. Da, wo das Binärsystem mit den Zeichen 0 und 1 auskommt, braucht das Oktalsystem dann schon acht (0 bis 7), das Dezimalsystem zehn (0 bis 9) und auch vom Hexadezimalsystem (mit 0 bis 9 und den Buchstaben A bis F für die Werte 10 bis 15) sollte man schon mal etwas gehört haben (wenn nicht, wäre hier der richtige Zeitpunkt, sich das z.B. in der Wikipedia anzusehen).
AVM verwendet hier also 32 verschiedene "Ziffern" und damit ist das offenbar ein Zahlensystem mit der Basis 32 ... daher nennt man diese Kodierung dann auch folgerichtig: Base32. Die gibt es auch an anderen Stellen (die Base64-Kodierung zur Darstellung beliebiger Inhalte nur mit ASCII-Zeichen ist aber bekannter), aber AVM verwendet hier wohl ein eigenes "Alphabet", denn die meisten anderen arbeiten mit 0 bis 5 für die zusätzlich benötigten 6 Werte. Warum AVM hier überhaupt auf Base32 zurückgreift, kann man auch nur vermuten ... die zusätzlichen 32 "Zeichen" für Base64 sind sicherlich nicht das Problem; notfalls ersetzt man die zwei Sonderzeichen im Zeichenvorrat (das sind das Plus und der Schrägstrich) durch andere, wenn man diese Zeichen für eigene Zwecke braucht.
Also rate ich mal hinsichtlich der Beweggründe und tippe auf: Verschleierung (für Laien) - auf Kosten zusätzlicher "Längen" bei der Kodierung, denn im Gegensatz zum Base64 (mit einem Verhältnis von 4 Zeichen für 3 Byte Binärdaten) braucht Base32 da mehr und bietet ungünstigere Bedingungen (8 Zeichen für 5 Byte Binärdaten) "aus Computersicht" bei den binären Daten (es müssen Vielfache von 5 sein bei der Länge der zu kodierenden Daten).
Kommen wir darauf zurück, wie das "AVM-Alphabet" wohl genutzt wird ... irgendwo in der Firmware gibt es bestimmt passende Zeichenketten. Das naheliegendste ist zunächst mal der Blick in die "libar7cfg.so", diese ist für die Be- und Verarbeitung der "Hauptdatei" (ar7.cfg) zuständig:
Code:
root@FB7490:~ $ strings /lib/libar7cfg.so | grep "ABCDEFGHIJKLMNOPQRSTUVWXYZ\|123456"
0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ123456
Da ist also unser Zeichenvorrat und wie man sehen kann, steht die "1" dann für den Wert 26 und die "6" für 31 - das "A" ist dann die Null in diesem "Zahlensystem".
Mit dieser Erkenntnis ausgerüstet, ist es jetzt recht simpel, aus der Base32-Form die binäre Darstellung zu gewinnen. Das habe ich im Laufe der Jahre in mehreren Formen realisiert ... von
Python über
Shell bis
C.
Aber mit diesen Daten kann man jetzt auch noch nicht so richtig viel anfangen ... was wir zunächst einmal brauchen, ist ein bekannter Klartext und das dazugehörige Chiffrat. Das ist schnell erledigt ... auf einer 7412 habe ich mir einen Benutzer "PeterPawn" mit dem Kennwort "PeterPawn" angelegt und das Ergebnis war dann das Folgende:
Code:
# sed -n -e "/^boxusers/,/^}/p" /var/flash/ar7.cfg
boxusers {
users {
enabled = yes;
id = 11;
name = "$$$$EWOYB6UU4CXNGTRIIFPBKROUTTMBZ53XATBLWTXHOFXRTO2UVC3I32RS132MAAC3BXGSM6HGYS51S4W1";
email = "";
password = "$$$$J4U52P1Y3JBDZWIXNCVQRVG2B63N4TJLJO456FTQNNOEUGJEQWXEXWHIOZBNYDTVI51X3CNLNF15W4W1";
vpn_access = no;
box_admin_rights = secobj_access_rights_readwrite_from_homenetwork_only;
nas_rights = secobj_access_rights_none;
phone_rights = secobj_access_rights_readwrite_from_homenetwork_only;
homeauto_rights = secobj_access_rights_readwrite_from_homenetwork_only;
[...]
}
remote_access_id = 0;
version = 1;
two_factor_auth_enabled = yes;
}
Hier sehen wir also schon mal zwei verschiedene Kodierungen, die eigentlich für denselben Wert stehen sollen ... offensichtlich gibt es neben dem Klartext und einem (bisher unbekannten) Schlüssel auch noch einen weiteren zufälligen Wert, der in das Chiffrat eingeht und damit dafür sorgt, daß sich jedesmal eine andere Darstellung ergibt. Egal, ob es sich dabei nun um das "Salz in der Suppe" (salt) handelt oder um einen "initialization vector" (IV) für eine Block-Chiffre, dieser zufällige Wert muß natürlich zusammen mit dem Chiffrat gespeichert werden, damit bei einer Entschlüsselung genau wieder dieser Wert verwendet werden kann.
Was wir auch schon aus der Erfahrung wissen (entsprechende Berichte gibt es seit Jahren) ... es gibt bei der Verschlüsselung für die interne Speicherung auch noch eine gerätespezifische Komponente, denn eine Datei von der einen FRITZ!Box läßt sich nicht ohne weiteres auf einer anderen FRITZ!Box entschlüsseln - das ging schon zu Zeiten von "allcfgconv -c" nicht. Wenn man sich ein wenig mit dem Aufbau einer FRITZ!Box befaßt, kommt man auch schnell dahinter, daß diese "Geräteeigenschaften" für die Verschlüsselung eigentlich nur im sogenannten "Urlader-Environment" gespeichert sein können ... das sind ein paar Eckdaten einer Box (inkl. variabler Daten, die auch bei zwei Geräten desselben Modells bei der Finalisierung der Boxen ab Werk unterschiedlich sind) und - da die Firmware an sich für alle Geräte eines Modells dieselbe ist - praktisch die einzige (maschinenlesbare) Stelle, wo das FRITZ!OS auf (dauerhaft existierende, denn diese Daten werden weder bei Recovery noch bei Werkseinstellungen gelöscht) Daten eines Gerätes zugreifen kann.
Wenn es also variable Größen gibt, dann können die praktisch nur dort drin stehen und wenn man dann etwas mit den Werten im Environment und einem (bekanntermaßen funktionierenden) Programm zum Dekodieren von verschlüsselten Daten experimentiert, stellt man recht schnell auch fest, daß diese variablen Daten offenbar aus dem Wert bei "maca" und "wlan_key" bestehen, die in praktisch jeder FRITZ!Box vorhanden sind. Zusätzlich kommt noch ein weiterer Wert hinzu, der aber nicht bei allen Boxen vorhanden ist und deshalb auf den ersten Blick nicht auffällt ... das ist "tr069_passphrase", was nur bei den Boxen mit konfiguriertem CWMP-Account (also einer TR-069-"Identität") vorhanden ist.
Es geht auch noch ein vierter Wert in diese gerätespezifischen Daten ein ... aber auch der fiel bisher eher nicht auf, weil es praktisch nur Geräte gab, in denen er immer denselben Wert hatte. Es handelt sich hierbei um "SerialNumber" und die meisten FRITZ!Boxen haben dort lediglich 16 Nullen (als Zeichen, also 0x30) stehen ... erst bei einigen neueren Modellen wird da jetzt tatsächlich die Seriennummer hinterlegt. Dieser Umstand (und der damit verbundene Ausfall meiner bisherigen Lösungen, weil ich diese 16 Nullen auch fest verdrahtet hatte) war es auch, der mich zur erneuten Beschäftigung mit dem Thema "Verschlüsselung im FRITZ!OS" zwang und im Zuge dessen kam dann der Entschluß, das nun doch zu veröffentlichen.
[ Die 16 fest verdrahteten Nullen waren das Ergebnis von früheren Untersuchungen mit einer Library, die über "LD_PRELOAD" eine Funktion von AVM ersetzte (HASH_Update - wie man die findet, kommt gleich) und deren Aufrufe protokollierte. Das machte es auch leichter, die korrekte "Reihenfolge" der gerätespezifischen Daten zu ermitteln, obwohl da die denkbaren Permutationen mit 8 Variablen (jeder Wert einmal mit und einmal ohne "newline" am Ende) auch begrenzt wären und rein experimentell ermittelt werden könnten. ]
Wir haben also experimentell dann vier Werte ermittelt, die für diese "Identität" einer FRITZ!Box eine Rolle spielen und für die weitere Betrachtung brauchen wir dann auch die zu den oben gezeigten Chiffraten passenden Werte aus der Box:
Code:
# sed -n -e "/^SerialNumber/p" -e "/^maca/p" -e "/^tr069_passphrase/p" -e "/^wlan_key/p" /proc/sys/urlader/environment
SerialNumber 0000000000000000
maca 5C:49:79:57:47:5A
wlan_key 25083886372223913969
Die Box hier hat also keinen CWMP-Account, damit gibt es nur drei Werte. Wie diese nun zu einer Variablen verknüpft werden, die als variable Größe in die Verschlüsselung eingehen kann (normalerweise als "key", aber denkbar wäre auch als "iv"), wissen wir aber damit noch lange nicht.
Zunächst einmal müssen wir irgendwie feststellen, welche (hoffentlich standardisierten) Algorithmen von AVM an dieser Stelle überhaupt verwendet werden. Dazu schauen wir uns mal ein paar Dateien aus der AVM-Firmware etwas genauer an und versuchen, die Abhängigkeiten zwischen den Bibliotheken dort zu ermitteln. Beginnen wir mit der "libar7cfg.so", die muß ja irgendwie in der Lage sein, so etwas zu ver- und entschlüsseln - wenn das nicht helfen sollte, bleibt uns immer noch ein altes "webdavcfginfo" ... schon anhand der Größe der Datei ist es relativ unwahrscheinlich, daß diese die gesamte Unterstützung der Verschlüsselung direkt enthält.
Code:
# /var/bintools/usr/bin/nm -D -B /lib/libar7cfg.so | grep " U "
U AES256_Environ
U CIPHER_Cleanup
U CIPHER_Init
U CIPHER_Update
U ConvertEmailAddrFromUTF8ToIDNA_WithAlloc
U Event_Add
U HASH_Final
U HASH_Init
U HASH_Update
U MD5Final
U MD5Init
U MD5Update
U MD5_Environ
U __ctype_b_loc
U _strlwr
U access
U atoi
U boxenv_boxnumber
U boxenv_config_is
U boxenv_hwrevision
U boxenv_is_release_firmware
U boxenv_serial
U boxenv_shortversion
U bugmsg
U close
U config_allocstruct
U config_freestruct
U config_loaderr2str
U config_memberbymagic
U config_set_pwfuncs
U config_setbstyle
U config_varfree
U config_varinit
U config_varload
U config_varload_merge
U config_varload_overwrite
U config_varsave
U createpathto
U crwmmap_mapmem
U crwmmap_unmap
U crwmmap_writerlock
U crwmmap_writerunlock
U csock_addr2str
U csock_isaddr6_zero
U debug_gethandle
U emailaddress_parse
U errmsg
U fclose
U fgets
U fopen
U fread
U get_major_of
U getenv
U getpid
U if_exists
U ipaccess_optim_rulestruct
U ipaccess_parse
U ipmasqfwrule_parse
U ipmasqfwruleex_parse
U is_eth_switch_installed
U localtime_r
U memblock_set
U memcmp
U memcpy
U memset
U mknod
U open
U read
U slab_cbcontext_free
U slab_dcalloc
U slab_dmalloc
U slab_dstrdup
U snprintf
U sprintf
U srand
U sscanf
U strcasecmp
U strcasestr
U strchr
U strcmp
U strcmp_safe
U strcpy
U stringlist_append_string
U stringlist_append_string_caseunique
U stringlist_append_string_unique
U stringlist_free
U stringlist_in
U stringlist_len
U stringlist_prepend_string
U stringlist_remove_string
U stringlist_split
U strlen
U strncasecmp
U strncmp
U strncpy
U strrchr
U strstr
U strtoul
U time
U timercb_elapsed
U unlink
U urlader_getenv
U urlader_getmac
U utillib_random
U write
Das sieht ja schon sehr nach dem aus, was wir eigentlich suchen ... die "libar7cfg.so" braucht also für ihre Arbeit (wir sehen ja nur die importierten Funktionen) auch die rot markierten aus anderen Bibliotheken (man kriegt auch noch heraus, welche Bibliotheken das wären) und dabei taucht sowohl ein "MD5_Environ" als auch ein "AES256_Environ" auf - es spricht einiges dafür, daß wir hier sowohl eine Digest- als auch eine Cipher-Funktion gefunden haben.
Eine Digest-Funktion macht aus Daten variabler Länge einen eindeutigen Wert mit fester Länge, der z.B. als "key" oder als "iv" (der muß der Blockgröße eines Cipher-Algorithmus entsprechen von der Größe) für eine Verschlüsselung verwendet werden kann oder als "Prüfsumme", ob Daten in verschlüsselter oder Klartext-Form korrekt (sprich: unverändert bzw. erfolgreich entschlüsselt) wurden.
Eine Cipher-Funktion (hier das symmetrische AES mit einer Schlüssellänge von 256 Bits, wobei auch das eine Blocklänge von 16 Byte (128 Bits) verwendet) kann dann als "Block-Chiffre" auch dazu verwendet werden, einen Datenstrom (reversibel) zu verschlüsseln, der aus mehr als einem Block (16 Byte sind etwas wenig) besteht. Die gebräuchlichste Variante (und m.W. auch die älteste) ist dabei "Cipher Block Chaining" (CBC) und das ist auch das Erste, was man bei einer Untersuchung hier probieren würde.
Wir haben also die chiffrierten Daten (nach Base32-Dekodierung) und eine Vorstellung, was da von AVM wohl verwendet wird. Mit diesem Wissen bewaffnet, kann man sich jetzt ans Experimentieren machen ... ob man dazu jetzt das OpenSSL-Kommandozeilen-Tool, irgendeine höhere Sprache mit Crypto-Unterstützung oder direkt C-Programmierung mit OpenSSL-Funktionen verwendet, bleibt den eigenen Vorlieben überlassen. Als (nachvollziehbare) Demonstration bietet sich aber wieder die Linux-Kommandozeile an und so benutzen wir hier auch diese erst einmal:
Code:
$ printf "%s" "EWOYB6UU4CXNGTRIIFPBKROUTTMBZ53XATBLWTXHOFXRTO2UVC3I32RS132MAAC3BXGSM6HGYS51S4W1" | decoder b32dec | hd
00000000 25 9d 80 fe 94 e8 ae d3 4e 28 41 5e 15 45 d4 9c |%.......N(A^.E..|
00000010 d8 1c fb 97 04 c2 bb 4e e7 71 6f 19 bb 74 a8 b8 |.......N.qo..t..|
00000020 8e 6e 32 d7 36 c0 00 5c 0d cd 26 7c e6 c4 bd a9 |.n2.6..\..&|....|
00000030 76 da |v.|
00000032
$ printf "%s" "J4U52P1Y3JBDZWIXNCVQRVG2B63N4TJLJO456FTQNNOEUGJEQWXEXWHIOZBNYDTVI51X3CNLNF15W4W1" | decoder b32dec | hd
00000000 4f 69 ed bf 58 e2 42 3c d9 17 68 ab 08 d4 db 0f |Oi..X.B<..h.....|
00000010 f8 de cd 2b 4b bb ef 96 70 6b 5c 4a 19 24 85 ae |...+K...pk\J.$..|
00000020 4b d8 e8 76 42 dc 0e 75 47 b5 7e 09 ab 69 75 eb |K..vB..uG.~..iu.|
00000030 76 da |v.|
00000032
Das ist also schon mal der binäre Inhalt der verschlüsselten Daten und damit wir den nicht jedesmal neu dekodieren müssen, speichern wir das Ergebnis einfach jeweils in einer Datei:
Code:
$ printf "%s" "EWOYB6UU4CXNGTRIIFPBKROUTTMBZ53XATBLWTXHOFXRTO2UVC3I32RS132MAAC3BXGSM6HGYS51S4W1" | decoder b32dec >cipher_1.bin
$ printf "%s" "J4U52P1Y3JBDZWIXNCVQRVG2B63N4TJLJO456FTQNNOEUGJEQWXEXWHIOZBNYDTVI51X3CNLNF15W4W1" | decoder b32dec >cipher_2.bin
Für eine AES-Verschlüsselung brauchen wir einen "key" und einen "iv" ... normalerweise würde der IV zufällig gewählt (der muß auch nicht geheim sein und wird damit unverschlüsselt übermittelt/gespeichert) und der Key mit einer passenden Funktion aus einem Kennwort generiert - z.B. mit PBKDF2 als standardisiertem Weg, wo auch noch ein "salt" (das man entweder im Klartext oder selbst noch einmal verschlüsselt speichern muß oder man verwendet einen festen Wert) eingearbeitet werden kann, damit das Ergebnis für dasselbe Kennwort variiert und nicht beim System A mit demselben Kennwort auch derselbe Wert aus der PBKDF (Password Based Key Derivation Function) herauskommt wie beim System B (das nimmt dann eben einen anderen "salt"-Wert).
Gehen wir davon aus, daß auch AVM den IV irgendwo speichern wird ... da ist dann die Speicherung am Beginn der Daten (diese können ja eine variable Länge haben) logischer, als wenn das irgendwo am Ende steht (auch wenn das ebenfalls machbar wäre). Nun wissen wir ja (MD5 und AES256 nehmen wir mal als gesicherte Erkenntnis, auch wenn es ganz am Beginn natürlich nur eine Hypothese ist, die man mal bestätigen muß), daß wir für AES256 auch einen IV mit 16 Byte Länge brauchen. Das wäre dann der oben grün markierte Teil des jeweiligen Chiffrats ... damit beginnen die Daten vermutlich doch eher erst bei Byte 16 einer unserer Dateien "cipher_x.bin". Das müssen wir zunächst auch mal splitten ... ansonsten wird das später zu unhandlich beim "Probieren".
Code:
$ dd if=cipher_1.bin of=iv_1.bin bs=16 count=1
1+0 records in
1+0 records out
16 bytes (16B) copied, 0.002797 seconds, 5.6KB/s
$ dd if=cipher_2.bin of=iv_2.bin bs=16 count=1
1+0 records in
1+0 records out
16 bytes (16B) copied, 0.002744 seconds, 5.7KB/s
$ dd if=cipher_1.bin of=data_1.bin bs=16 skip=1
2+1 records in
2+1 records out
34 bytes (34B) copied, 0.004771 seconds, 7.0KB/s
$ dd if=cipher_2.bin of=data_2.bin bs=16 skip=1
2+1 records in
2+1 records out
34 bytes (34B) copied, 0.004724 seconds, 7.0KB/s
$ hd iv_1.bin
00000000 25 9d 80 fe 94 e8 ae d3 4e 28 41 5e 15 45 d4 9c |%.......N(A^.E..|
00000010
$ hd data_1.bin
00000000 d8 1c fb 97 04 c2 bb 4e e7 71 6f 19 bb 74 a8 b8 |.......N.qo..t..|
00000010 8e 6e 32 d7 36 c0 00 5c 0d cd 26 7c e6 c4 bd a9 |.n2.6..\..&|....|
00000020 76 da |v.|
00000022
$ hd iv_2.bin
00000000 4f 69 ed bf 58 e2 42 3c d9 17 68 ab 08 d4 db 0f |Oi..X.B<..h.....|
00000010
$ hd data_2.bin
00000000 f8 de cd 2b 4b bb ef 96 70 6b 5c 4a 19 24 85 ae |...+K...pk\J.$..|
00000010 4b d8 e8 76 42 dc 0e 75 47 b5 7e 09 ab 69 75 eb |K..vB..uG.~..iu.|
00000020 76 da |v.|
00000022
Nun stehen wir also vor der Aufgabe, uns irgendwie einen Key aus den vorhandenen Daten der Box zu generieren ... wir bräuchten hier aber einen Key mit 256 Bits (also 32 Bytes). Merkwürdigerweise gibt es bei AVM aber in den Beziehungen zwischen den Bibliotheken keinen Hinweis auf eine PBKDF-Funktion oder irgendeine Hash-Funktion (bzw. eine Digest-Funktion, das ist im Prinzip dasselbe), die uns diese 32 Bytes als Hash-Wert liefern würde (z.B. käme hier SHA-256 in Frage).
Wir haben also eigentlich nur unsere MD5-Implementierung, um aus den variablen Daten einer FRITZ!Box irgendeinen (spezifischen) Wert mit fester Länge zu berechnen ... und auch dafür sind die Möglichkeiten jetzt nicht allzu üppig. Wir greifen hier aber mal nur zwei davon heraus (eine richtige und eine falsche) und generieren uns dafür einen MD5-Hash:
Code:
$ printf "5C:49:79:57:47:5A\n25083886372223913969\n0000000000000000" | openssl dgst -md5
(stdin)= 42e6943e51d3a9ae8209dd2d6c3534c8
$ printf "0000000000000000\n5C:49:79:57:47:5A\n25083886372223913969" | openssl dgst -md5
(stdin)= a41d7248990bc5a36ff6cc85547cb363
Ich will das jetzt nicht weiter auswalzen und ich habe die korrekte Reihenfolge für das Berechnen des Hash-Wertes auch auf anderem Weg ermittelt (s.o.), aber man kann sich natürlich auch mit den vier Variablen (mit und ohne "\n" sind es dann acht) die jeweiligen Kombinationen automatisch berechnen lassen - das sprengt aber den Rahmen. Der grüne Wert ist der korrekte "Geräteschlüssel" für meine 7412 und der rote dient uns nachher nur zur Demonstration, was bei der Entschlüsselung mit einem falschen Schlüssel passiert.
Wir "wissen" ja auch, daß als Verschlüsselung AES-256 verwendet wird ... damit sähe dann ein Versuch des Entschlüsselns z.B. so aus:
Code:
$ openssl enc -d -aes-256-cbc -K 42e6943e51d3a9ae8209dd2d6c3534c8 -iv 259d80fe94e8aed34e28415e1545d49c -P
salt=00530C4C77506108
key=42E6943E51D3A9AE8209DD2D6C3534C800000000000000000000000000000000
iv =259D80FE94E8AED34E28415E1545D49C
Weil unsere Schlüssellänge nicht so richtig passend erscheint, habe ich erst einmal die Daten anzeigen lassen (-P), wie sie mit diesem Aufruf verwendet würden. Wir sehen also, daß der Schlüssel einfach mit Nullen auf die benötigte Länge aufgefüllt wird. Gehen wir jetzt mit dem falschen Schlüssel an unseren ersten Versuch, sieht das Ergebnis so aus:
Code:
$ openssl enc -d -aes-256-cbc -K 42e6943e51d3a9ae8209dd2d6c3534c8 -iv 259d80fe94e8aed34e28415e1545d49c -in data_1.bin | hd
bad decrypt
2001658984:error:0606506D:lib(6):func(101):reason(109):NA:0:
00000000 6d 76 6e 4b 7f 41 3c 0f cf fc b4 64 1c 06 d0 f1 |mvnK.A<....d....|
00000010 f8 3f dc 04 82 61 87 a4 b7 33 54 2b 43 1b 51 16 |.?...a...3T+C.Q.|
00000020
Das Ergebnis sieht zwar mit den roten Zeilen recht entmutigend aus, aber da das Kommandozeilen-Tool von OpenSSL auch einen speziellen Aufbau der Datei erwartet, ist das ein Fehler, den man hier "aushalten" muß ... wenn man das mal mit einer OpenSSL-Version macht, die mit allen Schikanen erstellt wurde, sieht man auch, daß der Fehler erst beim "Finalisieren" des letzten Blocks auftritt:
Code:
$ openssl enc -d -aes-256-cbc -K 42e6943e51d3a9ae8209dd2d6c3534c8 -iv 259d80fe94e8aed34e28415e1545d49c -in data_1.bin | hd
bad decrypt
139794545268368:error:0606506D:digital envelope routines:EVP_DecryptFinal_ex:wrong final block length:evp_enc.c:589:
00000000 6d 76 6e 4b 7f 41 3c 0f cf fc b4 64 1c 06 d0 f1 |mvnK.A<....d....|
00000010 f8 3f dc 04 82 61 87 a4 b7 33 54 2b 43 1b 51 16 |.?...a...3T+C.Q.|
00000020
Solange wir dafür sorgen, daß unsere Daten immer mit einer Länge vorliegen, daß der letzte Block nicht genau "gefüllt" ist, können wir den Fehler ignorieren (wir haben ja auch keinen "envelope", der da stimmen könnte) - und AVM tut uns hier mit der Base32-Kodierung und deren "krummen Längen" auch noch den Gefallen, daß es nur selten Probleme gibt ... aber wir hängen einfach mal vorsichtshalber an unsere Daten noch ein paar binäre Nullen an:
Code:
$ dd if=/dev/zero bs=1 count=15 >> data_1.bin
15+0 records in
15+0 records out
15 bytes (15B) copied, 0.014798 seconds, 946B/s
$ dd if=/dev/zero bs=1 count=15 >> data_2.bin
15+0 records in
15+0 records out
15 bytes (15B) copied, 0.014798 seconds, 946B/s
$ hd data_1.bin
00000000 d8 1c fb 97 04 c2 bb 4e e7 71 6f 19 bb 74 a8 b8 |.......N.qo..t..|
00000010 8e 6e 32 d7 36 c0 00 5c 0d cd 26 7c e6 c4 bd a9 |.n2.6..\..&|....|
00000020 76 da 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |v...............|
00000030 00 |.|
00000031
$ hd data_2.bin
00000000 f8 de cd 2b 4b bb ef 96 70 6b 5c 4a 19 24 85 ae |...+K...pk\J.$..|
00000010 4b d8 e8 76 42 dc 0e 75 47 b5 7e 09 ab 69 75 eb |K..vB..uG.~..iu.|
00000020 76 da 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |v...............|
00000030 00 |.|
00000031
Wir haben jetzt also mindestens den letzten Block komplett und unsere binäre Null an Position 49 (0x31) stellt sicher, daß die Finalisierung erst für den nächsten Block stattfindet und die Daten davor noch komplett bearbeitet wurden ... allerdings rettet uns das auch nicht vor dem Fehler und wir werden diesen Fehler einfach ignorieren müssen:
Code:
$ openssl enc -d -aes-256-cbc -K 42e6943e51d3a9ae8209dd2d6c3534c8 -iv 259d80fe94e8aed34e28415e1545d49c -in data_1.bin | hd
bad decrypt
2006570088:error:0606506D:lib(6):func(101):reason(109):NA:0:
00000000 6d 76 6e 4b 7f 41 3c 0f cf fc b4 64 1c 06 d0 f1 |mvnK.A<....d....|
00000010 f8 3f dc 04 82 61 87 a4 b7 33 54 2b 43 1b 51 16 |.?...a...3T+C.Q.|
00000020 7e 92 95 c2 5d 36 5e 06 d4 e9 6b ba ec 80 a5 6e |~...]6^...k....n|
00000030
Aber das ist ja auch nur die Entschlüsselung mit einem der denkbaren Keys (kennt man die richtige Reihenfolge beim Hash-Berechnen nicht, muß man halt alle ausprobieren) und wenn wir das jetzt mit dem richtigen Key angehen, sieht es schon viel besser aus:
Code:
$ openssl enc -d -aes-256-cbc -K a41d7248990bc5a36ff6cc85547cb363 -iv 259d80fe94e8aed34e28415e1545d49c -in data_1.bin | hd
bad decrypt
1998218344:error:0606506D:lib(6):func(101):reason(109):NA:0:
00000000 4e 2b d5 d3 00 00 00 0a 50 65 74 65 72 50 61 77 |N+......PeterPaw|
00000010 6e 00 00 a3 95 b5 f8 5d fe 79 34 e1 bc 2c d6 eb |n......].y4..,..|
00000020 af fd a7 2a b6 2b e4 d7 fe 1b 3c ce 07 27 55 89 |...*.+....<..'U.|
00000030
Na das sieht doch schon wieder ganz anders aus ... wenn da im Klartext "PeterPawn" auftaucht, war die Entschlüsselung offenbar erfolgreich und dann stellt sich jetzt die Frage, was da eigentlich verschlüsselt wurde. Schon vor langer Zeit hatte ich entsprechende Tests zu den "Grenzen" der AVM-Daten gemacht und ermittelt, welche Länge der Klartext-Daten zu welcher Länge der verschlüsselten Daten führt ... das Ergebnis kann man u.a.
hier sehen (habe ich irgendwann mal versehentlich veröffentlicht). Der graue Text ist ohnehin "garbage" und resultiert nur aus unserer "Verlängerung" der Daten.
Für die ersten 22 Bytes Klartext braucht AVM also 50 Bytes Chiffrat, wobei das beim verwendeten Cipher-Algorithmus ja auf ein Vielfaches der Blockgröße zu reduzieren ist und die 2 Bytes "Overhead" nur der Tatsache geschuldet sind, daß die Base32-Kodierung immer in Fünfer-Gruppen erfolgt. Bleiben also 48 Bytes Daten übrig und davon gehen die ersten 16 Bytes auch noch für den IV ab. Aber die verbleibenden 32 Bytes sind ja immer noch deutlich mehr als der reine Klartext und damit wird da wohl noch etwas anderes mitverschlüsselt.
Und richtig ... vor den Klartextdaten stehen oben ja noch weitere 8 Bytes, wobei die Bedeutung der vier ab Offset 4 wohl augenfällig ist. Das ist einfach die Länge der Klartextdaten (hier mit einer binären Null als Ende einer Zeichenkette in C) als 32-Bit-Wert in "big endian"-Speicherung. Da bei AVM (fast) alles Zeichenketten sind, was da verschlüsselt wird, ist die Längenangabe halt immer um eins höher als der eigentliche Wert, damit braucht AVM also eigentlich diese 48 Bytes Chiffrat für 23 Zeichen Klartext, denn das "end of string" ist ja Bestandteil des Chiffrats.
Aber was könnte das "
4e 2b d5 d3" in den ersten vier Bytes denn sein? Da kommen wir wieder auf die eingangs mal erläuterte Bedeutung eines "Markers" für die richtige Entschlüsselung (mithin das richtige Kennwort) zurück ... hat man so eine Validierung vorgesehen, kann man anhand dieses Wertes sehen, ob die Entschlüsselung erfolgreich war oder nicht. Nun kann man einerseits hingehen und dafür eine "Signatur" verwenden, die immer aus denselben Zeichen besteht ... das hat zwar andere Nachteile (bei denkbaren Angriffen auf die Verschlüsselung), aber wir können ja auch leicht überprüfen, ob das eine Signatur ist oder nicht - wir nehmen einfach den nächsten Wert und lassen uns den entschlüsseln:
Code:
$ openssl enc -d -aes-256-cbc -K a41d7248990bc5a36ff6cc85547cb363 -iv 4f69edbf58e2423cd91768ab08d4db0f -in data_2.bin | hd
bad decrypt
2008532072:error:0606506D:lib(6):func(101):reason(109):NA:0:
00000000 0c bf be b1 00 00 00 0a 50 65 74 65 72 50 61 77 |........PeterPaw|
00000010 6e 00 50 c9 42 4a e0 ce 81 a7 05 02 f1 89 9a 3c |n.P.BJ.........<|
00000020 6a 4b 7d 8b c2 37 ea fe b4 63 64 bb 4a 8a 9d cb |jK}..7...cd.J...|
00000030
Die Entschlüsselung funktioniert also auch hier (nicht vergessen, den IV ebenfalls passend anzugeben) und wir sehen, daß die "relevanten" Inhalte der Daten (die Längenangabe und die Daten an sich, bis Offset 0x12) tatsächlich dieselben sind ... nur der "Müll" beim Auffüllen auf die Blockgröße ist ein anderer und auch der Inhalt der ersten vier Bytes.
Sollten diese jetzt tatsächlich so ein Marker sein, dann müssen sie aber (da sie sich ja unterscheiden für die beiden Klartext-Angaben) mehr als nur die Längenangabe und die Daten an sich absichern (also mehr als 14 Bytes ab Offset 4), ansonsten wären die Werte ja wieder identisch. Was liegt jetzt näher als das Einbeziehen des "Schmutzes" hinter den Daten (bis zur nächsten Blockgröße der Verschlüsselung) ... wären das wieder alles Nullen, ergäbe sich auch immer wieder derselbe "Prüfwert" am Beginn und daß es sich offenbar um zufälligen Inhalt handelt (egal, wie der erzeugt wurde), sieht man wieder im Vergleich der beiden Klartext-Puffer.
Bleibt also die Frage, wie man so etwas realisieren könnte ... wir hätten hier in unserem konkreten Fall 32 Byte (2 Blöcke) abzgl. der 4 Bytes für den Prüfwert, da der natürlich nicht selbst eingehen kann in die Berechnung und entweder als Nullen interpretiert wird (das haben wir beim Signieren der Firmware ja auch für die Signatur-Datei so gesehen) oder die Berechnung erfolgt erst ab Offset 4.
Trotzdem stellt sich immer noch die Frage, wie man da nun 32-Bit-Werte zur Prüfung erzeugen könnte und da wäre der erste Kandidat eine CRC32-Prüfsumme mit einem der bekannten Polynome. Das habe ich damals auch tatsächlich als erstes treudoof ausprobiert (schließlich hat AVM auch an einer Export-Datei einen solchen Wert zur Prüfung), aber irgendwann fiel mir dann doch auf, daß die libar7cfg.so (die das ja offenbar auch kann) per se gar nichts mit Im- oder Export zu tun hat ... also müßte diese Sicherung vielleicht doch mit irgendeiner Funktion erfolgen, die auch von der Library importiert wird.
Da blieb dann wieder schnell nur noch MD5 übrig ... allerdings erzeugt diese Funktion eben 16 Bytes. Nach ein wenig Probieren (auch mit längeren Werten, die nicht mehr in zwei Blöcke (das Minimum) paßten) stellte sich dann heraus, daß hier tatsächlich erneut MD5 als Digest verwendet wird - es werden aber nur die ersten vier Bytes des Hash-Wertes gespeichert und das mit der Länge hat AVM dann so gelöst, daß diese vier Bytes von der Länge der Daten abgezogen werden (also von den Vielfachen der vollen Blockgröße).
Das ergibt dann 28 Bytes für zwei Blöcke, 44 Bytes für drei Blöcke, usw. - jeweils eben mit dem "Müll", der da gerade im Buffer steht (ich glaube nicht, daß das initialisiert wird mit Zufallsdaten, weiß es aber nicht sicher, schlau wäre es aber in jedem Falle) und damit dafür sorgt, daß dieselben Daten nicht einmal als Hash-Wert dasselbe Ergebnis liefern. Stimmt dann nach dem Dechiffrieren der noch einmal berechnete Hash-Wert (auch wenn es nur die ersten vier Bytes sind, ist die Wahrscheinlichkeit für einen Zufallstreffer gering - geringer als bei CRC-Prüfsummen, wo man das gezielt herbeiführen kann mit dem richtigen "filler"), dann stimmte offensichtlich auch der Key und man kann den entschlüsselten Wert verwenden. Ansonsten nimmt AVM i.d.R. die verschlüsselten Daten genau so, wie sie kommen ... das ergibt dann beim Restore von Freetz-Sicherungen auf anderen Geräten diese lustigen Werte, wie man sie desöfteren in Freetz-Tickets findet.
Code:
$ openssl enc -d -aes-256-cbc -K a41d7248990bc5a36ff6cc85547cb363 -iv 259d80fe94e8aed34e28415e1545d49c -in data_1.bin | hd
bad decrypt
2009859176:error:0606506D:lib(6):func(101):reason(109):NA:0:
00000000 4e 2b d5 d3 00 00 00 0a 50 65 74 65 72 50 61 77 |N+......PeterPaw|
00000010 6e 00 00 a3 95 b5 f8 5d fe 79 34 e1 bc 2c d6 eb |n......].y4..,..|
00000020 af fd a7 2a b6 2b e4 d7 fe 1b 3c ce 07 27 55 89 |...*.+....<..'U.|
00000030
$ openssl enc -d -aes-256-cbc -K a41d7248990bc5a36ff6cc85547cb363 -iv 259d80fe94e8aed34e28415e1545d49c -in data_1.bin | dd bs=1 skip=4 count=28 | openssl dgst -md5
bad decrypt
2006807656:error:0606506D:lib(6):func(101):reason(109):NA:0:
28+0 records in
28+0 records out
28 bytes (28B) copied, 0.009273 seconds, 2.9KB/s
(stdin)= 4e2bd5d352c4acbce309247ad9572032
Das war dann auch schon "das Geheimnis" der AVM-Verschlüsselung (zumindest in Kurzform) ... das gilt es nun nachzubauen.
Wobei AVM da m.E. ohnehin selbst noch einen "off by one" macht, der mich fast wahnsinnig gemacht hat, als ich begonnen hatte, das mit der Verschlüsselung an sich zu untersuchen:
Code:
$ printf 42337a07b84fb9dcff9248a44f5663b3471818d10d9ae7148fcb7a8109d9687eb3d55b184fceb681ca927d969db6b03acb | decoder hexdec | openssl enc -d -aes256 -K a41d7248990bc5a36ff6cc85547cb363 -iv 2dc7a98e732e1dfe8f1ae15530323835 | hd
bad decrypt
140071090394768:error:0606506D:digital envelope routines:EVP_DecryptFinal_ex:wrong final block length:evp_enc.c:589:
00000000 e2 30 77 5f 00 00 00 18 61 62 63 64 65 66 67 68 |.0w_....abcdefgh|
00000010 69 6a 6b 6c 6d 6e 6f 40 71 72 73 2e 63 6f 6d 00 |ijklmno@qrs.com.|
00000020 4f 44 c7 84 1c e9 ae 95 5a a7 2b e7 e9 f8 07 cd |OD......Z.+.....|
00000030
Bei einer Länge der Klartextdaten von 24 (inkl. "end of string", also 23 "reine" Zeichen) würden die Daten genau in zwei Blöcke passen (24 Bytes + 4 Bytes Längenangaben (man sieht auch das 0x18 dort) + 4 Bytes Hash-Beginn) ... aber AVM beschickt die Verschlüsselung hier offenbar mit einem Byte mehr und das ergibt bereits bei 23 Zeichen Eingabe dann einen zusätzlichen Block bei der Verschlüsselung und einen Sprung um 15 Byte in der Länge der verschlüsselten Daten (oder 20, wenn kein "garbage" dabei war).
Das war dann die wirkungsvollste Verschleierung - bis ich da eine Systematik entdeckt hatte, brauchte es eine Weile.
Auch heute noch machen einige meiner Lösungen Probleme, wenn es zu diesen Grenzfällen bei der Länge und bei deren Berechnung kommt - einfach weil der "Sprung" dafür auch bei der Berechnung des Hash-Wertes in den ersten vier Bytes des Klartextes erfolgt und das dann genauso zu implementieren ist, wie es AVM (meiner Ansicht nach ist das tatsächlich auch ein Fehler und keine Absicht) macht. Die einzige "vernünftige" Erklärung wäre es halt, daß ohne irgendwelche Zufallsdaten am Ende auch bei der Berechnung des Hash-Wertes (über das Längenfeld und den Inhalt) immer derselbe Hash-Wert entstehen würde - vielleicht möchte man das vermeiden. Das finde ich dann angesichts von einfachen MD5-Hashes als Key aber schon weit vorausgedacht und es paßt für mich nicht wirklich zum Rest (komme ich gleich noch drauf).
Parallel dazu gibt es noch den Spezialfall einer Export-Datei ... hier können ja für den Key offensichtlich nicht die Eigenschaften des Gerätes herangezogen werden ... dann ließe sich die Datei ja nicht auf einem anderen Gerät entschlüsseln und importieren. Also muß AVM hier etwas anders machen ... und das fällt einem auch ins Auge, wenn man mal einen Blick in so eine Export-Datei wirft. Dort steht im Header der Datei (i.d.R. vor der Firmware-Version) ein Eintrag mit dem Namen "Password", offensichtlich gefolgt von einem verschlüsselten Wert, wie er auch an anderen Stellen auftritt.
Da wir inzwischen ja schon wissen, wie die Daten aussehen sollten nach der Entschlüsselung (und mit dem Hash-Wert auch selbst prüfen können, ob unser Kennwort stimmt), ist es dann wieder nur eine Fingerübung, diesen Wert solange mit wechselnden Keys zu traktieren, bis wir den passenden gefunden haben (ich hatte mir dafür entsprechende Hilfsprogramme geschrieben). Es gibt ja mindestens zwei Möglichkeiten, denn wir kennen Export-Dateien mit einem benutzerspezifizierten Kennwort und solche, die sich nur auf demselben Gerät wiederherstellen lassen (also wohl wieder den Geräteschlüssel benutzen).
Für die Variante mit dem Kennwort kommt man dann relativ schnell dahinter, daß es sich hier lediglich um den simplen MD5-Hash des Kennwortes handelt (ohne "newline" dahinter) und auch für die Variante ohne, wird man schnell fündig - nachdem man erst einmal verwirrt wurde, daß es sich dabei nicht um denselben Key handelt, wie er für die interne Speicherung verwendet wird. Auch hier ist halt "fröhliches Probieren" angesagt, aber die Permutationen sind wieder nicht so zahlreich - am Ende landet man bei der Seriennummer (bzw. für mich waren das immer irgendwelche Nullen und der Zusammenhang war mir bis vor kurzem gar nicht klar, weil die eben auf fast allen Geräten identisch war) und der MAC-Adresse, jeweils gefolgt von einmal "newline" (0x0A).
Zur Untersuchung brauchen wir erst einmal irgendwelche Daten, wir lassen einfach auf der 7412 mal die Konfiguration mit einem bekannten Kennwort exportieren und suchen uns das Kennwort am Beginn und das "PeterPawn" im Benutzernamen
Code:
# tr069fwupdate configexport YourFritz | sed -n -e '/\$\$\$\$/p'
Password=$$$$Q1PR24A6GC6BSRJSYRSWQCUZRFGFZR2QBAK6K6X166VPT16CYAXYN6XS63XRFM4IQP66VM52SEZ5YMJSIXEZNUTOE5OKV2EEJMFDQWLX
[...]
name = "$$$$2ZPGZQWW4LI3NM1UUJ3ARTKCF22UWTDNAPQ2EPZYO5VUAOLZJZWEMONFPYCQY2ABUA5OSCD46HNDPI46";
password = "$$$$6LIY5G1S1PQJNYX1EU5OHSNJJDQYBXHIJI2AFBLCWY3OJP4XV5SVL2SLEHHPAFJP3UAKQ43ECFWR2I46";
[...]
Mit dem Wissen um dieses Kennwort können wir jetzt (auch außerhalb der 7412) die ersten Daten entschlüsseln ... dabei setze ich jetzt gleich zwei der Dateien ein, die ich später noch beschreiben werde:
Code:
# user_password YourFritz | hd
00000000 ed 19 b0 84 62 9d 98 fc 45 5f 54 3d 7c 71 1c 47 |....b...E_T=|q.G|
00000010
# decode_secret -d Q1PR24A6GC6BSRJSYRSWQCUZRFGFZR2QBAK6K6X166VPT16CYAXYN6XS63XRFM4IQP66VM52SEZ5YMJSIXEZNUTOE5OKV2EEJMFDQWLX ed19b084629d98fc455f543d7c711c47
password : (016) ed19b084629d98fc455f543d7c711c47
input : (065) 869f1df41f30be194532c465680a99894c5cc7700815f57efaffeaf9ebe2c02f86fef2ff2f12b3a883fffab3db9133ec313245c996d26e279caaec844b0a385977
iv : (016) 869f1df41f30be194532c465680a9989
encoded : (049) 4c5cc7700815f57efaffeaf9ebe2c02f86fef2ff2f12b3a883fffab3db9133ec313245c996d26e279caaec844b0a385977
decoded : (048) 322b877800000020ab908ef57cadde611b8a8919caa632e1ab908ef57cadde611b8a8919caa632e17fa916daab1d9dfc
hmac : (004) 322b8778
length : 032
cleartext : (032) ab908ef57cadde611b8a8919caa632e1ab908ef57cadde611b8a8919caa632e1
Da der Hash-Wert stimmt, ist die Entschlüsselung offenbar geglückt und die letzte Zeile enthält (hier hexadezimal angezeigt) einen 256-Bit-Wert, der ja durchaus als Schlüssel für unser AES-256 in Frage käme. Schaut man noch einmal genauer hin, ist das aber lediglich eine Doppelung der ersten 16 Bytes (es würde mich nicht wundern, wenn das ein verdoppelter MD5-Hash irgendeines Zufallswertes ist) und dadurch wird der Wert auf 32 Bytes aufgeblasen (inkl. Hash und der bezieht auch die "garbage" am Ende mit ein (8 Bytes).
Versucht man sich jetzt mit diesem 256-Bit-Key an der Entschlüsselung der einzelnen Einstellungen in der Export-Datei, beißt man aber auf Granit:
Code:
# decode_secret -d 2ZPGZQWW4LI3NM1UUJ3ARTKCF22UWTDNAPQ2EPZYO5VUAOLZJZWEMONFPYCQY2ABUA5OSCD46HNDPI46 ab908ef57cadde611b8a8919caa632e1ab908ef57cadde611b8a8919caa632e1
Error decoding value, please check the specified password.
password : (032) ab908ef57cadde611b8a8919caa632e1ab908ef57cadde611b8a8919caa632e1
input : (050) de5e6cc2d6ead1c6b354a27808cd422ef74b4c6d03e1b23f3877ab4039794e6c4639a57e050c6c01a03ce9087df9da37a3bf
iv : (016) de5e6cc2d6ead1c6b354a27808cd422e
encoded : (034) f74b4c6d03e1b23f3877ab4039794e6c4639a57e050c6c01a03ce9087df9da37a3bf
decoded : (032) 84997cbc4458a4e4b9493f0446ebbfba5b07c84db84a8314070c4fa33e4e7c7d
hmac : (004) 84997cbc
length : 000
cleartext : (000)
Das war also offensichtlich schon mal nicht der richtige Key ... aber mit etwas Probieren stellt man dann schnell fest, daß da auch wieder nur der halbe Key verwendet wird (die "Güte" ist dank Doppelung ja ohnehin nicht wirklich besser als bei 128-Bit):
Code:
# decode_secret -d 2ZPGZQWW4LI3NM1UUJ3ARTKCF22UWTDNAPQ2EPZYO5VUAOLZJZWEMONFPYCQY2ABUA5OSCD46HNDPI46 ab908ef57cadde611b8a8919caa632e1
password : (016) ab908ef57cadde611b8a8919caa632e1
input : (050) de5e6cc2d6ead1c6b354a27808cd422ef74b4c6d03e1b23f3877ab4039794e6c4639a57e050c6c01a03ce9087df9da37a3bf
iv : (016) de5e6cc2d6ead1c6b354a27808cd422e
encoded : (034) f74b4c6d03e1b23f3877ab4039794e6c4639a57e050c6c01a03ce9087df9da37a3bf
decoded : (032) 70710d340000000a50657465725061776e000093496a7cad303b716668c09e16
hmac : (004) 70710d34
length : 009
cleartext : (009) 50657465725061776e
# decode_secret 2ZPGZQWW4LI3NM1UUJ3ARTKCF22UWTDNAPQ2EPZYO5VUAOLZJZWEMONFPYCQY2ABUA5OSCD46HNDPI46 ab908ef57cadde611b8a8919caa632e1;echo
PeterPawn
# decode_secret 6LIY5G1S1PQJNYX1EU5OHSNJJDQYBXHIJI2AFBLCWY3OJP4XV5SVL2SLEHHPAFJP3UAKQ43ECFWR2I46 ab908ef57cadde611b8a8919caa632e1;echo
PeterPawn
Wie man sieht, lassen sich auch beide Werte aus den Tiefen der Export-Datei mit demselben Key dechiffrieren - es ist halt nur ein zweistufiges Verfahren zur Gewinnung des richtigen Schlüssels bei einer Export-Datei.
Damit wäre die Beschreibung jetzt tatsächlich durch ... bleibt noch so etwas wie ein Zwischenfazit. Auch wenn die AVM-Verschlüsselung mit einem 256-Bit-Key protzt, ist es am Ende in Wirklichkeit alles nur die Sicherheit, die sich aus AES mit 128-Bit-Schlüsseln ergibt - wenn jeweils die Hälfte der 256 Bits schon vorher bekannt ist (hier sind es eben immer Nullen), dann ist AES-256 ein wenig Augenwischerei, jedenfalls wenn es um den "Schlüsselraum" geht. Ob MD5-Hashes tatsächlich das Mittel der Wahl sind (erst recht bei der einfachsten Variante mit dem Export-Kennwort), muß jeder selbst beurteilen. So, wie es zur Zeit aussieht, verwendet AVM wohl tatsächlich auch eine eigene Implementierung des "Rijndael-Algorithmus" (der später als Standard dann in "Advanced Encryption Standard" (AES) umbenannt wurde) und begeht damit ggf. schon den ersten Fehler. Selbst wenn das vielleicht früher mal erforderlich war, gibt es lange genug auch AES-256 in OpenSSL und es wäre längst an der Zeit gewesen, die eigene Implementierung:
Code:
$ /var/bintools/usr/bin/nm -D -B /lib/libavmcipher.so
0000153c T AES128_Environ
00001554 T AES192_Environ
0000156c T AES256_Environ
000013a4 T CIPHER_Cleanup
000011b8 T CIPHER_Final
00000f64 T CIPHER_Init
00001054 T CIPHER_Update
00001410 T CIPHER_keylen
00003e0c T DES3_Environ
00003cc8 T DES_Environ
00003bc4 T NULL_Environ
00009428 R S
w _ITM_deregisterTMCloneTable
w _ITM_registerTMCloneTable
w _Jv_RegisterClasses
0001a8fc B __bss_start
w __cxa_finalize
w __deregister_frame_info
w __register_frame_info
0001a8fc G _edata
0001a930 B _end
0001a8fc B _fbss
0001a710 D _fdata
00006260 T _fini
00000b70 T _ftext
00000aec T _init
00009ef0 R des_SPtrans
0001a920 B des_check_key
00005c20 T des_check_key_parity
00004c98 T des_decrypt3
000060e0 T des_ecb_encrypt
00005518 T des_ede3_cbc_encrypt
00003e30 T des_encrypt
0000450c T des_encrypt2
00004ac4 T des_encrypt3
000060bc T des_fixup_key_parity
00005c70 T des_is_weak_key
000060a4 T des_key_sched
00004e70 T des_ncbc_encrypt
00006054 T des_set_key
00005fcc T des_set_key_checked
00005d04 T des_set_key_unchecked
00005be0 T des_set_odd_parity
U memcmp
U memcpy
U memset
00001944 T rijndaelKeyEncToDec
00001590 T rijndaelKeySched
00001fbc T rijndael_Decrypt
00001aa0 T rijndael_Encrypt
000030a4 T rijndael_blockDecrypt
000026b0 T rijndael_blockEncrypt
00002604 T rijndael_cipherInit
000024a0 T rijndael_makeKey
00003740 T rijndael_padDecrypt
00002d64 T rijndael_padEncrypt
in einen Wrapper für ein Standard-Paket umzuwandeln. Daß es sich nicht um einen solchen handelt, kann man an den importierten Funktionen sehen.
Auch ist es eigentlich verpönt, irgendwelche Keys ganz simpel von einem Hash-Wert (wie beim Export-Kennwort) abzuleiten ... das ist anfällig für Attacken und die üblichen Funktionen zur Ableitung eines Keys von einem Kennwort (PBKDF2 hatte ich oben schon erwähnt) fügen diesem Vorgang der Kennwort-Generierung (auch ohne "salt", wenn also aus einem identischen Kennwort auch ein identischer Key werden soll) einiges an Komplexität hinzu, um genau solche Angreifer passend zu verlangsamen. Mit vorausberechneten MD5-Hashes (wenn sich das überhaupt lohnt) ist das jedenfalls relativ(!) leicht angreifbar - es macht bloß niemand, weil sich der Aufwand vermutlich nicht lohnt. Aber "sicher" ist eben etwas anderes ... zwischen einem "ausgezeichnet" und einem "genügend" können da Welten liegen.
Wenn es jedenfalls um das Nachbauen geht, so habe ich seit drei Jahren da die verschiedensten Lösungen am Start gehabt ... je nachdem, worum es dabei jeweils ging und wenn irgendetwas Neues gebraucht wurde, habe ich auch eher etwas Vorhandenes genommen, geändert und dann parallel zur "Vorlage" verwendet, anstatt das in eine "gemeinsame" Code-Basis zu integrieren. So ein "refactoring" ist eben ein erheblicher Aufwand und mich juckte es eher nicht, wenn ich denselben Code immer wieder verwendet habe ... es gab einfach keine Notwendigkeit, die Sachen irgendwie zusammenzufassen.
Das war jetzt für die Veröffentlichung dann wieder etwas anderes und so bin ich (wohl oder übel) hingegangen und habe einiges "from scratch" neu gemacht ... zwar mit älteren Dateien als Vorlage, aber im Prinzip ist wirklich alles neu (das war der Mai). Das führt aber auch dazu, daß einige Fehler sich wieder eingeschlichen haben (z.B. die oben erwähnte falsche Länge bei der Hash-Berechnung) und nun erst einmal mühsam gefunden und beseitigt sein wollen.
Ich lasse das Ganze hier jetzt etwas sacken und widme mich der Suche von Fehlern in den Shell-Dateien (den Fehler bei 22 bzw. 23 Zeichen habe ich gerade wiedergefunden in "decode_secrets") bzw. der Weiterentwicklung des neuen C-Programms, was die Funktionen der Shell-Skripte in einem einzigen Binary vereinen soll. Wenn das irgendwann mal so stabil sein sollte, daß ich es als halbwegs fertig ansehe, widme ich mich der Beschreibung der Shell-Skripte ... wobei ich jedem zum C-Programm raten würde, auch wenn die "Zutaten" für die Shell-Skripte normalerweise auf jedem Linux-System vorhanden sein sollten (auch die "bash" in Windows 10/x64 sollte ausreichen) oder sich aus dem Projekt-Repository (und aus dem YourFritz-Repository direkt daneben, denn die Shell-Skripte wollen die Script-Library aus diesem Repository sehen) laden lassen und dann auch ohne Übersetzung funktionieren.
Auch die Suche nach Tippfehlern schiebe ich erst einmal auf ... wer hier welche findet, darf sie erst einmal behalten. Ich editiere das irgendwann noch einmal, wenn ich Korrektur gelesen habe.