Buscant vulnerabilitats al navegador UC

Buscant vulnerabilitats al navegador UC

Introducció

A finals de març nosaltres reportat, que van descobrir una capacitat oculta per carregar i executar codi no verificat al navegador UC. Avui veurem amb detall com es produeix aquesta descàrrega i com els pirates informàtics la poden utilitzar per als seus propis propòsits.

Fa un temps, UC Browser es va anunciar i es va distribuir de manera molt agressiva: s'instal·lava als dispositius dels usuaris mitjançant programari maliciós, distribuït des de diversos llocs sota l'aparença de fitxers de vídeo (és a dir, els usuaris pensaven que baixaven, per exemple, un vídeo porno, però en comptes d'haver rebut un APK amb aquest navegador), va utilitzar pancartes espantoses amb missatges que indicaven que el navegador estava obsolet, vulnerable i coses semblants. Al grup oficial del navegador UC a VK hi ha tema, en què els usuaris poden queixar-se de la publicitat deslleial, n'hi ha molts exemples. El 2016 n'hi va haver fins i tot publicitat de vídeo en rus (sí, publicitat per a un navegador de bloqueig d'anuncis).

En el moment d'escriure aquest article, UC Browser té més de 500 d'instal·lacions a Google Play. Això és impressionant: només Google Chrome en té més. Entre les ressenyes es poden veure un munt de queixes sobre publicitat i redireccions a algunes aplicacions de Google Play. Aquest va ser el motiu de la nostra investigació: vam decidir veure si UC Browser estava fent alguna cosa dolenta. I va resultar que sí!

Al codi de l'aplicació, es va descobrir la possibilitat de descarregar i executar codi executable, que és contrari a les normes de publicació de sol·licituds a Google Play. A més de descarregar codi executable, UC Browser ho fa d'una manera insegura, que es pot utilitzar per llançar un atac MitM. A veure si podem fer un atac així.

Tot el que s'escriu a continuació és rellevant per a la versió del navegador UC que estava disponible a Google Play en el moment de l'estudi:

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

Vector d'atac

Al manifest del navegador UC podeu trobar un servei amb un nom que s'explica per si mateix com.uc.deployment.UpgradeDeployService.

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

Quan s'inicia aquest servei, el navegador fa una sol·licitud POST a puds.ucweb.com/upgrade/index.xhtml, que es pot veure al trànsit un temps després de la sortida. En resposta, pot rebre una ordre per descarregar alguna actualització o mòdul nou. Durant l'anàlisi, el servidor no va donar aquestes ordres, però vam observar que quan intentem obrir un PDF al navegador, fa una segona sol·licitud a l'adreça especificada anteriorment, després de la qual baixa la biblioteca nativa. Per dur a terme l'atac, vam decidir utilitzar aquesta característica del navegador UC: la possibilitat d'obrir PDF mitjançant una biblioteca nativa, que no es troba a l'APK i que baixa d'Internet si cal. Val la pena assenyalar que, teòricament, el navegador UC es pot veure obligat a descarregar alguna cosa sense la interacció de l'usuari, si proporcioneu una resposta ben formada a una sol·licitud que s'executa després d'iniciar el navegador. Però per fer-ho, hem d'estudiar el protocol d'interacció amb el servidor amb més detall, així que vam decidir que seria més fàcil editar la resposta interceptada i substituir la biblioteca per treballar amb PDF.

Així, quan un usuari vol obrir un PDF directament al navegador, es poden veure les peticions següents al trànsit:

Buscant vulnerabilitats al navegador UC

Primer hi ha una sol·licitud POST puds.ucweb.com/upgrade/index.xhtml, doncs
Es descarrega un arxiu amb una biblioteca per visualitzar formats PDF i d'oficina. És lògic suposar que la primera sol·licitud transmet informació sobre el sistema (almenys l'arquitectura per proporcionar la biblioteca requerida), i com a resposta a això el navegador rep informació sobre la biblioteca que cal descarregar: l'adreça i, possiblement , alguna cosa més. El problema és que aquesta sol·licitud està xifrada.

