Ser etter sårbarheter i UC Browser

Ser etter sårbarheter i UC Browser

Innledning

I slutten av mars har vi rapportert, at de oppdaget en skjult evne til å laste og kjøre ubekreftet kode i UC Browser. I dag skal vi se nærmere på hvordan denne nedlastingen skjer og hvordan hackere kan bruke den til sine egne formål.

For en tid siden ble UC Browser annonsert og distribuert svært aggressivt: den ble installert på brukernes enheter ved hjelp av skadelig programvare, distribuert fra forskjellige nettsteder under dekke av videofiler (dvs. brukere trodde de lastet ned for eksempel en pornovideo, men mottok i stedet en APK med denne nettleseren), brukte skumle bannere med meldinger om at nettleseren var utdatert, sårbar og sånt. I den offisielle UC Browser-gruppen på VK er det tema, der brukere kan klage på urettferdig annonsering, er det mange eksempler der. I 2016 var det jevnt videoannonsering på russisk (ja, reklame for en annonseblokkerende nettleser).

I skrivende stund har UC Browser over 500 000 000 installasjoner på Google Play. Dette er imponerende – bare Google Chrome har mer. Blant anmeldelsene kan du se ganske mange klager på annonsering og omdirigeringer til noen applikasjoner på Google Play. Dette var grunnen til vår forskning: vi bestemte oss for å se om UC Browser gjorde noe dårlig. Og det viste seg at han gjør det!

I applikasjonskoden ble muligheten til å laste ned og kjøre kjørbar kode oppdaget, som er i strid med reglene for publisering av søknader på Google Play. I tillegg til å laste ned kjørbar kode, gjør UC Browser det på en usikker måte, som kan brukes til å starte et MitM-angrep. La oss se om vi kan gjennomføre et slikt angrep.

Alt som er skrevet nedenfor er relevant for versjonen av UC Browser som var tilgjengelig på Google Play på tidspunktet for studien:

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

Angrepsvektor

I UC Browser-manifestet kan du finne 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 tjenesten starter, sender nettleseren en POST-forespørsel til puds.ucweb.com/upgrade/index.xhtml, som kan sees i trafikken en stund etter start. Som svar kan han motta en kommando om å laste ned en oppdatering eller ny modul. Under analysen ga ikke serveren slike kommandoer, men vi la merke til at når vi prøver å åpne en PDF i nettleseren, sender den en ny forespørsel til adressen spesifisert ovenfor, hvoretter den laster ned det opprinnelige biblioteket. For å utføre angrepet bestemte vi oss for å bruke denne funksjonen til UC Browser: muligheten til å åpne PDF ved å bruke et innebygd bibliotek, som ikke er i APK og som det laster ned fra Internett om nødvendig. Det er verdt å merke seg at teoretisk sett kan UC Browser bli tvunget til å laste ned noe uten brukerinteraksjon - hvis du gir et velutformet svar på en forespørsel som utføres etter at nettleseren er startet. Men for å gjøre dette, må vi studere interaksjonsprotokollen med serveren mer detaljert, så vi bestemte oss for at det ville være lettere å redigere det avlyttede svaret og erstatte biblioteket for å jobbe med PDF.

Så når en bruker ønsker å åpne en PDF direkte i nettleseren, kan følgende forespørsler ses i trafikken:

Ser etter sårbarheter i UC Browser

Først er det en POST-forespørsel til puds.ucweb.com/upgrade/index.xhtml, deretter
Et arkiv med bibliotek for visning av PDF- og kontorformater lastes ned. Det er logisk å anta at den første forespørselen overfører informasjon om systemet (i det minste arkitekturen for å gi det nødvendige biblioteket), og som svar på det mottar nettleseren noe informasjon om biblioteket som må lastes ned: adressen og ev. , noe annet. Problemet er at denne forespørselen er kryptert.

Be om fragment

Svarfragment

Ser etter sårbarheter i UC Browser

Ser etter sårbarheter i UC Browser

Selve biblioteket er pakket i ZIP og er ikke kryptert.

Ser etter sårbarheter i UC Browser

Søk etter trafikkdekrypteringskode

La oss prøve å tyde serverresponsen. La oss se på klassekoden com.uc.deployment.UpgradeDeployService: fra metode påStartCommand gå til com.uc.deployment.bx, og fra den 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 av en POST-forespørsel her. Vi legger merke til opprettelsen av en matrise på 16 byte og dens fylling: 0x5F, 0, 0x1F, -50 (=0xCE). Sammenfaller med det vi så i forespørselen ovenfor.

