Introduzione
À a fine di marzu noi
Qualchì tempu fà, UC Browser hè statu annunziatu è distribuitu assai aggressivu: hè statu stallatu nantu à i dispositi di l'utilizatori cù malware, distribuitu da parechji siti sottu l'apparizione di fugliali video (vale à dì, l'utilizatori pensanu chì scaricavanu, per esempiu, un video porno, ma invece ricevutu un APK cù stu navigatore), hà utilizatu banners spaventosi cù missaghji chì u navigatore era obsoletu, vulnerabile, è cose cusì. In u gruppu ufficiale UC Browser in VK ci hè
À u mumentu di a scrittura, UC Browser hà più di installazioni 500 in Google Play. Questu hè impressiunanti - solu Google Chrome hà più. Trà e recensioni pudete vede assai lagnanze nantu à publicità è redirects à alcune applicazioni in Google Play. Questu era u mutivu di a nostra ricerca: avemu decisu di vede se UC Browser facia qualcosa di male. È s'hè risultatu ch'ellu face !
In u codice di l'applicazione, a capacità di scaricà è eseguisce codice eseguibile hè stata scuperta,
Tuttu ciò chì hè scrittu quì sottu hè pertinente per a versione di UC Browser chì era dispunibule nantu à Google Play à u mumentu di u studiu:
package: com.UCMobile.intl
versionName: 12.10.8.1172
versionCode: 10598
sha1 APK-файла: f5edb2243413c777172f6362876041eb0c3a928c
Vettore d'attaccu
In u manifestu UC Browser pudete truvà un serviziu cù un nome auto-spiegativu com.uc.deployment.UpgradeDeployService.
<service android_exported="false" android_name="com.uc.deployment.UpgradeDeployService" android_process=":deploy" />
Quandu stu serviziu principia, u navigatore face una dumanda POST à
Allora, quandu un utilizatore vole apre un PDF direttamente in u navigatore, e seguenti richieste ponu esse vistu in u trafficu:
Prima ci hè una dumanda POST
Un archiviu cù una biblioteca per vede PDF è formati di l'uffiziu hè scaricatu. Hè logicu per suppone chì a prima dumanda trasmette infurmazione nantu à u sistema (almenu l'architettura per furnisce a biblioteca necessaria), è in risposta à questu u navigatore riceve qualchì infurmazione nantu à a biblioteca chì deve esse scaricata: l'indirizzu è, possibbilmente , calcosa altru. U prublema hè chì sta dumanda hè criptata.
Frammentu di dumanda
Frammentu di risposta
A biblioteca stessa hè imballata in ZIP è ùn hè micca criptata.
Cerca u codice di decrittazione di u trafficu
Pruvemu di decifrare a risposta di u servitore. Fighjemu u codice di a classe com.uc.deployment.UpgradeDeployService: da u metudu onStart Command andà à com.uc.deployment.bx, è da ellu à 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);
}
Avemu vistu a furmazione di una dumanda POST quì. Prestemu attenzione à a creazione di un array di 16 bytes è u so riempimentu: 0x5F, 0, 0x1F, -50 (=0xCE). Coincide cù ciò chì avemu vistu in a dumanda sopra.
In a listessa classa pudete vede una classa anidata chì hà un altru mètudu interessante:
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");
}
}
U metudu piglia un array di byte cum'è input è verifica chì u byte zero hè 0x60 o u terzu byte hè 0xD0, è u sicondu byte hè 1, 11 o 0x1F. Fighjemu a risposta da u servitore: u byte zero hè 0x60, u sicondu hè 0x1F, u terzu hè 0x60. Sembra ciò chì avemu bisognu. Ghjudicate da e linee ("up_decrypt", per esempiu), un metudu deve esse chjamatu quì chì decriptarà a risposta di u servitore.
Andemu à u metudu gj. Nota chì u primu argumentu hè u byte à l'offset 2 (vale à dì 0x1F in u nostru casu), è u sicondu hè a risposta di u servitore senza
primi 16 bytes.
public static byte[] j(int arg1, byte[] arg2) {
if(arg1 == 1) {
arg2 = c.c(arg2, c.adu);
}
else if(arg1 == 11) {
arg2 = m.aF(arg2);
}
else if(arg1 != 0x1F) {
}
else {
arg2 = EncryptHelper.decrypt(arg2);
}
return arg2;
}
Ovviamente, quì selezziunate un algoritmu di decryption, è u stessu byte chì hè in u nostru
casu uguale à 0x1F, denota una di e trè opzioni pussibuli.
Cuntinuemu à analizà u codice. Dopu à un paru di salti ci truvamu in un metudu cù un nome auto-spiegativu decryptBytesByKey.
Quì dui byte più sò separati da a nostra risposta, è una stringa hè ottenuta da elli. Hè chjaru chì in questu modu a chjave per decrypting u messagiu hè sceltu.
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;
}
Fighjendu avanti, avemu nutatu chì in questa tappa ùn avemu micca ancu ottene una chjave, ma solu u so "identificatore". Ottene a chjave hè un pocu più complicata.
In u prossimu metudu, dui paràmetri sò aghjuntu à quelli esistenti, facendu quattru: u numeru magicu 16, l'identificatore chjave, i dati criptati è una stringa incomprensibile (in u nostru casu, viotu).
public final byte[] l(String keyId, byte[] encrypted) throws SecException {
return this.ayJ().staticBinarySafeDecryptNoB64(16, keyId, encrypted, "");
}
Dopu una seria di transizzioni ghjunghjemu à u metudu staticBinarySafeDecryptNoB64 interfaccia com.alibaba.wireless.security.open.staticdataencrypt.IStaticDataEncryptComponent. Ùn ci hè micca classi in u codice di l'applicazione principale chì implementanu sta interfaccia. Ci hè una tale classa in u schedariu lib/armeabi-v7a/libsgmain.so, chì ùn hè micca veramente un .so, ma un .jar. U metudu chì ci interessa hè implementatu cusì:
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);
}
//...
}
Quì a nostra lista di paràmetri hè cumplementata cù dui interi più: 2 è 0. Ghjudicate da
tuttu, 2 significa decryption, cum'è in u metudu da Finale classa di sistema javax.crypto.Cipher. È tuttu questu hè trasferitu à un certu Router cù u numeru 10601 - questu hè apparentemente u numeru di cumanda.
Dopu à a prossima catena di transizzioni truvamu una classa chì implementa l'interfaccia IRuterComponent è u metudu 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);
}
}
È ancu classa Biblioteca JNIC, in quale u metudu nativu hè dichjaratu doCommandNative:
package com.taobao.wireless.security.adapter;
public class JNICLibrary {
public static native Object doCommandNative(int arg0, Object[] arg1);
}
Questu significa chì avemu bisognu di truvà un metudu in u codice nativu doCommandNative. È questu hè induve u divertimentu principia.
Obfuscazione di codice macchina
In u schedariu libsgmain.so (chì hè in realtà un .jar è in quale avemu truvatu l'implementazione di alcune interfacce di criptografia ghjustu sopra) ci hè una biblioteca nativa: libsgmainso-6.4.36.so. Apermu in IDA è uttene una mansa di scatuli di dialogu cù errori. U prublema hè chì a tabella di l'intestazione di a sezione hè invalida. Questu hè fattu apposta per complicà l'analisi.
Ma ùn hè micca necessariu: per carricà currettamente un schedariu ELF è analizà, una tabella di l'intestazione di u prugramma hè abbastanza. Per quessa, simpricimenti sguassate a tavola di a sezione, zeroing i campi currispundenti in l'intestazione.
Aprite u schedariu in IDA di novu.
Ci hè dui modi per dì à a macchina virtuale Java induve esattamente in a biblioteca nativa si trova l'implementazione di un metudu dichjaratu in u codice Java cum'è nativu. U primu hè di dà un nome di spezia Java_package_name_ClassName_MethodName.
U sicondu hè di registrà quandu si carica a biblioteca (in a funzione JNI_OnLoad)
usendu una chjama di funzione Registru Nativi.
In u nostru casu, se usemu u primu metudu, u nome deve esse cusì: Java_com_taobao_wireless_security_adapter_JNICLibrary_doCommandNative.
Ùn ci hè micca una tale funzione trà e funzioni esportate, chì significa chì avete bisognu di circà una chjama Registru Nativi.
Andemu à a funzione JNI_OnLoad è vedemu sta stampa:
Chì si passa quì ? À u primu sguardu, l'iniziu è a fine di a funzione sò tipici per l'architettura ARM. A prima struzzione nantu à a pila guarda u cuntenutu di i registri chì a funzione aduprà in u so funziunamentu (in questu casu, R0, R1 è R2), è ancu u cuntenutu di u registru LR, chì cuntene l'indirizzu di ritornu da a funzione. . L'ultima struzzione restaurà i registri salvati, è l'indirizzu di ritornu hè immediatamente postu in u registru di u PC - cusì torna da a funzione. Ma s'è guardate attentamente, vi vede chì l'istruzzioni penultima cambia l'indirizzu di ritornu guardatu nantu à a pila. Calculemu ciò chì sarà dopu
esecuzione di codice. Un certu indirizzu 1xB0 hè carricu in R130, 5 hè sottrattu da ellu, dopu hè trasferitu à R0 è 0x10 hè aghjuntu à questu. Risulta 0xB13B. Cusì, IDA pensa chì l'ultima struzzione hè un ritornu di funzione nurmale, ma in fattu hè andatu à l'indirizzu calculatu 0xB13B.
Hè vale a pena ricurdà quì chì i prucessori ARM anu dui modi è dui gruppi di struzzioni: ARM è Thumb. U pocu significativu di l'indirizzu dice à u processatore quale set di struzzioni hè stata utilizata. Questu hè, l'indirizzu hè in realtà 0xB13A, è unu in u pocu significativu indica u modalità Thumb.
Un "adapter" simili hè statu aghjuntu à u principiu di ogni funzione in questa biblioteca è
codice basura. Ùn ci stendemu più nantu à elli in dettagliu - ricurdemu solu
chì u veru principiu di quasi tutte e funzioni hè un pocu più luntanu.
Siccomu u codice ùn salta esplicitamente à 0xB13A, IDA stessu ùn hà micca ricunnisciutu chì u codice era situatu in questu locu. Per u listessu mutivu, ùn ricunnosce micca a maiò parte di u codice in a biblioteca cum'è codice, chì rende l'analisi un pocu difficiule. Dicemu à IDA chì questu hè u codice, è questu hè ciò chì succede:
A tavula principia chjaramente à 0xB144. Chì ci hè in sub_494C?
Quandu chjamate sta funzione in u registru LR, avemu l'indirizzu di a tavula citata prima (0xB144). In R0 - indice in questa tabella. Questu hè, u valore hè pigliatu da a tavula, aghjuntu à LR è u risultatu hè
l'indirizzu per andà. Pruvemu di calculà: 0xB144 + [0xB144 + 8* 4] = 0xB144 + 0x120 = 0xB264. Andemu à l'indirizzu ricevutu è vede literalmente un paru di struzzioni utili è andemu di novu à 0xB140:
Avà ci sarà una transizione à offset cù l'indice 0x20 da a tavula.
A ghjudicà da a dimensione di a tavula, ci saranu parechje transizioni tali in u codice. A quistione hè s'ellu hè pussibule di trattà questu più automaticamente, senza calculà manualmente l'indirizzi. E scripts è a capacità di patch codice in IDA venenu à u nostru aiutu:
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"
Pone u cursore nantu à a linea 0xB26A, eseguite u script è vede a transizione à 0xB4B0:
L'IDA di novu ùn hà micca ricunnisciutu sta zona cum'è codice. L'aiutemu è vedemu un altru disignu quì:
L'istruzzioni dopu à BLX ùn pare micca avè assai sensu, hè più cum'è un tipu di spustamentu. Fighjemu sub_4964:
È veramente, quì un dword hè pigliatu à l'indirizzu chì si trova in LR, aghjuntu à questu indirizzu, dopu chì u valore à l'indirizzu risultatu hè pigliatu è mette nantu à a pila. Inoltre, 4 hè aghjuntu à LR in modu chì dopu à vultà da a funzione, u listessu offset hè saltatu. Dopu chì u cumandimu POP {R1} piglia u valore risultatu da a pila. Se guardate ciò chì si trova à l'indirizzu 0xB4BA + 0xEA = 0xB5A4, vi vede qualcosa simili à una tabella di indirizzu:
Per patch stu disignu, avete bisognu di ottene dui parametri da u codice: l'offset è u numeru di registru in quale vulete mette u risultatu. Per ogni registru pussibule, avete da preparà un pezzu di codice in anticipu.
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"
Pusemu u cursore à u principiu di a struttura chì vulemu rimpiazzà - 0xB4B2 - è eseguite u script:
In più di e strutture digià citate, u codice cuntene ancu i seguenti:
Cum'è in u casu precedente, dopu à l'istruzzioni BLX ci hè un offset:
Pigliemu l'offset à l'indirizzu da LR, aghjunghje à LR è andemu quì. 0x72044 + 0xC = 0x72050. U script per stu disignu hè abbastanza simplice:
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"
Risultu di l'esecuzione di script:
Una volta chì tuttu hè patched in a funzione, pudete indicà IDA à u so veru principiu. Riuniscerà tuttu u codice di funzione, è pò esse decompiled usendu HexRays.
Decodifica di stringhe
Avemu amparatu à trattà cù l'obfuscazione di u codice macchina in a biblioteca libsgmainso-6.4.36.so da UC Browser è ricevutu u codice funzione 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;
}
Fighjemu più attente à e seguenti linee:
sub_73E24(&unk_83EA6, &v6, 49);
clazz = (jclass)((int (__fastcall *)(JNIEnv *, int *))(*env)->FindClass)(env, &v6);
In funzione sottu_73E24 u nome di a classe hè chjaramente esse decriptatu. Comu paràmetri di sta funzione, un punteru à e dati simili à i dati criptati, un certu buffer è un numeru sò passati. Ovviamente, dopu avè chjamatu a funzione, ci sarà una linea decriptata in u buffer, postu chì hè passatu à a funzione. Truvà a Classe, chì piglia u nome di a classe cum'è u sicondu paràmetru. Dunque, u numeru hè a dimensione di u buffer o a durata di a linea. Pruvemu di decifrare u nome di a classa, ci vole à dì s'ellu andemu in a direzzione bona. Fighjemu un ochju più vicinu à ciò chì succede in sottu_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;
}
funziunava sottu_7AF78 crea una istanza di un containeru per arrays di byte di a dimensione specificata (ùn stendemu micca nantu à sti cuntenituri in dettagliu). Eccu dui cuntenituri tali sò creati: unu cuntene a linea "DcO/lcK+h?m3c*q@" (hè faciule d'invintà chì questu hè una chjave), l'altru cuntene dati criptati. Dopu, i dui ogetti sò posti in una certa struttura, chì hè passatu à a funzione sottu_6115C. Marcamu ancu un campu cù u valore 3 in questa struttura.Videmu ciò chì succede à sta struttura dopu.
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;
}
U paràmetru di u cambiamentu hè un campu di struttura chì hè stata assignata prima u valore 3. Fighjate à u casu 3: à a funzione sottu_6364C i paràmetri sò passati da a struttura chì anu aghjustatu quì in a funzione precedente, vale à dì a chjave è i dati criptati. Se guardate attentamente sottu_6364C, pudete ricunnosce l'algoritmu RC4 in questu.
Avemu un algoritmu è una chjave. Pruvemu di decifrare u nome di a classe. Eccu ciò chì hè accadutu: com/taobao/wireless/security/adapter/JNICLibrary. Perfettu! Semu nantu à a strada bona.
Arbulu di cumanda
Avà avemu bisognu di truvà una sfida Registru Nativi, chì ci indicà à a funzione doCommandNative. Fighjemu e funzioni chjamate da JNI_OnLoad, è a truvamu in sottu_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;
}
È veramente, un metudu nativu cù u nome hè registratu quì doCommandNative. Avà sapemu u so indirizzu. Videmu ciò chì face.
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;
}
Da u nome pudete guessà chì quì hè u puntu di entrata di tutte e funzioni chì i sviluppatori anu decisu di trasfiriri à a biblioteca nativa. Semu interessati à u numeru di funzione 10601.
Pudete vede da u codice chì u numeru di cumanda pruduce trè numeri: cumanda / 10000, cumanda % 10000 / 100 и cumanda % 10, vale à dì, in u nostru casu, 1, 6 è 1. Questi trè numeri, è ancu un punteru à JNIEnv è l'argumenti passati à a funzione sò aghjuntu à una struttura è trasmessi. Utilizendu i trè numeri ottenuti (denutemu N1, N2 è N3), un arbre di cumanda hè custruitu.
Qualcosa cum'è questu:
L'arbre hè pienu dinamicamente JNI_OnLoad.
Trè numeri codificanu a strada in l'arbulu. Ogni foglia di l'arbulu cuntene l'indirizzu pocked di a funzione currispundente. A chjave hè in u node parent. Truvà u locu in u codice induve a funzione chì avemu bisognu hè aghjuntu à l'arbulu ùn hè micca difficiule s'ellu capisce tutte e strutture aduprate (ùn l'avemu micca discrittu per ùn gonfià un articulu digià abbastanza grande).
Più offuscazione
Avemu ricevutu l'indirizzu di a funzione chì deve decrypt u trafficu: 0x5F1AC. Ma hè troppu prestu per rallegra: i sviluppatori di UC Browser anu preparatu una altra sorpresa per noi.
Dopu avè ricivutu i paràmetri da l'array chì era furmatu in u codice Java, avemu
à a funzione à l'indirizzu 0x4D070. È quì ci aspetta un altru tipu di offuscazione di codice.
On met deux indices dans R7 et R4 :
On déplace le premier indice à R11 :
Per piglià un indirizzu da una tavula, utilizate un indice:
Dopu à andà à u primu indirizzu, u sicondu indice hè utilizatu, chì hè in R4. Ci sò 230 elementi in a tavula.
Cosa da fà? Pudete dì à IDA chì questu hè un switch: Edit -> Other -> Specify switch idiom.
U codice risultatu hè terribili. Ma, facendu u vostru modu à traversu a so jungla, pudete nutà una chjama à una funzione chì ci hè digià familiar sottu_6115C:
Ci era un cambiamentu in quale in casu 3 ci era una decryption cù l'algoritmu RC4. È in questu casu, a struttura passata à a funzione hè piena da i paràmetri passati doCommandNative. Ricordemu ciò chì avemu avutu quì magicInt cù u valore 16. Fighjemu u casu currispundente - è dopu à parechje transizzioni truvamu u codice per quale l'algoritmu pò esse identificatu.
Questu hè AES!
L'algoritmu esiste, tuttu ciò chì resta hè di ottene i so paràmetri: modalità, chjave è, possibbilmente, u vettore di inizializazione (a so prisenza dipende di u modu di funziunamentu di l'algoritmu AES). A struttura cun elli deve esse furmata in un locu prima di a chjama di a funzione sottu_6115C, Ma sta parte di u codice hè sopratuttu bè obfuscata, perchè l'idea nasce di patch u codice per chì tutti i paràmetri di a funzione di decryption sò scaricati in un schedariu.
Patch
Per ùn scrive micca tuttu u codice di patch in lingua assemblea manualmente, pudete lancià Android Studio, scrivite una funzione quì chì riceve i stessi parametri di input cum'è a nostra funzione di decifrazione è scrive in un schedariu, poi copia-incolla u codice chì u compilatore hà da esse. generà.
I nostri amichi da a squadra di u Browser UC anu ancu cura di a cunvenzione di aghjunghje codice. Ricordemu chì à u principiu di ogni funzione avemu un codice di basura chì pò esse facilmente rimpiazzatu cù qualsiasi altru. Moltu convenientu 🙂 Tuttavia, à u principiu di a funzione di destinazione ùn ci hè micca abbastanza spaziu per u codice chì salva tutti i paràmetri in un schedariu. Aviu avutu a split in parte è aduprà blocchi di basura da funzioni vicini. Ci era quattru parti in totale.
A prima parte:
In l'architettura ARM, i primi quattru paràmetri di funzione sò passati per i registri R0-R3, u restu, se qualchissia, hè passatu per a pila. U registru LR porta l'indirizzu di ritornu. Tuttu chistu deve esse salvatu in modu chì a funzione pò travaglià dopu à dump i so paràmetri. Avemu ancu bisognu di salvà tutti i registri chì avemu aduprà in u prucessu, cusì facemu PUSH.W {R0-R10,LR}. In R7 avemu l'indirizzu di a lista di i paràmetri passati à a funzione via a pila.
Utilizà a funzione fopen apremu u schedariu /data/local/tmp/aes in modu "ab".
vale à dì per aghjunghje. In R0 caricamu l'indirizzu di u nome di u schedariu, in R1 - l'indirizzu di a linea chì indica u modu. E quì u codice di basura finisci, cusì andemu à a funzione dopu. Per pudè cuntinuà à travaglià, mettimu à u principiu a transizione à u codice veru di a funzione, sguassendu a basura, è invece di a basura aghjunghjemu una continuazione di u patch.
Chjama fopen.
I primi trè paràmetri di a funzione Eram avè tipu tram. Siccomu avemu salvatu i registri à a pila à u principiu, pudemu solu passà a funzione fscrivi i so indirizzi nantu à a pila.
Dopu avemu trè strutture chì cuntenenu a dimensione di dati è un punteru à i dati per a chjave, u vettore di inizializazione è i dati criptati.
À a fine, chjude u schedariu, restaurà i registri è trasferisce u cuntrollu à a funzione vera Eram.
Raccogliemu un APK cù una libreria patched, firmamu, carichemu à u dispositivu / emulatore, è lanciamu. Avemu vistu chì u nostru dump hè creatu, è assai dati hè scrittu quì. U navigatore usa a criptografia micca solu per u trafficu, è tutta a criptografia passa per a funzione in quistione. Ma per qualchì mutivu i dati necessarii ùn sò micca quì, è a dumanda necessaria ùn hè micca visibile in u trafficu. Per ùn aspittà micca finu à chì u navigatore UC si degna per fà a dumanda necessaria, pigliamu a risposta criptata da u servitore ricevutu prima è patch l'applicazione di novu: aghjunghje a decryption à onCreate di l'attività principale.
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
Assemblamu, firmemu, installemu, lanciamu. Ricevemu una NullPointerException perchè u metudu hà tornatu null.
Durante l'analisi ulteriore di u codice, hè stata scuperta una funzione chì decifra i linii interessanti: "META-INF/" è ".RSA". Sembra chì l'applicazione verifica u so certificatu. O ancu genera chjave da questu. Ùn vogliu micca veramente affruntà ciò chì succede cù u certificatu, per quessa, l'avemu solu slip u certificatu currettu. Patch a linea criptata in modu chì invece di "META-INF/" avemu "BLABLINF/", crea un cartulare cù quellu nome in l'APK è aghjunghje u certificatu di u navigatore squirrel.
Assemblamu, firmemu, installemu, lanciamu. Bingo ! Avemu a chjave!
MitM
Avemu ricevutu una chjave è un vettore d'inizializazione uguale à a chjave. Pruvemu di decifrare a risposta di u servitore in modu CBC.
Avemu vistu l'URL di l'archiviu, qualcosa simili à MD5, "extract_unzipsize" è un numeru. Cuntrollamu: u MD5 di l'archiviu hè u stessu, a dimensione di a biblioteca unpacked hè a stessa. Pruvemu di patch sta biblioteca è dà à u navigatore. Per dimustrà chì a nostra biblioteca patched hè stata caricata, lanceremu una Intenzione di creà un SMS cù u testu "PWNED!" Sustituiremu duie risposte da u servitore:
U navigatore prova di scaricà l'archiviu parechje volte, dopu chì dà un errore. Apparentemente qualcosa
ùn li piace micca. In u risultatu di l'analisi di stu formatu torbu, hè risultatu chì u servitore trasmette ancu a dimensione di l'archiviu:
Hè codificata in LEB128. Dopu à u patch, a dimensione di l'archiviu cù a biblioteca hà cambiatu un pocu, cusì u navigatore hà cunsideratu chì l'archiviu hè stata telecaricata in modu currettu, è dopu à parechji tentativi hà fattu un errore.
Ajustemu a dimensione di l'archiviu... È - vittoria! 🙂 U risultatu hè in u video.
Cunsequenze è reazione di u sviluppatore
In u listessu modu, i pirate puderanu aduprà a funzione insicura di UC Browser per distribuisce è eseguisce biblioteche maliziusi. Queste biblioteche funzionanu in u cuntestu di u navigatore, perchè riceveranu tutti i so permessi di u sistema. In u risultatu, a capacità di vede Windows phishing, è ancu l'accessu à i schedarii di travagliu di l'aranciu squirrel Chinese, cumpresi logins, password è cookies almacenati in a basa di dati.
Avemu cuntattatu i sviluppatori di UC Browser è l'avemu infurmatu nantu à u prublema chì avemu trovu, pruvatu à indicà a vulnerabilità è u so periculu, ma ùn anu micca discututu nunda cun noi. Intantu, u navigatore hà cuntinuatu à affissà a so funzione periculosa in vista. Ma una volta avemu revelatu i dettagli di a vulnerabilità, ùn era più pussibule di ignurà cum'è prima. U 27 di marzu era
una nova versione di UC Browser 12.10.9.1193 hè stata liberata, chì accede à u servitore via HTTPS:
Inoltre, dopu a "riparazione" è finu à u mumentu di a scrittura di stu articulu, pruvate d'apre un PDF in un navigatore hà risultatu in un missaghju d'errore cù u testu "Oops, qualcosa hè andatu male!" Una dumanda à u servitore ùn hè micca stata fatta quandu pruvate d'apre un PDF, ma una dumanda hè stata fatta quandu u navigatore hè stata lanciata, chì suggerisce a capacità continuata di scaricà codice eseguibile in violazione di e regule di Google Play.
Source: www.habr.com