Op soek na kwesbaarhede in UC Browser

Op soek na kwesbaarhede in UC Browser

Inleiding

Einde Maart het ons gerapporteer, dat hulle 'n verborge vermoë ontdek het om ongeverifieerde kode in UC Browser te laai en uit te voer. Vandag sal ons in detail kyk hoe hierdie aflaai plaasvind en hoe hackers dit vir hul eie doeleindes kan gebruik.

'n Tyd gelede is UC Browser baie aggressief geadverteer en versprei: dit is op gebruikers se toestelle geïnstalleer met wanware, versprei vanaf verskeie werwe onder die dekmantel van videolêers (d.w.s. gebruikers het gedink hulle laai byvoorbeeld 'n pornvideo af, maar het eerder 'n APK met hierdie blaaier ontvang), het skrikwekkende baniere gebruik met boodskappe dat die blaaier verouderd, kwesbaar is en sulke goed. In die amptelike UC Browser-groep op VK is daar tema, waarin gebruikers kan kla oor onregverdige advertensies, is daar baie voorbeelde daar. In 2016 was daar selfs video-advertensies in Russies (ja, adverteer vir 'n blaaier wat advertensies blokkeer).

Ten tyde van die skryf daarvan het UC Browser meer as 500 installasies op Google Play. Dit is indrukwekkend - net Google Chrome het meer. Onder die resensies kan jy heelwat klagtes sien oor advertensies en herleidings na sommige toepassings op Google Play. Dit was die rede vir ons navorsing: ons het besluit om te kyk of UC Browser iets sleg doen. En dit het geblyk dat hy dit doen!

In die toepassingskode is die vermoë om uitvoerbare kode af te laai en uit te voer ontdek, wat strydig is met die reëls vir die publikasie van aansoeke op Google Play. Benewens die aflaai van uitvoerbare kode, doen UC Browser dit op 'n onveilige manier, wat gebruik kan word om 'n MitM-aanval te loods. Kom ons kyk of ons so 'n aanval kan uitvoer.

Alles wat hieronder geskryf is, is relevant vir die weergawe van UC Browser wat ten tyde van die studie op Google Play beskikbaar was:

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

Aanval vektor

In die UC Browser-manifes kan jy 'n diens met 'n selfverduidelikende naam vind com.uc.deployment.UpgradeDeployService.

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

Wanneer hierdie diens begin, rig die blaaier 'n POST-versoek aan puds.ucweb.com/upgrade/index.xhtml, wat 'n ruk na die wegspring in die verkeer gesien kan word. In reaksie hierop kan hy 'n opdrag ontvang om een ​​of ander opdatering of nuwe module af te laai. Tydens die ontleding het die bediener nie sulke opdragte gegee nie, maar ons het opgemerk dat wanneer ons probeer om 'n PDF in die blaaier oop te maak, dit 'n tweede versoek rig na die adres hierbo gespesifiseer, waarna dit die inheemse biblioteek aflaai. Om die aanval uit te voer, het ons besluit om hierdie kenmerk van UC Browser te gebruik: die vermoë om PDF oop te maak met 'n inheemse biblioteek, wat nie in die APK is nie en wat dit van die internet aflaai indien nodig. Dit is opmerklik dat, teoreties, UC Browser gedwing kan word om iets af te laai sonder gebruikersinteraksie - as jy 'n goed gevormde antwoord gee op 'n versoek wat uitgevoer word nadat die blaaier geloods is. Maar om dit te doen, moet ons die protokol van interaksie met die bediener in meer besonderhede bestudeer, so ons het besluit dat dit makliker sou wees om die onderskepte reaksie te wysig en die biblioteek te vervang om met PDF te werk.

Dus, wanneer 'n gebruiker 'n PDF direk in die blaaier wil oopmaak, kan die volgende versoeke in die verkeer gesien word:

Op soek na kwesbaarhede in UC Browser

Eerstens is daar 'n POST-versoek na puds.ucweb.com/upgrade/index.xhtmlwaarna
'n Argief met 'n biblioteek om PDF- en kantoorformate te bekyk, word afgelaai. Dit is logies om te aanvaar dat die eerste versoek inligting oor die stelsel versend (ten minste die argitektuur om die vereiste biblioteek te verskaf), en in reaksie daarop ontvang die blaaier inligting oor die biblioteek wat afgelaai moet word: die adres en, ev. , iets anders. Die probleem is dat hierdie versoek geïnkripteer is.