I samme klasse kan du se en nestet klasse som har en annen 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 tar en rekke byte som input og kontrollerer at nullbyten er 0x60 eller den tredje byten er 0xD0, og den andre byten er 1, 11 eller 0x1F. Vi ser på svaret fra serveren: nullbyten er 0x60, den andre er 0x1F, den tredje er 0x60. Høres ut som det vi trenger. Etter linjene å dømme ("up_decrypt", for eksempel), bør en metode kalles her som vil dekryptere serverens svar.
La oss gå videre til metoden gj. Merk at det første argumentet er byten ved offset 2 (dvs. 0x1F i vårt tilfelle), og det andre er serversvaret uten
første 16 byte.

     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 velger vi selvsagt en dekrypteringsalgoritme, og den samme byten som er i vår
sak lik 0x1F, angir ett av tre mulige alternativer.

Vi fortsetter å analysere koden. Etter et par hopp befinner vi oss i en metode med et selvforklarende navn decryptBytesByKey.

Her skilles ytterligere to byte fra responsen vår, og en streng hentes fra dem. Det er tydelig at på denne måten velges nøkkelen for å dekryptere meldingen.

    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 fremover, merker vi at vi på dette stadiet ennå ikke får en nøkkel, men bare dens "identifikator". Å få nøkkelen er litt mer komplisert.

I den neste metoden legges ytterligere to parametere til de eksisterende, og gjør fire av dem: det magiske tallet 16, nøkkelidentifikatoren, de krypterte dataene og en uforståelig streng (i vårt tilfelle tom).

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

Etter en rekke overganger kommer vi frem til metoden staticBinarySafeDecryptNoB64 grensesnitt com.alibaba.wireless.security.open.staticdataencrypt.IStaticDataEncryptComponent. Det er ingen klasser i hovedapplikasjonskoden som implementerer dette grensesnittet. Det er en slik klasse i filen lib/armeabi-v7a/libsgmain.so, som egentlig ikke er en .so, men en .jar. Metoden vi er interessert 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 vår liste over parametere supplert med ytterligere to heltall: 2 og 0. Dømme etter
alt, 2 betyr dekryptering, som i metoden doFinal systemklasse javax.crypto.Cipher. Og alt dette overføres til en bestemt ruter med nummeret 10601 - dette er tilsynelatende kommandonummeret.

Etter neste kjede av overganger finner vi en klasse som implementerer grensesnittet 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, der den opprinnelige metoden er deklarert doCommandNative:

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

Dette betyr at vi må finne en metode i den opprinnelige koden doCommandNative. Og det er her moroa begynner.

Tilsløring av maskinkode

I fil libsgmain.so (som faktisk er en .jar og der vi fant implementeringen av noen krypteringsrelaterte grensesnitt like ovenfor) er det ett innfødt bibliotek: libsgmainso-6.4.36.so. Vi åpner den i IDA og får en haug med dialogbokser med feil. Problemet er at seksjonsoverskriftstabellen er ugyldig. Dette gjøres med vilje for å komplisere analysen.

Ser etter sårbarheter i UC Browser

Men det er ikke nødvendig: for å laste en ELF-fil riktig og analysere den, er en programoverskriftstabell tilstrekkelig. Derfor sletter vi ganske enkelt seksjonstabellen, og nullstiller de tilsvarende feltene i overskriften.

Ser etter sårbarheter i UC Browser

Åpne filen i IDA igjen.

Det er to måter å fortelle den virtuelle Java-maskinen hvor nøyaktig i det opprinnelige biblioteket implementeringen av en metode som er erklært i Java-kode som native befinner seg. Den første er å gi den et artsnavn Java_pakkenavn_Klassenavn_Metodenavn.

Den andre er å registrere den når du laster inn biblioteket (i funksjonen JNI_OnLoad)
ved hjelp av et funksjonskall Registrer innfødte.

I vårt tilfelle, hvis vi bruker den første metoden, bør navnet være slik: Java_com_taobao_wireless_security_adapter_JNICLibrary_doCommandNative.

Det er ingen slik funksjon blant de eksporterte funksjonene, noe som betyr at du må se etter en samtale Registrer innfødte.
La oss gå til funksjonen JNI_OnLoad og vi ser dette bildet:

Ser etter sårbarheter i UC Browser

