Leder efter sårbarheder i UC Browser

Leder efter sårbarheder i UC Browser

Indledning

I slutningen af ​​marts har vi rapporteret, at de opdagede en skjult evne til at indlæse og køre ubekræftet kode i UC Browser. I dag vil vi se nærmere på, hvordan denne download sker, og hvordan hackere kan bruge den til deres egne formål.

For nogen tid siden blev UC Browser annonceret og distribueret meget aggressivt: den blev installeret på brugernes enheder ved hjælp af malware, distribueret fra forskellige websteder under dække af videofiler (dvs. brugere troede, at de downloadede f.eks. en pornovideo, men modtog i stedet en APK med denne browser), brugte skræmmende bannere med beskeder om, at browseren var forældet, sårbar og sådan noget. I den officielle UC Browser-gruppe på VK er der tema, hvor brugere kan klage over unfair reklame, er der mange eksempler der. I 2016 var der endda video annoncering på russisk (ja, reklame for en annonceblokerende browser).

I skrivende stund har UC Browser over 500 installationer på Google Play. Dette er imponerende - kun Google Chrome har mere. Blandt anmeldelserne kan du se en hel del klager over annoncering og omdirigeringer til nogle applikationer på Google Play. Dette var grunden til vores forskning: vi besluttede at se, om UC Browser gjorde noget dårligt. Og det viste sig, at han gør!

I applikationskoden blev muligheden for at downloade og køre eksekverbar kode opdaget, hvilket er i strid med reglerne for offentliggørelse af ansøgninger på Google Play. Udover at downloade eksekverbar kode, gør UC Browser det på en usikker måde, som kan bruges til at starte et MitM-angreb. Lad os se, om vi kan udføre sådan et angreb.

Alt skrevet nedenfor er relevant for den version af UC Browser, der var tilgængelig på Google Play på tidspunktet for undersøgelsen:

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

Angrebsvektor

I UC Browser-manifestet kan du finde en tjeneste med et selvforklarende navn com.uc.deployment.UpgradeDeployService.

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

Når denne tjeneste starter, sender browseren en POST-anmodning til puds.ucweb.com/upgrade/index.xhtml, som kan ses i trafikken et stykke tid efter starten. Som svar kan han modtage en kommando om at downloade en opdatering eller et nyt modul. Under analysen gav serveren ikke sådanne kommandoer, men vi bemærkede, at når vi forsøger at åbne en PDF i browseren, foretager den en anden anmodning til den ovenfor angivne adresse, hvorefter den downloader det oprindelige bibliotek. For at udføre angrebet besluttede vi at bruge denne funktion i UC Browser: evnen til at åbne PDF ved hjælp af et indbygget bibliotek, som ikke er i APK, og som det downloader fra internettet, hvis det er nødvendigt. Det er værd at bemærke, at UC Browser teoretisk set kan tvinges til at downloade noget uden brugerinteraktion - hvis du giver et velformuleret svar på en anmodning, der udføres efter browseren er startet. Men for at gøre dette skal vi studere interaktionsprotokollen med serveren mere detaljeret, så vi besluttede, at det ville være lettere at redigere det opsnappede svar og erstatte biblioteket for at arbejde med PDF.

Så når en bruger ønsker at åbne en PDF direkte i browseren, kan følgende anmodninger ses i trafikken:

Leder efter sårbarheder i UC Browser

Først er der en POST anmodning til puds.ucweb.com/upgrade/index.xhtml, derefter
Et arkiv med et bibliotek til visning af PDF- og kontorformater downloades. Det er logisk at antage, at den første anmodning sender information om systemet (i det mindste arkitekturen til at levere det nødvendige bibliotek), og som svar på det modtager browseren nogle oplysninger om biblioteket, der skal downloades: adressen og evt. , noget andet. Problemet er, at denne anmodning er krypteret.

Anmod om fragment

Svarfragment

Leder efter sårbarheder i UC Browser

Leder efter sårbarheder i UC Browser

Selve biblioteket er pakket i ZIP og er ikke krypteret.

Leder efter sårbarheder i UC Browser

Søg efter trafikdekrypteringskode

Lad os prøve at dechifrere serversvaret. Lad os se på klassekoden com.uc.deployment.UpgradeDeployService: fra metode onStartCommand gå til com.uc.deployment.bx, og fra det til 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);
}

