Suche nach Schwachstellen im UC Browser

Suche nach Schwachstellen im UC Browser

Einführung

Ende März haben wir berichtet, dass sie eine versteckte Möglichkeit entdeckt haben, nicht überprüften Code in UC Browser zu laden und auszuführen. Heute werden wir uns im Detail ansehen, wie dieser Download erfolgt und wie Hacker ihn für ihre eigenen Zwecke nutzen können.

Vor einiger Zeit wurde UC Browser sehr aggressiv beworben und verbreitet: Er wurde mithilfe von Malware auf den Geräten der Benutzer installiert und von verschiedenen Websites unter dem Deckmantel von Videodateien verbreitet (d. h. Benutzer dachten, sie würden beispielsweise ein Pornovideo herunterladen, aber stattdessen eine APK mit diesem Browser erhalten hat), verwendete gruselige Banner mit Meldungen, dass der Browser veraltet, anfällig und ähnliches sei. In der offiziellen UC-Browser-Gruppe auf VK gibt es Thema, in dem sich Nutzer über unlautere Werbung beschweren können, gibt es dort viele Beispiele. Im Jahr 2016 gab es sogar Videowerbung auf Russisch (ja, Werbung für einen werbeblockierenden Browser).

Zum Zeitpunkt des Verfassens dieses Artikels hat UC Browser über 500 Installationen bei Google Play. Das ist beeindruckend – nur Google Chrome bietet mehr. In den Rezensionen finden Sie zahlreiche Beschwerden über Werbung und Weiterleitungen zu einigen Anwendungen bei Google Play. Dies war der Grund für unsere Recherche: Wir beschlossen herauszufinden, ob UC Browser etwas Schlechtes macht. Und es stellte sich heraus, dass er es tut!

Im Anwendungscode wurde die Möglichkeit entdeckt, ausführbaren Code herunterzuladen und auszuführen. Dies verstößt gegen die Regeln für die Veröffentlichung von Bewerbungen bei Google Play. UC Browser lädt nicht nur ausführbaren Code herunter, sondern auch auf unsichere Weise, was zum Starten eines MitM-Angriffs genutzt werden kann. Mal sehen, ob wir einen solchen Angriff durchführen können.

Alles, was unten geschrieben steht, ist für die Version von UC Browser relevant, die zum Zeitpunkt der Studie bei Google Play verfügbar war:

package: com.UCMobile.intl
versionName: 12.10.8.1172
versionCode: 10598
sha1 APK-файла: f5edb2243413c777172f6362876041eb0c3a928c

Angriffsvektor

Im UC Browser-Manifest finden Sie einen Dienst mit einem selbsterklärenden Namen com.uc.deployment.UpgradeDeployService.

    <service android_exported="false" android_name="com.uc.deployment.UpgradeDeployService" android_process=":deploy" />

Wenn dieser Dienst startet, stellt der Browser eine POST-Anfrage an puds.ucweb.com/upgrade/index.xhtml, was einige Zeit nach dem Start im Verkehr zu sehen ist. Als Antwort erhält er möglicherweise den Befehl, ein Update oder ein neues Modul herunterzuladen. Während der Analyse hat der Server solche Befehle nicht gegeben, aber wir haben festgestellt, dass er beim Versuch, eine PDF-Datei im Browser zu öffnen, eine zweite Anfrage an die oben angegebene Adresse sendet und anschließend die native Bibliothek herunterlädt. Um den Angriff durchzuführen, haben wir uns entschieden, diese Funktion von UC Browser zu nutzen: die Möglichkeit, PDFs mit einer nativen Bibliothek zu öffnen, die nicht im APK enthalten ist und die bei Bedarf aus dem Internet heruntergeladen wird. Es ist erwähnenswert, dass UC Browser theoretisch dazu gezwungen werden kann, etwas ohne Benutzerinteraktion herunterzuladen – wenn Sie eine wohlgeformte Antwort auf eine Anfrage geben, die nach dem Start des Browsers ausgeführt wird. Dazu müssen wir jedoch das Protokoll der Interaktion mit dem Server genauer untersuchen. Daher haben wir beschlossen, dass es einfacher wäre, die abgefangene Antwort zu bearbeiten und die Bibliothek für die Arbeit mit PDF zu ersetzen.

Wenn ein Benutzer also ein PDF direkt im Browser öffnen möchte, sind im Datenverkehr folgende Anfragen zu sehen:

Suche nach Schwachstellen im UC Browser

Zuerst gibt es eine POST-Anfrage an puds.ucweb.com/upgrade/index.xhtmlDann
Ein Archiv mit einer Bibliothek zum Anzeigen von PDF- und Office-Formaten wird heruntergeladen. Es ist logisch anzunehmen, dass die erste Anfrage Informationen über das System übermittelt (zumindest die Architektur zur Bereitstellung der erforderlichen Bibliothek) und der Browser als Antwort darauf einige Informationen über die Bibliothek erhält, die heruntergeladen werden muss: die Adresse und möglicherweise , etwas anderes. Das Problem ist, dass diese Anfrage verschlüsselt ist.

Fragment anfordern

Antwortfragment

Suche nach Schwachstellen im UC Browser

Suche nach Schwachstellen im UC Browser

Die Bibliothek selbst ist im ZIP-Format verpackt und nicht verschlüsselt.

Suche nach Schwachstellen im UC Browser

Suchen Sie nach dem Verkehrsentschlüsselungscode

