Hallo, lieber Leser, ich studiere seit einiger Zeit mobile Anwendungen. Die meisten Anwendungen versuchen nicht, ihre "geheimen" Funktionen vor mir zu verbergen. Und ich bin zu diesem Zeitpunkt glücklich, weil ich nicht den verschleierten Code von jemandem studieren muss.

In diesem Artikel möchte ich meine Vision der Verschleierung teilen und über eine interessante Methode zum Verbergen von Geschäftslogik in Anwendungen mit NDK sprechen, die ich vor relativ kurzer Zeit gefunden habe. Wenn Sie also an Live-Beispielen für verschleierten Code in Android interessiert sind, frage ich nach Katze.
Unter der Verschleierung im Rahmen dieses Artikels verstehen wir die Reduktion des ausführbaren Codes einer Android-Anwendung auf eine schwer zu analysierende Form. Es gibt mehrere Gründe, die die Codeanalyse erschweren:
- Kein Unternehmen möchte in seinem "Inneren" herumgestochert werden.
- Auch wenn Sie eine Dummy-Anwendung haben, finden Sie dort immer interessante Dinge (ein Beispiel mit Instagram ).
Viele Entwickler lösen das Problem, indem sie einfach die ProGuard-Konfiguration abspalten. Dies ist nicht der beste Weg, um Daten zu schützen (wenn Sie zum ersten Mal davon hören, lesen Sie das Wiki ).
Ich möchte ein Beispiel dafür geben, warum der angebliche „Schutz“ mit ProGuard nicht funktioniert. Nehmen Sie ein einfaches Beispiel aus Google Samples.

Nachdem wir ProGuard mit einer Standardkonfiguration verbunden haben, erhalten wir einen dekompilierten Code:

"Ohhh, nichts ist klar", sagen wir und beruhigen uns. Aber nach ein paar Minuten des Wechsels zwischen Dateien finden wir ähnliche Codeteile:

In diesem Beispiel scheint der Anwendungscode eher schwach zu sein (Datenprotokollierung, Erstellen von Videoaufnahmen), sodass einige der im Originalcode verwendeten Methoden auch nach der Verarbeitung mit der ProGuard-Konfiguration leicht zu verstehen sind.
Schauen Sie sich außerdem die Datenklassen in Kotlin an. Die Standarddatenklasse erstellt die Methode "toString", die die Namen der Instanzvariablen und den Namen der Klasse selbst enthält.
Quelldatenklasse:

Es kann sich für eine Umkehrung in einen Leckerbissen verwandeln:

(automatische Generierung der toString-Methode in Kotlin)
Es stellt sich heraus, dass ProGuard sich weit vom gesamten Quellcode des Projekts entfernt.
Wenn ich Sie immer noch nicht von der Unangemessenheit überzeugt habe, den Code auf diese Weise zu schützen, versuchen wir, das Attribut ".source" in unserem Projekt zu belassen.
-keepattributes SourceFile
Diese Zeile ist in vielen OpenSource-Projekten enthalten. Sie können StackTrace anzeigen, wenn die Anwendung abstürzt. Wenn Sie jedoch ".source" aus dem Smali-Code ziehen, erhalten Sie die gesamte Projekthierarchie mit vollständigen Klassennamen.
Per Definition bedeutet Verschleierung, „den Quellcode in eine unlesbare Form zu bringen, um verschiedenen Empfangsarten entgegenzuwirken“. ProGuard (bei Verwendung mit einer Standardkonfiguration) macht den Code jedoch nicht unlesbar - es funktioniert als Minifier, komprimiert Namen und wirft zusätzliche Klassen aus dem Projekt.
Eine solche Verwendung von ProGuard ist eine einfache, aber für eine gute Verschleierungslösung nicht vollständig geeignete Lösung. Ein guter Entwickler muss dafür sorgen, dass der Wiederverkäufer (oder der Angreifer) Angst vor „chinesischen Schriftzeichen“ hat, die schwer zu entschlüsseln sind.
Wenn Sie mehr über ProGuard erfahren möchten, schlage ich den folgenden informativen Artikel vor .
Was verstecken wir?
Nun wollen wir sehen, was normalerweise in Anwendungen verborgen ist.
- Verschlüsselungsschlüssel:

- Spezifische Anwendungslogik:

Im Code kann häufig etwas Unerwartetes verborgen sein (Beobachtungen aus persönlicher Erfahrung), zum Beispiel:
- Namen der Projektentwickler
- Vollständiger Pfad zum Projekt
- Client_secret für das Oauth2-Protokoll
- PDF-Buch „Wie man für Android entwickelt“ (wahrscheinlich immer zur Hand)
Jetzt wissen wir, was sich in Android-Anwendungen verstecken kann, und können zur Hauptsache übergehen, nämlich zu Möglichkeiten, diese Daten zu verbergen.
Möglichkeiten zum Ausblenden von Daten
Option 1: Verstecken Sie nichts, lassen Sie alles in Sichtweite
In diesem Fall zeige ich dir nur dieses Bild :)
"Hilf Dasha, Geschäftslogik zu finden"