Vi ser dannelsen af ​​en POST-anmodning her. Vi er opmærksomme på oprettelsen af ​​en matrix på 16 bytes og dens fyldning: 0x5F, 0, 0x1F, -50 (=0xCE). Falder sammen med det, vi så i anmodningen ovenfor.

I den samme klasse kan du se en indlejret klasse, der har en anden interessant metode:

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

Metoden tager et array af bytes som input og kontrollerer, at nulbyten er 0x60 eller den tredje byte er 0xD0, og den anden byte er 1, 11 eller 0x1F. Vi ser på svaret fra serveren: nulbyten er 0x60, den anden er 0x1F, den tredje er 0x60. Det lyder som det, vi har brug for. At dømme efter linjerne ("up_decrypt", for eksempel), skal en metode kaldes her, som vil dekryptere serverens svar.
Lad os gå videre til metoden gj. Bemærk, at det første argument er byten ved offset 2 (dvs. 0x1F i vores tilfælde), og det andet er serversvaret uden
første 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;
}

Her vælger vi naturligvis en dekrypteringsalgoritme og den samme byte, der er i vores
tilfælde lig med 0x1F, angiver en af ​​tre mulige muligheder.

Vi fortsætter med at analysere koden. Efter et par hop befinder vi os i en metode med et selvforklarende navn decryptBytesByKey.

Her adskilles yderligere to bytes fra vores svar, og der opnås en streng fra dem. Det er klart, at på denne måde vælges nøglen til dekryptering af beskeden.

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

Når vi ser fremad, bemærker vi, at vi på dette stadium endnu ikke får en nøgle, men kun dens "identifikator". At få nøglen er lidt mere kompliceret.

I den næste metode føjes yderligere to parametre til de eksisterende, hvilket gør fire af dem: det magiske nummer 16, nøgleidentifikatoren, de krypterede data og en uforståelig streng (i vores tilfælde tom).

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

Efter en række overgange kommer vi frem til metoden staticBinarySafeDecryptNoB64 grænseflade com.alibaba.wireless.security.open.staticdataencrypt.IStaticDataEncryptComponent. Der er ingen klasser i hovedapplikationskoden, der implementerer denne grænseflade. Der er sådan en klasse i filen lib/armeabi-v7a/libsgmain.so, som faktisk ikke er en .so, men en .jar. Metoden vi er interesseret i implementeres som følger:

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

Her er vores liste over parametre suppleret med yderligere to heltal: 2 og 0. At dømme efter
alt, 2 betyder dekryptering, som i metoden doFinal system klasse javax.crypto.Cipher. Og alt dette overføres til en bestemt router med nummeret 10601 - dette er tilsyneladende kommandonummeret.

Efter den næste kæde af overgange finder vi en klasse, der implementerer grænsefladen IRouterComponent og metode 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);
}
}

Og også klasse JNICLibrary, hvor den oprindelige metode er deklareret doCommandNative:

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

Det betyder, at vi skal finde en metode i den oprindelige kode doCommandNative. Og det er her det sjove begynder.

Tilsløring af maskinkode

I fil libsgmain.so (som faktisk er en .jar, og hvor vi fandt implementeringen af ​​nogle krypteringsrelaterede grænseflader lige ovenfor) er der et indbygget bibliotek: libsgmainso-6.4.36.so. Vi åbner den i IDA og får en masse dialogbokse med fejl. Problemet er, at sektionsoverskriftstabellen er ugyldig. Dette gøres med vilje for at komplicere analysen.

Leder efter sårbarheder i UC Browser

Men det er ikke nødvendigt: for at indlæse en ELF-fil korrekt og analysere den, er en programoverskriftstabel tilstrækkelig. Derfor sletter vi blot sektionstabellen og nulstiller de tilsvarende felter i overskriften.

Leder efter sårbarheder i UC Browser

Åbn filen i IDA igen.

Der er to måder at fortælle den virtuelle Java-maskine, hvor nøjagtigt i det native bibliotek implementeringen af ​​en metode, der er erklæret i Java-kode som native, er placeret. Den første er at give den et artsnavn Java_pakkenavn_Klassenavn_Metodenavn.

Det andet er at registrere det, når biblioteket indlæses (i funktionen JNI_OnLoad)
ved hjælp af et funktionskald Registrer indfødte.

I vores tilfælde, hvis vi bruger den første metode, skal navnet være sådan: Java_com_taobao_wireless_security_adapter_JNICLibrary_doCommandNative.