Versoek fragment

Antwoord fragment

Op soek na kwesbaarhede in UC Browser

Op soek na kwesbaarhede in UC Browser

Die biblioteek self is in zip verpak en is nie geïnkripteer nie.

Op soek na kwesbaarhede in UC Browser

Soek vir verkeersdekripsiekode

Kom ons probeer om die bedienerantwoord te ontsyfer. Kom ons kyk na die klaskode com.uc.deployment.UpgradeDeployService: van metode opStartCommand gaan na com.uc.deployment.bx, en van dit na 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);
}

Ons sien die vorming van 'n POST-versoek hier. Ons gee aandag aan die skepping van 'n skikking van 16 grepe en die vulling daarvan: 0x5F, 0, 0x1F, -50 (=0xCE). Stem saam met wat ons in die versoek hierbo gesien het.

In dieselfde klas kan jy 'n geneste klas sien wat nog 'n interessante metode het:

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

Die metode neem 'n verskeidenheid grepe as invoer en kontroleer dat die nulgreep 0x60 is of die derde greep 0xD0 is, en die tweede greep is 1, 11 of 0x1F. Ons kyk na die reaksie van die bediener: die nulgreep is 0x60, die tweede is 0x1F, die derde is 0x60. Klink soos wat ons nodig het. Te oordeel aan die reëls ("up_decrypt", byvoorbeeld), moet 'n metode hier genoem word wat die bediener se reaksie sal dekripteer.
Kom ons gaan aan na die metode gj. Let daarop dat die eerste argument die greep by offset 2 is (d.i. 0x1F in ons geval), en die tweede is die bedienerrespons sonder
eerste 16 grepe.

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

Uiteraard kies ons hier 'n dekripsiealgoritme, en dieselfde greep wat in ons is
geval gelyk aan 0x1F, dui een van drie moontlike opsies aan.

Ons gaan voort om die kode te ontleed. Na 'n paar spronge bevind ons ons in 'n metode met 'n selfverduidelikende naam decryptBytesByKey.

Hier word nog twee grepe van ons antwoord geskei, en 'n string word daaruit verkry. Dit is duidelik dat op hierdie manier die sleutel vir die dekripteer van die boodskap gekies word.

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

As ons vorentoe kyk, neem ons kennis dat ons in hierdie stadium nog nie 'n sleutel kry nie, maar slegs die "identifiseerder". Om die sleutel te kry is 'n bietjie meer ingewikkeld.

In die volgende metode word nog twee parameters by die bestaande gevoeg, wat vier daarvan maak: die magiese nommer 16, die sleutelidentifiseerder, die geënkripteerde data en 'n onverstaanbare string (in ons geval, leeg).

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

Na 'n reeks oorgange kom ons by die metode uit staticBinarySafeDecryptNoB64 koppelvlak com.alibaba.wireless.security.open.staticdataencrypt.IStaticDataEncryptComponent. Daar is geen klasse in die hooftoepassingskode wat hierdie koppelvlak implementeer nie. Daar is so 'n klas in die lêer lib/armeabi-v7a/libsgmain.so, wat nie eintlik 'n .so is nie, maar 'n .jar. Die metode waarin ons belangstel, word soos volg geïmplementeer:

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 word ons lys parameters aangevul met nog twee heelgetalle: 2 en 0. Te oordeel aan
alles, 2 beteken dekripsie, soos in die metode doen Finale stelsel klas javax.crypto.Cipher. En dit alles word oorgedra na 'n sekere router met die nommer 10601 - dit is blykbaar die opdragnommer.

Na die volgende ketting van oorgange vind ons 'n klas wat die koppelvlak implementeer IRouterComponent en metode doen Command:

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 klas JNIC-biblioteek, waarin die inheemse metode verklaar word doCommandNative:

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

Dit beteken dat ons 'n metode in die inheemse kode moet vind doCommandNative. En dit is waar die pret begin.

Verduistering van masjienkode