Hva foregår her? Ved første øyekast er starten og slutten av funksjonen typisk for ARM-arkitektur. Den første instruksjonen på stabelen lagrer innholdet i registrene som funksjonen vil bruke i sin operasjon (i dette tilfellet R0, R1 og R2), samt innholdet i LR-registeret, som inneholder returadressen fra funksjonen . Den siste instruksen gjenoppretter de lagrede registre, og returadressen legges umiddelbart inn i PC-registeret - og returnerer dermed fra funksjonen. Men hvis du ser nøye etter, vil du legge merke til at den nest siste instruksjonen endrer returadressen som er lagret på stabelen. La oss beregne hvordan det vil bli etterpå
kodeutførelse. En bestemt adresse 1xB0 lastes inn i R130, 5 trekkes fra den, deretter overføres den til R0 og 0x10 legges til den. Det viser seg 0xB13B. Dermed tror IDA at den siste instruksjonen er en normal funksjonsretur, men faktisk går den til den beregnede adressen 0xB13B.

Det er verdt å huske her at ARM-prosessorer har to moduser og to sett med instruksjoner: ARM og Thumb. Den minst signifikante biten av adressen forteller prosessoren hvilket instruksjonssett som brukes. Det vil si at adressen faktisk er 0xB13A, og en i den minst signifikante biten indikerer Thumb-modusen.

En lignende "adapter" er lagt til i begynnelsen av hver funksjon i dette biblioteket og
søppelkode. Vi vil ikke dvele mer ved dem i detalj - vi husker bare
at den egentlige begynnelsen av nesten alle funksjoner er litt lenger unna.

Siden koden ikke eksplisitt hopper til 0xB13A, gjenkjente ikke IDA selv at koden var lokalisert på dette stedet. Av samme grunn gjenkjenner den ikke det meste av koden i biblioteket som kode, noe som gjør analyse noe vanskelig. Vi forteller IDA at dette er koden, og dette er hva som skjer:

Ser etter sårbarheter i UC Browser

Tabellen starter helt klart på 0xB144. Hva er i sub_494C?

Ser etter sårbarheter i UC Browser

Ved oppkalling av denne funksjonen i LR-registeret får vi adressen til den tidligere nevnte tabellen (0xB144). I R0 - indeks i denne tabellen. Det vil si at verdien tas fra tabellen, legges til LR og resultatet blir
adressen å gå til. La oss prøve å beregne det: 0xB144 + [0xB144 + 8* 4] = 0xB144 + 0x120 = 0xB264. Vi går til den mottatte adressen og ser bokstavelig talt et par nyttige instruksjoner og går igjen til 0xB140:

Ser etter sårbarheter i UC Browser

Nå vil det være en overgang ved offset med indeks 0x20 fra tabellen.

Etter størrelsen på tabellen å dømme vil det være mange slike overganger i koden. Spørsmålet oppstår om det på en eller annen måte er mulig å håndtere dette mer automatisk, uten å manuelt beregne adresser. Og skript og muligheten til å lappe kode i IDA kommer oss til hjelp:

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"

Plasser markøren på linje 0xB26A, kjør skriptet og se overgangen til 0xB4B0:

Ser etter sårbarheter i UC Browser

IDA gjenkjente igjen ikke dette området som en kode. Vi hjelper henne og ser et annet design der:

Ser etter sårbarheter i UC Browser

Instruksjonene etter BLX ser ikke ut til å gi mye mening, det er mer som en slags forskyvning. La oss se på sub_4964:

Ser etter sårbarheter i UC Browser

Og faktisk, her tas et dword på adressen som ligger i LR, lagt til denne adressen, hvoretter verdien på den resulterende adressen tas og legges på stabelen. Dessuten legges 4 til LR slik at etter retur fra funksjonen hoppes denne samme forskyvningen over. Deretter tar POP {R1}-kommandoen den resulterende verdien fra stabelen. Hvis du ser på hva som ligger på adressen 0xB4BA + 0xEA = 0xB5A4, vil du se noe som ligner på en adressetabell:

Ser etter sårbarheter i UC Browser

For å lappe dette designet, må du få to parametere fra koden: forskyvningen og registernummeret du vil sette resultatet i. For hvert mulig register må du forberede en kodebit på forhånd.

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 plasserer markøren i begynnelsen av strukturen som vi ønsker å erstatte - 0xB4B2 - og kjører skriptet:

Ser etter sårbarheter i UC Browser

I tillegg til de allerede nevnte strukturene inneholder koden også følgende:

Ser etter sårbarheter i UC Browser

Som i forrige tilfelle, etter BLX-instruksjonen er det en forskyvning:

Ser etter sårbarheter i UC Browser

Vi tar offset til adressen fra LR, legger det til LR og går dit. 0x72044 + 0xC = 0x72050. Skriptet for dette designet er ganske enkelt:

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 av skriptkjøring:

Ser etter sårbarheter i UC Browser

Når alt er lappet i funksjonen, kan du peke IDA til dens virkelige begynnelse. Den vil sette sammen all funksjonskoden, og den kan dekompileres ved hjelp av HexRays.

Dekoding av strenger

Vi har lært å håndtere obfuskering av maskinkode i biblioteket libsgmainso-6.4.36.so fra UC Browser og mottok funksjonskoden 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;
}

La oss se nærmere på følgende linjer:

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

I funksjon sub_73E24 klassenavnet blir tydelig dekryptert. Som parametere for denne funksjonen sendes en peker til data som ligner på krypterte data, en viss buffer og et tall. Åpenbart, etter å ha kalt funksjonen, vil det være en dekryptert linje i bufferen, siden den sendes til funksjonen FinnKlasse, som tar klassenavnet som den andre parameteren. Derfor er tallet størrelsen på bufferen eller lengden på linjen. La oss prøve å tyde klassenavnet, det skal fortelle oss om vi går i riktig retning. La oss se nærmere på hva som skjer 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;
}

Funksjon sub_7AF78 oppretter en forekomst av en beholder for byte-matriser av den angitte størrelsen (vi vil ikke dvele på disse beholderne i detalj). Her opprettes to slike beholdere: den ene inneholder linjen "DcO/lcK+h?m3c*q@" (det er lett å gjette at dette er en nøkkel), den andre inneholder krypterte data. Deretter plasseres begge objektene i en bestemt struktur, som sendes til funksjonen sub_6115C. La oss også merke et felt med verdien 3 i denne strukturen La oss se hva som skjer med denne strukturen videre.

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 som tidligere ble tildelt verdien 3. Se på tilfelle 3: til funksjonen sub_6364C parametere sendes fra strukturen som ble lagt til der i forrige funksjon, dvs. nøkkelen og krypterte data. Hvis du ser nøye på sub_6364C, kan du gjenkjenne RC4-algoritmen i den.

Vi har en algoritme og en nøkkel. La oss prøve å tyde klassenavnet. Her er hva som skjedde: com/taobao/wireless/security/adapter/JNICLibrary. Flott! Vi er på rett vei.

Kommandotre

Nå må vi finne en utfordring Registrer innfødte, som vil peke oss til funksjonen doCommandNative. La oss se på funksjonene kalt fra JNI_OnLoad, og vi finner 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 innfødt metode med navnet registrert her doCommandNative. Nå vet vi adressen hans. La oss se hva han gjø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 gjette at her er inngangspunktet for alle funksjonene som utviklerne bestemte seg for å overføre til det opprinnelige biblioteket. Vi er interessert i funksjon nummer 10601.

Du kan se fra koden at kommandonummeret produserer tre tall: kommando/10000, kommando % 10000 / 100 и kommando % 10, dvs. i vårt tilfelle, 1, 6 og 1. Disse tre tallene, samt en peker til JNIEnv og argumentene som sendes til funksjonen legges til en struktur og sendes videre. Ved å bruke de tre tallene som er oppnådd (la oss betegne dem N1, N2 og N3), bygges et kommandotre.

Noe sånt som dette:

Ser etter sårbarheter i UC Browser

Treet fylles ut dynamisk JNI_OnLoad.
Tre tall koder for banen i treet. Hvert blad på treet inneholder den pockede adressen til den tilsvarende funksjonen. Nøkkelen er i overordnet node. Å finne stedet i koden der funksjonen vi trenger legges til i treet er ikke vanskelig hvis du forstår alle strukturene som brukes (vi beskriver dem ikke for ikke å blåse opp en allerede ganske stor artikkel).

Mer forvirring

Vi mottok adressen til funksjonen som skal dekryptere trafikk: 0x5F1AC. Men det er for tidlig å glede seg: utviklerne av UC Browser har forberedt en ny overraskelse for oss.