Der er ingen sådan funktion blandt de eksporterede funktioner, hvilket betyder, at du skal lede efter et opkald Registrer indfødte.
Lad os gå til funktionen JNI_OnLoad og vi ser dette billede:

Leder efter sårbarheder i UC Browser

Hvad sker der her? Ved første øjekast er starten og slutningen af ​​funktionen typisk for ARM-arkitektur. Den første instruktion på stakken gemmer indholdet af de registre, som funktionen vil bruge i sin drift (i dette tilfælde R0, R1 og R2), samt indholdet af LR-registret, som indeholder returadressen fra funktionen . Den sidste instruktion gendanner de gemte registre, og returadressen placeres straks i pc-registret - og returnerer dermed fra funktionen. Men hvis du ser godt efter, vil du bemærke, at den næstsidste instruktion ændrer returadressen, der er gemt på stakken. Lad os regne ud, hvordan det vil være bagefter
kode eksekvering. En bestemt adresse 1xB0 indlæses i R130, 5 trækkes fra den, derefter overføres den til R0, og 0x10 tilføjes til den. Det viser sig 0xB13B. IDA mener således, at den sidste instruktion er en normal funktionsretur, men faktisk går den til den beregnede adresse 0xB13B.

Det er værd at huske på her, at ARM-processorer har to tilstande og to sæt instruktioner: ARM og Thumb. Den mindst signifikante bit af adressen fortæller processoren, hvilket instruktionssæt der bruges. Det vil sige, at adressen faktisk er 0xB13A, og en i den mindst signifikante bit angiver Thumb-tilstanden.

En lignende "adapter" er blevet tilføjet til begyndelsen af ​​hver funktion i dette bibliotek og
skraldekode. Vi vil ikke dvæle nærmere ved dem - vi husker bare
at den egentlige begyndelse af næsten alle funktioner er lidt længere væk.

Da koden ikke eksplicit hopper til 0xB13A, genkendte IDA ikke selv, at koden var placeret på dette sted. Af samme grund genkender den ikke det meste af koden i biblioteket som kode, hvilket gør analysen noget vanskelig. Vi fortæller IDA, at dette er koden, og det er, hvad der sker:

Leder efter sårbarheder i UC Browser

Tabellen starter klart ved 0xB144. Hvad er der i sub_494C?

Leder efter sårbarheder i UC Browser

Når vi kalder denne funktion i LR-registret, får vi adressen på den tidligere nævnte tabel (0xB144). I R0 - indeks i denne tabel. Det vil sige, at værdien tages fra tabellen, lægges til LR og resultatet er
adressen at gå til. Lad os prøve at beregne det: 0xB144 + [0xB144 + 8* 4] = 0xB144 + 0x120 = 0xB264. Vi går til den modtagne adresse og ser bogstaveligt talt et par nyttige instruktioner og går igen til 0xB140:

Leder efter sårbarheder i UC Browser

Nu vil der være en overgang ved offset med indeks 0x20 fra tabellen.

At dømme efter bordets størrelse vil der være mange sådanne overgange i koden. Spørgsmålet opstår, om det på en eller anden måde er muligt at håndtere dette mere automatisk uden manuelt at beregne adresser. Og scripts og muligheden for at patche kode i IDA kommer os til hjælp:

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"

Placer markøren på linje 0xB26A, kør scriptet og se overgangen til 0xB4B0:

Leder efter sårbarheder i UC Browser

IDA genkendte igen ikke dette område som en kode. Vi hjælper hende og ser et andet design der:

Leder efter sårbarheder i UC Browser

Instruktionerne efter BLX ser ikke ud til at give meget mening, det er mere som en form for forskydning. Lad os se på sub_4964:

Leder efter sårbarheder i UC Browser

Og ja, her tages et dword på adressen, der ligger i LR, tilføjet denne adresse, hvorefter værdien på den resulterende adresse tages og lægges på stakken. Der tilføjes også 4 til LR, så efter at have vendt tilbage fra funktionen, springes denne samme offset over. Hvorefter POP {R1}-kommandoen tager den resulterende værdi fra stakken. Hvis du ser på, hvad der ligger på adressen 0xB4BA + 0xEA = 0xB5A4, vil du se noget, der ligner en adressetabel:

Leder efter sårbarheder i UC Browser

For at lappe dette design skal du hente to parametre fra koden: offset og registernummeret, hvor du vil indsætte resultatet. For hvert muligt register skal du på forhånd forberede et stykke kode.

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"

Vi placerer markøren i begyndelsen af ​​strukturen, som vi vil erstatte - 0xB4B2 - og kører scriptet:

Leder efter sårbarheder i UC Browser

Ud over de allerede nævnte strukturer indeholder koden også følgende:

Leder efter sårbarheder i UC Browser

Som i det foregående tilfælde er der efter BLX-instruktionen en offset:

Leder efter sårbarheder i UC Browser

Vi tager offset til adressen fra LR, tilføjer det til LR og går derhen. 0x72044 + 0xC = 0x72050. Scriptet til dette design er ret simpelt:

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"

Resultat af scriptudførelse:

Leder efter sårbarheder i UC Browser

Når alt er lappet i funktionen, kan du pege IDA til dens rigtige begyndelse. Det vil samle al funktionskoden, og den kan dekompileres ved hjælp af HexRays.

Afkodning af strenge

Vi har lært at håndtere sløring af maskinkode i biblioteket libsgmainso-6.4.36.so fra UC Browser og modtog funktionskoden 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;
}

Lad os se nærmere på følgende linjer:

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

I funktion sub_73E24 klassenavnet bliver tydeligvis dekrypteret. Som parametre til denne funktion sendes en pointer til data svarende til krypterede data, en bestemt buffer og et nummer. Det er klart, efter at funktionen er blevet kaldt, vil der være en dekrypteret linje i bufferen, da den sendes til funktionen FindKlasse, som tager klassenavnet som den anden parameter. Derfor er tallet størrelsen af ​​bufferen eller længden af ​​linjen. Lad os prøve at tyde klassenavnet, det skulle fortælle os, om vi går i den rigtige retning. Lad os se nærmere på, hvad der sker i 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 opretter en forekomst af en container til byte-arrays af den angivne størrelse (vi vil ikke dvæle ved disse containere i detaljer). Her oprettes to sådanne beholdere: den ene indeholder linjen "DcO/lcK+h?m3c*q@" (det er let at gætte, at dette er en nøgle), den anden indeholder krypterede data. Dernæst placeres begge objekter i en bestemt struktur, som videregives til funktionen sub_6115C. Lad os også markere et felt med værdien 3 i denne struktur. Lad os se, hvad der derefter sker med denne struktur.

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

Switch-parameteren er et strukturfelt, der tidligere blev tildelt værdien 3. Se på tilfælde 3: til funktionen sub_6364C parametre overføres fra strukturen, der blev tilføjet der i den forrige funktion, dvs. nøglen og krypterede data. Hvis man ser nærmere på sub_6364C, kan du genkende RC4-algoritmen i den.

Vi har en algoritme og en nøgle. Lad os prøve at tyde klassenavnet. Her er hvad der skete: com/taobao/wireless/security/adapter/JNICLibrary. Store! Vi er på rette vej.

Kommandotræ

Nu skal vi finde en udfordring Registrer indfødte, som vil pege os på funktionen doCommandNative. Lad os se på funktionerne kaldet fra JNI_OnLoad, og vi finder det i 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;
}

Og faktisk er en indfødt metode med navnet registreret her doCommandNative. Nu kender vi hans adresse. Lad os se, hvad han gør.

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

Ved navnet kan du gætte, at her er indgangspunktet for alle de funktioner, som udviklerne besluttede at overføre til det oprindelige bibliotek. Vi er interesserede i funktion nummer 10601.

Du kan se fra koden, at kommandonummeret producerer tre tal: kommando/10000, kommando % 10000 / 100 и kommando % 10, altså i vores tilfælde 1, 6 og 1. Disse tre tal, samt en pegepind til JNIEnv og de argumenter, der sendes til funktionen, tilføjes til en struktur og videregives. Ved at bruge de tre opnåede tal (lad os betegne dem N1, N2 og N3), bygges et kommandotræ.

Noget som dette:

Leder efter sårbarheder i UC Browser

Træet udfyldes dynamisk JNI_OnLoad.
Tre tal koder stien i træet. Hvert blad af træet indeholder den pakkede adresse for den tilsvarende funktion. Nøglen er i den overordnede node. At finde det sted i koden, hvor den funktion, vi har brug for, er tilføjet til træet, er ikke svært, hvis du forstår alle de anvendte strukturer (vi beskriver dem ikke for ikke at blæse en allerede ret stor artikel op).

Mere sløring

Vi modtog adressen på den funktion, der skulle dekryptere trafik: 0x5F1AC. Men det er for tidligt at glæde sig: Udviklerne af UC Browser har forberedt endnu en overraskelse til os.

