Op zoek naar kwetsbaarheden in UC Browser

Op zoek naar kwetsbaarheden in UC Browser

Introductie

Eind maart hebben wij gerapporteerd, dat ze een verborgen mogelijkheid ontdekten om niet-geverifieerde code in UC Browser te laden en uit te voeren. Vandaag zullen we in detail bekijken hoe deze download plaatsvindt en hoe hackers deze voor hun eigen doeleinden kunnen gebruiken.

Enige tijd geleden werd op zeer agressieve wijze reclame gemaakt voor UC Browser en werd deze verspreid: het werd op de apparaten van gebruikers geïnstalleerd met behulp van malware, verspreid vanaf verschillende sites onder het mom van videobestanden (d.w.z. gebruikers dachten dat ze bijvoorbeeld een pornovideo aan het downloaden waren, maar in plaats daarvan een APK ontvangen met deze browser), enge banners gebruikt met berichten dat de browser verouderd, kwetsbaar was en dat soort dingen. In de officiële UC Browser-groep op VK is er тема, waarin gebruikers kunnen klagen over oneerlijke reclame, daar zijn veel voorbeelden van. In 2016 was dat zelfs zo video-advertenties in het Russisch (ja, reclame voor een browser die advertenties blokkeert).

Op het moment van schrijven heeft UC Browser meer dan 500 installaties op Google Play. Dit is indrukwekkend: alleen Google Chrome heeft meer. Onder de recensies zie je behoorlijk wat klachten over advertenties en omleidingen naar sommige applicaties op Google Play. Dit was de reden voor ons onderzoek: we besloten om te kijken of UC Browser iets slechts deed. En het bleek dat hij dat doet!

In de applicatiecode werd de mogelijkheid ontdekt om uitvoerbare code te downloaden en uit te voeren, wat in strijd is met de regels voor het publiceren van applicaties op Google Play. Naast het downloaden van uitvoerbare code, doet UC Browser dit op een onveilige manier, die kan worden gebruikt om een ​​MitM-aanval te lanceren. Laten we kijken of we zo'n aanval kunnen uitvoeren.

Alles hieronder is relevant voor de versie van UC Browser die ten tijde van het onderzoek beschikbaar was op Google Play:

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

Aanvalsvector

In het UC Browser-manifest vindt u een dienst met een voor zichzelf sprekende naam com.uc.deployment.UpgradeDeployService.

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

Wanneer deze dienst start, doet de browser een POST-verzoek naar puds.ucweb.com/upgrade/index.xhtml, wat enige tijd na de start in het verkeer te zien is. Als reactie hierop kan hij een opdracht ontvangen om een ​​update of nieuwe module te downloaden. Tijdens de analyse heeft de server dergelijke opdrachten niet gegeven, maar we hebben gemerkt dat wanneer we een pdf in de browser proberen te openen, deze een tweede verzoek doet naar het hierboven opgegeven adres, waarna de oorspronkelijke bibliotheek wordt gedownload. Om de aanval uit te voeren, hebben we besloten deze functie van UC Browser te gebruiken: de mogelijkheid om PDF te openen met behulp van een eigen bibliotheek, die niet in de APK staat en die indien nodig van internet wordt gedownload. Het is vermeldenswaard dat UC Browser theoretisch gezien kan worden gedwongen iets te downloaden zonder tussenkomst van de gebruiker - als u een goed geformuleerd antwoord geeft op een verzoek dat wordt uitgevoerd nadat de browser is gestart. Maar om dit te doen, moeten we het interactieprotocol met de server in meer detail bestuderen, dus besloten we dat het gemakkelijker zou zijn om het onderschepte antwoord te bewerken en de bibliotheek te vervangen voor het werken met PDF.

Wanneer een gebruiker dus een PDF rechtstreeks in de browser wil openen, zijn de volgende verzoeken te zien in het verkeer:

Op zoek naar kwetsbaarheden in UC Browser

Eerst is er een POST-verzoek aan puds.ucweb.com/upgrade/index.xhtml, dan
Er wordt een archief met een bibliotheek voor het bekijken van PDF- en Office-formaten gedownload. Het is logisch om aan te nemen dat het eerste verzoek informatie over het systeem verzendt (tenminste de architectuur om de vereiste bibliotheek te bieden), en als reactie daarop ontvangt de browser enige informatie over de bibliotheek die moet worden gedownload: het adres en mogelijk , iets anders. Het probleem is dat dit verzoek gecodeerd is.