Diese kostengünstige und völlig kostenlose Lösung eignet sich für:
- Einfache Anwendungen, die nicht mit dem Netzwerk interagieren und keine vertraulichen Benutzerinformationen speichern.
- Anwendungen, die nur die öffentliche API verwenden.
Option 2: Verwenden Sie ProGuard mit den richtigen Einstellungen
Diese Entscheidung hat immer noch das Recht auf Leben, weil sie zuallererst einfach und kostenlos ist. Trotz der oben genannten Nachteile hat es ein erhebliches Plus: Wenn die ProGuard-Regeln korrekt konfiguriert sind, kann die Anwendung wirklich verschleiert werden.
Sie müssen jedoch verstehen, dass der Entwickler bei einer solchen Lösung nach jeder Assembly dekompilieren und prüfen muss, ob alles in Ordnung ist. Nachdem der Entwickler (und sein Unternehmen) einige Minuten mit dem Studieren der APK-Datei verbracht haben, können sie sich auf die Sicherheit ihres Produkts verlassen.
So studieren Sie die APK-DateiDas Überprüfen der Anwendung auf Verschleierung ist recht einfach.
Um die APK-Datei aus dem Projekt zu erhalten, gibt es verschiedene Möglichkeiten:
- aus dem Projektverzeichnis nehmen (in Android Studio lautet der Name des Ordners normalerweise "build");
- Installieren Sie die Anwendung auf Ihrem Smartphone und holen Sie sich die APK mit der Anwendung „Apk Extractor“.
Danach erhalten wir mit dem Apktool-Dienstprogramm den Smali-Code (Anweisungen, um hierher zu gelangen, https://ibotpeaches.imtqy.com/Apktool/documentation ) und versuchen, in den Zeilen des Projekts etwas zu finden, das verdächtig gelesen wurde. Um nach lesbaren Codes zu suchen, können Sie sich übrigens mit vorgefertigten Bash-Befehlen eindecken.
Diese Lösung eignet sich für:
- Spielzeuganwendungen, Online-Shop-Anwendungen usw.;
- Anwendungen, bei denen es sich wirklich um Thin Clients handelt und bei denen alle Daten ausschließlich von der Serverseite eingehen.
- Anwendungen, die nicht auf alle Banner „Sichere Anwendung Nr. 1“ schreiben.
Option 3: Verwenden Sie Open Source Obfuscator
Leider kenne ich die wirklich guten kostenlosen Verschleierer für mobile Anwendungen nicht. Und die Verschleierer, die im Netzwerk zu finden sind, können Ihnen viel Kopfzerbrechen bereiten, da es zu schwierig sein wird, ein solches Projekt für neue Versionen der API zusammenzustellen.
In der Vergangenheit werden vorhandene coole Verschleierer unter Maschinencode (für C / C ++) erstellt. Gute Beispiele:
Zum Beispiel ersetzt Movfuscator alle Opcodes durch movs, macht den Code linear und entfernt alle Zweige. Es wird jedoch dringend davon abgeraten, diese Methode der Verschleierung in einem Kampfprojekt zu verwenden, da der Code dann sehr langsam und schwer werden kann.
Diese Lösung eignet sich für Anwendungen, bei denen der Hauptteil des Codes NDK ist.
Option 4: Verwenden Sie eine proprietäre Lösung
Dies ist die kompetenteste Wahl für ernsthafte Anwendungen als proprietäre Software:
a) unterstützt;
b) wird immer relevant sein.
Ein Beispiel für verschleierten Code bei Verwendung solcher Lösungen:

In diesem Code-Snippet sehen Sie:
- Die unverständlichsten Namen von Variablen (mit russischen Buchstaben);
- Chinesische Schriftzeichen in den Zeilen, die nicht klar machen, was wirklich im Projekt passiert;
- Dem Projekt wurden viele Traps hinzugefügt („switch“, „goto“), die den Anwendungscodeflow stark verändern.
Diese Lösung eignet sich für:
- Banken
- Versicherungsunternehmen;
- Mobilfunkbetreiber, Passwortspeicheranwendungen usw.
Option 5: Verwenden Sie React-Native
Ich habe mich entschlossen, diesen Punkt hervorzuheben, da das Schreiben plattformübergreifender Anwendungen mittlerweile zu einer sehr beliebten Aktivität geworden ist.
Neben einer sehr großen Community verfügt JS über eine sehr große Anzahl offener Verschleierer. Zum Beispiel können sie Ihre Anwendung in Emoticons verwandeln:

