Buscando vulnerabilidades no navegador UC

Buscando vulnerabilidades no navegador UC

Introdución

A finais de marzo nós informou, que descubriron unha capacidade oculta para cargar e executar código non verificado no navegador UC. Hoxe veremos en detalle como se produce esta descarga e como os hackers poden usala para os seus propios fins.

Hai tempo, UC Browser publicouse e distribuíuse de forma moi agresiva: instalouse nos dispositivos dos usuarios mediante software malicioso, distribuído desde varios sitios baixo o pretexto de ficheiros de vídeo (é dicir, os usuarios pensaban que estaban descargando, por exemplo, un vídeo porno, pero no seu lugar recibiu un APK con este navegador), utilizou banners asustados con mensaxes de que o navegador estaba desactualizado, vulnerable e cousas así. No grupo oficial UC Browser en VK hai tema, no que os usuarios poden queixarse ​​de publicidade desleal, hai moitos exemplos aí. En 2016 houbo incluso publicidade de vídeo en ruso (si, publicidade para un navegador de bloqueo de anuncios).

No momento de escribir este artigo, UC Browser ten máis de 500 instalacións en Google Play. Isto é impresionante: só Google Chrome ten máis. Entre as críticas pódense ver bastantes queixas sobre publicidade e redireccións a algunhas aplicacións de Google Play. Este foi o motivo da nosa investigación: decidimos ver se UC Browser estaba facendo algo malo. E resultou que si!

No código da aplicación, descubriuse a capacidade de descargar e executar código executable, o que é contrario ás normas de publicación de solicitudes en Google Play. Ademais de descargar código executable, UC Browser faino de forma insegura, que se pode usar para lanzar un ataque MitM. A ver se podemos levar a cabo un ataque así.

Todo o escrito a continuación é relevante para a versión do navegador UC que estaba dispoñible en Google Play no momento do estudo:

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

Vector de ataque

No manifesto do navegador UC podes atopar un servizo cun nome que se explica por si mesmo com.uc.deployment.UpgradeDeployService.

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

Cando se inicia este servizo, o navegador fai unha solicitude POST a puds.ucweb.com/upgrade/index.xhtml, que se pode ver no tráfico tempo despois da saída. Como resposta, pode recibir un comando para descargar algunha actualización ou módulo novo. Durante a análise, o servidor non deu estes comandos, pero observamos que cando tentamos abrir un PDF no navegador, fai unha segunda solicitude ao enderezo especificado anteriormente, despois de que descarga a biblioteca nativa. Para levar a cabo o ataque, decidimos utilizar esta función de UC Browser: a posibilidade de abrir PDF mediante unha biblioteca nativa, que non está no APK e que descarga de Internet se é necesario. Paga a pena notar que, teoricamente, o navegador UC pode verse obrigado a descargar algo sen interacción do usuario, se ofrece unha resposta ben formada a unha solicitude que se executa despois de iniciar o navegador. Pero para iso, necesitamos estudar o protocolo de interacción co servidor con máis detalle, polo que decidimos que sería máis fácil editar a resposta interceptada e substituír a biblioteca por traballar con PDF.

Así, cando un usuario quere abrir un PDF directamente no navegador, pódense ver as seguintes solicitudes no tráfico:

Buscando vulnerabilidades no navegador UC

Primeiro hai unha solicitude POST para puds.ucweb.com/upgrade/index.xhtml, entón
Descarga un arquivo cunha biblioteca para ver PDF e formatos ofimáticos. É lóxico supoñer que a primeira solicitude transmite información sobre o sistema (polo menos a arquitectura para proporcionar a biblioteca requirida), e en resposta a ela o navegador recibe algunha información sobre a biblioteca que hai que descargar: o enderezo e, posiblemente , algo máis. O problema é que esta solicitude está cifrada.

Solicitar fragmento

Fragmento de resposta

Buscando vulnerabilidades no navegador UC

Buscando vulnerabilidades no navegador UC

A propia biblioteca está empaquetada en ZIP e non está cifrada.

Buscando vulnerabilidades no navegador UC

Busca o código de descifrado de tráfico

Imos tentar descifrar a resposta do servidor. Vexamos o código da clase com.uc.deployment.UpgradeDeployService: do método onStartCommand Ir a com.uc.deployment.bx, e desde el 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);
}