Fragment aanvragen

Antwoordfragment

Op zoek naar kwetsbaarheden in UC Browser

Op zoek naar kwetsbaarheden in UC Browser

De bibliotheek zelf is verpakt in ZIP en is niet gecodeerd.

Op zoek naar kwetsbaarheden in UC Browser

Zoek naar verkeersdecoderingscode

Laten we proberen het antwoord van de server te ontcijferen. Laten we naar de klassecode kijken com.uc.deployment.UpgradeDeployService: van methode opStartCommand ga naar com.uc.deployment.bx, en van daaruit naar 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);
}

We zien hier de vorming van een POST-verzoek. We besteden aandacht aan het maken van een array van 16 bytes en de vulling ervan: 0x5F, 0, 0x1F, -50 (=0xCE). Valt samen met wat we in het bovenstaande verzoek zagen.

In dezelfde klasse zie je een geneste klasse die een andere interessante methode heeft:

        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");
}
}

De methode gebruikt een array van bytes als invoer en controleert of de nulbyte 0x60 is, of de derde byte 0xD0, en de tweede byte 1, 11 of 0x1F. We kijken naar het antwoord van de server: de nulbyte is 0x60, de tweede is 0x1F, de derde is 0x60. Klinkt als wat we nodig hebben. Afgaande op de regels ("up_decrypt", bijvoorbeeld), zou hier een methode moeten worden aangeroepen die het antwoord van de server zal decoderen.
Laten we verder gaan met de methode gj. Merk op dat het eerste argument de byte op offset 2 is (in ons geval dus 0x1F), en het tweede het antwoord van de server is zonder
eerste 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;
}

Het is duidelijk dat we hier een decoderingsalgoritme selecteren, en dezelfde byte die in onze
geval gelijk aan 0x1F, geeft een van de drie mogelijke opties aan.

We blijven de code analyseren. Na een paar sprongen komen we terecht in een methode met een voor zichzelf sprekende naam decryptBytesByKey.

Hier worden nog twee bytes gescheiden van ons antwoord, en er wordt een string uit verkregen. Het is duidelijk dat op deze manier de sleutel voor het decoderen van het bericht wordt geselecteerd.

    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;
}

Vooruitkijkend merken we op dat we in dit stadium nog geen sleutel hebben verkregen, maar alleen de “identificatiecode”. Het verkrijgen van de sleutel is iets ingewikkelder.

In de volgende methode worden nog twee parameters toegevoegd aan de bestaande, waardoor er vier ontstaan: het magische getal 16, de sleutelidentificatie, de gecodeerde gegevens en een onbegrijpelijke string (in ons geval leeg).

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

Na een reeks transities komen we tot de methode staticBinarySafeDecryptNoB64 koppel com.alibaba.wireless.security.open.staticdataencrypt.IStaticDataEncryptComponent. Er zijn geen klassen in de hoofdapplicatiecode die deze interface implementeren. Er is zo'n klasse in het bestand lib/armeabi-v7a/libsgmain.so, wat eigenlijk geen .so is, maar een .jar. De methode waarin we geïnteresseerd zijn, wordt als volgt geïmplementeerd:

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 wordt onze lijst met parameters aangevuld met nog twee gehele getallen: 2 en 0. Te oordelen naar
alles, 2 betekent decodering, zoals in de methode doFinal systeem klasse javax.crypto.Cipher. En dit alles wordt overgebracht naar een bepaalde router met het nummer 10601 - dit is blijkbaar het opdrachtnummer.

Na de volgende reeks overgangen vinden we een klasse die de interface implementeert IRouterComponent en methode doeCommand:

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);
}
}

En ook klasse JNICBibliotheek, waarin de native methode wordt gedeclareerd doCommandNative:

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

Dit betekent dat we een methode in de native code moeten vinden doCommandNative. En dit is waar het plezier begint.

Verduistering van machinecode