In lêer libsgmain.so (wat eintlik 'n .jar is en waarin ons die implementering van sommige enkripsieverwante koppelvlakke net hierbo gevind het) is daar een inheemse biblioteek: libsgmainso-6.4.36.so. Ons maak dit oop in IDA en kry 'n klomp dialoogkassies met foute. Die probleem is dat die afdelingkoptabel ongeldig is. Dit word doelbewus gedoen om die analise te bemoeilik.

Op soek na kwesbaarhede in UC Browser

Maar dit is nie nodig nie: om 'n ELF-lêer korrek te laai en dit te ontleed, is 'n programkoptabel voldoende. Daarom verwyder ons eenvoudig die afdelingstabel en nul die ooreenstemmende velde in die kopskrif uit.

Op soek na kwesbaarhede in UC Browser

Maak die lêer weer in IDA oop.

Daar is twee maniere om die Java virtuele masjien te vertel waar presies in die inheemse biblioteek die implementering van 'n metode wat in Java-kode as inheems verklaar is, geleë is. Die eerste is om dit 'n spesienaam te gee Java_package_name_ClassName_MethodName.

Die tweede is om dit te registreer wanneer die biblioteek gelaai word (in die funksie JNI_OnLoad)
met behulp van 'n funksie-oproep Registreer Naturelle.

In ons geval, as ons die eerste metode gebruik, moet die naam soos volg wees: Java_com_taobao_wireless_security_adapter_JNICLibrary_doCommandNative.

Daar is nie so 'n funksie onder die uitgevoer funksies nie, wat beteken dat jy 'n oproep moet soek Registreer Naturelle.
Kom ons gaan na die funksie JNI_OnLoad en ons sien hierdie prentjie:

Op soek na kwesbaarhede in UC Browser

Wat gaan hier aan? Met die eerste oogopslag is die begin en einde van die funksie tipies vir ARM-argitektuur. Die eerste instruksie op die stapel stoor die inhoud van die registers wat die funksie in sy werking sal gebruik (in hierdie geval, R0, R1 en R2), sowel as die inhoud van die LR-register, wat die terugkeeradres van die funksie bevat . Die laaste instruksie herstel die gestoorde registers, en die terugstuuradres word onmiddellik in die rekenaarregister geplaas - dus terug van die funksie af. Maar as jy mooi kyk, sal jy sien dat die voorlaaste instruksie die terugstuuradres wat op die stapel gestoor is, verander. Kom ons bereken hoe dit daarna sal wees
kode uitvoering. 'n Sekere adres 1xB0 word in R130 gelaai, 5 word daarvan afgetrek, dan word dit na R0 oorgedra en 0x10 word daarby gevoeg. Dit blyk 0xB13B. Dus, IDA dink dat die laaste instruksie 'n normale funksie terugkeer is, maar in werklikheid gaan dit na die berekende adres 0xB13B.

Dit is die moeite werd om hier te onthou dat ARM-verwerkers twee modusse en twee stelle instruksies het: ARM en Thumb. Die minste betekenisvolle bietjie van die adres vertel die verwerker watter instruksiestel gebruik word. Dit wil sê, die adres is eintlik 0xB13A, en een in die minste betekenisvolle bietjie dui die Duim-modus aan.

'n Soortgelyke "adapter" is bygevoeg aan die begin van elke funksie in hierdie biblioteek en
vullis kode. Ons gaan nie verder in detail daaroor uitwei nie – ons onthou net
dat die werklike begin van byna alle funksies 'n bietjie verder weg is.

Aangesien die kode nie uitdruklik na 0xB13A spring nie, het IDA self nie erken dat die kode op hierdie plek geleë is nie. Om dieselfde rede herken dit die meeste van die kode in die biblioteek nie as kode nie, wat ontleding ietwat moeilik maak. Ons sê vir IDA dat dit die kode is, en dit is wat gebeur:

Op soek na kwesbaarhede in UC Browser

Die tabel begin duidelik by 0xB144. Wat is in sub_494C?

Op soek na kwesbaarhede in UC Browser

As ons hierdie funksie in die LR-register oproep, kry ons die adres van die voorheen genoemde tabel (0xB144). In R0 - indeks in hierdie tabel. Dit wil sê, die waarde word uit die tabel geneem, by LR gevoeg en die resultaat is
die adres om na te gaan. Kom ons probeer dit bereken: 0xB144 + [0xB144 + 8* 4] = 0xB144 + 0x120 = 0xB264. Ons gaan na die ontvangde adres en sien letterlik 'n paar nuttige instruksies en gaan weer na 0xB140:

Op soek na kwesbaarhede in UC Browser

Nou sal daar 'n oorgang wees teen offset met indeks 0x20 vanaf die tabel.

Te oordeel aan die grootte van die tabel, sal daar baie sulke oorgange in die kode wees. Die vraag ontstaan ​​of dit moontlik is om dit op een of ander manier meer outomaties te hanteer, sonder om adresse met die hand te bereken. En skrifte en die vermoë om kode in IDA te pleister kom 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"

Plaas die wyser op reël 0xB26A, voer die skrif uit en sien die oorgang na 0xB4B0:

Op soek na kwesbaarhede in UC Browser

IDA het weer nie hierdie area as 'n kode herken nie. Ons help haar en sien 'n ander ontwerp daar:

Op soek na kwesbaarhede in UC Browser

Die instruksies na BLX maak blykbaar nie veel sin nie, dit is meer soos 'n soort verplasing. Kom ons kyk na sub_4964:

Op soek na kwesbaarhede in UC Browser

En inderdaad, hier word 'n dword geneem by die adres wat in LR lê, by hierdie adres gevoeg, waarna die waarde by die resulterende adres geneem en op die stapel geplaas word. 4 word ook by LR gevoeg sodat dieselfde afwyking na terugkeer van die funksie oorgeslaan word. Daarna neem die POP {R1}-opdrag die resulterende waarde uit die stapel. As jy kyk na wat by adres 0xB4BA + 0xEA = 0xB5A4 geleë is, sal jy iets soortgelyk aan 'n adrestabel sien:

Op soek na kwesbaarhede in UC Browser

Om hierdie ontwerp te pleister, sal jy twee parameters van die kode moet kry: die offset en die registernommer waarin jy die resultaat wil plaas. Vir elke moontlike register sal jy vooraf 'n stukkie kode moet voorberei.

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"

Ons plaas die wyser aan die begin van die struktuur wat ons wil vervang - 0xB4B2 - en voer die skrif uit:

Op soek na kwesbaarhede in UC Browser

Benewens die reeds genoemde strukture, bevat die kode ook die volgende:

Op soek na kwesbaarhede in UC Browser

Soos in die vorige geval, na die BLX-instruksie is daar 'n verrekening:

Op soek na kwesbaarhede in UC Browser

Ons neem die afslag na die adres vanaf LR, voeg dit by LR en gaan soontoe. 0x72044 + 0xC = 0x72050. Die skrif vir hierdie ontwerp is redelik 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 skripuitvoering:

Op soek na kwesbaarhede in UC Browser

Sodra alles in die funksie gelap is, kan jy IDA na die regte begin daarvan wys. Dit sal al die funksiekode saamvoeg, en dit kan gedekompileer word met behulp van HexRays.

Dekodering snare

Ons het geleer om te gaan met verduistering van masjienkode in die biblioteek libsgmainso-6.4.36.so van UC Browser en het die funksiekode ontvang 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;
}

