Untersuchung eines unbekannten Archivs

Umzug. Neue Stadt. Jobsuche. Selbst fĂŒr einen IT-Experten kann dies lange dauern. Eine Reihe von Interviews, die sich im Allgemeinen sehr Ă€hnlich sind. Und wie es normalerweise passiert, wenn Sie bereits eine Stelle gefunden haben, wird nach einer Weile ein interessantes BĂŒro angekĂŒndigt.

Es war schwer zu verstehen, was sie speziell tat, ihr Interessengebiet war jedoch das Studium der Software anderer Leute. Es klingt faszinierend, obwohl Sie, wenn Sie feststellen, dass dies kein Anbieter zu sein scheint, der Software fĂŒr die Cybersicherheit veröffentlicht, eine Sekunde innehalten und anfangen, Ihre RĂŒben zu kratzen.



Kurzum: Sie warfen das Archiv ab und boten an, es als Testaufgabe zu untersuchen und zu versuchen, eine bestimmte Signatur basierend auf den prĂ€sentierten Eingabedaten zu berechnen. Es ist erwĂ€hnenswert, dass ich nur sehr wenig Erfahrung mit solchen AktivitĂ€ten hatte und wahrscheinlich deshalb in der ersten Iteration der Lösung nur ein paar Stunden Zeit hatte - dann wurde die Motivation, dies zu tun, zunichte gemacht. Und ja, natĂŒrlich habe ich als erstes versucht, es auf dem Telefon / Emulator auszufĂŒhren - diese Anwendung ist ungĂŒltig.

Was wir haben: ein Archiv mit der Erweiterung ".apk" . Ich habe die Aufgabe selbst unter den Spoiler gestellt, damit sie nicht von Suchmaschinen indiziert wird: Was ist, wenn die Jungs es nicht mögen, dass ich die Lösung auf Habr stelle?

Aufgabe selbst
Die APK enthĂ€lt Funktionen zum Generieren von Signaturen fĂŒr ein assoziatives Array.
Versuchen Sie, eine Signatur fĂŒr den folgenden Datensatz zu erhalten:

{ "user" : "LeetD3vM4st3R", "password": "__s33cr$$tV4lu3__", "hash": "34765983265937875692356935636464" } 


Ärmel hochkrempeln


Es wird gesagt, dass das Archiv die FunktionalitĂ€t zum Signieren eines assoziativen Arrays enthĂ€lt. Unter der Dateierweiterung verstehen wir sofort, dass es sich um eine fĂŒr Android geschriebene Anwendung handelt. Zuerst packen wir das Archiv aus. TatsĂ€chlich handelt es sich hierbei um ein regulĂ€res ZIP-Archiv, mit dem jeder Archivierer problemlos umgehen kann. Ich habe das Dienstprogramm apktool verwendet und, wie sich herausstellte, versehentlich ein paar Rechen umgangen. Ja, es passiert (normalerweise das Gegenteil, ja?). Der Zauber ist ziemlich einfach:

 apktool d task.zip 

Es stellt sich heraus, dass der Code und die Ressourcen in der APK-Datei auch in separaten BinĂ€rdateien gespeichert sind und andere Software benötigt wird, um sie zu extrahieren. apktool hat implizit Klassenbytes und Ressourcen herausgezogen und alles in eine natĂŒrliche Dateihierarchie zerlegt. Sie können fortfahren.

 ├── AndroidManifest.xml ├── apktool.yml ├── lib │   └── arm64-v8a ├── original │   ├── AndroidManifest.xml │   └── META-INF ├── res │   ├── anim │   ├── color │   ├── drawable │   ├── layout │   ├── layout-watch-v20 │   ├── mipmap-anydpi-v26 │   ├── values │   └── values-af ├── smali │   ├── android │   ├── butterknife │   ├── com │   ├── net │   └── org └── unknown   └── org 

Wir sehen eine Ă€hnliche Hierarchie (haben die vereinfachte Version verlassen) und versuchen herauszufinden, wo wir anfangen sollen. Es ist erwĂ€hnenswert, dass ich trotzdem einmal ein paar kleine Anwendungen fĂŒr Android geschrieben habe, so dass mir die Essenz eines Teils der Verzeichnisse und im Allgemeinen die Prinzipien der Android-Anwendungen des GerĂ€ts ungefĂ€hr klar sind.

