Inleiding
Einde Maart het ons
'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
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,
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
Dus, wanneer 'n gebruiker 'n PDF direk in die blaaier wil oopmaak, kan die volgende versoeke in die verkeer gesien word:
Eerstens is daar 'n POST-versoek na
'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
Die biblioteek self is in zip verpak en is nie geïnkripteer nie.
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.
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.
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:
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:
Die tabel begin duidelik by 0xB144. Wat is in sub_494C?
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:
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:
IDA het weer nie hierdie area as 'n kode herken nie. Ons help haar en sien 'n ander ontwerp daar:
Die instruksies na BLX maak blykbaar nie veel sin nie, dit is meer soos 'n soort verplasing. Kom ons kyk na sub_4964:
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:
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:
Benewens die reeds genoemde strukture, bevat die kode ook die volgende:
Soos in die vorige geval, na die BLX-instruksie is daar 'n verrekening:
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:
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:
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:
Ons skuif die eerste indeks na R11:
Om 'n adres uit 'n tabel te kry, gebruik 'n indeks:
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.
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:
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.
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:
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.
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.
Vervolgens het ons drie strukture wat die datagrootte bevat en 'n wyser na die data vir die sleutel, inisialiseringsvektor en geënkripteerde data.
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.
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:
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:
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.
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:
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