Versuchen wir, die Serverantwort zu entschlüsseln. Schauen wir uns den Klassencode an com.uc.deployment.UpgradeDeployService: von Methode onStartCommand gehe zu com.uc.deployment.bx, und von ihm zu com.uc.browser.core.dcfe:

    public final void e(l arg9) {
int v4_5;
String v3_1;
byte[] v3;
byte[] v1 = null;
if(arg9 == null) {
v3 = v1;
}
else {
v3_1 = arg9.iGX.ipR;
StringBuilder v4 = new StringBuilder("[");
v4.append(v3_1);
v4.append("]product:");
v4.append(arg9.iGX.ipR);
v4 = new StringBuilder("[");
v4.append(v3_1);
v4.append("]version:");
v4.append(arg9.iGX.iEn);
v4 = new StringBuilder("[");
v4.append(v3_1);
v4.append("]upgrade_type:");
v4.append(arg9.iGX.mMode);
v4 = new StringBuilder("[");
v4.append(v3_1);
v4.append("]force_flag:");
v4.append(arg9.iGX.iEo);
v4 = new StringBuilder("[");
v4.append(v3_1);
v4.append("]silent_mode:");
v4.append(arg9.iGX.iDQ);
v4 = new StringBuilder("[");
v4.append(v3_1);
v4.append("]silent_type:");
v4.append(arg9.iGX.iEr);
v4 = new StringBuilder("[");
v4.append(v3_1);
v4.append("]silent_state:");
v4.append(arg9.iGX.iEp);
v4 = new StringBuilder("[");
v4.append(v3_1);
v4.append("]silent_file:");
v4.append(arg9.iGX.iEq);
v4 = new StringBuilder("[");
v4.append(v3_1);
v4.append("]apk_md5:");
v4.append(arg9.iGX.iEl);
v4 = new StringBuilder("[");
v4.append(v3_1);
v4.append("]download_type:");
v4.append(arg9.mDownloadType);
v4 = new StringBuilder("[");
v4.append(v3_1);
v4.append("]download_group:");
v4.append(arg9.mDownloadGroup);
v4 = new StringBuilder("[");
v4.append(v3_1);
v4.append("]download_path:");
v4.append(arg9.iGH);
v4 = new StringBuilder("[");
v4.append(v3_1);
v4.append("]apollo_child_version:");
v4.append(arg9.iGX.iEx);
v4 = new StringBuilder("[");
v4.append(v3_1);
v4.append("]apollo_series:");
v4.append(arg9.iGX.iEw);
v4 = new StringBuilder("[");
v4.append(v3_1);
v4.append("]apollo_cpu_arch:");
v4.append(arg9.iGX.iEt);
v4 = new StringBuilder("[");
v4.append(v3_1);
v4.append("]apollo_cpu_vfp3:");
v4.append(arg9.iGX.iEv);
v4 = new StringBuilder("[");
v4.append(v3_1);
v4.append("]apollo_cpu_vfp:");
v4.append(arg9.iGX.iEu);
ArrayList v3_2 = arg9.iGX.iEz;
if(v3_2 != null && v3_2.size() != 0) {
Iterator v3_3 = v3_2.iterator();
while(v3_3.hasNext()) {
Object v4_1 = v3_3.next();
StringBuilder v5 = new StringBuilder("[");
v5.append(((au)v4_1).getName());
v5.append("]component_name:");
v5.append(((au)v4_1).getName());
v5 = new StringBuilder("[");
v5.append(((au)v4_1).getName());
v5.append("]component_ver_name:");
v5.append(((au)v4_1).aDA());
v5 = new StringBuilder("[");
v5.append(((au)v4_1).getName());
v5.append("]component_ver_code:");
v5.append(((au)v4_1).gBl);
v5 = new StringBuilder("[");
v5.append(((au)v4_1).getName());
v5.append("]component_req_type:");
v5.append(((au)v4_1).gBq);
}
}
j v3_4 = new j();
m.b(v3_4);
h v4_2 = new h();
m.b(v4_2);
ay v5_1 = new ay();
v3_4.hS("");
v3_4.setImsi("");
v3_4.hV("");
v5_1.bPQ = v3_4;
v5_1.bPP = v4_2;
v5_1.yr(arg9.iGX.ipR);
v5_1.gBF = arg9.iGX.mMode;
v5_1.gBI = arg9.iGX.iEz;
v3_2 = v5_1.gAr;
c.aBh();
v3_2.add(g.fs("os_ver", c.getRomInfo()));
v3_2.add(g.fs("processor_arch", com.uc.b.a.a.c.getCpuArch()));
v3_2.add(g.fs("cpu_arch", com.uc.b.a.a.c.Pb()));
String v4_3 = com.uc.b.a.a.c.Pd();
v3_2.add(g.fs("cpu_vfp", v4_3));
v3_2.add(g.fs("net_type", String.valueOf(com.uc.base.system.a.Jo())));
v3_2.add(g.fs("fromhost", arg9.iGX.iEm));
v3_2.add(g.fs("plugin_ver", arg9.iGX.iEn));
v3_2.add(g.fs("target_lang", arg9.iGX.iEs));
v3_2.add(g.fs("vitamio_cpu_arch", arg9.iGX.iEt));
v3_2.add(g.fs("vitamio_vfp", arg9.iGX.iEu));
v3_2.add(g.fs("vitamio_vfp3", arg9.iGX.iEv));
v3_2.add(g.fs("plugin_child_ver", arg9.iGX.iEx));
v3_2.add(g.fs("ver_series", arg9.iGX.iEw));
v3_2.add(g.fs("child_ver", r.aVw()));
v3_2.add(g.fs("cur_ver_md5", arg9.iGX.iEl));
v3_2.add(g.fs("cur_ver_signature", SystemHelper.getUCMSignature()));
v3_2.add(g.fs("upgrade_log", i.bjt()));
v3_2.add(g.fs("silent_install", String.valueOf(arg9.iGX.iDQ)));
v3_2.add(g.fs("silent_state", String.valueOf(arg9.iGX.iEp)));
v3_2.add(g.fs("silent_file", arg9.iGX.iEq));
v3_2.add(g.fs("silent_type", String.valueOf(arg9.iGX.iEr)));
v3_2.add(g.fs("cpu_archit", com.uc.b.a.a.c.Pc()));
v3_2.add(g.fs("cpu_set", SystemHelper.getCpuInstruction()));
boolean v4_4 = v4_3 == null || !v4_3.contains("neon") ? false : true;
v3_2.add(g.fs("neon", String.valueOf(v4_4)));
v3_2.add(g.fs("cpu_cores", String.valueOf(com.uc.b.a.a.c.Jl())));
v3_2.add(g.fs("ram_1", String.valueOf(com.uc.b.a.a.h.Po())));
v3_2.add(g.fs("totalram", String.valueOf(com.uc.b.a.a.h.OL())));
c.aBh();
v3_2.add(g.fs("rom_1", c.getRomInfo()));
v4_5 = e.getScreenWidth();
int v6 = e.getScreenHeight();
StringBuilder v7 = new StringBuilder();
v7.append(v4_5);
v7.append("*");
v7.append(v6);
v3_2.add(g.fs("ss", v7.toString()));
v3_2.add(g.fs("api_level", String.valueOf(Build$VERSION.SDK_INT)));
v3_2.add(g.fs("uc_apk_list", SystemHelper.getUCMobileApks()));
Iterator v4_6 = arg9.iGX.iEA.entrySet().iterator();
while(v4_6.hasNext()) {
Object v6_1 = v4_6.next();
v3_2.add(g.fs(((Map$Entry)v6_1).getKey(), ((Map$Entry)v6_1).getValue()));
}
v3 = v5_1.toByteArray();
}
if(v3 == null) {
this.iGY.iGI.a(arg9, "up_encode", "yes", "fail");
return;
}
v4_5 = this.iGY.iGw ? 0x1F : 0;
if(v3 == null) {
}
else {
v3 = g.i(v4_5, v3);
if(v3 == null) {
}
else {
v1 = new byte[v3.length + 16];
byte[] v6_2 = new byte[16];
Arrays.fill(v6_2, 0);
v6_2[0] = 0x5F;
v6_2[1] = 0;
v6_2[2] = ((byte)v4_5);
v6_2[3] = -50;
System.arraycopy(v6_2, 0, v1, 0, 16);
System.arraycopy(v3, 0, v1, 16, v3.length);
}
}
if(v1 == null) {
this.iGY.iGI.a(arg9, "up_encrypt", "yes", "fail");
return;
}
if(TextUtils.isEmpty(this.iGY.mUpgradeUrl)) {
this.iGY.iGI.a(arg9, "up_url", "yes", "fail");
return;
}
StringBuilder v0 = new StringBuilder("[");
v0.append(arg9.iGX.ipR);
v0.append("]url:");
v0.append(this.iGY.mUpgradeUrl);
com.uc.browser.core.d.c.i v0_1 = this.iGY.iGI;
v3_1 = this.iGY.mUpgradeUrl;
com.uc.base.net.e v0_2 = new com.uc.base.net.e(new com.uc.browser.core.d.c.i$a(v0_1, arg9));
v3_1 = v3_1.contains("?") ? v3_1 + "&dataver=pb" : v3_1 + "?dataver=pb";
n v3_5 = v0_2.uc(v3_1);
m.b(v3_5, false);
v3_5.setMethod("POST");
v3_5.setBodyProvider(v1);
v0_2.b(v3_5);
this.iGY.iGI.a(arg9, "up_null", "yes", "success");
this.iGY.iGI.b(arg9);
}