ZunÀchst entscheide ich mich, einfach durch die Dateien zu gehen. Ich öffne AndroidManifest.xml und beginne sinnvoll zu lesen. Meine Aufmerksamkeit wird von einem seltsamen Attribut angezogen

 android:supportsRtl="true" 

Es stellt sich heraus, dass er fĂŒr die UnterstĂŒtzung von Sprachen mit dem Buchstaben "von rechts nach links" in der Anwendung verantwortlich ist. Wir beginnen uns anzustrengen. Nicht gut.

Außerdem klammert sich mein Blick an den unbekannten Ordner. Darunter befindet sich eine Hierarchie des Formulars: org.apache.commons.codec.language.bm und eine große Anzahl von Textdateien mit unklarem Inhalt. Google den vollstĂ€ndigen Namen des Pakets und es stellt sich heraus, was hier gespeichert ist, was mit dem Suchalgorithmus fĂŒr Wörter zusammenhĂ€ngt, die dem angegebenen phonetisch Ă€hnlich sind. Ehrlich gesagt begann ich mich hier stĂ€rker anzustrengen. Nachdem ich mich ein bisschen in den Verzeichnissen umgesehen hatte, fand ich tatsĂ€chlich den Code selbst und dann begann der Spaß. Ich wurde nicht von dem ĂŒblichen Java-Bytecode getroffen, mit dem ich einmal herumspielen konnte, sondern von etwas anderem. Sehr Ă€hnlich, aber anders.

Wie sich herausstellte, hat Android eine eigene virtuelle Maschine - Dalvik. Und wie jede angesehene virtuelle Maschine verfĂŒgt sie ĂŒber einen eigenen Bytecode. Es scheint, dass ich beim ersten Versuch, dieses Problem zu lösen, in diesem traurigen Ton eine Pause ankĂŒndigte, mich verbeugte, den Vorhang fallen ließ und alles 4 Monate lang warf, bis meine Neugier mich vollstĂ€ndig beendete.

Ärmel hochkrempeln [2]


"Aber kann es nicht so sein, dass alles einfacher ist?" - Dies ist die Frage, die ich mir gestellt habe, als ich die Aufgabe zum zweiten Mal gestartet habe. Ich begann im Internet nach einem Dekompiler von smali nach Java zu suchen. Ich habe nur gesehen, dass es unmöglich ist, diesen Prozess eindeutig durchzufĂŒhren. Mit gerunzelter Stirn ging er zu Github und fuhr ein paar SchlĂŒsselbegriffe in die Suchzeile. Der erste kam smali2java .

 git clone gradle build java -jar smali2java.jar .. 

Fehler Ich sehe eine riesige Stapelverfolgung und Fehler auf mehreren Seiten des Terminals. Nachdem ich ein wenig ĂŒber die Essenz des Inhalts gelesen habe (und Emotionen von der GrĂ¶ĂŸe der Stapelspur abgehalten habe), stelle ich fest, dass dieses Tool auf der Grundlage einer bestimmten beschriebenen Grammatik funktioniert und der Bytecode, den sie getroffen hat, eindeutig nicht mit dieser ĂŒbereinstimmt. Ich öffne den kleinen Bytecode und sehe darin Anmerkungen, Synthesemethoden und andere seltsame Konstruktionen. In Java-Bytecode gab es so etwas nicht! Wie lange Löschen!

Weitere Details
Wie sich herausstellte, ist der virtuellen Dalvik-Maschine (sowie der JVM) die Existenz von Konzepten wie inneren / Ă€ußeren Klassen (gelesene verschachtelte Klassen) nicht bekannt, und der Compiler generiert die sogenannten "synthetischen" Methoden, um den Zugriff von der verschachtelten Klasse auf zu ermöglichen externe Felder zum Beispiel.

Als Beispiel:


Wenn die Ă€ußere Klasse (OuterClass) ein Feld hat

 public class OuterClass { List a; ... } 

Damit die private Klasse auf das Feld der externen Klasse zugreifen kann, generiert der Compiler implizit die folgende Methode:

 static synthetic java.util.List getList(OuterClass p1) { p1 = p1.a; return p1; } 

Aufgrund einer solchen „Motorraum“ -KĂŒche wird auch die Arbeit einiger anderer Mechanismen erreicht, die die Sprache bietet.

Sie können diese Frage von hier aus genauer untersuchen.