Efter at have modtaget parametrene fra det array, der blev dannet i Java-koden, får vi
til funktionen på adressen 0x4D070. Og her venter os en anden type kodeforvirring.

Vi sætter to indekser i R7 og R4:

Leder efter sårbarheder i UC Browser

Vi flytter det første indeks til R11:

Leder efter sårbarheder i UC Browser

For at få en adresse fra en tabel, brug et indeks:

Leder efter sårbarheder i UC Browser

Efter at have gået til den første adresse, bruges det andet indeks, som er i R4. Der er 230 elementer i tabellen.

Hvad skal man gøre ved det? Du kan fortælle IDA, at dette er en switch: Rediger -> Andet -> Angiv switch-idiom.

Leder efter sårbarheder i UC Browser

Den resulterende kode er forfærdelig. Men når du går gennem dens jungle, kan du bemærke et opkald til en funktion, som vi allerede kender sub_6115C:

Leder efter sårbarheder i UC Browser

Der var en switch, hvor der i tilfælde 3 var en dekryptering ved hjælp af RC4-algoritmen. Og i dette tilfælde udfyldes strukturen, der sendes til funktionen, fra de parametre, der er sendt til doCommandNative. Lad os huske, hvad vi havde der magicInt med værdien 16. Vi ser på det tilsvarende tilfælde - og efter flere overgange finder vi koden, som algoritmen kan identificeres med.

Leder efter sårbarheder i UC Browser

Dette er AES!

Algoritmen eksisterer, det eneste, der er tilbage, er at opnå dens parametre: tilstand, nøgle og muligvis initialiseringsvektoren (dens tilstedeværelse afhænger af AES-algoritmens driftstilstand). Strukturen med dem skal dannes et sted før funktionskaldet sub_6115C, men denne del af koden er særligt godt sløret, så ideen opstår at patche koden, så alle parametre for dekrypteringsfunktionen dumpes ind i en fil.

Lappe

For ikke at skrive al patchkoden i assemblersprog manuelt, kan du starte Android Studio, skrive en funktion der, der modtager de samme inputparametre som vores dekrypteringsfunktion og skrive til en fil, og derefter kopiere og indsætte koden, som compileren vil frembringe.

Vores venner fra UC Browser-teamet sørgede også for bekvemmeligheden ved at tilføje kode. Lad os huske, at i begyndelsen af ​​hver funktion har vi skraldekode, som nemt kan erstattes med enhver anden. Meget praktisk 🙂 I begyndelsen af ​​målfunktionen er der dog ikke plads nok til koden, der gemmer alle parametre i en fil. Jeg var nødt til at dele den op i dele og bruge skraldeblokke fra nabofunktioner. Der var fire dele i alt.

Første del:

Leder efter sårbarheder i UC Browser

I ARM-arkitekturen sendes de første fire funktionsparametre gennem registrene R0-R3, resten, hvis nogen, føres gennem stakken. LR-registret bærer returadressen. Alt dette skal gemmes, så funktionen kan fungere, efter at vi har dumpet dens parametre. Vi skal også gemme alle de registre, som vi vil bruge i processen, så vi gør PUSH.W {R0-R10,LR}. I R7 får vi adressen på listen over parametre sendt til funktionen via stakken.

Brug af funktionen åben lad os åbne filen /data/local/tmp/aes i "ab"-tilstand
altså til tilføjelse. I R0 indlæser vi adressen på filnavnet, i R1 - adressen på linjen, der angiver tilstanden. Og her slutter skraldekoden, så vi går videre til næste funktion. For at det skal fortsætte med at fungere, sætter vi i begyndelsen overgangen til den rigtige kode for funktionen, omgå skraldet, og i stedet for skraldet tilføjer vi en fortsættelse af lappen.

Leder efter sårbarheder i UC Browser

Ringer åben.

Funktionens første tre parametre aes har type int. Da vi gemte registrene i stakken i begyndelsen, kan vi simpelthen videregive funktionen fwrite deres adresser på stakken.

Leder efter sårbarheder i UC Browser

Dernæst har vi tre strukturer, der indeholder datastørrelsen og en pointer til dataene for nøglen, initialiseringsvektoren og krypterede data.

Leder efter sårbarheder i UC Browser

Til sidst skal du lukke filen, gendan registrene og overføre kontrollen til den rigtige funktion aes.

