HackEx - How to not code an API
Es musste ja so kommen: Der Hacker hacked mal wieder das Hackerspiel. Dieser Artikel ist schon lange geplant, aber erst jetzt habe ich es geschafft ihre neuste API anzusprechen.
Aber ganz Langsam: HackEx ist ein Online-Spiel für Android Handys. Es geht darum, andere Spieler zu Hacken, ihnen ihr Guthaben zu stehen und die eigene Software für besser Verteidigung/Angriffe aufzurüsten. Das Spiel wurde von den Entwicklern ein “wenig” vernachlässigt, so dass bald Bots und Scripter die Leaderboards übernahmen. Allerdings ist die Punkteverteilung für diese Leaderboards auch nicht durchdacht: Ein Angriff auf ein Spieler Level 1, welcher nur 4 Sekunden dauert, bringt genau so viele Punkte wie ein Angriff auf einen Spieler viel höheren Levels.
Die JSon-API ist einfach gestrickt: Man bekommt beim Login ein Token zugewiesen, was man in folgenden Requests als “X-API-KEY” im Header mitschickt. Weitere Befehle, wie z.B. das abrufen von aktuellen Prozessen (/v5/user_processes) geschieht über GET Requests. Zum Senden von bestimmten Angriffen werden meist Argumente als POST Request übergeben.
Bisher hört sich die API doch ganz vernüftig an. Bis auf, dass sie bis vor kurzem Unverschlüsselt war und somit Leuten das Botten ermöglichte. Aber welches Browsergame kennt das nicht? Nein, ich möchte auf viel schlimmere Sachen herraus:
- Die API sendet viel zu viele Daten: Die API schickt Daten an den User herraus, die er nicht so direkt zu Gesicht bekommen darf. Um auf einen gegnerischen Computer zuzugreifen, ist es zunächst nötig seinen PC zu “Hacken”. Über die API können wir einfach alle Infos abrufen, die wir auch über uns selbst erhalten. Dass heißt seine Software-Levels, seine Freundesliste, ja sogar seine Prozesse gibt die API freiwillig Preis.
Eigentlich intere Daten, wie die Erfolgschance eines Prozesses, die nirgends im eigentlichen Programm verwendung finden werden mitgesendet.
Und abschließend: um auf das Konto des gehackten Computers zuzugreifen muss man zunächst das Bankkonto “cracken”, was wiederrum seine Zeit dauert. Über die API kann man den Kontostand einfach auslesen, ohne zuvor dieses Konto gecrackt zu haben. In meinen Augen Fatal! - Die API validiert nicht: Um einem User ein Spam-Upload unterzujubeln ist es ebenfalls zunächst nötig seinen Computer zu hacken. Mit der API fügt man einfach einen Upload-Prozess mit der gewünschten User-ID hinzu und der Upload startet. Und das Opfer kann nichts dagegen unternehmen.
Immerhin der Zugriff auf fremde Bankkonten ist nicht möglich. Hier wird validiert, ob das Konto gecracked wurde und ob sich das Encrypter-Level inzwischen verändert hat. - Das Spiel ermöglicht Credit-Transfer: Dies ist kein API Problem, sondern vielmehr die Unüberlegtheit des Programmierers. Multiple Accounts sind kein Problem. So kann man mehrere Accounts zum “Farmen” verwenden und das Geld regelmäsig (über die API) zum Hauptaccount senden.
Fazit: Die API gibt unvalidiert den kompletten Datensatz der Datenbank aus, selbst wenn es sich um sensible und nirgends verwendete Daten handelt.
Ein erster Bot war schnell geschrieben. Ziel war es zunächst Credits zu farmen: Es wurden zufällige User gescannt und ihre Firewall “gehackt”. Im Anschluss gab der Bot den Befehl zum Konto Cracken und fügte sie ihm Anschluss in eine “Cracked User” Liste dazu. Diese Liste wurde nun alle halbe Stunde durchlaufen und das Geld aus den jeweiligen Konten in die eigene Sparbüchse überwiesen.
Effektiver, so stellte sich bald herraus, war das nutzen vom Spam Uploads auf inaktive User. Die API gibt nämlich auch den Zeitpunkt des letzten Logins zurück. Und wenn dieser User inaktiv ist, so wird auch der gewinnbringende Spam nicht gelöscht. Der Upload funktionierte, wie schon oben erwähnt, über die Sicherheitslücke im System. So konnte innerhalb von Minunten, hunderte User mit Spam infiziert werden. Und das gab richtig viele virtuelle Credits.
Überraschenderweiße brachten die Entwicker von HackEx ein neues Update herraus, was einige “gravierende Sicherheitslücken” schloss. Die maximale Prozess - und Spamanzahl wurde auf 600 beschränkt und die API bekam eine erste Signatur welche mitgeschickt wurde. Da der Artikel sich später mit der neusten Signatur der API beschäftigt halte ich mich hier kurz: Es wurde der aktuelle Timestamp des nutzers in “sig2″ festgehalten. In “sig1″ landeten alle Argumente (ob GET oder POST) mit ein wenig Encodierten Sachen vor und hinter den betreffenden Daten. Dieser so generierte String wurde noch SHA1 gehashed und an den Server geschickt. Den Code konnte man dank Java Decompile fast 1 zu 1 in C# übernehmen. Arbeitsaufwand: 30 Minuten und der Bot lief wieder.
Anscheinend hatten andere ebenfalls diese Signaturentechnik durchschaut und implementiert, denn 3 Tage später gab es ein neues API Update, diesesmal etwas kreativer: In Android ist es möglich nativen Code (Shared Libraries wie man sie aus Linux kennt) in kombination mit C++ Code und Standartlibraries zu verwenden. Viele Spiele lagern performancelastige Dinge in nativen Code aus, da die Java Wrapper einfach zu viel Zeit kosten. So wurde die Signaturmethode in eine externe Library ausgelagert. Ohne groß Ahnung von diesen nativen Methoden zu haben ging ich davon aus, dass man die Shared Library, welche auch unter x86 kompiliert war, unter jedem passenden Linux einbinden und aufrufen kann. (Siehe den Thread dazu auf Coderz.cc). Nachdem ich eines besseren Belehrt wurde stellte sich die Frage, wie man diesen Code denn nun debuggen kann. In IDA Pro sah ich zwar, dass sich an dem eigentlichen Prinzip des SHA1 Hashing nichts geändert hatte, aber dynamische Stringoperationen machten mir beim Analysieren einen Strich durch die Rechnung. Was tun?
Es gibt einen GDB Server auf Android, welcher über diverse Tricks mit ADB angesprochen und gestartet werden kann. Das alles ist in einem Bash-Script des NDK (Native Development Kit für Android) zusammengefasst. Nachdem die Konfiguration unter Windows, Cygwin und weiteren Tricks nach mehreren Stunden gescheitert ist, wurde eine Linux VM eingerichtet. Schnell war die Hello-JNI (eine simple App welche das native Java Native Interface verwendet) modifziert dass sie die libHackEx.so und libstlport_shared.so läd. Auch war schnell die Klasse aus der HackEx Applikation rekonstruktiert, die auf diese nativen Libraries zugreift und den eigentlichen String hashed. Nun wurde es spannend, denn es ging ans testen und debuggen. Da der Debugger erst später an die App attached wurde ein bildschrimfüllender Button eingebaut der die native Library aufruft:
Button myButton = new Button(this); // dynamischer Button myButton.setText("Push Me"); myButton.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { HashMap<String,String> testHM = new HashMap<String,String&>(); // Neue HashMap String test = com.byeline.hackex.utils.SettingsManager.flux(testHM); // Message Box zum Ausgeben des gehashten Strings } }); setContentView(myButton); // Button Bildschirmfüllend |
Nachdem das Handy via ADB verbunden war, die Applikation als Debugable installiert und gestartet war wurde es spannend:
theuser@ubuntu:~/Downloads/ndk/samples/hello-jni$ ndk-gdb WARNING: The shell running this script isn't bash. Although we try to avoid bashism in scripts, things can happen. /home/theuser/Downloads/ndk/ndk-gdb: 148: /home/theuser/Downloads/ndk/ndk-gdb: Bad substitution /home/theuser/Downloads/ndk/ndk-gdb: 555: [: 1: unexpected operator /home/theuser/Downloads/ndk/ndk-gdb: 771: [: armeabi-v7a: unexpected operator GNU gdb (GDB) 7.3.1-gg2 Copyright (C) 2011 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "--host=x86_64-linux-gnu --target=arm-linux-android". For bug reporting instructions, please see: <http://source.android.com/source/report-bugs.html>. warning: .dynamic section for "/home/theuser/Downloads/ndk/samples/hello-jni/obj/local/armeabi-v7a/linker" is not at the expected address (wrong library or version mismatch?) warning: Could not load shared library symbols for 74 libraries, e.g. libstdc++.so. Use the "info sharedlibrary" command to see the complete listing. Do you need "set solib-search-path" or "set sysroot"? (gdb)
Jippi! Trotz einigen Fehlermeldungen wurde der debugger attached. Fehlen nur noch die shared Libraries. Durch “info shared” wurde schnell klar, dass über 70 shared Libs geladen wurden. Interessant sind die letzten Zeilen:
No libbcc.so No libRS.so No librs_jni.so No libandroid.so No libwebcore.so 0x5a5c2ba0 0x5a5c4020 Yes /home/theuser/Downloads/ndk/samples/hello-jni/obj/local/armeabi-v7a/libhello-jni.so 0x5a854890 0x5a8711ec Yes (*) /home/theuser/Downloads/ndk/samples/hello-jni/obj/local/armeabi-v7a/libstlport_shared.so 0x5a5c8b50 0x5a5cc8e0 Yes (*) /home/theuser/Downloads/ndk/samples/hello-jni/obj/local/armeabi-v7a/libHackEx.so No gralloc.montblanc.so (*): Shared library is missing debugging information.
Für die uns interessierenden Libs wurden die Symbole geladen, somit haben wir Zugriff auf die Export Table und können dort Breakpoints setzen! Leider hatte ich weder von gdb, noch vom ARM Befehlssatz große Ahnung. Also hieß es rumsuchen und raten. In IDA Pro wurde gesehn, dass zunächst sha1_begin(), dann sha1_hash in einer Schleife und als letzes sha1_end() aufgerufen wurde. Die exported Funktionen fand GDB zum Glück:
(gdb) br sha1_hash Breakpoint 1 at 0x5a5ca95a (gdb) c Continuing. // Einmal auf den Button gedrückt Breakpoint 1, 0x5a5ca95a in sha1_hash () from /home/theuser/Downloads/ndk/samples/hello-jni/obj/local/armeabi-v7a/libHackEx.so
BAM, wir sind drin. Nur, was jetzt? sha1_hash bekommt in jedem Aufruf einen weiteren Character übergeben, in diesem Fall sogar einen Zeiger auf den nächsten Buchstaben. Laut Wikipedia werden Parameter in den Registern r4 bis r7 übergeben. Versuchen wir doch mal den Input als String zu interpretieren:
(gdb) x/s $r4 0xbeffd2ec: "" (gdb) x/s $r5 0x1: (gdb) x/s $r6 0xbeffd2ec: "" (gdb) x/s $r7 0x5a8895f0: "1101101101sig21410714733343WqZnwjpaVZNvWDpJhqHCHhWtNfu86CkmtCAVErbQO"
Ein weiterer ausgelassener Freundensschrei! Sieht fast so aus als hätten wir den finalen SHA1 String gefunden. Zerlegen wir in doch etwas:
Am Anfang: 1101101101 Parameter und Timestamp: [KEY]sig2 [VALUE]1410714733343 ( = 14.9.2024 19:12:13) Am Ende: WqZnwjpaVZNvWDpJhqHCHhWtNfu86CkmtCAVErbQO
Jetzt ist nur noch die Frage, wo andere Parameter in dem String plaziert werden. Es muss da eine Konvention geben, denn sonst kann der Server die Signatur ja nicht verifizieren. Fügen wir doch in Java ein paar HashMap Elemente hinzu. Der nun generierte String sieht aus aus:
Am Anfang: 1101101101 Array Elemete: sig2 1410715171220 cKey aValue bKeyKey testValue2 aKeytest Value Am Ende: WqZnwjpaVZNvWDpJhqHCHhWtNfu86CkmtCAVErbQO
Sieht so aus als werden sie Alphabetisch rückwärts geordnet. Damit ist auch dieser Signaturalgorithmus wieder gebrochen. Und das Spam hochladen geht weiter. Immerhin ein guter Ansatz vom Entwickler!
Abschliesend gilt zu sagen: Das letzte war in der Tat eine harte Nuss, da man wirklich in eine Android Applikation reindebuggen musste und dafür erstmal die Konfiguration einrichten durfte. Das kostet Neven, aber um so schöner wenn es dann Funktioniert!
Greez
Dieser Artikel ist ja nun schon eine Weile her.
Meine Frage an dich, wäre so etwas erneut möglich?
Ich habe einmal probiert die Credits mit ‘Lucky-Patcher’ und ‘Freedom’ zu manipulieren was nicht geklappt hat und mit der App ‘Gamehacker’ bin auch gescheitert.
Mir ist eine Sache noch aufgefallen, man kann die App nicht starten wenn man die Datum -/ Zeiteinstellung auf manuell stellt, dass bedeutet ja das die App mit der Systemzeit synchronisiert.
Ich würde mich nämlich gerne einmal in die Thematik RE einarbeiten.
Was genau meinst Du mit “So etwas erneut möglich”? Soweit ich weis hat HackEx bisher kein Update rausgebracht, daher ist die API weitehin die selbe.
Die Credits (IAP) zu manipulieren ist in diesem Spiel nicht möglich, da auch die Credits serverseitig gespeichert werden. Solche “Fake”-Käufe sind nur in Apps möglich, welche die Credits primär auf dem Gerät selbst speichern.
Zum Datum: Es wird die Server-Zeit für alle Berechnungen genutzt. Weshalb die mit der lokalen Uhrzeit übereinstimmen muss, keine Ahnung