Kom ons kyk van naderby na die volgende reëls:

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

In funksie sub_73E24 die klasnaam word duidelik gedekripteer. As parameters vir hierdie funksie word 'n wyser na data soortgelyk aan geïnkripteer data, 'n sekere buffer en 'n nommer deurgegee. Na die oproep van die funksie sal daar natuurlik 'n gedekripteerde lyn in die buffer wees, aangesien dit na die funksie oorgedra word Soek Klas, wat die klasnaam as die tweede parameter neem. Daarom is die getal die grootte van die buffer of die lengte van die lyn. Kom ons probeer om die klasnaam te ontsyfer, dit moet vir ons sê of ons in die regte rigting gaan. Kom ons kyk van naderby na wat in gebeur 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;
}

Funksie sub_7AF78 skep 'n instansie van 'n houer vir grepe-skikkings van die gespesifiseerde grootte (ons sal nie in detail oor hierdie houers stilstaan ​​nie). Hier word twee sulke houers geskep: een bevat die lyn "DcO/lcK+h?m3c*q@" (dit is maklik om te raai dat dit 'n sleutel is), die ander bevat geënkripteerde data. Vervolgens word beide voorwerpe in 'n sekere struktuur geplaas, wat na die funksie oorgedra word sub_6115C. Kom ons merk ook in hierdie struktuur 'n veld met die waarde 3. Kom ons kyk wat volgende met hierdie struktuur gebeur.

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

Die skakelparameter is 'n struktuurveld wat voorheen die waarde 3 toegeken is. Kyk na geval 3: na die funksie sub_6364C parameters word deurgegee vanaf die struktuur wat daar bygevoeg is in die vorige funksie, dit wil sê die sleutel en geënkripteerde data. As jy mooi kyk na sub_6364C, kan jy die RC4-algoritme daarin herken.

Ons het 'n algoritme en 'n sleutel. Kom ons probeer om die klasnaam te ontsyfer. Hier is wat gebeur het: com/taobao/wireless/security/adapter/JNICLibrary. Puik! Ons is op die regte pad.

Bevelboom

Nou moet ons 'n uitdaging vind Registreer Naturelle, wat ons na die funksie sal wys doCommandNative. Kom ons kyk na die funksies wat vanaf genoem word JNI_OnLoad, en ons vind dit in 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, 'n inheemse metode met die naam is hier geregistreer doCommandNative. Nou ken ons sy adres. Kom ons kyk wat hy doen.

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

Met die naam kan jy raai dat hier die toegangspunt is van al die funksies wat die ontwikkelaars besluit het om na die inheemse biblioteek oor te dra. Ons stel belang in funksie nommer 10601.

U kan uit die kode sien dat die opdragnommer drie getalle produseer: bevel/10000, opdrag % 10000 / 100 и opdrag % 10, d.w.s. in ons geval, 1, 6 en 1. Hierdie drie getalle, sowel as 'n wyser na JNIEnv en die argumente wat na die funksie oorgedra word, word by 'n struktuur gevoeg en deurgegee. Deur die drie nommers te gebruik wat verkry is (kom ons noem hulle N1, N2 en N3), word 'n opdragboom gebou.

Iets soos hierdie:

Op soek na kwesbaarhede in UC Browser

Die boom word dinamies ingevul JNI_OnLoad.
Drie syfers kodeer die pad in die boom. Elke blaar van die boom bevat die posadres van die ooreenstemmende funksie. Die sleutel is in die ouernodus. Om die plek in die kode te vind waar die funksie wat ons nodig het by die boom gevoeg word, is nie moeilik as jy al die strukture wat gebruik word verstaan ​​nie (ons beskryf dit nie om nie 'n reeds taamlike groot artikel op te blaas nie).

Meer verduistering

Ons het die adres ontvang van die funksie wat verkeer moet dekripteer: 0x5F1AC. Maar dit is te vroeg om bly te wees: die ontwikkelaars van UC Browser het nog 'n verrassing vir ons voorberei.

Nadat ons die parameters ontvang het van die skikking wat in die Java-kode gevorm is, kry ons
na die funksie by adres 0x4D070. En hier wag 'n ander soort kodeverduistering op ons.

Ons plaas twee indekse in R7 en R4:

Op soek na kwesbaarhede in UC Browser

Ons skuif die eerste indeks na R11:

Op soek na kwesbaarhede in UC Browser

Om 'n adres uit 'n tabel te kry, gebruik 'n indeks:

Op soek na kwesbaarhede in UC Browser

Nadat u na die eerste adres gegaan het, word die tweede indeks gebruik, wat in R4 is. Daar is 230 elemente in die tabel.

Wat om daaraan te doen? Jy kan vir IDA sê dat dit 'n skakelaar is: Wysig -> Ander -> Spesifiseer skakel-idioom.

Op soek na kwesbaarhede in UC Browser

Die gevolglike kode is verskriklik. Maar as jy jou pad deur sy oerwoud maak, kan jy 'n oproep na 'n funksie sien wat reeds aan ons bekend is sub_6115C:

Op soek na kwesbaarhede in UC Browser

Daar was 'n skakelaar waarin daar in geval 3 'n dekripsie was met behulp van die RC4-algoritme. En in hierdie geval word die struktuur wat na die funksie oorgedra word, gevul vanaf die parameters wat na oorgedra word doCommandNative. Kom ons onthou wat ons daar gehad het magicInt met die waarde 16. Ons kyk na die ooreenstemmende geval - en na verskeie oorgange vind ons die kode waarmee die algoritme geïdentifiseer kan word.

Op soek na kwesbaarhede in UC Browser

Dit is AES!

Die algoritme bestaan, al wat oorbly is om sy parameters te verkry: modus, sleutel en, moontlik, die inisialiseringsvektor (die teenwoordigheid daarvan hang af van die bedryfsmodus van die AES-algoritme). Die struktuur met hulle moet iewers voor die funksie-oproep gevorm word sub_6115C, maar hierdie deel van die kode is veral goed verduister, so die idee ontstaan ​​om die kode te pleister sodat alle parameters van die dekripsiefunksie in 'n lêer gestort word.

Pleister

Om nie al die pleisterkode in samestellingstaal met die hand te skryf nie, kan jy Android Studio begin, 'n funksie daar skryf wat dieselfde invoerparameters as ons dekripsiefunksie ontvang en na 'n lêer skryf, en dan die kode wat die samesteller sal kopieer-plak genereer.

Ons vriende van die UC Browser-span het ook gesorg vir die gemak van die byvoeging van kode. Laat ons onthou dat ons aan die begin van elke funksie vulliskode het wat maklik met enige ander vervang kan word. Baie gerieflik 🙂 Aan die begin van die teikenfunksie is daar egter nie genoeg spasie vir die kode wat al die parameters in 'n lêer stoor nie. Ek moes dit in dele verdeel en vullisblokke van naburige funksies gebruik. Daar was altesaam vier dele.

Die eerste deel:

Op soek na kwesbaarhede in UC Browser

In die ARM-argitektuur word die eerste vier funksieparameters deur registers R0-R3 gevoer, die res, indien enige, word deur die stapel gestuur. Die LR-register dra die terugstuuradres. Dit alles moet gestoor word sodat die funksie kan werk nadat ons sy parameters gestort het. Ons moet ook al die registers stoor wat ons in die proses gaan gebruik, so ons doen PUSH.W {R0-R10,LR}. In R7 kry ons die adres van die lys parameters wat deur die stapel na die funksie gestuur word.

Gebruik die funksie fopen kom ons maak die lêer oop /data/local/tmp/aes in "ab" modus
dit wil sê vir byvoeging. In R0 laai ons die adres van die lêernaam, in R1 - die adres van die lyn wat die modus aandui. En hier eindig die vulliskode, so ons gaan aan na die volgende funksie. Om dit aan te hou werk, plaas ons aan die begin die oorgang na die regte kode van die funksie, omseil die vullis, en in plaas van die vullis voeg ons 'n voortsetting van die pleister by.

Op soek na kwesbaarhede in UC Browser

Roep fopen.

Die eerste drie parameters van die funksie aes tipe het int. Aangesien ons die registers aan die begin in die stapel gestoor het, kan ons eenvoudig die funksie slaag fskryf hul adresse op die stapel.

Op soek na kwesbaarhede in UC Browser

Vervolgens het ons drie strukture wat die datagrootte bevat en 'n wyser na die data vir die sleutel, inisialiseringsvektor en geënkripteerde data.

Op soek na kwesbaarhede in UC Browser

Maak aan die einde die lêer toe, herstel die registers en dra beheer oor na die regte funksie aes.

Ons versamel 'n APK met 'n gelapte biblioteek, teken dit, laai dit op na die toestel/emulator en begin dit. Ons sien dat ons stortingsterrein geskep word, en baie data word daar geskryf. Die blaaier gebruik enkripsie nie net vir verkeer nie, en alle enkripsie gaan deur die betrokke funksie. Maar om een ​​of ander rede is die nodige data nie daar nie, en die vereiste versoek is nie sigbaar in die verkeer nie. Om nie te wag totdat UC Browser waardig is om die nodige versoek te maak nie, kom ons neem die geënkripteerde antwoord van die bediener wat vroeër ontvang is en pleister die toepassing weer: voeg die dekripsie by onCreate van die hoofaktiwiteit.

    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

Ons monteer, teken, installeer, loods. Ons ontvang 'n NullPointerException omdat die metode nul teruggestuur het.

Tydens verdere ontleding van die kode is 'n funksie ontdek wat interessante reëls ontsyfer: "META-INF/" en ".RSA". Dit lyk of die toepassing sy sertifikaat verifieer. Of genereer selfs sleutels daaruit. Ek wil nie regtig handel oor wat met die sertifikaat gebeur nie, so ons sal dit net die korrekte sertifikaat glip. Kom ons pleister die geënkripteerde lyn sodat ons in plaas van "META-INF/" "BLABLINF/" kry, 'n vouer met daardie naam in die APK skep en die eekhoringblaaiersertifikaat daar byvoeg.

Ons monteer, teken, installeer, loods. Bingo! Ons het die sleutel!

MitM

Ons het 'n sleutel en 'n inisialiseringsvektor gelyk aan die sleutel ontvang. Kom ons probeer om die bedienerreaksie in CBC-modus te dekripteer.

Op soek na kwesbaarhede in UC Browser

Ons sien die argief-URL, iets soortgelyk aan MD5, "extract_unzipsize" en 'n nommer. Ons kyk: die MD5 van die argief is dieselfde, die grootte van die uitgepakte biblioteek is dieselfde. Ons probeer hierdie biblioteek regmaak en dit aan die blaaier gee. Om te wys dat ons gelapte biblioteek gelaai het, sal ons 'n voorneme begin om 'n SMS te skep met die teks "PWNED!" Ons sal twee antwoorde van die bediener vervang: puds.ucweb.com/upgrade/index.xhtml en om die argief af te laai. In die eerste vervang ons MD5 (die grootte verander nie na uitpak nie), in die tweede gee ons die argief met die gelapte biblioteek.

Die blaaier probeer die argief verskeie kere aflaai, waarna dit 'n fout gee. Blykbaar iets
hy hou nie van. As gevolg van die ontleding van hierdie troebel formaat, het dit geblyk dat die bediener ook die grootte van die argief oordra:

Op soek na kwesbaarhede in UC Browser

Dit is geënkodeer in LEB128. Na die pleister het die grootte van die argief met die biblioteek 'n bietjie verander, so die blaaier het gedink dat die argief skeef afgelaai is, en na verskeie pogings het dit 'n fout gegooi.

Ons pas die grootte van die argief aan... En – oorwinning! 🙂 Die resultaat is in die video.

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

Gevolge en ontwikkelaarreaksie

Op dieselfde manier kan hackers die onveilige kenmerk van UC Browser gebruik om kwaadwillige biblioteke te versprei en te bestuur. Hierdie biblioteke sal in die konteks van die blaaier werk, so hulle sal al sy stelseltoestemmings ontvang. As gevolg hiervan, die vermoë om phishing-vensters te vertoon, sowel as toegang tot die werklêers van die oranje Chinese eekhoring, insluitend aanmeldings, wagwoorde en koekies wat in die databasis gestoor is.

Ons het die ontwikkelaars van UC Browser gekontak en hulle ingelig oor die probleem wat ons gevind het, probeer om die kwesbaarheid en die gevaar daarvan uit te wys, maar hulle het niks met ons bespreek nie. Intussen het die blaaier voortgegaan om sy gevaarlike kenmerk in die oog te wys. Maar sodra ons die besonderhede van die kwesbaarheid onthul het, was dit nie meer moontlik om dit soos voorheen te ignoreer nie. 27 Maart was
'n nuwe weergawe van UC Browser 12.10.9.1193 is vrygestel, wat toegang tot die bediener verkry het via HTTPS: puds.ucweb.com/upgrade/index.xhtml.

Daarbenewens, na die "regstelling" en tot die tyd van die skryf van hierdie artikel, het probeer om 'n PDF in 'n blaaier oop te maak, 'n foutboodskap tot gevolg gehad met die teks "Oeps, iets het verkeerd geloop!" 'n Versoek aan die bediener is nie gemaak toe daar probeer is om 'n PDF oop te maak nie, maar 'n versoek is gemaak toe die blaaier geloods is, wat dui op die voortgesette vermoë om uitvoerbare kode af te laai in stryd met Google Play-reëls.

Bron: will.com

Voeg 'n opmerking