Wir sehen hier die Bildung einer POST-Anfrage. Wir achten auf die Erstellung eines Arrays von 16 Bytes und dessen Füllung: 0x5F, 0, 0x1F, -50 (=0xCE). Stimmt mit dem überein, was wir in der obigen Anfrage gesehen haben.

In derselben Klasse können Sie eine verschachtelte Klasse sehen, die über eine weitere interessante Methode verfügt:

        public final void a(l arg10, byte[] arg11) {
f v0 = this.iGQ;
StringBuilder v1 = new StringBuilder("[");
v1.append(arg10.iGX.ipR);
v1.append("]:UpgradeSuccess");
byte[] v1_1 = null;
if(arg11 == null) {
}
else if(arg11.length < 16) {
}
else {
if(arg11[0] != 0x60 && arg11[3] != 0xFFFFFFD0) {
goto label_57;
}
int v3 = 1;
int v5 = arg11[1] == 1 ? 1 : 0;
if(arg11[2] != 1 && arg11[2] != 11) {
if(arg11[2] == 0x1F) {
}
else {
v3 = 0;
}
}
byte[] v7 = new byte[arg11.length - 16];
System.arraycopy(arg11, 16, v7, 0, v7.length);
if(v3 != 0) {
v7 = g.j(arg11[2], v7);
}
if(v7 == null) {
goto label_57;
}
if(v5 != 0) {
v1_1 = g.P(v7);
goto label_57;
}
v1_1 = v7;
}
label_57:
if(v1_1 == null) {
v0.iGY.iGI.a(arg10, "up_decrypt", "yes", "fail");
return;
}
q v11 = g.b(arg10, v1_1);
if(v11 == null) {
v0.iGY.iGI.a(arg10, "up_decode", "yes", "fail");
return;
}
if(v0.iGY.iGt) {
v0.d(arg10);
}
if(v0.iGY.iGo != null) {
v0.iGY.iGo.a(0, ((o)v11));
}
if(v0.iGY.iGs) {
v0.iGY.a(((o)v11));
v0.iGY.iGI.a(v11, "up_silent", "yes", "success");
v0.iGY.iGI.a(v11);
return;
}
v0.iGY.iGI.a(v11, "up_silent", "no", "success");
}
}

Die Methode verwendet ein Byte-Array als Eingabe und prüft, ob das Null-Byte 0x60 oder das dritte Byte 0xD0 und das zweite Byte 1, 11 oder 0x1F ist. Wir schauen uns die Antwort des Servers an: Das Nullbyte ist 0x60, das zweite ist 0x1F, das dritte ist 0x60. Klingt nach dem, was wir brauchen. Den Zeilen nach zu urteilen (zum Beispiel „up_decrypt“) sollte hier eine Methode aufgerufen werden, die die Antwort des Servers entschlüsselt.
Kommen wir zur Methode gj. Beachten Sie, dass das erste Argument das Byte bei Offset 2 (in unserem Fall also 0x1F) ist und das zweite die Serverantwort ohne ist
ersten 16 Bytes.

     public static byte[] j(int arg1, byte[] arg2) {
if(arg1 == 1) {
arg2 = c.c(arg2, c.adu);
}
else if(arg1 == 11) {
arg2 = m.aF(arg2);
}
else if(arg1 != 0x1F) {
}
else {
arg2 = EncryptHelper.decrypt(arg2);
}
return arg2;
}

Offensichtlich wählen wir hier einen Entschlüsselungsalgorithmus und dasselbe Byte aus, das sich in unserem befindet
Fall gleich 0x1F, bezeichnet eine von drei möglichen Optionen.

Wir analysieren weiterhin den Code. Nach ein paar Sprüngen befinden wir uns in einer Methode mit einem selbsterklärenden Namen decryptBytesByKey.

Hier werden zwei weitere Bytes von unserer Antwort getrennt und daraus ein String gewonnen. Es ist klar, dass auf diese Weise der Schlüssel zum Entschlüsseln der Nachricht ausgewählt wird.

    private static byte[] decryptBytesByKey(byte[] bytes) {
byte[] v0 = null;
if(bytes != null) {
try {
if(bytes.length < EncryptHelper.PREFIX_BYTES_SIZE) {
}
else if(bytes.length == EncryptHelper.PREFIX_BYTES_SIZE) {
return v0;
}
else {
byte[] prefix = new byte[EncryptHelper.PREFIX_BYTES_SIZE];  // 2 байта
System.arraycopy(bytes, 0, prefix, 0, prefix.length);
String keyId = c.ayR().d(ByteBuffer.wrap(prefix).getShort()); // Выбор ключа
if(keyId == null) {
return v0;
}
else {
a v2 = EncryptHelper.ayL();
if(v2 == null) {
return v0;
}
else {
byte[] enrypted = new byte[bytes.length - EncryptHelper.PREFIX_BYTES_SIZE];
System.arraycopy(bytes, EncryptHelper.PREFIX_BYTES_SIZE, enrypted, 0, enrypted.length);
return v2.l(keyId, enrypted);
}
}
}
}
catch(SecException v7_1) {
EncryptHelper.handleDecryptException(((Throwable)v7_1), v7_1.getErrorCode());
return v0;
}
catch(Throwable v7) {
EncryptHelper.handleDecryptException(v7, 2);
return v0;
}
}
return v0;
}

Mit Blick auf die Zukunft stellen wir fest, dass wir zu diesem Zeitpunkt noch keinen Schlüssel erhalten, sondern nur dessen „Identifikator“. Den Schlüssel zu bekommen ist etwas komplizierter.

Bei der nächsten Methode werden zwei weitere Parameter zu den vorhandenen hinzugefügt, sodass vier davon entstehen: die magische Zahl 16, die Schlüsselkennung, die verschlüsselten Daten und eine unverständliche Zeichenfolge (in unserem Fall leer).

    public final byte[] l(String keyId, byte[] encrypted) throws SecException {
return this.ayJ().staticBinarySafeDecryptNoB64(16, keyId, encrypted, "");
}

Nach einer Reihe von Übergängen gelangen wir zur Methode staticBinarySafeDecryptNoB64 Schnittstelle com.alibaba.wireless.security.open.staticdataencrypt.IStaticDataEncryptComponent. Im Hauptanwendungscode gibt es keine Klassen, die diese Schnittstelle implementieren. Es gibt eine solche Klasse in der Datei lib/armeabi-v7a/libsgmain.so, was eigentlich kein .so, sondern ein .jar ist. Die Methode, an der wir interessiert sind, wird wie folgt implementiert:

package com.alibaba.wireless.security.a.i;
// ...
public class a implements IStaticDataEncryptComponent {
private ISecurityGuardPlugin a;
// ...
private byte[] a(int mode, int magicInt, int xzInt, String keyId, byte[] encrypted, String magicString) {
return this.a.getRouter().doCommand(10601, new Object[]{Integer.valueOf(mode), Integer.valueOf(magicInt), Integer.valueOf(xzInt), keyId, encrypted, magicString});
}
// ...
private byte[] b(int magicInt, String keyId, byte[] encrypted, String magicString) {
return this.a(2, magicInt, 0, keyId, encrypted, magicString);
}
// ...
public byte[] staticBinarySafeDecryptNoB64(int magicInt, String keyId, byte[] encrypted, String magicString) throws SecException {
if(keyId != null && keyId.length() > 0 && magicInt >= 0 && magicInt < 19 && encrypted != null && encrypted.length > 0) {
return this.b(magicInt, keyId, encrypted, magicString);
}
throw new SecException("", 301);
}
//...
}