In bestand libsgmain.so (wat eigenlijk een .jar is en waarin we de implementatie van enkele encryptiegerelateerde interfaces net hierboven hebben gevonden) is er één native bibliotheek: libsgmainso-6.4.36.so. We openen het in IDA en krijgen een aantal dialoogvensters met fouten. Het probleem is dat de sectiekoptabel ongeldig is. Dit is met opzet gedaan om de analyse te bemoeilijken.

Op zoek naar kwetsbaarheden in UC Browser

Maar dat is niet nodig: om een ​​ELF-bestand correct te laden en te analyseren, is een programmakoptabel voldoende. Daarom verwijderen we eenvoudigweg de sectietabel, waarbij de overeenkomstige velden in de koptekst op nul worden gezet.

Op zoek naar kwetsbaarheden in UC Browser

Open het bestand opnieuw in IDA.

Er zijn twee manieren om de virtuele Java-machine te vertellen waar zich precies in de native bibliotheek de implementatie bevindt van een methode die in Java-code als native is gedeclareerd. De eerste is om het een soortnaam te geven Java_pakketnaam_Klassenaam_Methodenaam.

De tweede is om het te registreren bij het laden van de bibliotheek (in de function JNI_OnLoad)
met behulp van een functieaanroep RegistreerNatives.

Als we in ons geval de eerste methode gebruiken, zou de naam er als volgt uit moeten zien: Java_com_taobao_wireless_security_adapter_JNICLibrary_doCommandNative.

Een dergelijke functie bestaat niet onder de geëxporteerde functies, wat betekent dat u naar een oproep moet zoeken RegistreerNatives.
Laten we naar de functie gaan JNI_OnLoad en we zien deze foto:

Op zoek naar kwetsbaarheden in UC Browser

Wat is hier aan de hand? Op het eerste gezicht zijn het begin en einde van de functie typisch voor ARM-architectuur. De eerste instructie op de stapel slaat de inhoud op van de registers die de functie zal gebruiken bij de werking ervan (in dit geval R0, R1 en R2), evenals de inhoud van het LR-register, dat het retouradres van de functie bevat. . De laatste instructie herstelt de opgeslagen registers en het retouradres wordt onmiddellijk in het pc-register geplaatst - en keert dus terug uit de functie. Maar als je goed kijkt, zul je merken dat de voorlaatste instructie het retouradres verandert dat op de stapel is opgeslagen. Laten we berekenen hoe het erna zal zijn
uitvoering van code. Een bepaald adres 1xB0 wordt in R130 geladen, 5 wordt ervan afgetrokken, vervolgens overgebracht naar R0 en er wordt 0x10 aan toegevoegd. Het blijkt 0xB13B. IDA denkt dus dat de laatste instructie een normale functieretour is, maar gaat in feite naar het berekende adres 0xB13B.

Het is de moeite waard eraan te herinneren dat ARM-processors twee modi en twee sets instructies hebben: ARM en Thumb. Het minst significante bit van het adres vertelt de processor welke instructieset wordt gebruikt. Dat wil zeggen, het adres is feitelijk 0xB13A, en één in de minst significante bit geeft de duimmodus aan.

Een soortgelijke “adapter” is toegevoegd aan het begin van elke functie in deze bibliotheek en
vuilniscode. We zullen er niet verder op ingaan - we onthouden het gewoon
dat het echte begin van vrijwel alle functies iets verder weg ligt.

Omdat de code niet expliciet naar 0xB13A springt, herkende IDA zelf niet dat de code zich op deze locatie bevond. Om dezelfde reden herkent het programma het grootste deel van de code in de bibliotheek niet als code, wat analyse enigszins lastig maakt. We vertellen IDA dat dit de code is, en dit is wat er gebeurt:

Op zoek naar kwetsbaarheden in UC Browser

De tabel begint duidelijk bij 0xB144. Wat zit er in sub_494C?

Op zoek naar kwetsbaarheden in UC Browser

Wanneer we deze functie in het LR-register aanroepen, krijgen we het adres van de eerder genoemde tabel (0xB144). In R0 - index in deze tabel. Dat wil zeggen, de waarde wordt uit de tabel gehaald, toegevoegd aan LR en het resultaat is
het adres waar u heen moet. Laten we proberen het te berekenen: 0xB144 + [0xB144 + 8* 4] = 0xB144 + 0x120 = 0xB264. We gaan naar het ontvangen adres en zien letterlijk een paar nuttige instructies en gaan opnieuw naar 0xB140:

Op zoek naar kwetsbaarheden in UC Browser

Nu zal er een overgang plaatsvinden op offset met index 0x20 uit de tabel.

Afgaande op de grootte van de tabel zullen er veel van dergelijke overgangen in de code voorkomen. De vraag rijst of het mogelijk is om hier op de een of andere manier automatischer mee om te gaan, zonder handmatig adressen te berekenen. En scripts en de mogelijkheid om code in IDA te patchen komen ons te hulp:

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"

Plaats de cursor op regel 0xB26A, voer het script uit en zie de overgang naar 0xB4B0:

Op zoek naar kwetsbaarheden in UC Browser

IDA herkende dit gebied opnieuw niet als een code. We helpen haar en zien daar een ander ontwerp:

Op zoek naar kwetsbaarheden in UC Browser

De instructies na BLX lijken niet veel zin te hebben, het lijkt meer op een soort verplaatsing. Laten we eens kijken naar sub_4964:

Op zoek naar kwetsbaarheden in UC Browser

En inderdaad, hier wordt een dword genomen op het adres dat in LR ligt, toegevoegd aan dit adres, waarna de waarde op het resulterende adres wordt genomen en op de stapel wordt gezet. Ook wordt 4 toegevoegd aan LR zodat na terugkeer uit de functie dezelfde offset wordt overgeslagen. Daarna haalt het POP {R1}-commando de resulterende waarde uit de stapel. Als je kijkt naar wat zich op adres 0xB4BA + 0xEA = 0xB5A4 bevindt, zie je iets dat lijkt op een adrestabel:

Op zoek naar kwetsbaarheden in UC Browser

Om dit ontwerp te patchen, heb je twee parameters uit de code nodig: de offset en het registernummer waarin je het resultaat wilt plaatsen. Voor elke mogelijke registratie zul je vooraf een stukje code moeten voorbereiden.

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"

We plaatsen de cursor aan het begin van de structuur die we willen vervangen - 0xB4B2 - en voeren het script uit:

Op zoek naar kwetsbaarheden in UC Browser

Naast de reeds genoemde structuren bevat de code ook het volgende:

Op zoek naar kwetsbaarheden in UC Browser

Net als in het vorige geval is er na de BLX-instructie een offset:

Op zoek naar kwetsbaarheden in UC Browser

We nemen de offset naar het adres van LR, voegen deze toe aan LR en gaan daarheen. 0x72044 + 0xC = 0x72050. Het script voor dit ontwerp is vrij eenvoudig:

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"

Resultaat van scriptuitvoering:

Op zoek naar kwetsbaarheden in UC Browser

Zodra alles in de functie is gepatcht, kunt u IDA naar het echte begin wijzen. Het voegt alle functiecode samen en kan worden gedecompileerd met HexRays.

Tekenreeksen decoderen

We hebben geleerd om te gaan met verduistering van machinecode in de bibliotheek libsgmainso-6.4.36.so van UC Browser en ontving de functiecode 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;
}

Laten we de volgende regels eens nader bekijken:

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

In functie sub_73E24 de klassenaam wordt duidelijk gedecodeerd. Als parameters voor deze functie worden een verwijzing naar gegevens die lijken op gecodeerde gegevens, een bepaalde buffer en een nummer doorgegeven. Uiteraard zal er na het aanroepen van de functie een gedecodeerde regel in de buffer staan, aangezien deze wordt doorgegeven aan de functie VindKlasse, waarbij de klassenaam als tweede parameter wordt gebruikt. Daarom is het getal de grootte van de buffer of de lengte van de lijn. Laten we proberen de klassenaam te ontcijferen, deze zou ons moeten vertellen of we in de goede richting gaan. Laten we eens nader bekijken wat er in gebeurt 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;
}