Vemos a formación dunha solicitude POST aquí. Prestamos atención á creación dunha matriz de 16 bytes e ao seu recheo: 0x5F, 0, 0x1F, -50 (=0xCE). Coincide co que vimos na solicitude anterior.

Na mesma clase podes ver unha clase aniñada que ten outro método interesante:

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

O método toma unha matriz de bytes como entrada e comproba que o byte cero é 0x60 ou o terceiro byte é 0xD0 e o segundo byte é 1, 11 ou 0x1F. Observamos a resposta do servidor: o byte cero é 0x60, o segundo é 0x1F, o terceiro é 0x60. Parece o que necesitamos. A xulgar polas liñas ("up_decrypt", por exemplo), aquí debería chamarse un método que descifrará a resposta do servidor.
Pasemos ao método gj. Teña en conta que o primeiro argumento é o byte no offset 2 (é dicir, 0x1F no noso caso), e o segundo é a resposta do servidor sen
primeiros 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;
}

Obviamente, aquí seleccionamos un algoritmo de descifrado, e o mesmo byte que está no noso
caso igual a 0x1F, indica unha das tres opcións posibles.

Seguimos analizando o código. Despois dun par de saltos atopámonos nun método cun nome que se explica por si mesmo descifrarBytesByKey.

Aquí sepáranse dous bytes máis da nosa resposta e obtense unha cadea deles. Está claro que deste xeito se selecciona a clave para descifrar a mensaxe.

    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 ao futuro, observamos que nesta fase aínda non obtemos unha clave, senón só o seu “identificador”. Conseguir a chave é un pouco máis complicado.

No seguinte método engádense dous parámetros máis aos existentes, facendo catro deles: o número máxico 16, o identificador da clave, os datos cifrados e unha cadea incomprensible (no noso caso, baleira).

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

Despois dunha serie de transicións chegamos ao método staticBinarySafeDecryptNoB64 interface com.alibaba.wireless.security.open.staticdataencrypt.IStaticDataEncryptComponent. Non hai clases no código da aplicación principal que implementen esta interface. Hai unha clase deste tipo no ficheiro lib/armeabi-v7a/libsgmain.so, que en realidade non é un .so, senón un .jar. O método que nos interesa está implementado do seguinte xeito:

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í a nosa lista de parámetros complétase con dous números enteiros máis: 2 e 0. A xulgar por
todo, 2 significa descifrado, como no método doFinal clase do sistema javax.crypto.Cipher. E todo isto transfírese a un determinado enrutador co número 10601: este é aparentemente o número de comando.

Despois da seguinte cadea de transicións atopamos unha clase que implementa a interface IRouterComponent e método doComando:

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

E tamén clase Biblioteca JNIC, no que se declara o método nativo doCommandNative:

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

Isto significa que necesitamos atopar un método no código nativo doCommandNative. E aquí é onde comeza a diversión.

Ofuscación do código máquina

En arquivo libsgmain.so (que en realidade é un .jar e no que atopamos a implementación dalgunhas interfaces relacionadas co cifrado xusto arriba) hai unha biblioteca nativa: libsgmainso-6.4.36.so. Abrímolo en IDA e obtemos unha morea de caixas de diálogo con erros. O problema é que a táboa de cabeceira de sección non é válida. Isto faise a propósito para complicar a análise.

Buscando vulnerabilidades no navegador UC

Pero non é necesario: para cargar correctamente un ficheiro ELF e analizalo abonda cunha táboa de cabeceira do programa. Polo tanto, simplemente eliminamos a táboa de seccións, eliminando os campos correspondentes na cabeceira.

Buscando vulnerabilidades no navegador UC

Abre o ficheiro en IDA de novo.

Hai dúas formas de indicarlle á máquina virtual Java onde se atopa exactamente na biblioteca nativa a implementación dun método declarado en código Java como nativo. O primeiro é darlle un nome de especie Java_package_name_ClassName_MethodName.

O segundo é rexistralo ao cargar a biblioteca (na función JNI_OnLoad)
usando unha chamada de función Rexistrarse nativos.

No noso caso, se usamos o primeiro método, o nome debería ser así: Java_com_taobao_wireless_security_adapter_JNICLibrary_doCommandNative.