Ich würde Sie gerne zu dieser Lösung beraten, aber dann wird Ihr Projekt kaum schneller als eine Schildkröte funktionieren.
Indem wir jedoch die Anforderungen an die Verschleierung von Code reduzieren, können wir ein wirklich gut geschütztes Projekt erstellen. Also google "js obfuscator" und verschleiere unsere Ausgabe-Bundle-Datei.
Diese Lösung eignet sich für diejenigen, die bereit sind, eine plattformübergreifende Anwendung auf React Native zu schreiben.
XamarinEs wäre sehr interessant, Informationen über Verschleierer auf Xamarin zu erhalten. Wenn Sie Erfahrung mit deren Verwendung haben, teilen Sie uns dies bitte in den Kommentaren mit.
Option 6: Verwenden Sie NDK
Ich selbst musste oft NDK in meinem Code verwenden. Und ich weiß, dass einige Entwickler glauben, dass die Verwendung von NDK ihre Anwendung vor Umkehrern schützt. Dies ist nicht ganz richtig. Zuerst müssen Sie verstehen, wie das Verstecken mit dem NDK funktioniert.

Es stellt sich sehr einfach heraus. Der Code enthält einige JNI-Konventionen, die beim Aufrufen von C / C ++ - Code in einem Projekt wie folgt konvertiert werden.
NativeSummator Native Class:

Implementierung der nativen Summenmethode:

Implementierung der nativen statischen Summenmethode:

Es wird deutlich, dass Sie zum Aufrufen der nativen Methode die Funktionssuche Java_<package name>_<Static?><class>_<method>
in der dynamischen Bibliothek verwenden.
Wenn Sie sich den Dalvik / ART-Code ansehen, finden Sie die folgenden Zeilen:

( Quelle )
Zuerst generieren wir die folgende Zeile Java_<package name>_<class>_<method>
aus dem Java-Objekt und versuchen dann, die Methode in der dynamischen Bibliothek mit dem Aufruf "dlsym" aufzulösen, um die Funktion zu finden, die wir im NDK benötigen.
So funktioniert JNI. Das Hauptproblem besteht darin, dass durch Dekompilieren der dynamischen Bibliothek alle Methoden in voller Ansicht angezeigt werden:

Wir müssen also eine solche Lösung finden, damit die Adresse der Funktion verschleiert wird.
Zuerst habe ich versucht, Daten direkt in unsere JNI-Tabelle zu schreiben, aber mir wurde klar, dass ASLR- Mechanismen und verschiedene Versionen von Android es mir einfach nicht erlauben würden, diese Methode auf allen Geräten zum Laufen zu bringen. Dann habe ich mich entschlossen herauszufinden, welche Methoden das NDK Entwicklern bietet.
Und siehe da , es gab eine "RegisterNatives" -Methode, die genau das tut, was wir brauchen (ruft die interne Funktion dvmRegisterJNIMethod auf ).
Wir definieren ein Array, das unsere native Methode beschreibt:

Und wir registrieren unsere deklarierte Methode in der Funktion JNI_OnLoad (die Methode wird aufgerufen, nachdem die dynamische Bibliothek initialisiert wurde, tausend ):

Hurra, wir haben selbst die Funktion "hideFunc" versteckt. Wenden Sie jetzt unseren bevorzugten llvm-Verschleierer an und genießen Sie die Sicherheit des Codes in seiner endgültigen Form.
Diese Lösung eignet sich für Anwendungen, die bereits NDK verwenden (das Verbinden von NDK bringt viele Schwierigkeiten mit dem Projekt mit sich, sodass diese Lösung für Nicht-NDK-Anwendungen nicht so relevant ist).
Fazit
Tatsächlich sollte die Anwendung keine vertraulichen Daten speichern oder erst nach Benutzerauthentifizierung zugänglich sein. Es kommt jedoch vor, dass die Geschäftslogik Entwickler dazu zwingt, Token, Schlüssel und bestimmte Elemente der Codelogik in der Anwendung zu speichern. Ich hoffe, dieser Artikel hilft Ihnen, wenn Sie solche sensiblen Daten nicht weitergeben und ein „offenes Buch“ für Schriftgelehrte sein möchten.
Ich glaube, dass die Verschleierung ein wichtiger struktureller Bestandteil jeder modernen Anwendung ist.
Denken Sie sorgfältig über Probleme beim Ausblenden von Code nach und suchen Sie nicht nach einfachen Wegen! :) :)
Übrigens, danke an den Benutzer miproblema für die Hilfe in einigen Fragen. Abonnieren Sie ihren Telegrammkanal, es ist dort interessant.
Vielen Dank auch an die Benutzer von sverkunchik und SCaptainCAP für ihre Hilfe bei der Bearbeitung des Artikels.