Functie sub_7AF78 creëert een exemplaar van een container voor byte-arrays van de opgegeven grootte (we zullen niet in detail op deze containers ingaan). Hier worden twee van dergelijke containers gemaakt: één bevat de regel "DcO/lcK+h?m3c*q@" (het is gemakkelijk te raden dat dit een sleutel is), de andere bevat gecodeerde gegevens. Vervolgens worden beide objecten in een bepaalde structuur geplaatst, die wordt doorgegeven aan de functie sub_6115C. Laten we in deze structuur ook een veld markeren met de waarde 3. Laten we eens kijken wat er vervolgens met deze structuur gebeurt.

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;
}

De parameter switch is een structuurveld dat eerder de waarde 3 kreeg. Kijk naar geval 3: naar de functie sub_6364C parameters worden doorgegeven vanuit de structuur die daar in de vorige functie is toegevoegd, d.w.z. de sleutel en gecodeerde gegevens. Als je goed kijkt sub_6364C, herken je het RC4-algoritme erin.

We hebben een algoritme en een sleutel. Laten we proberen de klassenaam te ontcijferen. Dit is wat er gebeurde: com/taobao/wireless/security/adapter/JNICLibrary. Geweldig! Wij zijn op de goede weg.

Commandoboom

Nu moeten we een uitdaging vinden RegistreerNatives, wat ons naar de functie zal verwijzen doCommandNative. Laten we eens kijken naar de functies die worden aangeroepen vanuit JNI_OnLoad, en wij vinden het erin 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;
}

En inderdaad, hier is een native methode met de naam geregistreerd doCommandNative. Nu weten we zijn adres. Laten we eens kijken wat hij doet.

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;
}

Bij de naam kun je raden dat hier het startpunt is van alle functies die de ontwikkelaars besloten hebben over te zetten naar de native bibliotheek. Wij zijn geïnteresseerd in functienummer 10601.

Je kunt aan de code zien dat het commandonummer drie cijfers oplevert: commando/10000, opdracht % 10000 / 100 и opdracht % 10, d.w.z. in ons geval 1, 6 en 1. Deze drie cijfers, evenals een verwijzing naar JNIEnv en de argumenten die aan de functie worden doorgegeven, worden aan een structuur toegevoegd en doorgegeven. Met behulp van de drie verkregen getallen (laten we ze N1, N2 en N3 aanduiden) wordt een commandoboom gebouwd.

Iets zoals dit:

Op zoek naar kwetsbaarheden in UC Browser

De boom wordt dynamisch ingevuld JNI_OnLoad.
Drie cijfers coderen het pad in de boom. Elk blad van de boom bevat het pokdalige adres van de bijbehorende functie. De sleutel bevindt zich in het bovenliggende knooppunt. Het vinden van de plaats in de code waar de functie die we nodig hebben aan de boom wordt toegevoegd, is niet moeilijk als je alle gebruikte structuren begrijpt (we beschrijven ze niet om een ​​toch al vrij groot artikel niet op te blazen).

Nog meer verduistering

We hebben het adres ontvangen van de functie die verkeer moet decoderen: 0x5F1AC. Maar het is te vroeg om blij te zijn: de ontwikkelaars van UC Browser hebben nog een verrassing voor ons voorbereid.

Na ontvangst van de parameters van de array die in de Java-code is gevormd, krijgen we
naar de functie op adres 0x4D070. En hier wacht ons een ander soort codeverduistering.

We plaatsen twee indices in R7 en R4:

Op zoek naar kwetsbaarheden in UC Browser

We verschuiven de eerste index naar R11:

Op zoek naar kwetsbaarheden in UC Browser

Gebruik een index om een ​​adres uit een tabel te halen:

Op zoek naar kwetsbaarheden in UC Browser

Nadat naar het eerste adres is gegaan, wordt de tweede index gebruikt, die zich in R4 bevindt. Er zijn 230 elementen in de tabel.

Wat moet je eraan doen? U kunt aan IDA vertellen dat dit een switch is: Bewerken -> Overige -> Specificeer switch-idioom.

Op zoek naar kwetsbaarheden in UC Browser

De resulterende code is eng. Maar terwijl je je een weg baant door de jungle, kun je een oproep opmerken naar een functie die ons al bekend is sub_6115C:

Op zoek naar kwetsbaarheden in UC Browser