Non hai tal función entre as funcións exportadas, o que significa que debes buscar unha chamada Rexistrarse nativos.
Imos á función JNI_OnLoad e vemos esta imaxe:

Buscando vulnerabilidades no navegador UC

Que está pasando aquí? A primeira vista, o inicio e o final da función son típicos da arquitectura ARM. A primeira instrución da pila almacena o contido dos rexistros que a función utilizará no seu funcionamento (neste caso, R0, R1 e R2), así como o contido do rexistro LR, que contén o enderezo de retorno da función. . A última instrución restaura os rexistros gardados e o enderezo de retorno colócase inmediatamente no rexistro do PC, volvendo así da función. Pero se observas con atención, notarás que a penúltima instrución cambia o enderezo de retorno almacenado na pila. Imos calcular como será despois
execución de código. Un determinado enderezo 1xB0 cárgase en R130, 5 déixase, despois transfírese a R0 e engádese 0x10. Resulta 0xB13B. Así, IDA pensa que a última instrución é un retorno de función normal, pero en realidade vai ao enderezo calculado 0xB13B.

Paga a pena lembrar aquí que os procesadores ARM teñen dous modos e dous conxuntos de instrucións: ARM e Thumb. O bit menos significativo do enderezo indica ao procesador que conxunto de instrucións se está a utilizar. É dicir, o enderezo é en realidade 0xB13A, e un no bit menos significativo indica o modo Thumb.

Engadiuse un "adaptador" similar ao comezo de cada función desta biblioteca e
código de lixo. Non nos ocuparemos deles máis en detalle, só lembramos
que o inicio real de case todas as funcións está un pouco máis lonxe.

Dado que o código non salta explícitamente a 0xB13A, o propio IDA non recoñeceu que o código estaba situado nesta localización. Polo mesmo motivo, non recoñece a maior parte do código da biblioteca como código, o que dificulta algo a análise. Dicimos a IDA que este é o código, e isto é o que ocorre:

Buscando vulnerabilidades no navegador UC

A táboa comeza claramente en 0xB144. Que hai en sub_494C?

Buscando vulnerabilidades no navegador UC

Ao chamar a esta función no rexistro LR, obtemos o enderezo da táboa mencionada anteriormente (0xB144). En R0 - índice nesta táboa. É dicir, o valor tómase da táboa, engádese a LR e o resultado é
o enderezo ao que ir. Tentemos calculalo: 0xB144 + [0xB144 + 8* 4] = 0xB144 + 0x120 = 0xB264. Imos ao enderezo recibido e vemos literalmente un par de instrucións útiles e de novo imos a 0xB140:

Buscando vulnerabilidades no navegador UC

Agora haberá unha transición no offset co índice 0x20 da táboa.

A xulgar polo tamaño da táboa, haberá moitas transicións deste tipo no código. Xorde a pregunta de se é posible dalgún xeito tratar isto de forma máis automática, sen calcular manualmente os enderezos. E os scripts e a capacidade de parchear código en IDA veñen na nosa axuda:

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"

Coloque o cursor na liña 0xB26A, execute o script e vexa a transición a 0xB4B0:

Buscando vulnerabilidades no navegador UC

IDA de novo non recoñeceu esta área como un código. Axudámola e vemos alí outro deseño:

Buscando vulnerabilidades no navegador UC

As instrucións despois de BLX non parecen ter moito sentido, é máis como algún tipo de desprazamento. Vexamos sub_4964:

Buscando vulnerabilidades no navegador UC

E de feito, aquí tómase un dword no enderezo que se atopa en LR, engádese a este enderezo, despois de que se toma o valor do enderezo resultante e se coloca na pila. Ademais, engádese 4 a LR para que despois de regresar da función, se salte este mesmo desplazamento. Despois diso, o comando POP {R1} toma o valor resultante da pila. Se miras o que se atopa no enderezo 0xB4BA + 0xEA = 0xB5A4, verás algo semellante a unha táboa de enderezos:

Buscando vulnerabilidades no navegador UC

Para parchear este deseño, terás que obter dous parámetros do código: o offset e o número de rexistro no que queres poñer o resultado. Para cada rexistro posible, terás que preparar un anaco de código previamente.

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"