Fragment de sol·licitud

Fragment de resposta

Buscant vulnerabilitats al navegador UC

Buscant vulnerabilitats al navegador UC

La biblioteca en si està empaquetada en ZIP i no està xifrada.

Buscant vulnerabilitats al navegador UC

Cerca el codi de desxifrat de trànsit

Intentem desxifrar la resposta del servidor. Vegem el codi de la classe com.uc.deployment.UpgradeDeployService: del mètode onStartCommand anar a com.uc.deployment.bx, i d'ell a 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);
}

Aquí veiem la formació d'una sol·licitud POST. Prestem atenció a la creació d'una matriu de 16 bytes i al seu ompliment: 0x5F, 0, 0x1F, -50 (=0xCE). Coincideix amb el que vam veure a la sol·licitud anterior.

A la mateixa classe podeu veure una classe imbricada que té un altre mètode interessant:

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

El mètode pren una matriu de bytes com a entrada i comprova que el byte zero sigui 0x60 o el tercer byte sigui 0xD0 i el segon byte sigui 1, 11 o 0x1F. Mirem la resposta del servidor: el byte zero és 0x60, el segon és 0x1F, el tercer és 0x60. Sembla el que necessitem. A jutjar per les línies ("up_decrypt", per exemple), aquí s'hauria d'anomenar un mètode que desxifrarà la resposta del servidor.
Passem al mètode gj. Tingueu en compte que el primer argument és el byte a l'offset 2 (és a dir, 0x1F en el nostre cas), i el segon és la resposta del servidor sense
primers 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;
}

Òbviament, aquí seleccionem un algorisme de desxifrat, i el mateix byte que hi ha al nostre
cas igual a 0x1F, denota una de les tres opcions possibles.

Continuem analitzant el codi. Després d'un parell de salts ens trobem en un mètode amb un nom que s'explica per si mateix decryptBytesByKey.

Aquí se separen dos bytes més de la nostra resposta i se n'obté una cadena. És evident que d'aquesta manera es selecciona la clau per desxifrar el missatge.

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

De cara al futur, observem que en aquesta fase encara no obtenim una clau, sinó només el seu “identificador”. Obtenir la clau és una mica més complicat.

En el següent mètode s'afegeixen dos paràmetres més als existents, fent-ne quatre: el número màgic 16, l'identificador de clau, les dades xifrades i una cadena incomprensible (en el nostre cas, buida).

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

Després d'una sèrie de transicions arribem al mètode staticBinarySafeDecryptNoB64 interfície com.alibaba.wireless.security.open.staticdataencrypt.IStaticDataEncryptComponent. No hi ha classes al codi de l'aplicació principal que implementin aquesta interfície. Hi ha aquesta classe al fitxer lib/armeabi-v7a/libsgmain.so, que en realitat no és un .so, sinó un .jar. El mètode que ens interessa s'implementa de la següent manera:

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

Aquí la nostra llista de paràmetres es complementa amb dos nombres enters més: 2 i 0. A jutjar per
tot, 2 significa desxifrat, com en el mètode doFinal classe del sistema javax.crypto.Cipher. I tot això es transfereix a un determinat encaminador amb el número 10601: sembla que aquest és el número d'ordre.

Després de la següent cadena de transicions trobem una classe que implementa la interfície IRouterComponent i mètode 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);
}
}

I també classe Biblioteca JNIC, en què es declara el mètode natiu doCommandNative:

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

Això vol dir que hem de trobar un mètode al codi natiu doCommandNative. I aquí és on comença la diversió.

Ofuscament del codi màquina

A l'arxiu libsgmain.so (que en realitat és un .jar i en el qual hem trobat la implementació d'algunes interfícies relacionades amb el xifratge just a sobre) hi ha una biblioteca nativa: libsgmainso-6.4.36.so. L'obrim a IDA i obtenim un munt de quadres de diàleg amb errors. El problema és que la taula de capçalera de la secció no és vàlida. Això es fa a propòsit per complicar l'anàlisi.