Hilft nicht. Er schwört sogar auf einen scheinbar nicht verdĂ€chtigen Bytecode. Ich öffne den Quellcode des Dekompilers, lese und sehe etwas sehr Seltsames: Selbst hinduistische Programmierer (bei allem Respekt) hĂ€tten dies nicht geschrieben. Ein Gedanke schleicht sich ein: nicht wirklich der generierte Code. Ich lehne die Idee fĂŒr ungefĂ€hr 30 Minuten ab und versuche zu verstehen, was der Fehler ist. KOMPLIZIERT. Ich öffne Github wieder - und wirklich einen durch Grammatik generierten Parser. Und hier ist der Generator selbst. Alles weglegen und versuchen, sich von der anderen Seite zu nĂ€hern.

Es ist erwĂ€hnenswert, dass ich wenig spĂ€ter immer noch versuchte, die Grammatik und stellenweise sogar den Bytecode selbst zu Ă€ndern, damit der Dekompiler ihn noch verarbeiten konnte. Aber selbst als der Bytecode in Bezug auf die Dekompiler-Grammatik gĂŒltig wurde, hat das Programm einfach nichts an mich zurĂŒckgegeben. Open Source ...

Ich blĂ€ttere durch den Bytecode und stoße auf mir unbekannte Konstanten. Googeln, ich treffe das gleiche im Buch ĂŒber Reverse-Android-Anwendungen. Ich erinnere mich, dass dies nur die vom Compiler-PrĂ€prozessor zugewiesene ID ist, die den Ressourcen der Android-Anwendung zugewiesen ist (die Zeitkonstante fĂŒr das Schreiben von Code ist R. *). In der nĂ€chsten halben Stunde werde ich kurz untersuchen, welche Register fĂŒr was verantwortlich sind, in welcher Reihenfolge die Argumente ĂŒbergeben werden, und mich im Allgemeinen mit der Syntax befassen.

Wie sieht es aus?


Ich habe das Layout des Hauptanwendungsfensters gefunden und daraus bereits verstanden, was in der Anwendung vor sich geht: Auf dem Hauptbildschirm (AktivitĂ€t) befindet sich eine RecyclerView (bedingt eine Ansicht, die UI-Objekte wiederverwenden kann, die derzeit nicht fĂŒr die Speichernutzung angezeigt werden) mit Eingabefeldern SchlĂŒssel / Wert-Paare, einige SchaltflĂ€chen, die fĂŒr das HinzufĂŒgen eines neuen SchlĂŒssel / Wert-Paares zu einem bestimmten abstrakten Container verantwortlich sind, und eine SchaltflĂ€che, die eine Signatur (Signatur) fĂŒr diesen Container generiert.

Wenn ich mir die Anmerkungen ansehe und eine bestimmte Menge Code beobachte, die der generierten verdĂ€chtig Ă€hnlich ist, beginne ich zu googeln. Das Projekt verwendet die ButterKnife-Bibliothek, mit der mithilfe von Anmerkungen () -> bind () UI-Elemente automatisch aufgeblasen werden können. Wenn die Klasse Anmerkungen enthĂ€lt, erstellt der ButterKnife-Anmerkungsprozessor implizit eine weitere Binder-Klasse der Form <original_class> __ViewBinding , die die gesamte Drecksarbeit unter der Haube erledigt. Eigentlich habe ich all diese Informationen aus nur einer MainActivity-Datei erhalten, nachdem ich die Ähnlichkeit der Java-Quelle manuell daraus wiederhergestellt habe. Nach einer halben Stunde wurde mir klar, dass die Anmerkungen dieser Bibliothek auch einen RĂŒckruf fĂŒr SchaltflĂ€chenaktionen festlegen können, und ich fand die SchlĂŒsselfunktionen, die tatsĂ€chlich fĂŒr das HinzufĂŒgen eines SchlĂŒssel / Wert-Paares zum Container und das Generieren einer Signatur verantwortlich waren.

NatĂŒrlich musste ich mich wĂ€hrend des Studiums mit den „Innereien“ verschiedener Bibliotheken und Plugins befassen, da selbst schöne Landos mit Cookies nicht alle AnwendungsfĂ€lle und Details abdecken, was meiner Meinung nach fĂŒr jeden „Umkehrer“ eine gĂ€ngige Praxis ist.

Faulheit ist ein Freund eines Programmierers