Colocamos o cursor ao comezo da estrutura que queremos substituír - 0xB4B2 - e executamos o script:

Buscando vulnerabilidades no navegador UC

Ademais das estruturas xa mencionadas, o código tamén contén o seguinte:

Buscando vulnerabilidades no navegador UC

Como no caso anterior, despois da instrución BLX hai unha compensación:

Buscando vulnerabilidades no navegador UC

Levamos o desplazamento ao enderezo de LR, engadímolo a LR e imos alí. 0x72044 + 0xC = 0x72050. O script para este deseño é bastante sinxelo:

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"

Resultado da execución do script:

Buscando vulnerabilidades no navegador UC

Unha vez que todo estea remendado na función, podes apuntar a IDA ao seu inicio real. Reunirá todo o código da función e pódese descompilar usando HexRays.

Decodificación de cadeas

Aprendemos a xestionar a ofuscación do código máquina na biblioteca libsgmainso-6.4.36.so de UC Browser e recibiu o código de función 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;
}

Vexamos máis de cerca as seguintes liñas:

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

En función sub_73E24 o nome da clase está sendo claramente descifrado. Como parámetros desta función, pásase un punteiro a datos semellante aos datos cifrados, un determinado búfer e un número. Obviamente, despois de chamar á función, haberá unha liña descifrada no búfer, xa que se pasa á función Buscar clase, que toma o nome da clase como segundo parámetro. Polo tanto, o número é o tamaño do buffer ou a lonxitude da liña. Intentemos descifrar o nome da clase, debería indicarnos se imos na boa dirección. Vexamos máis de cerca o que acontece en 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ón sub_7AF78 crea unha instancia dun contenedor para matrices de bytes do tamaño especificado (non nos detendremos nestes contenedores en detalle). Aquí créanse dous contedores deste tipo: un contén a liña "DcO/lcK+h?m3c*q@" (é doado adiviñar que esta é unha chave), a outra contén datos cifrados. A continuación, os dous obxectos colócanse nunha determinada estrutura, que se pasa á función sub_6115C. Marquemos tamén nesta estrutura un campo co valor 3. A continuación vexamos que pasa con esta estrutura.

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

O parámetro switch é un campo de estrutura ao que previamente se lle asignou o valor 3. Mire o caso 3: á función sub_6364C Os parámetros pásanse desde a estrutura que se engadiron alí na función anterior, é dicir, a clave e os datos cifrados. Se miras ben sub_6364C, pode recoñecer o algoritmo RC4 nel.

Temos un algoritmo e unha clave. Imos tentar descifrar o nome da clase. Aquí está o que pasou: com/taobao/wireless/security/adapter/JNICLibrary. Genial! Estamos no bo camiño.

Árbore de comandos

Agora temos que buscar un reto Rexistrarse nativos, que nos indicará a función doCommandNative. Vexamos as funcións chamadas desde JNI_OnLoad, e atopámolo en 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;
}

E, de feito, aquí rexístrase un método nativo co nome doCommandNative. Agora sabemos o seu enderezo. A ver que fai.

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

Polo nome podes adiviñar que aquí está o punto de entrada de todas as funcións que os desenvolvedores decidiron transferir á biblioteca nativa. Estamos interesados ​​na función número 10601.

Podes ver no código que o número de comando produce tres números: comando/10000, comando % 10000/100 и comando % 10, é dicir, no noso caso, 1, 6 e 1. Estes tres números, así como un punteiro para JNIEnv e os argumentos pasados ​​á función engádense a unha estrutura e transmítense. Usando os tres números obtidos (denotémolos N1, N2 e N3), constrúese unha árbore de comandos.

Algo coma isto:

Buscando vulnerabilidades no navegador UC

A árbore énchese dinámicamente JNI_OnLoad.
Tres números codifican o camiño na árbore. Cada folla da árbore contén o enderezo pocked da función correspondente. A chave está no nodo pai. Atopar o lugar no código onde se engade á árbore a función que necesitamos non é difícil se entendes todas as estruturas utilizadas (non as describimos para non inchar un artigo xa bastante grande).

Máis ofuscación

Recibimos o enderezo da función que debería descifrar o tráfico: 0x5F1AC. Pero é demasiado cedo para alegrarnos: os desenvolvedores de UC Browser preparáronnos outra sorpresa.