Buscant vulnerabilitats al navegador UC

Però no és necessari: per carregar correctament un fitxer ELF i analitzar-lo, n'hi ha prou amb una taula de capçalera del programa. Per tant, simplement suprimim la taula de seccions, posant a zero els camps corresponents a la capçalera.

Buscant vulnerabilitats al navegador UC

Torneu a obrir el fitxer a IDA.

Hi ha dues maneres de dir a la màquina virtual Java on es troba exactament a la biblioteca nativa la implementació d'un mètode declarat en codi Java com a natiu. El primer és donar-li un nom d'espècie Nom_paquet_Java_NomClasse_NomMètode.

El segon és registrar-lo en carregar la biblioteca (a la funció JNI_OnLoad)
utilitzant una trucada de funció Registrar-se als nadius.

En el nostre cas, si fem servir el primer mètode, el nom hauria de ser així: Java_com_taobao_wireless_security_adapter_JNICLibrary_doCommandNative.

No hi ha aquesta funció entre les funcions exportades, el que significa que heu de buscar una trucada Registrar-se als nadius.
Anem a la funció JNI_OnLoad i veiem aquesta imatge:

Buscant vulnerabilitats al navegador UC

Que està passant aquí? A primera vista, l'inici i el final de la funció són típics de l'arquitectura ARM. La primera instrucció de la pila emmagatzema el contingut dels registres que la funció utilitzarà en el seu funcionament (en aquest cas, R0, R1 i R2), així com el contingut del registre LR, que conté l'adreça de retorn de la funció. . L'última instrucció restaura els registres desats i l'adreça de retorn es col·loca immediatament al registre de l'ordinador, tornant així de la funció. Però si us fixeu bé, notareu que la penúltima instrucció canvia l'adreça de retorn emmagatzemada a la pila. Calculem com serà després
execució de codi. Una determinada adreça 1xB0 es carrega a R130, se'n resta 5, després es transfereix a R0 i s'hi afegeix 0x10. Resulta que 0xB13B. Per tant, IDA creu que l'última instrucció és un retorn de funció normal, però de fet està saltant a l'adreça calculada 0xB13B.

Val la pena recordar aquí que els processadors ARM tenen dos modes i dos conjunts d'instruccions: ARM i Thumb. El bit menys significatiu de l'adreça indica al processador quin conjunt d'instruccions s'està utilitzant. És a dir, l'adreça és en realitat 0xB13A, i una del bit menys significatiu indica el mode Thumb.

S'ha afegit un "adaptador" similar al començament de cada funció d'aquesta biblioteca i
codi d'escombraries. No ens atendrem més en detall, només recordem
que l'inici real de quasi totes les funcions està una mica més lluny.

Com que el codi no salta explícitament a 0xB13A, la pròpia IDA no va reconèixer que el codi es trobava en aquesta ubicació. Per la mateixa raó, no reconeix la major part del codi de la biblioteca com a codi, cosa que dificulta una mica l'anàlisi. Li diem a IDA que aquest és el codi, i això és el que passa:

Buscant vulnerabilitats al navegador UC

La taula comença clarament a 0xB144. Què hi ha a sub_494C?

Buscant vulnerabilitats al navegador UC

En cridar aquesta funció al registre LR, obtenim l'adreça de la taula esmentada anteriorment (0xB144). En R0 - índex d'aquesta taula. És a dir, el valor es pren de la taula, s'afegeix a LR i el resultat és
l'adreça a on anar. Intentem calcular-ho: 0xB144 + [0xB144 + 8* 4] = 0xB144 + 0x120 = 0xB264. Anem a l'adreça rebuda i veiem literalment un parell d'instruccions útils i tornem a anar a 0xB140:

Buscant vulnerabilitats al navegador UC

Ara hi haurà una transició al desplaçament amb l'índex 0x20 de la taula.