Nachdem ich mehr Zeit mit der zweiten Quelle verbracht hatte, war ich völlig mĂŒde und stellte fest, dass es nicht möglich war, Brei zu kochen. Ich klettere wieder auf Github und diesmal schaue ich genauer hin. Ich finde das Smali2PsuedoJava- Projekt - einen Dekompiler in „Pseudo-Java-Code“. Selbst wenn dieses Dienstprogramm zumindest etwas zu einem menschlichen Aussehen fĂŒhren kann, dann ist der Autor fĂŒr mich ein Becher seines Lieblingsbiers (na ja, oder setzen Sie Github zunĂ€chst ein Sternchen).

Und es funktioniert wirklich! Wirkung auf das Gesicht:



Treffen Sie Cipher.so


Wenig spĂ€ter, als ich den Java-Pseudocode des Projekts studiere und ihn unglĂ€ubig mit dem kleinen Bytecode vergleiche, finde ich eine seltsame Bibliothek im Code - Cipher.so. Beim Googeln stelle ich fest, dass es sich um die VerschlĂŒsselung einer Reihe von Kompilierungszeitwerten im APK-Archiv handelt. Dies ist normalerweise erforderlich, wenn die Anwendung Konstanten des Formulars verwendet: IP-Adressen, Anmeldeinformationen fĂŒr eine externe Datenbank, Token fĂŒr die Autorisierung usw. - Was kann mit Hilfe des Reverse Engineering der Anwendung erhalten werden. Es stimmt, der Autor schreibt klar, dass dieses Projekt aufgegeben wird, sagen sie, gehen weg. Das wird interessant.

Diese Bibliothek bietet Zugriff auf Werte ĂŒber die Java-Bibliothek, wobei die spezifische Methode der fĂŒr uns interessante SchlĂŒssel ist. Es weckt nur mein Interesse und ich beginne tiefer zu klettern.

Kurz gesagt, was macht Cipher.so und wie funktioniert es:

  • In der Gradle-Datei unseres Projekts werden die SchlĂŒssel und die entsprechenden Werte registriert
  • Alle SchlĂŒsselwerte werden automatisch in eine separate dynamische Bibliothek (.so) gepackt, die beim Kompilieren generiert wird. Ja - ja, wird generiert.
  • Diese SchlĂŒssel können dann von Java-Methoden bezogen werden, die von Cipher.so generiert wurden
  • Nach dem Erstellen der APK werden die SchlĂŒsselnamen von MD5 gehasht (fĂŒr mehr Sicherheit natĂŒrlich).

Nachdem ich die benötigte dynamische Bibliothek im Archivordner gefunden habe, wĂ€hle ich sie aus. Als erfahrene Umkehrung (nein) versuche ich zunĂ€chst, mit einer einfachen zu beginnen - ich beschließe, den Abschnitt mit Konstanten und nach interessanten Linien in einem ELF-Ă€hnlichen Binar zu betrachten. Leider fehlen Benutzer des Mac, die sich sofort lesen, und vor dem Beginn sagen wir das GeschĂ€tzte:

 brew install binuitls 

Und vergessen Sie nicht, den Pfad zu / usr / local in PATH zu schreiben, denn Brew schĂŒtzt Sie auf Gentleman-Weise vor allem ...

 greadelf -p .rodata lib/arm64-v8a/libcipher-lib.so | head -n 15 

Wir beschrĂ€nken die Ausgabe auf die ersten 15 Zeilen, da dies sonst fĂŒr einen unvorbereiteten Ingenieur zu einem Schock fĂŒhren kann.



In den unteren Adressen bemerken wir verdĂ€chtige Zeilen. Wie ich beim Studium der Quellen von Cipher.so herausgefunden habe, werden die SchlĂŒssel und Werte in die ĂŒbliche std :: map eingefĂŒgt : Dies gibt nur wenige Informationen, aber wir wissen, dass es im Binar neben verschlĂŒsselten Passwörtern auch verschleierte SchlĂŒssel gibt.

Wie ist die VerschlĂŒsselung von Werten? Beim Studium der Quelle stellte ich fest, dass die VerschlĂŒsselung mit AES erfolgt - dem standardmĂ€ĂŸigen symmetrischen VerschlĂŒsselungssystem. Wenn es also verschlĂŒsselte Werte gibt, sollte sich der SchlĂŒssel in der NĂ€he befinden ... Nachdem ich ihn eine Weile studiert hatte, stieß ich im selben Projekt auf ein Problem mit dem provokanten Titel „Unsichere SchlĂŒsselspeicherung: Geheimnisse sind sehr leicht abzurufen“ . Darin fand ich heraus, dass der SchlĂŒssel in klarer Form im Binar gespeichert ist, und fand den EntschlĂŒsselungsalgorithmus. Im Beispiel befand sich der SchlĂŒssel an der Nulladresse, und obwohl ich verstanden hatte, dass der Compiler ihn an einer anderen Stelle im Abschnitt .rodata der BinĂ€rdatei ablegen konnte, entschied ich, dass diese verdĂ€chtige Einheit an der Nulladresse der SchlĂŒssel ist.