Despois de recibir os parámetros da matriz que se formou no código Java, obtemos
á función no enderezo 0x4D070. E aquí espéranos outro tipo de ofuscación de código.

Poñemos dous índices en R7 e R4:

Buscando vulnerabilidades no navegador UC

Cambiamos o primeiro índice a R11:

Buscando vulnerabilidades no navegador UC

Para obter un enderezo dunha táboa, use un índice:

Buscando vulnerabilidades no navegador UC

Despois de ir ao primeiro enderezo, utilízase o segundo índice, que está en R4. Hai 230 elementos na táboa.

Que facer ao respecto? Podes dicirlle a IDA que este é un interruptor: Editar -> Outro -> Especificar o idioma do cambio.

Buscando vulnerabilidades no navegador UC

O código resultante dá medo. Pero, abrindo o teu camiño pola súa selva, podes notar unha chamada a unha función que xa nos coñecemos sub_6115C:

Buscando vulnerabilidades no navegador UC

Había un interruptor no que no caso 3 había un descifrado mediante o algoritmo RC4. E neste caso, a estrutura pasada á función énchese a partir dos parámetros pasados doCommandNative. Lembremos o que tiñamos alí magicInt co valor 16. Observamos o caso correspondente -e despois de varias transicións atopamos o código polo que se pode identificar o algoritmo.

Buscando vulnerabilidades no navegador UC

Isto é AES!

O algoritmo existe, só queda obter os seus parámetros: modo, clave e, posiblemente, o vector de inicialización (a súa presenza depende do modo de funcionamento do algoritmo AES). A estrutura con eles debe formarse nalgún lugar antes da chamada de función sub_6115C, pero esta parte do código está especialmente ben ofuscada, polo que xorde a idea de parchear o código para que todos os parámetros da función de descifrado se volquen nun ficheiro.

Parche

Para non escribir todo o código do parche en linguaxe ensamblador manualmente, pode iniciar Android Studio, escribir alí unha función que reciba os mesmos parámetros de entrada que a nosa función de descifrado e escriba nun ficheiro, despois copiar e pegar o código que fará o compilador. xerar.

Os nosos amigos do equipo de UC Browser tamén se ocuparon da comodidade de engadir código. Lembremos que ao comezo de cada función temos un código lixo que pode ser facilmente substituído por calquera outro. Moi cómodo 🙂 Non obstante, ao comezo da función de destino non hai espazo suficiente para o código que garda todos os parámetros nun ficheiro. Tiven que dividilo en partes e usar bloques de lixo de funcións veciñas. Foron catro partes en total.

A primeira parte:

Buscando vulnerabilidades no navegador UC

Na arquitectura ARM, os catro primeiros parámetros de función pásanse a través dos rexistros R0-R3, o resto, se hai, pásanse pola pila. O rexistro LR leva o enderezo de retorno. Todo isto debe ser gardado para que a función poida funcionar despois de volcar os seus parámetros. Tamén necesitamos gardar todos os rexistros que utilizaremos no proceso, polo que facemos PUSH.W {R0-R10,LR}. En R7 obtemos o enderezo da lista de parámetros pasados ​​á función a través da pila.

Usando a función fopen imos abrir o ficheiro /data/local/tmp/aes en modo "ab".
é dicir, para adición. En R0 cargamos o enderezo do nome do ficheiro, en R1 - o enderezo da liña que indica o modo. E aquí remata o código lixo, así que pasamos á seguinte función. Para que siga funcionando, poñemos ao principio a transición ao código real da función, evitando o lixo, e en lugar do lixo engadimos unha continuación do parche.

Buscando vulnerabilidades no navegador UC

Chamando fopen.

Os tres primeiros parámetros da función AES ter tipo int. Xa que gardamos os rexistros na pila ao principio, podemos simplemente pasar a función f escribir os seus enderezos na pila.

Buscando vulnerabilidades no navegador UC

A continuación temos tres estruturas que conteñen o tamaño dos datos e un punteiro aos datos para a clave, o vector de inicialización e os datos cifrados.

Buscando vulnerabilidades no navegador UC

Ao final, pecha o ficheiro, restablece os rexistros e transfire o control á función real AES.