Hier wird unsere Parameterliste um zwei weitere ganze Zahlen ergänzt: 2 und 0. Gemessen an
Alles, 2 bedeutet Entschlüsselung, wie in der Methode doFinal Systemklasse javax.crypto.Cipher. Und das alles wird an einen bestimmten Router mit der Nummer 10601 übertragen – das ist offenbar die Befehlsnummer.

Nach der nächsten Übergangskette finden wir eine Klasse, die die Schnittstelle implementiert IRouterComponent und Methode doCommand:

package com.alibaba.wireless.security.mainplugin;
import com.alibaba.wireless.security.framework.IRouterComponent;
import com.taobao.wireless.security.adapter.JNICLibrary;
public class a implements IRouterComponent {
public a() {
super();
}
public Object doCommand(int arg2, Object[] arg3) {
return JNICLibrary.doCommandNative(arg2, arg3);
}
}

Und auch Klasse JNICLibrary, in dem die native Methode deklariert ist doCommandNative:

package com.taobao.wireless.security.adapter;
public class JNICLibrary {
public static native Object doCommandNative(int arg0, Object[] arg1);
}

Das bedeutet, dass wir eine Methode im nativen Code finden müssen doCommandNative. Und hier beginnt der Spaß.

Verschleierung von Maschinencode

Im Ordner libsgmain.so (bei dem es sich eigentlich um eine .jar-Datei handelt und in der wir oben die Implementierung einiger verschlüsselungsbezogener Schnittstellen gefunden haben) gibt es eine native Bibliothek: libsgmainso-6.4.36.so. Wir öffnen es in IDA und erhalten eine Reihe von Dialogfeldern mit Fehlern. Das Problem besteht darin, dass die Abschnittskopftabelle ungültig ist. Dies geschieht mit Absicht, um die Analyse zu erschweren.

Suche nach Schwachstellen im UC Browser

Dies ist jedoch nicht erforderlich: Um eine ELF-Datei korrekt zu laden und zu analysieren, reicht eine Programm-Header-Tabelle aus. Daher löschen wir einfach die Abschnittstabelle und löschen die entsprechenden Felder in der Kopfzeile.

Suche nach Schwachstellen im UC Browser

Öffnen Sie die Datei erneut in IDA.

Es gibt zwei Möglichkeiten, der Java Virtual Machine mitzuteilen, wo genau in der nativen Bibliothek sich die Implementierung einer im Java-Code als nativ deklarierten Methode befindet. Die erste besteht darin, ihm einen Artnamen zu geben Java_package_name_ClassName_MethodName.

Die zweite besteht darin, es beim Laden der Bibliothek zu registrieren (in der Funktion JNI_OnLoad)
mit einem Funktionsaufruf RegistrierenNatives.

Wenn wir in unserem Fall die erste Methode verwenden, sollte der Name so lauten: Java_com_taobao_wireless_security_adapter_JNICLibrary_doCommandNative.

Unter den exportierten Funktionen gibt es keine solche Funktion, was bedeutet, dass Sie nach einem Aufruf suchen müssen RegistrierenNatives.
Kommen wir zur Funktion JNI_OnLoad und wir sehen dieses Bild:

Suche nach Schwachstellen im UC Browser

Was ist denn hier los? Auf den ersten Blick sind Anfang und Ende der Funktion typisch für die ARM-Architektur. Der erste Befehl auf dem Stapel speichert den Inhalt der Register, die die Funktion bei ihrer Operation verwenden wird (in diesem Fall R0, R1 und R2), sowie den Inhalt des LR-Registers, das die Rücksprungadresse der Funktion enthält . Der letzte Befehl stellt die gespeicherten Register wieder her und die Rücksprungadresse wird sofort im PC-Register abgelegt – und kehrt somit von der Funktion zurück. Wenn Sie jedoch genau hinschauen, werden Sie feststellen, dass die vorletzte Anweisung die auf dem Stapel gespeicherte Rücksprungadresse ändert. Berechnen wir mal, wie es danach sein wird
Codeausführung. Eine bestimmte Adresse 1xB0 wird in R130 geladen, 5 davon subtrahiert, dann nach R0 übertragen und 0x10 dazu addiert. Es stellt sich 0xB13B heraus. Daher geht IDA davon aus, dass der letzte Befehl eine normale Funktionsrückgabe ist, tatsächlich geht er jedoch an die berechnete Adresse 0xB13B.

An dieser Stelle sei daran erinnert, dass ARM-Prozessoren über zwei Modi und zwei Befehlssätze verfügen: ARM und Thumb. Das niedrigstwertige Bit der Adresse teilt dem Prozessor mit, welcher Befehlssatz verwendet wird. Das heißt, die Adresse lautet tatsächlich 0xB13A und eins im niedrigstwertigen Bit gibt den Thumb-Modus an.

Am Anfang jeder Funktion in dieser Bibliothek wurde ein ähnlicher „Adapter“ hinzugefügt
Müllcode. Wir werden nicht näher darauf eingehen – wir erinnern uns einfach
dass der eigentliche Beginn fast aller Funktionen etwas weiter entfernt liegt.

Da der Code nicht explizit zu 0xB13A springt, hat IDA selbst nicht erkannt, dass sich der Code an dieser Stelle befindet. Aus dem gleichen Grund erkennt es den Großteil des Codes in der Bibliothek nicht als Code, was die Analyse etwas erschwert. Wir teilen IDA mit, dass dies der Code ist, und Folgendes passiert:

Suche nach Schwachstellen im UC Browser

Die Tabelle beginnt eindeutig bei 0xB144. Was ist in sub_494C?

Suche nach Schwachstellen im UC Browser

Beim Aufruf dieser Funktion im LR-Register erhalten wir die Adresse der zuvor genannten Tabelle (0xB144). In R0 - Index in dieser Tabelle. Das heißt, der Wert wird aus der Tabelle genommen, zu LR addiert und das Ergebnis ist
die Adresse, zu der man gehen soll. Versuchen wir es zu berechnen: 0xB144 + [0xB144 + 8* 4] = 0xB144 + 0x120 = 0xB264. Wir gehen zur empfangenen Adresse und sehen buchstäblich ein paar nützliche Anweisungen und gehen wieder zu 0xB140:

Suche nach Schwachstellen im UC Browser

Nun erfolgt ein Übergang am Offset mit Index 0x20 aus der Tabelle.

Gemessen an der Größe der Tabelle wird es im Code viele solcher Übergänge geben. Es stellt sich die Frage, ob es möglich ist, dies irgendwie automatisierter zu bewältigen, ohne die Adressen manuell zu berechnen. Und Skripte und die Möglichkeit, Code in IDA zu patchen, kommen uns zu Hilfe:

def put_unconditional_branch(source, destination):
offset = (destination - source - 4) >> 1
if offset > 2097151 or offset < -2097152:
raise RuntimeError("Invalid offset")
if offset > 1023 or offset < -1024:
instruction1 = 0xf000 | ((offset >> 11) & 0x7ff)
instruction2 = 0xb800 | (offset & 0x7ff)
patch_word(source, instruction1)
patch_word(source + 2, instruction2)
else:
instruction = 0xe000 | (offset & 0x7ff)
patch_word(source, instruction)
ea = here()
if get_wide_word(ea) == 0xb503: #PUSH {R0,R1,LR}
ea1 = ea + 2
if get_wide_word(ea1) == 0xbf00: #NOP
ea1 += 2
if get_operand_type(ea1, 0) == 1 and get_operand_value(ea1, 0) == 0 and get_operand_type(ea1, 1) == 2:
index = get_wide_dword(get_operand_value(ea1, 1))
print "index =", hex(index)
ea1 += 2
if get_operand_type(ea1, 0) == 7:
table = get_operand_value(ea1, 0) + 4
elif get_operand_type(ea1, 1) == 2:
table = get_operand_value(ea1, 1) + 4
else:
print "Wrong operand type on", hex(ea1), "-", get_operand_type(ea1, 0), get_operand_type(ea1, 1)
table = None
if table is None:
print "Unable to find table"
else:
print "table =", hex(table)
offset = get_wide_dword(table + (index << 2))
put_unconditional_branch(ea, table + offset)
else:
print "Unknown code", get_operand_type(ea1, 0), get_operand_value(ea1, 0), get_operand_type(ea1, 1) == 2
else:
print "Unable to detect first instruction"

Platzieren Sie den Cursor auf Zeile 0xB26A, führen Sie das Skript aus und sehen Sie sich den Übergang zu 0xB4B0 an:

Suche nach Schwachstellen im UC Browser

IDA erkannte diesen Bereich erneut nicht als Code. Wir helfen ihr und sehen dort ein weiteres Design:

Suche nach Schwachstellen im UC Browser

Die Anweisungen nach BLX scheinen wenig Sinn zu ergeben, es handelt sich eher um eine Art Verschiebung. Schauen wir uns sub_4964 an:

Suche nach Schwachstellen im UC Browser

Und tatsächlich wird hier ein Dword an der in LR liegenden Adresse genommen, zu dieser Adresse addiert, woraufhin der Wert an der resultierenden Adresse genommen und auf den Stapel gelegt wird. Außerdem wird 4 zu LR hinzugefügt, sodass nach der Rückkehr von der Funktion derselbe Offset übersprungen wird. Anschließend übernimmt der Befehl POP {R1} den resultierenden Wert vom Stapel. Wenn Sie sich ansehen, was sich an der Adresse 0xB4BA + 0xEA = 0xB5A4 befindet, sehen Sie etwas Ähnliches wie eine Adresstabelle:

Suche nach Schwachstellen im UC Browser

Um dieses Design zu patchen, müssen Sie zwei Parameter aus dem Code abrufen: den Offset und die Registernummer, in die Sie das Ergebnis einfügen möchten. Für jedes mögliche Register müssen Sie im Voraus einen Code vorbereiten.

patches = {}
patches[0] = (0x00, 0xbf, 0x01, 0x48, 0x00, 0x68, 0x02, 0xe0)
patches[1] = (0x00, 0xbf, 0x01, 0x49, 0x09, 0x68, 0x02, 0xe0)
patches[2] = (0x00, 0xbf, 0x01, 0x4a, 0x12, 0x68, 0x02, 0xe0)
patches[3] = (0x00, 0xbf, 0x01, 0x4b, 0x1b, 0x68, 0x02, 0xe0)
patches[4] = (0x00, 0xbf, 0x01, 0x4c, 0x24, 0x68, 0x02, 0xe0)
patches[5] = (0x00, 0xbf, 0x01, 0x4d, 0x2d, 0x68, 0x02, 0xe0)
patches[8] = (0x00, 0xbf, 0xdf, 0xf8, 0x06, 0x80, 0xd8, 0xf8, 0x00, 0x80, 0x01, 0xe0)
patches[9] = (0x00, 0xbf, 0xdf, 0xf8, 0x06, 0x90, 0xd9, 0xf8, 0x00, 0x90, 0x01, 0xe0)
patches[10] = (0x00, 0xbf, 0xdf, 0xf8, 0x06, 0xa0, 0xda, 0xf8, 0x00, 0xa0, 0x01, 0xe0)
patches[11] = (0x00, 0xbf, 0xdf, 0xf8, 0x06, 0xb0, 0xdb, 0xf8, 0x00, 0xb0, 0x01, 0xe0)
ea = here()
if (get_wide_word(ea) == 0xb082 #SUB SP, SP, #8
and get_wide_word(ea + 2) == 0xb503): #PUSH {R0,R1,LR}
if get_operand_type(ea + 4, 0) == 7:
pop = get_bytes(ea + 12, 4, 0)
if pop[1] == 'xbc':
register = -1
r = get_wide_byte(ea + 12)
for i in range(8):
if r == (1 << i):
register = i
break
if register == -1:
print "Unable to detect register"
else:
address = get_wide_dword(ea + 8) + ea + 8
for b in patches[register]:
patch_byte(ea, b)
ea += 1
if ea % 4 != 0:
ea += 2
patch_dword(ea, address)
elif pop[:3] == 'x5dxf8x04':
register = ord(pop[3]) >> 4
if register in patches:
address = get_wide_dword(ea + 8) + ea + 8
for b in patches[register]:
patch_byte(ea, b)
ea += 1
patch_dword(ea, address)
else:
print "POP instruction not found"
else:
print "Wrong operand type on +4:", get_operand_type(ea + 4, 0)
else:
print "Unable to detect first instructions"

Wir platzieren den Cursor am Anfang der Struktur, die wir ersetzen möchten – 0xB4B2 – und führen das Skript aus:

Suche nach Schwachstellen im UC Browser

Zusätzlich zu den bereits erwähnten Strukturen enthält der Code auch Folgendes:

Suche nach Schwachstellen im UC Browser

Wie im vorherigen Fall gibt es nach der BLX-Anweisung einen Offset:

Suche nach Schwachstellen im UC Browser

Wir nehmen den Offset zur Adresse von LR, addieren ihn zu LR und gehen dorthin. 0x72044 + 0xC = 0x72050. Das Skript für dieses Design ist recht einfach:

def put_unconditional_branch(source, destination):
offset = (destination - source - 4) >> 1
if offset > 2097151 or offset < -2097152:
raise RuntimeError("Invalid offset")
if offset > 1023 or offset < -1024:
instruction1 = 0xf000 | ((offset >> 11) & 0x7ff)
instruction2 = 0xb800 | (offset & 0x7ff)
patch_word(source, instruction1)
patch_word(source + 2, instruction2)
else:
instruction = 0xe000 | (offset & 0x7ff)
patch_word(source, instruction)
ea = here()
if get_wide_word(ea) == 0xb503: #PUSH {R0,R1,LR}
ea1 = ea + 6
if get_wide_word(ea + 2) == 0xbf00: #NOP
ea1 += 2
offset = get_wide_dword(ea1)
put_unconditional_branch(ea, (ea1 + offset) & 0xffffffff)
else:
print "Unable to detect first instruction"

Ergebnis der Skriptausführung:

Suche nach Schwachstellen im UC Browser

Sobald alles in der Funktion gepatcht ist, können Sie IDA auf seinen eigentlichen Anfang verweisen. Der gesamte Funktionscode wird zusammengesetzt und kann mithilfe von HexRays dekompiliert werden.

Zeichenfolgen dekodieren

Wir haben gelernt, mit der Verschleierung von Maschinencode in der Bibliothek umzugehen libsgmainso-6.4.36.so vom UC Browser und erhielt den Funktionscode JNI_OnLoad.

