TL; DR. Der Artikel befasst sich mit der umgekehrten Entwicklung des Dropbox-Clients, dem Hacken der Verschleierungs- und Dekompilierungsmechanismen des Clients in Python sowie dem Ändern des Programms, um Debugging-Funktionen zu aktivieren, die im normalen Modus ausgeblendet sind. Wenn Sie nur an dem entsprechenden Code und den Anweisungen interessiert sind, scrollen Sie bis zum Ende. Zum Zeitpunkt dieses Schreibens ist der Code mit den neuesten Dropbox-Versionen kompatibel, die auf dem CPython 3.6-Interpreter basieren.Einführung
Dropbox hat mich sofort fasziniert. Das Konzept ist immer noch täuschend einfach. Hier ist der Ordner. Legen Sie dort Dateien ab. Es ist synchronisiert. Zu einem anderen Gerät gehen. Es wird wieder synchronisiert. Ordner und Dateien erschienen jetzt auch dort!
Die Menge an versteckten Hintergrundarbeiten ist wirklich erstaunlich. Erstens verschwinden nicht alle Probleme, mit denen Sie beim Erstellen und Verwalten einer plattformübergreifenden Anwendung für die wichtigsten Desktop-Betriebssysteme (OS X, Linux, Windows) zu kämpfen haben. Hinzu kommt die Unterstützung verschiedener Webbrowser und verschiedener mobiler Betriebssysteme. Und wir sprechen nur über die Kundenseite. Ich interessiere mich auch für das Dropbox-Backend, mit dem ich mit der wahnsinnig hohen Arbeitslast von einer halben Milliarde Benutzern eine solche Skalierbarkeit und geringe Latenz erreichen konnte.
Aus diesen Gründen habe ich immer gerne beobachtet, was Dropbox unter der Haube macht und wie es sich im Laufe der Jahre entwickelt hat. Vor ungefähr acht Jahren habe ich zum ersten Mal versucht herauszufinden, wie der Dropbox-Client tatsächlich funktioniert, als ich im Hotel die Übertragung von unbekanntem Verkehr bemerkte. Die Untersuchung ergab, dass dies Teil der Dropbox-Funktion LanSync ist, mit der Sie schneller synchronisieren können, wenn Dropbox-Hosts im selben LAN Zugriff auf dieselben Dateien haben. Das Protokoll wurde jedoch nicht dokumentiert, und ich wollte mehr wissen. Aus diesem Grund habe ich mich entschlossen, einen genaueren Blick darauf zu werfen, und daher fast das gesamte Programm rückentwickelt. Diese Studie wurde nie veröffentlicht, obwohl ich manchmal Notizen mit einigen Leuten geteilt habe.
Als wir Anvil Ventures eröffneten, schätzten Chris und ich eine Reihe von Tools für die Speicherung, Freigabe und Zusammenarbeit von Dokumenten. Eine davon war natürlich Dropbox, und für mich ist dies ein weiterer Grund, alte Studien auszugraben und sie auf die aktuelle Version des Clients zu überprüfen.
Entschlüsselung und Deobfuscation
Zuerst habe ich den Client für Linux heruntergeladen und schnell herausgefunden, dass er in Python geschrieben wurde. Da die Python-Lizenz recht zulässig ist, können Benutzer den Python-Interpreter zusammen mit anderen Abhängigkeiten wie kommerzieller Software leicht ändern und verteilen. Dann begann ich mit dem Reverse Engineering, um zu verstehen, wie der Client funktioniert.
Zu diesem Zeitpunkt befanden sich die Dateien mit dem Bytecode in einer ZIP-Datei, die mit einer ausführbaren Binärdatei kombiniert war. Die Hauptbinärdatei war nur ein modifizierter Python-Interpreter, der durch Erfassen von Python-Importmechanismen geladen wurde. Jeder nachfolgende Importaufruf wurde mit einer Analyse der ZIP-Datei auf diese Binärdatei umgeleitet. Natürlich ist es einfach, diese ZIP aus der Binärdatei zu extrahieren. Das nützliche
binwalk- Tool ruft es beispielsweise mit allen bytekompilierten .pyc-Dateien ab.
Dann konnte ich die Verschlüsselung für .pyc-Dateien nicht aufheben, aber am Ende nahm ich das allgemeine Objekt der Standard-Python-Bibliothek und kompilierte es neu, indem ich eine Hintertür hineinschob. Nachdem der Dropbox-Client dieses Objekt geladen hatte, konnte ich problemlos beliebigen Python-Code in einem funktionierenden Interpreter ausführen. Obwohl ich dies selbst entdeckt habe, haben Florian Leda und Nicolas Raff 2012 in einer
Präsentation auf Hack.lu dieselbe Methode angewendet.
Die Möglichkeit, laufenden Code in Dropbox zu untersuchen und zu bearbeiten, hat viel gezeigt. Der Code verwendete mehrere Schutztricks, um das Ablegen von Codeobjekten zu erschweren. In einem normalen CPython-Interpreter ist es beispielsweise einfach, einen kompilierten Bytecode wiederherzustellen, der eine Funktion darstellt. Ein einfaches Beispiel:
>>> def f(i=0): ... return i * i ... >>> f.__code__ <code object f at 0x109deb540, file "<stdin>", line 1> >>> f.__code__.co_code b'|\x00|\x00\x14\x00S\x00' >>> import dis >>> dis.dis(f) 2 0 LOAD_FAST 0 (i) 2 LOAD_FAST 0 (i) 4 BINARY_MULTIPLY 6 RETURN_VALUE >>>
In der kompilierten Version von
Objects / codeobject.c wurde die Eigenschaft
co_code jedoch aus der geöffneten Liste entfernt. Diese Mitgliederliste sieht normalerweise ungefähr so aus:
static PyMemberDef code_memberlist[] = { ... {"co_flags", T_INT, OFF(co_flags), READONLY}, {"co_code", T_OBJECT, OFF(co_code), READONLY}, {"co_consts", T_OBJECT, OFF(co_consts), READONLY}, ... };
Das Verschwinden der Eigenschaft
co_code
macht es unmöglich, diese
co_code
.
Darüber hinaus wurden andere Bibliotheken wie der Standard-Python-
Disassembler entfernt. Am Ende gelang es mir immer noch, die Codeobjekte in Dateien zu speichern, aber ich konnte sie immer noch nicht dekompilieren. Es dauerte eine Weile, bis mir klar wurde, dass die vom Dropbox-Interpreter verwendeten Opcodes nicht mit den Standard-Opcodes von Python übereinstimmen. Daher war es notwendig, die neuen Opcodes zu verstehen, um die Codeobjekte wieder in den ursprünglichen Python-Bytecode zu schreiben.
Eine Option ist die Neuzuordnung von Opcodes. Soweit ich weiß, wurde diese Technik von Rich Smith entwickelt und auf der
Defcon 18 eingeführt . In diesem Vortrag zeigte er auch das
pyREtic- Tool für das Reverse Engineering von Python-Bytecode im Speicher. Es scheint, dass der pyREtic-Code schlecht unterstützt wird und das Tool auf die "alten" Python 2.x-Binärdateien abzielt. Um sich mit den Techniken vertraut zu machen, die Rich entwickelt hat, wird dringend empfohlen, seine Leistung zu sehen.
Die Opcode-Übersetzungsmethode verwendet alle Codeobjekte der Standard-Python-Bibliothek und vergleicht sie mit Objekten, die aus der Dropbox-Binärdatei extrahiert wurden. Beispiel:
Codeobjekte aus
hashlib.pyc oder
socket.pyc , die sich in der Standardbibliothek befinden.
0x43
, wenn der
0x43
Opcode jedes Mal dem
0x43
0x21-
0x21
, können wir nach und nach eine Übersetzungstabelle zum Umschreiben von Codeobjekten erstellen. Diese Codeobjekte können dann durch den Python-Dekompiler übergeben werden. Zum Dump benötigen Sie noch einen korrigierten Interpreter mit dem richtigen
co_code
Objekt.
Eine andere Möglichkeit besteht darin, das Serialisierungsformat zu hacken. In Python wird Serialisierung als
Marshalling bezeichnet . Das Deserialisieren verschleierter Dateien auf die übliche Weise funktionierte nicht. Beim Reverse Engineering der Binärdatei in IDA Pro habe ich den Entschlüsselungsschritt entdeckt. Soweit ich weiß, war Hagen Fritch der erste, der etwas zu diesem Thema öffentlich veröffentlichte, in
seinem Blog . Dort verweist er auf Änderungen in neuen Versionen von Dropbox (als Dropbox von Python 2.5 auf Python 2.7 wechselte). Der Algorithmus funktioniert wie folgt:
- Beim Entpacken einer Pyc-Datei wird ein Header gelesen, um die Version des Marshallings zu bestimmen. Dieses Format ist nicht dokumentiert, außer für die Implementierung von CPython selbst.
- Das Format definiert eine Liste von Typen, die darin codiert sind. Typen
True
, False
, floats
usw., aber der wichtigste ist der Typ für die oben genannten Python code object
, code object
.
- Beim Laden des Codeobjekts werden zunächst zwei zusätzliche Werte aus der Eingabedatei gelesen.
- Der erste ist der 32-Bit-
random
.
- Der zweite Wert ist eine 32-Bit-
length
, die die Größe des serialisierten Codeobjekts angibt.
- Dann werden die
rand
und rand
an eine einfache RNG-Funktion übergeben, die seed
generiert.
- Dieser Startwert wird an den Mersenne-Wirbel geliefert, der vier 32-Bit-Werte generiert.
- Zusammen ergeben diese vier Werte einen Verschlüsselungsschlüssel für serialisierte Daten. Der Verschlüsselungsalgorithmus entschlüsselt dann die Daten unter Verwendung des Tiny Encryption Algorithm .
In meinem Code habe ich das Python-Unmarshaling-Verfahren von Grund auf neu geschrieben. Der Teil, der die Codeobjekte entschlüsselt, sieht ungefähr so aus wie das folgende Fragment. Es ist zu beachten, dass diese Methode rekursiv aufgerufen werden muss. Das Objekt der obersten Ebene für eine Pyc-Datei ist ein Codeobjekt, das selbst Codeobjekte enthält, die Klassen, Funktionen oder Lambdas sein können. Sie können wiederum Methoden, Funktionen oder Lambdas enthalten. Dies sind alles Codeobjekte in der Hierarchie!
def load_code(self): rand = self.r_long() length = self.r_long() seed = rng(rand, length) mt = MT19937(seed) key = [] for i in range(0, 4): key.append(mt.extract_number())
Die Möglichkeit, Codeobjekte zu entschlüsseln, bedeutet, dass Sie nach dem Deserialisieren der Prozeduren den tatsächlichen Bytecode neu schreiben müssen. Codeobjekte enthalten Informationen zu Zeilennummern, Konstanten und anderen Informationen. Der eigentliche Bytecode befindet sich im
co_code
Objekt. Wenn wir die Opcode-Übersetzungstabelle erstellt haben, können wir einfach die verschleierten Dropbox-Werte durch die Standard-Python 3.6-Äquivalente ersetzen.
Jetzt haben die Codeobjekte das übliche Python 3.6-Format und können an den Dekompiler übergeben werden. Die Qualität der Python-Dekompilierer ist dank des
uncompyle6- Projekts von R. Bernstein erheblich
gestiegen . Die Dekompilierung ergab ein ziemlich gutes Ergebnis, und ich konnte alles in einem Tool zusammenfassen, das die aktuelle Version des Dropbox-Clients nach besten Kräften dekompiliert.
Wenn Sie dieses
Repository klonen und den Anweisungen folgen, sieht das Ergebnis folgendermaßen aus:
...
__main__ - INFO - Dropbox / client / features / browse_search / __ init __. pyc wurde erfolgreich dekompiliert
__main__ - INFO - Entschlüsseln, Patchen und Dekompilieren von _bootstrap_overrides.pyc
__main__ - INFO - _bootstrap_overrides.pyc wurde erfolgreich dekompiliert
__main__ - INFO - 3713 Dateien verarbeitet (3591 erfolgreich dekompiliert, 122 fehlgeschlagen)
opcodemap - WARNUNG - NICHT Opcode-Map schreiben, da Force Overwrite nicht festgelegt ist
Dies bedeutet, dass Sie jetzt ein
out/
-Verzeichnis mit einer dekompilierten Version des Dropbox-Quellcodes haben.
Aktivieren der Dropbox-Ablaufverfolgung
In Open Source suchte ich nach etwas Interessantem und das folgende Fragment erregte meine Aufmerksamkeit. Die Trace-Handler in
out/dropbox/client/high_trace.py
werden nur installiert, wenn die Assembly nicht eingefroren ist oder der magische Schlüssel oder das Cookie, das die Funktionalität einschränkt, nicht in Zeile
1430
.
1424 def install_global_trace_handlers(flags=None, args=None): 1425 global _tracing_initialized 1426 if _tracing_initialized: 1427 TRACE('!! Already enabled tracing system') 1428 return 1429 _tracing_initialized = True 1430 if not build_number.is_frozen() or magic_trace_key_is_set() or limited_support_cookie_is_set(): 1431 if not os.getenv('DBNOLOCALTRACE'): 1432 add_trace_handler(db_thread(LtraceThread)().trace) 1433 if os.getenv('DBTRACEFILE'): 1434 pass
Das Erwähnen eingefrorener Builds bezieht sich auf die internen Debug-Builds von Dropbox. Und etwas höher in derselben Datei finden Sie solche Zeilen:
272 def is_valid_time_limited_cookie(cookie): 273 try: 274 try: 275 t_when = int(cookie[:8], 16) ^ 1686035233 276 except ValueError: 277 return False 278 else: 279 if abs(time.time() - t_when) < SECONDS_PER_DAY * 2 and md5(make_bytes(cookie[:8]) + b'traceme').hexdigest()[:6] == cookie[8:]: 280 return True 281 except Exception: 282 report_exception() 283 284 return False 285 286 287 def limited_support_cookie_is_set(): 288 dbdev = os.getenv('DBDEV') 289 return dbdev is not None and is_valid_time_limited_cookie(dbdev) build_number/environment.py
Wie Sie der Methode "
limited_support_cookie_is_set
" in Zeile
287
limited_support_cookie_is_set
, ist die Ablaufverfolgung nur aktiviert, wenn die Umgebungsvariable "
DBDEV
korrekt auf Cookies mit einer begrenzten Lebensdauer festgelegt ist. Das ist interessant! Und jetzt wissen wir, wie man solche zeitlich begrenzten Cookies generiert. Nach dem Namen zu urteilen, können Dropbox-Ingenieure solche Cookies generieren und dann in einigen Fällen vorübergehend die Ablaufverfolgung aktivieren, wenn dies zur Unterstützung von Kunden erforderlich ist. Nach dem Neustart von Dropbox oder dem Neustart des Computers läuft das angegebene Cookie automatisch ab, auch wenn es noch vorhanden ist. Ich nehme an, dies sollte beispielsweise eine Leistungsverschlechterung aufgrund kontinuierlicher Ablaufverfolgung verhindern. Es macht es auch schwierig, Dropbox zurückzuentwickeln.
Ein kleines Skript kann diese Cookies jedoch einfach ständig generieren und setzen. So etwas wie das:
Dann wird ein zeitbasiertes Cookie erstellt:
$ python3 setenv.py DBDEV=38b28b3f349714; export DBDEV;
Laden Sie dann die Ausgabe dieses Skripts korrekt in die Umgebung und führen Sie den Dropbox-Client aus.
$ eval `python3 setenv.py` $ ~/.dropbox-dist/dropbox-lnx_64-71.4.108/dropbox
Dies beinhaltet die Trace-Ausgabe mit farbenfroher Formatierung und so weiter. Es sieht ungefähr so aus wie dieser nicht registrierte Client:

Neuen Code implementieren
Das alles ist ein bisschen lustig.
out/build_number/environment.pyc
wir den dekompilierten Code weiter untersuchen, finden wir
out/build_number/environment.pyc
. Es gibt eine Funktion, die prüft, ob ein bestimmter magischer Schlüssel installiert ist. Dieser Schlüssel ist im Code nicht fest codiert, sondern wird mit dem SHA-256-Hash verglichen. Hier ist das entsprechende Snippet.
1 import hashlib, os 2 from typing import Optional, Text 3 _MAGIC_TRACE_KEY_IS_SET = None 4 5 def magic_trace_key_is_set(): 6 global _MAGIC_TRACE_KEY_IS_SET 7 if _MAGIC_TRACE_KEY_IS_SET is None: 8 dbdev = os.getenv('DBDEV') or '' 9 if isinstance(dbdev, Text): 10 bytes_dbdev = dbdev.encode('ascii') 11 else: 12 bytes_dbdev = dbdev 13 dbdev_hash = hashlib.sha256(bytes_dbdev).hexdigest() 14 _MAGIC_TRACE_KEY_IS_SET = dbdev_hash == 'e27eae61e774b19f4053361e523c771a92e838026da42c60e6b097d9cb2bc825' 15 return _MAGIC_TRACE_KEY_IS_SET
Diese Methode wird mehrmals an verschiedenen Stellen im Code aufgerufen, um zu überprüfen, ob der Magic Trace-Schlüssel festgelegt ist. Ich habe versucht, den SHA-256-Hash mit der Brute Force von
John the Ripper zu knacken, aber eine einfache Brute Force hat zu lange gedauert, und ich konnte die Anzahl der Optionen nicht reduzieren, da es keine Vermutungen zum Inhalt gab. In Dropbox können Entwickler über einen bestimmten fest codierten Entwicklungsschlüssel verfügen, den sie bei Bedarf installieren und den "magischen Schlüssel" -Modus des Clients für die Ablaufverfolgung aktivieren.
Das ärgerte mich, weil ich einen schnellen und einfachen Weg finden wollte, um Dropbox mit diesem Schlüsselsatz für die Rückverfolgung zu starten. Also habe ich ein Marshalling-Verfahren geschrieben, das verschlüsselte Pyc-Dateien gemäß Dropbox-Verschlüsselung generiert. So konnte ich meinen eigenen Code eingeben oder einfach den obigen Hash ersetzen. Dieser Code im Github-Repository befindet sich in der Datei
patchzip.py
. Infolgedessen wird der Hash durch den SHA-256-Hash von
ANVILVENTURES
. Anschließend wird das Codeobjekt erneut verschlüsselt und in einer Zip-Datei abgelegt, in der der gesamte verschleierte Code gespeichert wird. Auf diese Weise können Sie Folgendes tun:
$ DBDEV = ANVILVENTURES; DBDEV exportieren;
$ ~ / .dropbox-dist / dropbox-lnx_64-71.4.108 / dropbox
Jetzt werden alle Debugging-Funktionen angezeigt, wenn Sie mit der rechten Maustaste auf das Dropbox-Symbol in der Taskleiste klicken.
dropbox/webdebugger/server.py
ich die dekompilierten Quellen weiter untersuchte, entdeckte ich in der Datei
dropbox/webdebugger/server.py
eine Methode namens
is_enabled
. Es sieht so aus, als würde geprüft, ob der integrierte Web-Debugger aktiviert werden soll. Zunächst überprüft er den erwähnten magischen Schlüssel. Da wir den SHA-256-Hash ersetzt haben, können wir den Wert einfach auf
ANVILVENTURES
. Der zweite Teil in den Zeilen
201
und
202
prüft, ob es eine Umgebungsvariable mit dem Namen
DB<x>
gibt, deren
x
gleich dem SHA-256-Hash ist. Der Wert der Umgebung setzt, wie wir bereits gesehen haben, zeitlich begrenzte Cookies.
191 @classmethod 192 def is_enabled(cls): 193 if cls._magic_key_set: 194 return cls._magic_key_set 195 else: 196 cls._magic_key_set = False 197 if not magic_trace_key_is_set(): 198 return False 199 for var in os.environ: 200 if var.startswith('DB'): 201 var_hash = hashlib.sha256(make_bytes(var[2:])).hexdigest() 202 if var_hash == '5df50a9c69f00ac71f873d02ff14f3b86e39600312c0b603cbb76b8b8a433d3ff0757214287b25fb01' and is_valid_time_limited_cookie(os.environ[var]): 203 cls._magic_key_set = True 204 return True 205 206 return False
Wenn Sie diesen Hash mit genau der gleichen Technik durch den zuvor verwendeten SHA-256 ersetzen, können Sie das zuvor geschriebene
setenv
Skript jetzt wie
setenv
ändern:
$ cat setenv.py … c = generate_time_cookie() output_env("DBDEV", "ANVILVENTURES") output_env("DBANVILVENTURES", c) $ python3 setenv.py DBDEV=ANVILVENTURES; export DBDEV; DBANVILVENTURES=38b285c4034a67; export DBANVILVENTURES $ eval `python3 setenv.py` $ ~/.dropbox-dist/dropbox-lnx_64-71.4.108/dropbox
Wie Sie sehen, wird nach dem Starten des Clients ein neuer TCP-Port zum Abhören geöffnet. Es wird nicht geöffnet, wenn Umgebungsvariablen nicht richtig eingestellt sind.
$ netstat --tcp -lnp | grep dropbox
tcp 0 0 127.0.0.1:4242 0.0.0.0:* LISTEN 1517 / dropbox
Weiter im Code finden Sie die WebSocket-Oberfläche in der Datei
webpdb.pyc
. Dies ist ein Wrapper für Standard-Python-
PDF- Dienstprogramme. Der Zugriff auf die Schnittstelle erfolgt über einen HTTP-Server an diesem Port. Lassen Sie uns
den Websocket-Client installieren und
ausprobieren :
$ websocat -t ws: //127.0.0.1: 4242 / pdb
--Return--
> /home/gvb/dropbox/webdebugger/webpdb.pyc(101)run()->Keine
>
(Pdb) aus build_number.environment importiere magic_trace_key_is_set als ms
(Pdb) ms ()
Stimmt
Somit haben wir jetzt einen vollwertigen Debugger im Client, der im Übrigen wie bisher funktioniert. Wir können beliebigen Python-Code ausführen, wir konnten das interne Debug-Menü und die Trace-Funktionen aktivieren. All dies wird bei der weiteren Analyse des Dropbox-Clients sehr hilfreich sein.
Fazit
Wir konnten Dropbox zurückentwickeln, Code-Entschlüsselungs- und Injection-Tools schreiben, die mit aktuellen Dropbox-Clients auf Basis von Python 3.6 funktionieren. Wir haben die einzelnen versteckten Funktionen rückgängig gemacht und aktiviert. Offensichtlich wird der Debugger wirklich beim weiteren Hacken helfen. Insbesondere bei einer Reihe von Dateien, die aufgrund der Nachteile von decompyle6 nicht erfolgreich dekompiliert werden können.
Code
Der Code ist
auf Github zu finden. Gebrauchsanweisung dort. Dieses Repository enthält auch meinen alten Code aus dem Jahr 2011. Es sollte mit nur wenigen Änderungen funktionieren, vorausgesetzt, jemand hat ältere Versionen von Dropbox, die auf Python 2.7 basieren.
Das Repository enthält auch Skripte zum Senden von Opcodes, Anweisungen zum Festlegen von Dropbox-Umgebungsvariablen und alles, was zum Ändern der Zip-Datei erforderlich ist.
Danksagung
Vielen Dank an Brian von Anvil Ventures für die Überprüfung meines Codes. Die Arbeit an diesem Code wurde mehrere Jahre lang fortgesetzt. Von Zeit zu Zeit habe ich ihn aktualisiert, neue Methoden eingeführt und Fragmente neu geschrieben, um ihn für neue Versionen von Dropbox wiederherzustellen.
Wie bereits erwähnt, ist die Arbeit von Rich Smith, Florian Ledoux und Nicolas Raff sowie von Hagen Fritch ein guter Ausgangspunkt für das Reverse Engineering von Python-Anwendungen. Insbesondere ihre Arbeit ist relevant für die umgekehrte Entwicklung einer der größten Python-Anwendungen der Welt - des Dropbox-Clients.
Es ist anzumerken, dass die Dekompilierung des Python-Codes dank des von R. Bernstein geleiteten uncompyle6-Projekts stark fortgeschritten ist. Dieser Dekompiler hat viele verschiedene Python-Dekompiler kompiliert und verbessert.
Vielen Dank auch an die Kollegen Brian, Austin, Stefan und Chris für die Überprüfung dieses Artikels.