A jutjar per la mida de la taula, hi haurà moltes transicions d'aquest tipus al codi. Es planteja la pregunta de si és possible fer-ho d'alguna manera de manera més automàtica, sense calcular manualment les adreces. I els scripts i la capacitat d'aplicar codi a IDA ens ajuden:

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"

Col·loqueu el cursor a la línia 0xB26A, executeu l'script i mireu la transició a 0xB4B0:

Buscant vulnerabilitats al navegador UC

L'IDA de nou no va reconèixer aquesta àrea com a codi. L'ajudem i hi veiem un altre disseny:

Buscant vulnerabilitats al navegador UC

Les instruccions posteriors a BLX no semblen tenir gaire sentit, s'assembla més a algun tipus de desplaçament. Mirem sub_4964:

Buscant vulnerabilitats al navegador UC

I de fet, aquí s'agafa un dword a l'adreça que es troba a LR, s'afegeix a aquesta adreça, després del qual s'agafa el valor de l'adreça resultant i es posa a la pila. A més, s'afegeix 4 a LR de manera que després de tornar de la funció, es salta aquest mateix desplaçament. Després d'això, l'ordre POP {R1} pren el valor resultant de la pila. Si mireu el que es troba a l'adreça 0xB4BA + 0xEA = 0xB5A4, veureu alguna cosa semblant a una taula d'adreces:

Buscant vulnerabilitats al navegador UC

Per aplicar un pedaç a aquest disseny, haureu d'obtenir dos paràmetres del codi: el desplaçament i el número de registre en què voleu posar el resultat. Per a cada registre possible, haureu de preparar un codi prèviament.

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"

Col·loquem el cursor al principi de l'estructura que volem substituir - 0xB4B2 - i executem l'script:

Buscant vulnerabilitats al navegador UC

A més de les estructures ja esmentades, el codi també conté el següent:

Buscant vulnerabilitats al navegador UC

Com en el cas anterior, després de la instrucció BLX hi ha un desplaçament:

Buscant vulnerabilitats al navegador UC

Agafem el desplaçament a l'adreça de LR, l'afegim a LR i hi anem. 0x72044 + 0xC = 0x72050. El guió per a aquest disseny és bastant senzill:

def put_unconditional_branch(source, destination):
offset = (destination - source - 4) >> 1
if offset > 2097151 or offset < -2097152:
raise RuntimeError("Invalid offset")
if offset > 1023 or offset < -1024:
instruction1 = 0xf000 | ((offset >> 11) & 0x7ff)
instruction2 = 0xb800 | (offset & 0x7ff)
patch_word(source, instruction1)
patch_word(source + 2, instruction2)
else:
instruction = 0xe000 | (offset & 0x7ff)
patch_word(source, instruction)
ea = here()
if get_wide_word(ea) == 0xb503: #PUSH {R0,R1,LR}
ea1 = ea + 6
if get_wide_word(ea + 2) == 0xbf00: #NOP
ea1 += 2
offset = get_wide_dword(ea1)
put_unconditional_branch(ea, (ea1 + offset) & 0xffffffff)
else:
print "Unable to detect first instruction"

Resultat de l'execució de l'script:

Buscant vulnerabilitats al navegador UC

Una vegada que tot estigui pegat a la funció, podeu apuntar IDA al seu inici real. Reuneix tot el codi de funció i es pot descompilar mitjançant HexRays.

Descodificació de cadenes

Hem après a fer front a l'ofuscament del codi màquina a la biblioteca libsgmainso-6.4.36.so del navegador UC i va rebre el codi de funció 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;
}

Mirem més de prop les línies següents:

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

En funció sub_73E24 el nom de la classe s'està desxifrant clarament. Com a paràmetres d'aquesta funció, es passa un punter a dades semblant a les dades xifrades, un determinat buffer i un número. Òbviament, després de cridar la funció, hi haurà una línia desxifrada a la memòria intermèdia, ja que es passa a la funció. Cerca classe, que pren el nom de la classe com a segon paràmetre. Per tant, el número és la mida de la memòria intermèdia o la longitud de la línia. Intentem desxifrar el nom de la classe, ens hauria de dir si anem en la direcció correcta. Fem una ullada més de prop al que passa a 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;
}