int __fastcall real_JNI_OnLoad(JavaVM *vm)
{
int result; // r0
jclass clazz; // r0 MAPDST
int v4; // r0
JNIEnv *env; // r4
int v6; // [sp-40h] [bp-5Ch]
int v7; // [sp+Ch] [bp-10h]
v7 = *(_DWORD *)off_8AC00;
if ( !vm )
goto LABEL_39;
sub_7C4F4();
env = (JNIEnv *)sub_7C5B0(0);
if ( !env )
goto LABEL_39;
v4 = sub_72CCC();
sub_73634(v4);
sub_73E24(&unk_83EA6, &v6, 49);
clazz = (jclass)((int (__fastcall *)(JNIEnv *, int *))(*env)->FindClass)(env, &v6);
if ( clazz
&& (sub_9EE4(),
sub_71D68(env),
sub_E7DC(env) >= 0
&& sub_69D68(env) >= 0
&& sub_197B4(env, clazz) >= 0
&& sub_E240(env, clazz) >= 0
&& sub_B8B0(env, clazz) >= 0
&& sub_5F0F4(env, clazz) >= 0
&& sub_70640(env, clazz) >= 0
&& sub_11F3C(env) >= 0
&& sub_21C3C(env, clazz) >= 0
&& sub_2148C(env, clazz) >= 0
&& sub_210E0(env, clazz) >= 0
&& sub_41B58(env, clazz) >= 0
&& sub_27920(env, clazz) >= 0
&& sub_293E8(env, clazz) >= 0
&& sub_208F4(env, clazz) >= 0) )
{
result = (sub_B7B0(env, clazz) >> 31) | 0x10004;
}
else
{
LABEL_39:
result = -1;
}
return result;
}

Schauen wir uns die folgenden Zeilen genauer an:

  sub_73E24(&unk_83EA6, &v6, 49);
clazz = (jclass)((int (__fastcall *)(JNIEnv *, int *))(*env)->FindClass)(env, &v6);

In Funktion sub_73E24 Der Klassenname wird eindeutig entschlüsselt. Als Parameter dieser Funktion werden ein Zeiger auf Daten, die verschlüsselten Daten ähneln, ein bestimmter Puffer und eine Nummer übergeben. Offensichtlich befindet sich nach dem Aufruf der Funktion eine entschlüsselte Zeile im Puffer, da diese an die Funktion übergeben wird Findclass, der den Klassennamen als zweiten Parameter annimmt. Daher ist die Zahl die Größe des Puffers oder die Länge der Zeile. Versuchen wir, den Klassennamen zu entschlüsseln. Er sollte uns sagen, ob wir in die richtige Richtung gehen. Schauen wir uns genauer an, was passiert sub_73E24.

int __fastcall sub_73E56(unsigned __int8 *in, unsigned __int8 *out, size_t size)
{
int v4; // r6
int v7; // r11
int v8; // r9
int v9; // r4
size_t v10; // r5
int v11; // r0
struc_1 v13; // [sp+0h] [bp-30h]
int v14; // [sp+1Ch] [bp-14h]
int v15; // [sp+20h] [bp-10h]
v4 = 0;
v15 = *(_DWORD *)off_8AC00;
v14 = 0;
v7 = sub_7AF78(17);
v8 = sub_7AF78(size);
if ( !v7 )
{
v9 = 0;
goto LABEL_12;
}
(*(void (__fastcall **)(int, const char *, int))(v7 + 12))(v7, "DcO/lcK+h?m3c*q@", 16);
if ( !v8 )
{
LABEL_9:
v4 = 0;
goto LABEL_10;
}
v4 = 0;
if ( !in )
{
LABEL_10:
v9 = 0;
goto LABEL_11;
}
v9 = 0;
if ( out )
{
memset(out, 0, size);
v10 = size - 1;
(*(void (__fastcall **)(int, unsigned __int8 *, size_t))(v8 + 12))(v8, in, v10);
memset(&v13, 0, 0x14u);
v13.field_4 = 3;
v13.field_10 = v7;
v13.field_14 = v8;
v11 = sub_6115C(&v13, &v14);
v9 = v11;
if ( v11 )
{
if ( *(_DWORD *)(v11 + 4) == v10 )
{
qmemcpy(out, *(const void **)v11, v10);
v4 = *(_DWORD *)(v9 + 4);
}
else
{
v4 = 0;
}
goto LABEL_11;
}
goto LABEL_9;
}
LABEL_11:
sub_7B148(v7);
LABEL_12:
if ( v8 )
sub_7B148(v8);
if ( v9 )
sub_7B148(v9);
return v4;
}

Funktion sub_7AF78 Erstellt eine Instanz eines Containers für Byte-Arrays der angegebenen Größe (wir werden uns nicht im Detail mit diesen Containern befassen). Hier werden zwei solcher Container erstellt: Einer enthält die Zeile „DcO/lcK+h?m3c*q@“ (Es ist leicht zu erraten, dass es sich hierbei um einen Schlüssel handelt), der andere enthält verschlüsselte Daten. Anschließend werden beide Objekte in einer bestimmten Struktur platziert, die an die Funktion übergeben wird sub_6115C. Markieren wir in dieser Struktur auch ein Feld mit dem Wert 3. Schauen wir mal, was als nächstes mit dieser Struktur passiert.

int __fastcall sub_611B4(struc_1 *a1, _DWORD *a2)
{
int v3; // lr
unsigned int v4; // r1
int v5; // r0
int v6; // r1
int result; // r0
int v8; // r0
*a2 = 820000;
if ( a1 )
{
v3 = a1->field_14;
if ( v3 )
{
v4 = a1->field_4;
if ( v4 < 0x19 )
{
switch ( v4 )
{
case 0u:
v8 = sub_6419C(a1->field_0, a1->field_10, v3);
goto LABEL_17;
case 3u:
v8 = sub_6364C(a1->field_0, a1->field_10, v3);
goto LABEL_17;
case 0x10u:
case 0x11u:
case 0x12u:
v8 = sub_612F4(
a1->field_0,
v4,
*(_QWORD *)&a1->field_8,
*(_QWORD *)&a1->field_8 >> 32,
a1->field_10,
v3,
a2);
goto LABEL_17;
case 0x14u:
v8 = sub_63A28(a1->field_0, v3);
goto LABEL_17;
case 0x15u:
sub_61A60(a1->field_0, v3, a2);
return result;
case 0x16u:
v8 = sub_62440(a1->field_14);
goto LABEL_17;
case 0x17u:
v8 = sub_6226C(a1->field_10, v3);
goto LABEL_17;
case 0x18u:
v8 = sub_63530(a1->field_14);
LABEL_17:
v6 = 0;
if ( v8 )
{
*a2 = 0;
v6 = v8;
}
return v6;
default:
LOWORD(v5) = 28032;
goto LABEL_5;
}
}
}
}
LOWORD(v5) = -27504;
LABEL_5:
HIWORD(v5) = 13;
v6 = 0;
*a2 = v5;
return v6;
}

Der Schalterparameter ist ein Strukturfeld, dem zuvor der Wert 3 zugewiesen wurde. Schauen Sie sich Fall 3: der Funktion an sub_6364C Es werden Parameter aus der Struktur übergeben, die dort in der vorherigen Funktion hinzugefügt wurden, also der Schlüssel und verschlüsselte Daten. Wenn man genau hinschaut sub_6364C, man erkennt darin den RC4-Algorithmus.

Wir haben einen Algorithmus und einen Schlüssel. Versuchen wir, den Klassennamen zu entschlüsseln. Folgendes ist passiert: com/taobao/wireless/security/adapter/JNICLibrary. Großartig! Wir sind auf dem richtigen Weg.

Befehlsbaum

Jetzt müssen wir eine Herausforderung finden RegistrierenNatives, was uns auf die Funktion verweisen wird doCommandNative. Schauen wir uns die aufgerufenen Funktionen an JNI_OnLoad, und wir finden es darin sub_B7B0:

int __fastcall sub_B7F6(JNIEnv *env, jclass clazz)
{
char signature[41]; // [sp+7h] [bp-55h]
char name[16]; // [sp+30h] [bp-2Ch]
JNINativeMethod method; // [sp+40h] [bp-1Ch]
int v8; // [sp+4Ch] [bp-10h]
v8 = *(_DWORD *)off_8AC00;
decryptString((unsigned __int8 *)&unk_83ED9, (unsigned __int8 *)name, 0x10u);// doCommandNative
decryptString((unsigned __int8 *)&unk_83EEA, (unsigned __int8 *)signature, 0x29u);// (I[Ljava/lang/Object;)Ljava/lang/Object;
method.name = name;
method.signature = signature;
method.fnPtr = sub_B69C;
return ((int (__fastcall *)(JNIEnv *, jclass, JNINativeMethod *, int))(*env)->RegisterNatives)(env, clazz, &method, 1) >> 31;
}

Und tatsächlich ist hier eine native Methode mit dem Namen registriert doCommandNative. Jetzt kennen wir seine Adresse. Mal sehen, was er macht.

int __fastcall doCommandNative(JNIEnv *env, jobject obj, int command, jarray args)
{
int v5; // r5
struc_2 *a5; // r6
int v9; // r1
int v11; // [sp+Ch] [bp-14h]
int v12; // [sp+10h] [bp-10h]
v5 = 0;
v12 = *(_DWORD *)off_8AC00;
v11 = 0;
a5 = (struc_2 *)malloc(0x14u);
if ( a5 )
{
a5->field_0 = 0;
a5->field_4 = 0;
a5->field_8 = 0;
a5->field_C = 0;
v9 = command % 10000 / 100;
a5->field_0 = command / 10000;
a5->field_4 = v9;
a5->field_8 = command % 100;
a5->field_C = env;
a5->field_10 = args;
v5 = sub_9D60(command / 10000, v9, command % 100, 1, (int)a5, &v11);
}
free(a5);
if ( !v5 && v11 )
sub_7CF34(env, v11, &byte_83ED7);
return v5;
}

Anhand des Namens können Sie erraten, dass sich hier der Einstiegspunkt aller Funktionen befindet, die die Entwickler beschlossen haben, in die native Bibliothek zu übertragen. Uns interessiert die Funktion Nummer 10601.

Sie können dem Code entnehmen, dass die Befehlsnummer drei Zahlen erzeugt: Befehl/10000, Befehl % 10000 / 100 и Befehl % 10, also in unserem Fall 1, 6 und 1. Diese drei Zahlen sowie ein Zeiger auf JNIEnv und die an die Funktion übergebenen Argumente werden einer Struktur hinzugefügt und weitergegeben. Unter Verwendung der drei erhaltenen Zahlen (nennen wir sie N1, N2 und N3) wird ein Befehlsbaum erstellt.

Etwas wie das:

Suche nach Schwachstellen im UC Browser

Der Baum wird dynamisch ausgefüllt JNI_OnLoad.
Drei Zahlen kodieren den Pfad im Baum. Jedes Blatt des Baums enthält die gepockte Adresse der entsprechenden Funktion. Der Schlüssel befindet sich im übergeordneten Knoten. Wenn Sie alle verwendeten Strukturen verstehen, ist es nicht schwierig, die Stelle im Code zu finden, an der die von uns benötigte Funktion zum Baum hinzugefügt wird (wir beschreiben sie nicht, um einen ohnehin schon recht umfangreichen Artikel nicht aufzublähen).

Mehr Verschleierung

Wir haben die Adresse der Funktion erhalten, die den Datenverkehr entschlüsseln soll: 0x5F1AC. Doch zum Jubeln ist es noch zu früh: Die Entwickler von UC Browser haben eine weitere Überraschung für uns vorbereitet.

Nachdem wir die Parameter aus dem Array erhalten haben, das im Java-Code gebildet wurde, erhalten wir
zur Funktion an Adresse 0x4D070. Und hier erwartet uns eine weitere Art der Code-Verschleierung.

Wir haben zwei Indizes in R7 und R4 eingefügt:

Suche nach Schwachstellen im UC Browser

Wir verschieben den ersten Index nach R11:

Suche nach Schwachstellen im UC Browser

Um eine Adresse aus einer Tabelle abzurufen, verwenden Sie einen Index:

Suche nach Schwachstellen im UC Browser

Nach dem Aufrufen der ersten Adresse wird der zweite Index verwendet, der sich in R4 befindet. Die Tabelle enthält 230 Elemente.

Was tun dagegen? Sie können IDA mitteilen, dass es sich um einen Schalter handelt: Bearbeiten -> Andere -> Schaltersprache angeben.

Suche nach Schwachstellen im UC Browser

Der resultierende Code ist schrecklich. Aber wenn Sie sich durch den Dschungel bewegen, können Sie einen Aufruf einer Funktion bemerken, die uns bereits bekannt ist sub_6115C:

Suche nach Schwachstellen im UC Browser

Es gab einen Schalter, bei dem im Fall 3 eine Entschlüsselung mit dem RC4-Algorithmus erfolgte. Und in diesem Fall wird die an die Funktion übergebene Struktur aus den übergebenen Parametern gefüllt doCommandNative. Erinnern wir uns daran, was wir dort hatten magicInt mit dem Wert 16. Wir schauen uns den entsprechenden Fall an – und finden nach mehreren Übergängen den Code, anhand dessen der Algorithmus identifiziert werden kann.

Suche nach Schwachstellen im UC Browser

Das ist AES!

Der Algorithmus existiert, es müssen nur noch seine Parameter ermittelt werden: Modus, Schlüssel und möglicherweise der Initialisierungsvektor (sein Vorhandensein hängt vom Betriebsmodus des AES-Algorithmus ab). Die Struktur mit ihnen muss irgendwo vor dem Funktionsaufruf gebildet werden sub_6115C, aber dieser Teil des Codes ist besonders gut verschleiert, sodass die Idee entsteht, den Code so zu patchen, dass alle Parameter der Entschlüsselungsfunktion in einer Datei abgelegt werden.

Patch

Um nicht den gesamten Patchcode manuell in Assemblersprache schreiben zu müssen, können Sie Android Studio starten, dort eine Funktion schreiben, die dieselben Eingabeparameter wie unsere Entschlüsselungsfunktion empfängt und in eine Datei schreibt, und dann den Code kopieren und einfügen, den der Compiler benötigt generieren.

Unsere Freunde vom UC Browser-Team haben sich auch um die Bequemlichkeit des Code-Hinzufügens gekümmert. Denken wir daran, dass wir am Anfang jeder Funktion Müllcode haben, der leicht durch jeden anderen ersetzt werden kann. Sehr praktisch 🙂 Allerdings ist am Anfang der Zielfunktion nicht genügend Platz für den Code, der alle Parameter in einer Datei speichert. Ich musste es in Teile aufteilen und Müllblöcke aus benachbarten Funktionen verwenden. Insgesamt gab es vier Teile.

Der erste Teil:

Suche nach Schwachstellen im UC Browser

In der ARM-Architektur werden die ersten vier Funktionsparameter über die Register R0–R3 geleitet, der Rest, falls vorhanden, wird über den Stapel geleitet. Das LR-Register trägt die Rücksprungadresse. All dies muss gespeichert werden, damit die Funktion funktionieren kann, nachdem wir ihre Parameter gesichert haben. Wir müssen auch alle Register speichern, die wir im Prozess verwenden werden, also führen wir PUSH.W {R0-R10,LR} aus. In R7 erhalten wir die Adresse der Parameterliste, die über den Stack an die Funktion übergeben wird.