Etter å ha mottatt parametrene fra matrisen som ble dannet i Java-koden, får vi
til funksjonen på adressen 0x4D070. Og her venter en annen type kodeforvirring på oss.

Vi legger to indekser i R7 og R4:

Ser etter sårbarheter i UC Browser

Vi skifter den første indeksen til R11:

Ser etter sårbarheter i UC Browser

For å få en adresse fra en tabell, bruk en indeks:

Ser etter sårbarheter i UC Browser

Etter å ha gått til den første adressen, brukes den andre indeksen, som er i R4. Det er 230 elementer i tabellen.

Hva skal man gjøre med det? Du kan fortelle IDA at dette er en bryter: Rediger -> Annet -> Spesifiser bryterspråk.

Ser etter sårbarheter i UC Browser

Den resulterende koden er skummel. Men når du tar deg gjennom jungelen, kan du legge merke til et anrop til en funksjon som allerede er kjent for oss sub_6115C:

Ser etter sårbarheter i UC Browser

Det var en bryter der det i tilfelle 3 var en dekryptering ved hjelp av RC4-algoritmen. Og i dette tilfellet fylles strukturen som sendes til funksjonen fra parameterne som sendes til doCommandNative. La oss huske hva vi hadde der magicInt med verdien 16. Vi ser på det tilsvarende tilfellet - og etter flere overganger finner vi koden som algoritmen kan identifiseres med.

Ser etter sårbarheter i UC Browser

Dette er AES!

Algoritmen eksisterer, alt som gjenstår er å få dens parametere: modus, nøkkel og muligens initialiseringsvektoren (dens tilstedeværelse avhenger av driftsmodusen til AES-algoritmen). Strukturen med dem må dannes et sted før funksjonskallet sub_6115C, men denne delen av koden er spesielt godt tilslørt, så ideen oppstår om å lappe koden slik at alle parametere til dekrypteringsfunksjonen blir dumpet inn i en fil.

Lapp

For ikke å skrive all oppdateringskoden på assemblerspråk manuelt, kan du starte Android Studio, skrive en funksjon der som mottar de samme inngangsparametrene som vår dekrypteringsfunksjon og skrive til en fil, og deretter kopiere og lime inn koden som kompilatoren vil generere.

Våre venner fra UC Browser-teamet tok seg også av bekvemmeligheten med å legge til kode. La oss huske at i begynnelsen av hver funksjon har vi søppelkode som enkelt kan erstattes med en hvilken som helst annen. Veldig praktisk 🙂 I begynnelsen av målfunksjonen er det imidlertid ikke nok plass til koden som lagrer alle parameterne i en fil. Jeg måtte dele den opp i deler og bruke søppelblokker fra nabofunksjoner. Det var fire deler totalt.

Første del:

Ser etter sårbarheter i UC Browser

I ARM-arkitekturen sendes de fire første funksjonsparametrene gjennom registrene R0-R3, resten, hvis noen, blir sendt gjennom stabelen. LR-registeret bærer returadressen. Alt dette må lagres slik at funksjonen kan fungere etter at vi har dumpet parameterne. Vi må også lagre alle registrene som vi skal bruke i prosessen, så vi gjør PUSH.W {R0-R10,LR}. I R7 får vi adressen til listen over parametere som sendes til funksjonen via stabelen.

Bruker funksjonen åpen la oss åpne filen /data/local/tmp/aes i "ab"-modus
dvs. for tillegg. I R0 laster vi inn adressen til filnavnet, i R1 - adressen til linjen som indikerer modusen. Og her slutter søppelkoden, så vi går videre til neste funksjon. For at det skal fortsette å fungere, legger vi i begynnelsen overgangen til den virkelige koden til funksjonen, omgår søppelet, og i stedet for søppelet legger vi til en fortsettelse av lappen.

Ser etter sårbarheter i UC Browser

Ringer åpen.

De tre første parameterne til funksjonen aes har type int. Siden vi lagret registrene i stabelen i begynnelsen, kan vi ganske enkelt sende funksjonen fskriv deres adresser på stabelen.

Ser etter sårbarheter i UC Browser

Deretter har vi tre strukturer som inneholder datastørrelsen og en peker til dataene for nøkkelen, initialiseringsvektoren og krypterte data.

Ser etter sårbarheter i UC Browser

På slutten lukker du filen, gjenoppretter registrene og overfører kontrollen til den virkelige funksjonen aes.