Versuch Nr. 1: Ich entschlĂŒssele die Werte und glaube, dass der VerschlĂŒsselungsschlĂŒssel der gleiche ist. Der Fehler. OpenSSL weist darauf hin, dass etwas nicht stimmt. Nachdem ich die Quellen von Cipher.so ein wenig gelesen habe, verstehe ich, dass, wenn der Benutzer wĂ€hrend der Assemblierung keinen SchlĂŒssel angibt, der StandardschlĂŒssel verwendet wird - Cipher.so@DEFAULT .

Versuch 2: Erneuter Fehler. Hmm ... Wird es durch diese Konstante wirklich neu definiert? Es ist ganz einfach, einen Fehler zu machen: verwirrender in Gradle geschriebener Code mit "weg" -Formatierung. Ich ĂŒberprĂŒfe noch einmal. Alles scheint so zu sein.

Anstelle der SchlĂŒssel sind ihre MD5-Hashes, und dann versuche ich mein GlĂŒck zu versuchen und einen Dienst mit Regenbogentischen zu eröffnen. Voila - einer der SchlĂŒssel ist das Wort "Passwort". Es gibt keine Sekunde. Es gibt uns natĂŒrlich nicht viel. Diese beiden SchlĂŒssel befinden sich an den Adressen 240 bzw. 2a2. Im Prinzip ist es einfach, sie sofort zu erkennen - 32 Zeichen (MD5).

Ich habe alles noch einmal ĂŒberprĂŒft und versucht, die EntschlĂŒsselung mit allen anderen Zeilen (die sich in den unteren Adressen befinden) als SchlĂŒssel fĂŒr die EntschlĂŒsselung durchzufĂŒhren - alles ist vergebens.
Es gibt also einen anderen geheimen SchlĂŒssel, der Algorithmus der Aktionen scheint korrekt zu sein. Ich werfe diese Aufgabe beiseite und versuche mich nicht zu begraben.

Nachdem ich ein bisschen im Container-Signatur-Algorithmus gestöbert habe, sehe ich immer noch Aufrufe der Cipher.so-Bibliothek und des Codes, der auch die kryptografischen Funktionen der Java-Bibliothek verwendet.

Ein RÀtsel (das ich nie gelöst habe)