Er was een schakelaar waarbij in geval 3 een decodering plaatsvond met behulp van het RC4-algoritme. En in dit geval wordt de structuur die aan de functie wordt doorgegeven, gevuld met de doorgegeven parameters doCommandNative. Laten we niet vergeten wat we daar hadden magieInt met de waarde 16. We kijken naar het overeenkomstige geval - en na verschillende overgangen vinden we de code waarmee het algoritme kan worden geïdentificeerd.

Op zoek naar kwetsbaarheden in UC Browser

Dit is AES!

Het algoritme bestaat, het enige dat overblijft is het verkrijgen van de parameters: modus, sleutel en mogelijk de initialisatievector (de aanwezigheid ervan hangt af van de bedrijfsmodus van het AES-algoritme). De structuur ermee moet ergens vóór de functieaanroep worden gevormd sub_6115C, maar dit deel van de code is bijzonder goed versluierd, dus ontstaat het idee om de code zo te patchen dat alle parameters van de decoderingsfunctie in een bestand worden gedumpt.

Lapje

Om niet alle patchcode handmatig in assembleertaal te schrijven, kunt u Android Studio starten, daar een functie schrijven die dezelfde invoerparameters ontvangt als onze decoderingsfunctie en naar een bestand schrijven, en vervolgens de code kopiëren en plakken die de compiler zal gebruiken genereren.

Onze vrienden van het UC Browser-team zorgden ook voor het gemak van het toevoegen van code. Laten we niet vergeten dat we aan het begin van elke functie afvalcode hebben die gemakkelijk door een andere kan worden vervangen. Erg handig 🙂 Aan het begin van de doelfunctie is er echter niet genoeg ruimte voor de code die alle parameters in een bestand opslaat. Ik moest het in delen opsplitsen en afvalblokken van aangrenzende functies gebruiken. Er waren in totaal vier delen.

Eerste deel:

Op zoek naar kwetsbaarheden in UC Browser

In de ARM-architectuur worden de eerste vier functieparameters door de registers R0-R3 doorgegeven, terwijl de rest, indien aanwezig, door de stapel wordt doorgegeven. Het LR-register bevat het retouradres. Dit alles moet worden opgeslagen, zodat de functie kan werken nadat we de parameters ervan hebben gedumpt. We moeten ook alle registers opslaan die we in het proces zullen gebruiken, dus we doen PUSH.W {R0-R10,LR}. In R7 krijgen we het adres van de lijst met parameters die via de stapel aan de functie zijn doorgegeven.

De functie gebruiken open laten we het bestand openen /data/local/tmp/aes in de "ab"-modus
d.w.z. voor toevoeging. In R0 laden we het adres van de bestandsnaam, in R1 - het adres van de regel die de modus aangeeft. En hier eindigt de afvalcode, dus gaan we verder met de volgende functie. Om ervoor te zorgen dat het blijft werken, hebben we aan het begin de overgang naar de echte code van de functie geplaatst, waarbij we de rommel omzeilen, en in plaats van de rommel voegen we een voortzetting van de patch toe.

Op zoek naar kwetsbaarheden in UC Browser

Roeping open.

De eerste drie parameters van de functie aes soort hebben int. Omdat we de registers in het begin op de stapel hebben opgeslagen, kunnen we de functie eenvoudig doorgeven schrijven hun adressen op de stapel.

Op zoek naar kwetsbaarheden in UC Browser

Vervolgens hebben we drie structuren die de gegevensgrootte bevatten en een verwijzing naar de gegevens voor de sleutel, initialisatievector en gecodeerde gegevens.

Op zoek naar kwetsbaarheden in UC Browser

Sluit aan het einde het bestand, herstel de registers en draag de besturing over aan de echte functie aes.

We verzamelen een APK met een gepatchte bibliotheek, ondertekenen deze, uploaden deze naar het apparaat/emulator en starten deze. We zien dat onze dump wordt gemaakt en dat er veel gegevens naartoe worden geschreven. De browser gebruikt niet alleen encryptie voor verkeer, en alle encryptie verloopt via de betreffende functie. Maar om de een of andere reden zijn de benodigde gegevens er niet en is het vereiste verzoek niet zichtbaar in het verkeer. Om niet te wachten tot UC Browser zich verwaardigt om het noodzakelijke verzoek te doen, nemen we het gecodeerde antwoord van de eerder ontvangen server en patchen we de applicatie opnieuw: voeg de decodering toe aan onCreate van de hoofdactiviteit.

    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