Vi indsamler en APK med et patchet bibliotek, signerer den, uploader den til enheden/emulatoren og starter den. Vi ser, at vores losseplads bliver skabt, og der bliver skrevet en masse data. Browseren bruger ikke kun kryptering til trafik, og al kryptering går gennem den pågældende funktion. Men af ​​en eller anden grund er de nødvendige data der ikke, og den påkrævede anmodning er ikke synlig i trafikken. For ikke at vente, indtil UC Browser ærger sig til at fremsætte den nødvendige anmodning, lad os tage det krypterede svar fra serveren, der blev modtaget tidligere, og patch applikationen igen: tilføj dekrypteringen til onCreate af hovedaktiviteten.

    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

Vi samler, signerer, installerer, lancerer. Vi modtager en NullPointerException, fordi metoden returnerede null.

Under yderligere analyse af koden blev der opdaget en funktion, der dechifrerer interessante linjer: "META-INF/" og ".RSA". Det ser ud til, at applikationen bekræfter sit certifikat. Eller endda genererer nøgler fra det. Jeg ønsker ikke rigtig at beskæftige mig med, hvad der sker med certifikatet, så vi smider det bare det rigtige certifikat. Lad os lappe den krypterede linje, så vi i stedet for "META-INF/" får "BLABLINF/", opretter en mappe med det navn i APK'en og tilføjer egernbrowsercertifikatet der.

Vi samler, signerer, installerer, lancerer. Bingo! Vi har nøglen!

MitM

Vi modtog en nøgle og en initialiseringsvektor svarende til nøglen. Lad os prøve at dekryptere serversvaret i CBC-tilstand.

Leder efter sårbarheder i UC Browser

Vi ser arkivets URL, noget der ligner MD5, "extract_unzipsize" og et nummer. Vi tjekker: MD5 i arkivet er den samme, størrelsen på det udpakkede bibliotek er den samme. Vi forsøger at patche dette bibliotek og give det til browseren. For at vise, at vores patchede bibliotek er blevet indlæst, lancerer vi en hensigt om at oprette en SMS med teksten "PWNED!" Vi erstatter to svar fra serveren: puds.ucweb.com/upgrade/index.xhtml og for at downloade arkivet. I den første erstatter vi MD5 (størrelsen ændres ikke efter udpakning), i den anden giver vi arkivet med det patchede bibliotek.

Browseren forsøger at downloade arkivet flere gange, hvorefter den giver en fejl. Tilsyneladende noget
han kan ikke lide. Som et resultat af at analysere dette skumle format viste det sig, at serveren også transmitterer størrelsen af ​​arkivet:

Leder efter sårbarheder i UC Browser

Det er kodet i LEB128. Efter patchen ændrede størrelsen på arkivet med biblioteket sig lidt, så browseren vurderede, at arkivet var downloadet skævt, og efter flere forsøg kastede det en fejl.

Vi tilpasser størrelsen på arkivet... Og – sejr! 🙂 Resultatet er i videoen.

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

Konsekvenser og udviklerreaktion

På samme måde kunne hackere bruge den usikre funktion i UC Browser til at distribuere og køre ondsindede biblioteker. Disse biblioteker fungerer i browserens kontekst, så de vil modtage alle dens systemtilladelser. Som et resultat, muligheden for at vise phishing-vinduer samt adgang til arbejdsfilerne for det orange kinesiske egern, herunder logins, adgangskoder og cookies gemt i databasen.

Vi kontaktede udviklerne af UC Browser og informerede dem om det problem, vi fandt, forsøgte at påpege sårbarheden og dens fare, men de diskuterede ikke noget med os. I mellemtiden fortsatte browseren med at fremvise sin farlige funktion i almindeligt syn. Men når vi først afslørede detaljerne om sårbarheden, var det ikke længere muligt at ignorere det som før. 27. marts var
en ny version af UC Browser 12.10.9.1193 blev frigivet, som fik adgang til serveren via HTTPS: puds.ucweb.com/upgrade/index.xhtml.

Derudover resulterede forsøg på at åbne en PDF i en browser efter "rettelsen" og indtil tidspunktet for skrivning af denne artikel i en fejlmeddelelse med teksten "Ups, noget gik galt!" Der blev ikke foretaget en anmodning til serveren, da man forsøgte at åbne en PDF, men en anmodning blev fremsat, da browseren blev startet, hvilket antyder den fortsatte mulighed for at downloade eksekverbar kode i strid med Google Play-reglerne.

Kilde: www.habr.com

Tilføj en kommentar