Recollemos un APK cunha biblioteca parcheada, asínao, cargámolo no dispositivo/emulador e lanzámolo. Vemos que se está creando o noso vertedoiro e alí se escriben moitos datos. O navegador usa o cifrado non só para o tráfico, e todo o cifrado pasa pola función en cuestión. Pero por algún motivo os datos necesarios non están alí e a solicitude requirida non é visible no tráfico. Para non agardar a que UC Browser se digne a facer a solicitude necesaria, tomemos a resposta cifrada do servidor recibida anteriormente e parcheemos de novo a aplicación: engade o descifrado a onCreate da actividade 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

Montamos, asinamos, instalamos, lanzamos. Recibimos unha NullPointerException porque o método devolveu nulo.

Durante unha análise posterior do código, descubriuse unha función que descifra liñas interesantes: “META-INF/” e “.RSA”. Parece que a aplicación está verificando o seu certificado. Ou mesmo xera claves a partir del. Realmente non quero tratar o que está a suceder co certificado, así que simplemente enviarémoslle o certificado correcto. Parchemos a liña cifrada para que no canto de "META-INF/" teñamos "BLABLINF/", creemos un cartafol con ese nome no APK e engademos alí o certificado do navegador de esquilo.

Montamos, asinamos, instalamos, lanzamos. Bingo! Temos a chave!

MitM

Recibimos unha clave e un vector de inicialización igual á chave. Tentemos descifrar a resposta do servidor en modo CBC.

Buscando vulnerabilidades no navegador UC

Vemos o URL do arquivo, algo parecido a MD5, "extract_unzipsize" e un número. Comprobamos: o MD5 do arquivo é o mesmo, o tamaño da biblioteca desempaquetada é o mesmo. Estamos tentando parchear esta biblioteca e darlla ao navegador. Para mostrar que a nosa biblioteca parcheada se cargou, lanzaremos un intento de crear unha SMS co texto "PWNED!" Substituiremos dúas respostas do servidor: puds.ucweb.com/upgrade/index.xhtml e para descargar o arquivo. No primeiro substituímos MD5 (o tamaño non cambia despois de desempaquetar), no segundo damos o arquivo coa biblioteca parcheada.

O navegador tenta descargar o arquivo varias veces, despois de que dá un erro. Aparentemente algo
non lle gusta. Como resultado da análise deste formato turbio, resultou que o servidor tamén transmite o tamaño do arquivo:

Buscando vulnerabilidades no navegador UC

Está codificado en LEB128. Despois do parche, o tamaño do arquivo coa biblioteca cambiou un pouco, polo que o navegador considerou que o arquivo estaba descargado de forma torcida e, despois de varios intentos, arroxou un erro.

Axustamos o tamaño do arquivo... E – vitoria! 🙂 O resultado está no vídeo.

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

Consecuencias e reacción do desenvolvedor

Do mesmo xeito, os hackers poderían utilizar a función insegura do navegador UC para distribuír e executar bibliotecas maliciosas. Estas bibliotecas funcionarán no contexto do navegador, polo que recibirán todos os permisos do sistema. Como resultado, a capacidade de mostrar as fiestras de phishing, así como o acceso aos ficheiros de traballo do esquío chinés laranxa, incluíndo inicios de sesión, contrasinais e cookies almacenadas na base de datos.

Contactamos cos desenvolvedores de UC Browser e informámoslles sobre o problema que atopamos, intentamos sinalar a vulnerabilidade e o seu perigo, pero non comentaron nada connosco. Mentres tanto, o navegador seguía facendo gala da súa perigosa función á vista. Pero unha vez que revelamos os detalles da vulnerabilidade, xa non era posible ignorala como antes. O 27 de marzo foi
lanzouse unha nova versión de UC Browser 12.10.9.1193, que accedeu ao servidor a través de HTTPS: puds.ucweb.com/upgrade/index.xhtml.

Ademais, despois da "corrección" e ata o momento de escribir este artigo, ao tentar abrir un PDF nun navegador apareceu unha mensaxe de erro co texto "Vaia, algo saíu mal!" Non se realizou unha solicitude ao servidor cando se tentaba abrir un PDF, pero si se iniciou o navegador, o que insinúa a posibilidade continuada de descargar código executable incumprindo as regras de Google Play.

Fonte: www.habr.com

Engadir un comentario