Wij assembleren, ondertekenen, installeren, lanceren. We ontvangen een NullPointerException omdat de methode null retourneert.

Tijdens verdere analyse van de code werd een functie ontdekt die interessante regels ontcijfert: “META-INF/” en “.RSA”. Het lijkt erop dat de applicatie het certificaat verifieert. Of genereert er zelfs sleutels van. Ik wil niet echt ingaan op wat er met het certificaat gebeurt, dus we geven het gewoon het juiste certificaat. Laten we de gecodeerde regel patchen zodat we in plaats van “META-INF/” “BLABLINF/” krijgen, een map met die naam in de APK maken en daar het eekhoornbrowsercertificaat toevoegen.

Wij assembleren, ondertekenen, installeren, lanceren. Bingo! Wij hebben de sleutel!

MitM

We hebben een sleutel en een initialisatievector ontvangen die gelijk is aan de sleutel. Laten we proberen het serverantwoord in de CBC-modus te decoderen.

Op zoek naar kwetsbaarheden in UC Browser

We zien de archief-URL, iets dat lijkt op MD5, “extract_unzipsize” en een nummer. We controleren: de MD5 van het archief is hetzelfde, de grootte van de uitgepakte bibliotheek is hetzelfde. We proberen deze bibliotheek te patchen en aan de browser te geven. Om te laten zien dat onze gepatchte bibliotheek is geladen, lanceren we een Intent om een ​​sms te maken met de tekst “PWNED!” We zullen twee reacties van de server vervangen: puds.ucweb.com/upgrade/index.xhtml en om het archief te downloaden. In de eerste vervangen we MD5 (de grootte verandert niet na het uitpakken), in de tweede geven we het archief met de gepatchte bibliotheek.

De browser probeert meerdere keren het archief te downloaden, waarna hij een foutmelding geeft. Blijkbaar iets
hij houdt niet van. Als resultaat van de analyse van dit duistere formaat bleek dat de server ook de grootte van het archief doorgeeft:

Op zoek naar kwetsbaarheden in UC Browser

Het is gecodeerd in LEB128. Na de patch veranderde de grootte van het archief met de bibliotheek een beetje, dus de browser was van mening dat het archief verkeerd was gedownload en na verschillende pogingen gaf het een fout.

We passen de grootte van het archief aan... En – overwinning! 🙂 Het resultaat staat in de video.

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

Gevolgen en reactie van de ontwikkelaar

Op dezelfde manier kunnen hackers de onveilige functie van UC Browser gebruiken om kwaadaardige bibliotheken te verspreiden en uit te voeren. Deze bibliotheken werken in de context van de browser, zodat ze alle systeemrechten ontvangen. Als gevolg hiervan is er de mogelijkheid om phishing-vensters weer te geven, evenals toegang tot de werkbestanden van de oranje Chinese eekhoorn, inclusief logins, wachtwoorden en cookies die in de database zijn opgeslagen.

We hebben contact opgenomen met de ontwikkelaars van UC Browser en hen geïnformeerd over het probleem dat we hebben gevonden, geprobeerd te wijzen op de kwetsbaarheid en het gevaar ervan, maar ze hebben niets met ons besproken. Ondertussen bleef de browser in het volle zicht pronken met zijn gevaarlijke functie. Maar toen we eenmaal de details van de kwetsbaarheid hadden onthuld, was het niet langer mogelijk om deze zoals voorheen te negeren. 27 maart was
er is een nieuwe versie van UC Browser 12.10.9.1193 uitgebracht, die via HTTPS toegang kreeg tot de server: puds.ucweb.com/upgrade/index.xhtml.

Bovendien resulteerde het proberen om een ​​PDF in een browser te openen na de “fix” en tot het moment van schrijven van dit artikel in een foutmelding met de tekst “Oeps, er is iets misgegaan!” Er is geen verzoek aan de server gedaan bij een poging een pdf te openen, maar er is wel een verzoek gedaan toen de browser werd gestart, wat erop wijst dat er nog steeds uitvoerbare code kan worden gedownload, wat in strijd is met de regels van Google Play.

Bron: www.habr.com

Voeg een reactie