Funció sub_7AF78 crea una instància d'un contenidor per a matrius de bytes de la mida especificada (no ens detenem en aquests contenidors en detall). Aquí es creen dos contenidors: un conté la línia "DcO/lcK+h?m3c*q@" (és fàcil endevinar que es tracta d'una clau), l'altra conté dades xifrades. A continuació, tots dos objectes es col·loquen en una estructura determinada, que es passa a la funció sub_6115C. També marquem en aquesta estructura un camp amb el valor 3. Vegem a continuació què passa amb aquesta estructura.

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

El paràmetre switch és un camp d'estructura al qual se li havia assignat prèviament el valor 3. Mireu el cas 3: a la funció sub_6364C els paràmetres es passen de l'estructura que s'hi va afegir a la funció anterior, és a dir, la clau i les dades xifrades. Si us fixeu bé sub_6364C, hi podeu reconèixer l'algorisme RC4.

Tenim un algorisme i una clau. Intentem desxifrar el nom de la classe. Això és el que va passar: com/taobao/wireless/security/adapter/JNICLibrary. Genial! Estem pel bon camí.

Arbre de comandaments

Ara hem de trobar un repte Registrar-se als nadius, que ens indicarà la funció doCommandNative. Vegem les funcions anomenades des JNI_OnLoad, i el trobem a 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;
}

I de fet, aquí es registra un mètode natiu amb el nom doCommandNative. Ara sabem la seva adreça. A veure què fa.

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

Pel nom podeu endevinar que aquí hi ha el punt d'entrada de totes les funcions que els desenvolupadors van decidir transferir a la biblioteca nativa. Ens interessa la funció número 10601.

Podeu veure al codi que el número d'ordre produeix tres números: comanda/10000, comanda % 10000/100 и comanda % 10, és a dir, en el nostre cas, 1, 6 i 1. Aquests tres nombres, així com un punter a JNIEnv i els arguments passats a la funció s'afegeixen a una estructura i es transmeten. Utilitzant els tres nombres obtinguts (anotem-los N1, N2 i N3), es construeix un arbre d'ordres.

Alguna cosa com això:

Buscant vulnerabilitats al navegador UC

L'arbre s'omple dinàmicament JNI_OnLoad.
Tres números codifiquen el camí a l'arbre. Cada fulla de l'arbre conté l'adreça de la funció corresponent. La clau es troba al node pare. Trobar el lloc del codi on s'afegeix a l'arbre la funció que necessitem no és difícil si enteneu totes les estructures utilitzades (no les descriurem per no inflar un article ja força gran).

Més ofuscació

Hem rebut l'adreça de la funció que hauria de desxifrar el trànsit: 0x5F1AC. Però és massa aviat per alegrar-nos: els desenvolupadors d'UC Browser ens han preparat una altra sorpresa.

Després de rebre els paràmetres de la matriu que es va formar al codi Java, obtenim
a la funció a l'adreça 0x4D070. I aquí ens espera un altre tipus d'ofuscament de codi.

Posem dos índexs a R7 i R4:

Buscant vulnerabilitats al navegador UC

Canviem el primer índex a R11:

Buscant vulnerabilitats al navegador UC

Per obtenir una adreça d'una taula, utilitzeu un índex:

Buscant vulnerabilitats al navegador UC

Després d'anar a la primera adreça, s'utilitza el segon índex, que es troba a R4. Hi ha 230 elements a la taula.

Què fer al respecte? Podeu dir a l'IDA que es tracta d'un commutador: Edita -> Altres -> Especifica l'idioma del commutador.

Buscant vulnerabilitats al navegador UC

El codi resultant fa por. Però, fent camí per la seva selva, podeu notar una trucada a una funció que ja ens és familiar sub_6115C:

Buscant vulnerabilitats al navegador UC

Hi havia un interruptor en el qual en el cas 3 hi havia un desxifrat mitjançant l'algoritme RC4. I en aquest cas, l'estructura que es passa a la funció s'omple des dels paràmetres passats doCommandNative. Recordem el que teníem allà magicInt amb el valor 16. Observem el cas corresponent -i després de diverses transicions trobem el codi pel qual es pot identificar l'algorisme.

Buscant vulnerabilitats al navegador UC

Això és AES!

L'algorisme existeix, només queda obtenir els seus paràmetres: mode, clau i, possiblement, el vector d'inicialització (la seva presència depèn del mode de funcionament de l'algorisme AES). L'estructura amb ells s'ha de formar en algun lloc abans de la trucada de funció sub_6115C, però aquesta part del codi està especialment enfosquida, per la qual cosa sorgeix la idea de pegar el codi perquè tots els paràmetres de la funció de desxifrat s'aboquin en un fitxer.

Pegat

Per no escriure tot el codi de pedaç en llenguatge ensamblador manualment, podeu iniciar Android Studio, escriure una funció allà que rebi els mateixos paràmetres d'entrada que la nostra funció de desxifrat i escriu en un fitxer, després copiar i enganxar el codi que el compilador farà. generar.

Els nostres amics de l'equip de UC Browser també es van encarregar de la comoditat d'afegir codi. Recordem que al principi de cada funció tenim un codi escombraries que es pot substituir fàcilment per qualsevol altre. Molt convenient 🙂 Tanmateix, al principi de la funció de destinació no hi ha prou espai per al codi que desi tots els paràmetres en un fitxer. Vaig haver de dividir-lo en parts i utilitzar blocs d'escombraries de funcions veïnes. Hi havia quatre parts en total.

La primera part:

Buscant vulnerabilitats al navegador UC

A l'arquitectura ARM, els quatre primers paràmetres de funció es passen pels registres R0-R3, la resta, si n'hi ha, es passen per la pila. El registre LR porta l'adreça de retorn. Tot això s'ha de desar perquè la funció pugui funcionar després d'abocar els seus paràmetres. També hem de desar tots els registres que utilitzarem en el procés, així que fem PUSH.W {R0-R10,LR}. A R7 obtenim l'adreça de la llista de paràmetres passats a la funció a través de la pila.

Utilitzant la funció obrir obrim el fitxer /data/local/tmp/aes en mode "ab".
és a dir, per afegir. A R0 carreguem l'adreça del nom del fitxer, a R1, l'adreça de la línia que indica el mode. I aquí acaba el codi escombraries, així que passem a la següent funció. Perquè continuï funcionant, posem al principi la transició al codi real de la funció, obviant les escombraries, i en lloc de les escombraries afegim una continuació del pedaç.

Buscant vulnerabilitats al navegador UC

Trucant obrir.

Els tres primers paràmetres de la funció aes tenir tipus int. Com que vam desar els registres a la pila al principi, simplement podem passar la funció f escriure les seves adreces a la pila.

Buscant vulnerabilitats al navegador UC

A continuació, tenim tres estructures que contenen la mida de les dades i un punter a les dades de la clau, el vector d'inicialització i les dades xifrades.

Buscant vulnerabilitats al navegador UC

Al final, tanqueu el fitxer, restaurau els registres i transferiu el control a la funció real aes.

Recollim un APK amb una biblioteca pegada, el signem, el pengem al dispositiu/emulador i l'iniciem. Veiem que s'està creant el nostre abocador i s'hi escriuen moltes dades. El navegador utilitza el xifratge no només per al trànsit, i tot el xifratge passa per la funció en qüestió. Però per algun motiu les dades necessàries no hi són i la sol·licitud requerida no és visible al trànsit. Per no esperar fins que UC Browser es digni a fer la sol·licitud necessària, agafem la resposta xifrada del servidor rebuda anteriorment i tornem a pegar l'aplicació: afegiu el desxifrat a onCreate de l'activitat principal.

    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

Muntem, signem, instal·lem, posem en marxa. Rebem una NullPointerException perquè el mètode ha retornat null.

Durant l'anàlisi posterior del codi, es va descobrir una funció que desxifra línies interessants: “META-INF/” i “.RSA”. Sembla que l'aplicació està verificant el seu certificat. O fins i tot en genera claus. Realment no vull ocupar-me del que passa amb el certificat, així que només li posarem el certificat correcte. Apliquem la línia xifrada perquè en comptes de "META-INF/" tinguem "BLABLINF/", creeu una carpeta amb aquest nom a l'APK i afegiu-hi el certificat del navegador d'esquirol.

Muntem, signem, instal·lem, posem en marxa. Bingo! Tenim la clau!

MitM

Hem rebut una clau i un vector d'inicialització iguals a la clau. Intentem desxifrar la resposta del servidor en mode CBC.

Buscant vulnerabilitats al navegador UC

Veiem l'URL de l'arxiu, una cosa semblant a MD5, "extract_unzipsize" i un número. Comprovem: el MD5 de l'arxiu és el mateix, la mida de la biblioteca descomprimida és la mateixa. Estem intentant pegar aquesta biblioteca i donar-la al navegador. Per mostrar que la nostra biblioteca pegada s'ha carregat, llançarem una intenció de crear un SMS amb el text "PWNED!" Substituirem dues respostes del servidor: puds.ucweb.com/upgrade/index.xhtml i per descarregar l'arxiu. En el primer substituïm MD5 (la mida no canvia després de desempaquetar), en el segon donem l'arxiu amb la biblioteca pegada.

El navegador intenta descarregar l'arxiu diverses vegades, després de les quals dóna un error. Aparentment alguna cosa
a ell no li agrada. Com a resultat de l'anàlisi d'aquest format tèrbol, va resultar que el servidor també transmet la mida de l'arxiu:

Buscant vulnerabilitats al navegador UC

Està codificat en LEB128. Després del pedaç, la mida de l'arxiu amb la biblioteca va canviar una mica, de manera que el navegador va considerar que l'arxiu s'havia descarregat malament i, després de diversos intents, va llançar un error.

Ajustem la mida de l'arxiu... I – victòria! 🙂 El resultat és al vídeo.

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

Conseqüències i reacció del desenvolupador

De la mateixa manera, els pirates informàtics podrien utilitzar la funció insegura del navegador UC per distribuir i executar biblioteques malicioses. Aquestes biblioteques funcionaran en el context del navegador, de manera que rebran tots els permisos del sistema. Com a resultat, la possibilitat de mostrar finestres de pesca, així com l'accés als fitxers de treball de l'esquirol xinès taronja, inclosos els inicis de sessió, les contrasenyes i les galetes emmagatzemades a la base de dades.

Ens vam posar en contacte amb els desenvolupadors d'UC Browser i els vam informar del problema que vam trobar, vam intentar assenyalar la vulnerabilitat i el seu perill, però no van parlar de res amb nosaltres. Mentrestant, el navegador continuava fent gala de la seva característica perillosa a la vista. Però un cop vam revelar els detalls de la vulnerabilitat, ja no era possible ignorar-la com abans. El 27 de març va ser
es va publicar una nova versió del navegador UC 12.10.9.1193, que va accedir al servidor mitjançant HTTPS: puds.ucweb.com/upgrade/index.xhtml.

A més, després de la "correcció" i fins al moment d'escriure aquest article, intentar obrir un PDF en un navegador va donar lloc a un missatge d'error amb el text "Vaja, alguna cosa va fallar!" No es va fer una sol·licitud al servidor quan es va intentar obrir un PDF, però es va fer una sol·licitud quan es va iniciar el navegador, la qual cosa indica la possibilitat de continuar baixant el codi executable infringint les regles de Google Play.

Font: www.habr.com

Afegeix comentari