Verwendung der Funktion öffnen Lasst uns die Datei öffnen /data/local/tmp/aes im „ab“-Modus
d.h. zur Ergänzung. In R0 laden wir die Adresse des Dateinamens, in R1 die Adresse der Zeile, die den Modus angibt. Und hier endet der Müllcode, also fahren wir mit der nächsten Funktion fort. Damit es weiterhin funktioniert, setzen wir am Anfang den Übergang zum echten Code der Funktion unter Umgehung des Mülls und fügen anstelle des Mülls eine Fortsetzung des Patches hinzu.

Suche nach Schwachstellen im UC Browser

Berufung öffnen.

Die ersten drei Parameter der Funktion Aes Typ haben int. Da wir die Register zu Beginn auf dem Stack gespeichert haben, können wir die Funktion einfach übergeben fschreiben ihre Adressen auf dem Stapel.

Suche nach Schwachstellen im UC Browser

Als nächstes haben wir drei Strukturen, die die Datengröße und einen Zeiger auf die Daten für den Schlüssel, den Initialisierungsvektor und die verschlüsselten Daten enthalten.

Suche nach Schwachstellen im UC Browser

Schließen Sie am Ende die Datei, stellen Sie die Register wieder her und übergeben Sie die Kontrolle an die eigentliche Funktion Aes.

Wir sammeln eine APK mit einer gepatchten Bibliothek, signieren sie, laden sie auf das Gerät/den Emulator hoch und starten sie. Wir sehen, dass unser Dump erstellt wird und viele Daten dorthin geschrieben werden. Der Browser verwendet Verschlüsselung nicht nur für den Datenverkehr, und die gesamte Verschlüsselung erfolgt über die jeweilige Funktion. Aber aus irgendeinem Grund sind die notwendigen Daten nicht vorhanden und die erforderliche Anfrage ist im Datenverkehr nicht sichtbar. Um nicht zu warten, bis UC Browser die erforderliche Anfrage stellt, nehmen wir die verschlüsselte Antwort vom Server, die wir zuvor erhalten haben, und patchen die Anwendung erneut: Fügen Sie die Entschlüsselung zu onCreate der Hauptaktivität hinzu.

    const/16 v1, 0x62
new-array v1, v1, [B
fill-array-data v1, :encrypted_data
const/16 v0, 0x1f
invoke-static {v0, v1}, Lcom/uc/browser/core/d/c/g;->j(I[B)[B
move-result-object v1
array-length v2, v1
invoke-static {v2}, Ljava/lang/String;->valueOf(I)Ljava/lang/String;
move-result-object v2
const-string v0, "ololo"
invoke-static {v0, v2}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I

Wir montieren, signieren, installieren, starten. Wir erhalten eine NullPointerException, weil die Methode null zurückgegeben hat.

Bei der weiteren Analyse des Codes wurde eine Funktion entdeckt, die interessante Zeilen entschlüsselt: „META-INF/“ und „.RSA“. Es sieht so aus, als ob die Anwendung ihr Zertifikat überprüft. Oder sogar Schlüssel daraus generiert. Ich möchte mich nicht wirklich damit befassen, was mit dem Zertifikat passiert, also schieben wir ihm einfach das richtige Zertifikat zu. Lassen Sie uns die verschlüsselte Zeile so patchen, dass wir anstelle von „META-INF/“ „BLABLIF/“ erhalten, erstellen Sie einen Ordner mit diesem Namen in der APK und fügen Sie dort das Squirrel-Browser-Zertifikat hinzu.

Wir montieren, signieren, installieren, starten. Bingo! Wir haben den Schlüssel!

MitM

Wir haben einen Schlüssel und einen dem Schlüssel entsprechenden Initialisierungsvektor erhalten. Versuchen wir, die Serverantwort im CBC-Modus zu entschlüsseln.

Suche nach Schwachstellen im UC Browser

Wir sehen die Archiv-URL, etwas Ähnliches wie MD5, „extract_unzipsize“ und eine Zahl. Wir prüfen: Der MD5 des Archivs ist gleich, die Größe der entpackten Bibliothek ist gleich. Wir versuchen, diese Bibliothek zu patchen und dem Browser zur Verfügung zu stellen. Um zu zeigen, dass unsere gepatchte Bibliothek geladen wurde, starten wir einen Intent, um eine SMS mit dem Text „PWNED!“ zu erstellen. Wir werden zwei Antworten vom Server ersetzen: puds.ucweb.com/upgrade/index.xhtml und um das Archiv herunterzuladen. Im ersten Fall ersetzen wir MD5 (die Größe ändert sich nach dem Entpacken nicht), im zweiten geben wir das Archiv mit der gepatchten Bibliothek weiter.

Der Browser versucht mehrmals, das Archiv herunterzuladen, woraufhin er eine Fehlermeldung ausgibt. Anscheinend etwas
er mag nicht. Als Ergebnis der Analyse dieses unklaren Formats stellte sich heraus, dass der Server auch die Größe des Archivs übermittelt:

Suche nach Schwachstellen im UC Browser

Es ist in LEB128 kodiert. Nach dem Patch änderte sich die Größe des Archivs mit der Bibliothek ein wenig, sodass der Browser davon ausging, dass das Archiv falsch heruntergeladen wurde, und nach mehreren Versuchen einen Fehler ausgab.

Wir passen die Größe des Archivs an... Und – Sieg! 🙂 Das Ergebnis gibt es im Video.

https://www.youtube.com/watch?v=Nfns7uH03J8

Konsequenzen und Entwicklerreaktion

Auf die gleiche Weise könnten Hacker die unsichere Funktion von UC Browser nutzen, um schädliche Bibliotheken zu verbreiten und auszuführen. Diese Bibliotheken funktionieren im Kontext des Browsers und erhalten daher alle dessen Systemberechtigungen. Dadurch besteht die Möglichkeit, Phishing-Fenster anzuzeigen, sowie Zugriff auf die Arbeitsdateien des Orange Chinese Squirrel, einschließlich in der Datenbank gespeicherter Logins, Passwörter und Cookies.

Wir haben die Entwickler von UC Browser kontaktiert und sie über das gefundene Problem informiert und versucht, auf die Schwachstelle und ihre Gefahr hinzuweisen, aber sie haben nichts mit uns besprochen. Unterdessen stellte der Browser seine gefährliche Funktion weiterhin öffentlich zur Schau. Aber nachdem wir die Details der Schwachstelle enthüllt hatten, war es nicht mehr möglich, sie wie zuvor zu ignorieren. Der 27. März war
Es wurde eine neue Version von UC Browser 12.10.9.1193 veröffentlicht, die über HTTPS auf den Server zugreift: puds.ucweb.com/upgrade/index.xhtml.

Darüber hinaus führte der Versuch, eine PDF-Datei in einem Browser zu öffnen, nach der „Behebung“ und bis zum Zeitpunkt des Verfassens dieses Artikels zu einer Fehlermeldung mit dem Text „Ups, etwas ist schiefgelaufen!“ Beim Versuch, eine PDF-Datei zu öffnen, wurde keine Anfrage an den Server gestellt, wohl aber beim Starten des Browsers, was darauf hindeutet, dass weiterhin die Möglichkeit besteht, ausführbaren Code herunterzuladen, was gegen die Google Play-Regeln verstößt.

Source: habr.com

Kommentar hinzufügen