Vi samler inn en APK med et lappet bibliotek, signerer den, laster den opp til enheten/emulatoren og starter den. Vi ser at dumpen vår blir opprettet, og det skrives mye data der. Nettleseren bruker kryptering ikke bare for trafikk, og all kryptering går gjennom den aktuelle funksjonen. Men av en eller annen grunn er ikke de nødvendige dataene der, og den nødvendige forespørselen er ikke synlig i trafikken. For ikke å vente til UC-nettleseren er villig til å gjøre den nødvendige forespørselen, la oss ta det krypterte svaret fra serveren mottatt tidligere og lappe applikasjonen på nytt: legg til dekrypteringen til onCreate av 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 monterer, signerer, installerer, lanserer. Vi mottar et NullPointerException fordi metoden returnerte null.

Under videre analyse av koden ble det oppdaget en funksjon som tyder interessante linjer: “META-INF/” og “.RSA”. Det ser ut til at applikasjonen bekrefter sertifikatet. Eller til og med genererer nøkler fra den. Jeg ønsker egentlig ikke å forholde meg til hva som skjer med sertifikatet, så vi gir det rett sertifikat. La oss lappe den krypterte linjen slik at vi i stedet for "META-INF/" får "BLABLINF/", opprett en mappe med det navnet i APK-en og legg til squirrel-nettlesersertifikatet der.

Vi monterer, signerer, installerer, lanserer. Bingo! Vi har nøkkelen!

MITM

Vi mottok en nøkkel og en initialiseringsvektor lik nøkkelen. La oss prøve å dekryptere serverresponsen i CBC-modus.

Ser etter sårbarheter i UC Browser

Vi ser arkivets URL, noe som ligner på MD5, "extract_unzipsize" og et nummer. Vi sjekker: MD5-en til arkivet er den samme, størrelsen på det utpakkede biblioteket er den samme. Vi prøver å lappe dette biblioteket og gi det til nettleseren. For å vise at biblioteket vårt har lastet inn, vil vi lansere en intensjon om å lage en SMS med teksten "PWNED!" Vi vil erstatte to svar fra serveren: puds.ucweb.com/upgrade/index.xhtml og for å laste ned arkivet. I den første erstatter vi MD5 (størrelsen endres ikke etter utpakking), i den andre gir vi arkivet med det lappede biblioteket.

Nettleseren prøver å laste ned arkivet flere ganger, hvoretter det gir en feilmelding. Tilsynelatende noe
han liker ikke. Som et resultat av å analysere dette grumsete formatet, viste det seg at serveren også overfører størrelsen på arkivet:

Ser etter sårbarheter i UC Browser

Den er kodet i LEB128. Etter oppdateringen endret størrelsen på arkivet med biblioteket seg litt, så nettleseren vurderte at arkivet ble lastet ned skjevt, og etter flere forsøk ga det en feil.

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

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

Konsekvenser og utbyggerreaksjon

På samme måte kan hackere bruke den usikre funksjonen til UC Browser til å distribuere og kjøre ondsinnede biblioteker. Disse bibliotekene vil fungere i sammenheng med nettleseren, så de vil motta alle systemtillatelsene. Som et resultat, muligheten til å vise phishing-vinduer, samt tilgang til arbeidsfilene til det oransje kinesiske ekornet, inkludert pålogginger, passord og informasjonskapsler lagret i databasen.

Vi kontaktet utviklerne av UC Browser og informerte dem om problemet vi fant, prøvde å påpeke sårbarheten og dens fare, men de diskuterte ikke noe med oss. I mellomtiden fortsatte nettleseren å vise frem den farlige funksjonen i synlig skue. Men når vi først avslørte detaljene om sårbarheten, var det ikke lenger mulig å ignorere det som før. 27. mars var
en ny versjon av UC Browser 12.10.9.1193 ble utgitt, som fikk tilgang til serveren via HTTPS: puds.ucweb.com/upgrade/index.xhtml.

I tillegg, etter "fiksen" og frem til tidspunktet for skriving av denne artikkelen, resulterte forsøk på å åpne en PDF i en nettleser i en feilmelding med teksten "Beklager, noe gikk galt!" Det ble ikke sendt en forespørsel til serveren ved forsøk på å åpne en PDF, men en forespørsel ble gjort da nettleseren ble startet, noe som antyder fortsatt evne til å laste ned kjørbar kode i strid med Google Play-reglene.

Kilde: www.habr.com

Legg til en kommentar