In der Funktion, die fĂŒr die VerschlĂŒsselung verantwortlich ist, wird ganz am Anfang nach SchlĂŒsseln im Container gesucht.

 public byte[] a(java/util/Map p1) { v0 = p1.size() v1 = 0x0; if (v0 != 0) goto :cond_0 p1 = new byte[v1]; return p1; :cond_0 v0 = "user"; v0 = p1.containsKey(v0) if (v0 == 0) goto :cond_1 p1 = new byte[v1]; return p1; ... 

Wörtlich: Wenn es einen "Benutzer" -SchlĂŒssel gibt, ist dieser Container nicht signiert (eine Nullsignatur wird zurĂŒckgegeben). Ein seltsames GefĂŒhl: Es scheint, als sei das Problem gelöst, aber es scheint irgendwie verdĂ€chtig einfach. Warum dann alles andere erfinden? In die Irre fĂŒhren? Warum habe ich diesen Code dann noch nicht fließend studiert? Hmm ...

Nein, nicht wahr. Ich habe die Antwort eines bestimmten Benutzers in einem blauen Messenger angegeben, dessen Kontakte mir bei der Auftragserteilung zur VerfĂŒgung gestellt wurden. Weiter graben. Vielleicht Ă€ndert sich der EingabeschlĂŒssel / Wertesatz irgendwie, wenn er dem Container hinzugefĂŒgt wird? Ich habe den Code sorgfĂ€ltig gelesen.

Bitte beachten Sie, dass der Dekompiler Anmerkungen aus dem Smali-Code entfernt hat. Was ist, wenn er etwas Wichtiges entfernt hat? Ich ĂŒberprĂŒfe die Hauptdateien - es scheint, nichts Wesentliches. Alles Wichtige ist vorhanden, aber die Bedeutung geht nicht verloren. Ich ĂŒberprĂŒfe RĂŒckruffunktionen, die fĂŒr das Schreiben eines SchlĂŒssel / Wert-Paares von einer bedingten TextBox in interne Container verantwortlich sind. Ich habe nichts Verbrecherisches gefunden.

Ich wurde in jeder Codezeile so skeptisch wie möglich - ich kann niemandem mehr vertrauen.

Einfache Lösung Nr. 2: Ich habe festgestellt, dass der Signaturvorgang damit beginnt, dass in der Signatur des Zertifikats, mit dem die Anwendung signiert wurde, ein Wert (Teilzeichenfolge in der Zeichenfolge) vorhanden ist.

 @OnClick //   protected void huvot324yo873yvo837yvo() { String signature = "no data"; boolean result = some_packages.isKeyInSignature(this); if result { Map map = new HashMap(); ... 

Die Bedeutung selbst liegt natĂŒrlich verschlĂŒsselt in diesem unglĂŒcklichen Binar. Und tatsĂ€chlich, wenn dieser Wert nicht in der Signatur enthalten ist, signiert der Algorithmus nichts, sondern gibt einfach die Zeichenfolge "keine Daten" als Signatur zurĂŒck ... Wieder werden wir fĂŒr Cipher genommen ...

SchlĂŒsselentschlĂŒsselung Endkampf


Um das Ausmaß der Tragödie zu verstehen, war ich folgendermaßen verwirrt:

Ich machte einen Hex-Dump von diesem Abschnitt und schaute in die ersten beiden Zeilen, deren Verdacht von Anfang an nicht nachließ.



Wenn Sie darauf achten, ist das Zeichen, das die Zeilen hier trennt, '0x00'. Es wird auch hĂ€ufig von der Standard-C-Bibliothek in Zeichenfolgenfunktionen verwendet. Aus diesem Grund ist es nicht weniger interessant, welche Art von Leerzeichen sich in der Mitte der ersten Zeile befindet. Als nĂ€chstes beginnen verrĂŒckte Versuche, bei denen der SchlĂŒssel ist:

  • ganze erste Reihe
  • erste Zeile vor dem Leerzeichen
  • erste Zeile vom Raum bis zum Ende
  • ...

Der Grad der Paranoia kann bereits geschÀtzt werden. Wenn Sie nicht verstehen, wie schwierig und gerissen die Aufgabe sein sollte, fahren Sie los. Und doch nicht das. Dann kam mir der Gedanke: "Funktioniert der Algorithmus aufgrund eines Problems auf meinem Computer korrekt?". Im Allgemeinen ist die Abfolge der Aktionen dort logisch und hat keine Fragen aufgeworfen. Die Frage lautet jedoch: Tun die Befehle auf meinem Computer, was sie von ihnen verlangen? Also, was denkst du?

Nachdem alle Schritte manuell ĂŒberprĂŒft wurden, stellte sich heraus, dass

 echo "some_base64_input" | openssl base64 -d 

Bei einigen Eingabeargumenten wird plötzlich eine leere Zeichenfolge zurĂŒckgegeben. Hmm.

Durch Ersetzen durch den ersten base64-Decoder auf dem Computer und Durchsuchen der Hauptkandidaten wurde sofort ein geeigneter SchlĂŒssel gefunden und die SchlĂŒssel wurden entsprechend entschlĂŒsselt.

Abrufen von Signaturen aus einem Zertifikat


 class a { public static boolean isKeyInSignature(android.content.Context p1) { v0 = 0x0; try TRY_0{ v1 = p1.getPackageManager() p0 = p1.getPackageName() v2 = 0x40; // GET_SIGNATURES PackageInfo p0 = v1.getPackageInfo(p0, v2) android.content.pm.Signature[] p0 = p0.signatures; // Order are not guaranteed v1 = p0.length; v2 = 0x0; :goto_0 if (v2 >= v1) goto :cond_1 v3 = p0[v2]; String v3 = v3.toCharsString() String v4 = net.idik.lib.cipher.so.CipherClient.a() v3 = v3.contains(v4) }TRY_0 catch TRY_0 (android/content/pm/PackageManager$NameNotFoundException) goto :catch_0; if (v3 == 0) goto :cond_0 p1 = 0x1; return p1; :cond_0 v2 = v2 + 0x1; goto :goto_0 :catch_0 p0 = Thrown Exception p1.printStackTrace() :cond_1 return v0; } 

So sieht der generierte Pseudocode nach meinen kleinen Änderungen aus. Verwirrt ein paar Dinge:

  • schlechte Kenntnisse der Kryptographie und der "KĂŒche" der GerĂ€tezertifikate
  • Laut Dokumentation garantiert diese Methode nicht die Reihenfolge der Zertifikate in der zurĂŒckgegebenen Sammlung, und dementsprechend wĂ€re es nicht möglich, in derselben Reihenfolge zu durchlaufen - was wĂ€re, wenn die Anwendung mit mehr als einem Zertifikat signiert wĂ€re?
  • Mangel an Wissen darĂŒber, wie das Zertifikat aus der APK extrahiert werden kann, da nicht klar ist, was Android Runtime in diesem Fall tut

Ich musste mich mit all diesen Themen befassen und das Ergebnis war wie folgt:

  • Das Zertifikat selbst befindet sich im Verzeichnis original / META-INF / CERT.RSA

    In diesem Verzeichnis gibt es nur eine Datei mit dieser Erweiterung. Dies bedeutet, dass die Anwendung mit nur einem Zertifikat signiert ist
  • Auf der Website ĂŒber Research Engineering von Android-Anwendungen wurde eine Liste gefunden, die die Signatur extrahieren kann, die wir wie Android benötigen. Zumindest laut Autor.

Durch AusfĂŒhren dieses Codes kann ich die Signatur herausfinden, und in Wirklichkeit ist der SchlĂŒssel, den wir benötigen, eine Teilzeichenfolge. Mach weiter. Einfache Lösung Nr. 2 wird weggefegt.

In der Tat befindet sich der SchlĂŒssel im Zertifikat. Es bleibt nur zu verstehen, was als nĂ€chstes kommt, denn wenn wir den "Benutzer" -SchlĂŒssel haben, erhalten wir alle auch eine Nullsignatur, und wie wir oben erfahren haben, ist dies die falsche Antwort.

Schreiben Sie die Dokumentation sorgfÀltig!


Weitere Studien zur Tatsache, dass die aus den Textfeldern eingegebenen Daten geĂ€ndert werden, werden mangels Beweisen verworfen. Paranoia rollt mit neuer Kraft: Vielleicht ist der Code, der die Signatur aus dem Zertifikat gezogen hat, falsch oder handelt es sich um eine Code-Implementierung fĂŒr alte Android-Versionen? Ich öffne die Dokumentation erneut und sehe Folgendes: ( https://developer.android.com/reference/android/content/pm/Signature.html#toChars () ):



Hinweis: Die Funktion codiert die Signatur als ASCII-Text. Die Ausgabe, die ich oben erhalten habe, war eine hexadezimale Darstellung der Daten. Diese API kam mir seltsam vor, aber wenn Sie der Dokumentation glauben, stellt sich heraus, dass ich erneut blockiert war und der verschlĂŒsselte SchlĂŒssel keine Teilzeichenfolge der Signatur ist. Nachdem ich eine Weile nachdenklich auf dem Code gesessen hatte, konnte ich es nicht ertragen und öffnete den Quellcode fĂŒr diese Klasse. https://android.googlesource.com/platform/frameworks/base/+/e639da7/core/java/android/content/pm/Signature.java

Die Antwort ließ nicht lange auf sich warten. Und tatsĂ€chlich im Code selbst - ein ÖlgemĂ€lde: Das Ausgabeformat ist eine gewöhnliche Hex-Zeichenfolge. Und jetzt denke: Entweder verstehe ich etwas nicht oder die Dokumentation ist "leicht" falsch geschrieben. Nachdem ich in keiner Weise gescholten hatte, machte ich mich wieder an die Arbeit.

Zusammenfassung


Die folgenden n Stunden sind vergangen:

  • ÜberprĂŒfen der Richtigkeit der Arbeit im Code mit RecyclerView und Ermitteln des Verhaltens durch den Quellcode seitdem , StackOverflow
  • , , Java. , - («user») . .

, ( ).

Nein. , . , , , , . . — , , , .

, , . . , . , - , , « », , .

- c – arturbrsg .

Stay tuned.

Source: https://habr.com/ru/post/de450394/


All Articles