Buscando vulnerabilidades en UC Browser

Buscando vulnerabilidades en UC Browser

introducción

A finales de marzo reportado, que descubrieron una capacidad oculta para cargar y ejecutar código no verificado en UC Browser. Hoy veremos en detalle cómo se produce esta descarga y cómo los piratas informáticos pueden utilizarla para sus propios fines.

Hace algún tiempo, UC Browser se publicitaba y distribuía de forma muy agresiva: se instalaba en los dispositivos de los usuarios mediante malware, se distribuía desde varios sitios bajo la apariencia de archivos de vídeo (es decir, los usuarios pensaban que estaban descargando, por ejemplo, un vídeo porno, pero en lugar de recibir un APK con este navegador), usó pancartas aterradoras con mensajes de que el navegador estaba desactualizado, era vulnerable y cosas así. En el grupo oficial de UC Browser en VK hay tema, en el que los usuarios pueden quejarse de publicidad desleal, hay muchos ejemplos allí. En 2016 hubo incluso publicidad en video en ruso (sí, publicidad de un navegador que bloquea los anuncios).

Al momento de escribir este artículo, UC Browser tiene más de 500 de instalaciones en Google Play. Esto es impresionante: sólo Google Chrome tiene más. Entre las reseñas se pueden ver bastantes quejas sobre publicidad y redirecciones a algunas aplicaciones en Google Play. Este fue el motivo de nuestra investigación: decidimos ver si UC Browser estaba haciendo algo malo. ¡Y resultó que sí!

En el código de la aplicación, se descubrió la capacidad de descargar y ejecutar código ejecutable, lo cual es contrario a las reglas para publicar solicitudes en Google Play. Además de descargar código ejecutable, UC Browser lo hace de forma insegura, lo que puede utilizarse para lanzar un ataque MitM. Veamos si podemos llevar a cabo tal ataque.

Todo lo escrito a continuación es relevante para la versión de UC Browser que estaba disponible en Google Play en el momento del estudio:

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

Vector de ataque

En el manifiesto de UC Browser puede encontrar un servicio con un nombre que se explica por sí mismo. com.uc.deployment.UpgradeDeployService.

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

Cuando se inicia este servicio, el navegador realiza una solicitud POST para puds.ucweb.com/upgrade/index.xhtml, que se puede ver en el tráfico poco tiempo después de la salida. En respuesta, puede recibir un comando para descargar alguna actualización o módulo nuevo. Durante el análisis, el servidor no dio tales comandos, pero notamos que cuando intentamos abrir un PDF en el navegador, realiza una segunda solicitud a la dirección especificada anteriormente, después de lo cual descarga la biblioteca nativa. Para llevar a cabo el ataque, decidimos utilizar esta característica de UC Browser: la capacidad de abrir PDF usando una biblioteca nativa, que no está en el APK y que se descarga de Internet si es necesario. Vale la pena señalar que, en teoría, se puede obligar a UC Browser a descargar algo sin la interacción del usuario, si proporciona una respuesta bien formada a una solicitud que se ejecuta después de iniciar el navegador. Pero para hacer esto, necesitamos estudiar con más detalle el protocolo de interacción con el servidor, por lo que decidimos que sería más fácil editar la respuesta interceptada y reemplazar la biblioteca para trabajar con PDF.

Entonces, cuando un usuario quiere abrir un PDF directamente en el navegador, se pueden ver las siguientes solicitudes en el tráfico:

Buscando vulnerabilidades en UC Browser

Primero hay una solicitud POST para puds.ucweb.com/upgrade/index.xhtmldespués de lo cual
Se descarga un archivo con una biblioteca para ver formatos PDF y Office. Es lógico suponer que la primera solicitud transmite información sobre el sistema (al menos la arquitectura para proporcionar la biblioteca requerida) y, en respuesta, el navegador recibe cierta información sobre la biblioteca que debe descargarse: la dirección y, posiblemente, , algo más. El problema es que esta solicitud está cifrada.

Fragmento de solicitud

Fragmento de respuesta

Buscando vulnerabilidades en UC Browser

Buscando vulnerabilidades en UC Browser

La biblioteca en sí está empaquetada en ZIP y no está cifrada.

Buscando vulnerabilidades en UC Browser

Buscar código de descifrado de tráfico

Intentemos descifrar la respuesta del servidor. Veamos el código de clase. com.uc.deployment.UpgradeDeployService: del método Comando onStart ir com.uc.deployment.bx, y de ahí 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 la formación de una solicitud POST aquí. Prestamos atención a la creación de un array de 16 bytes y su llenado: 0x5F, 0, 0x1F, -50 (=0xCE). Coincide con lo que vimos en la solicitud anterior.

En la misma clase puedes ver una clase anidada que tiene otro 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");
}
}

El método toma una matriz de bytes como entrada y verifica que el byte cero sea 0x60 o que el tercer byte sea 0xD0 y el segundo byte sea 1, 11 o 0x1F. Observamos la respuesta del servidor: el byte cero es 0x60, el segundo es 0x1F, el tercero es 0x60. Suena como lo que necesitamos. A juzgar por las líneas (“up_decrypt”, por ejemplo), aquí se debe llamar a un método que descifrará la respuesta del servidor.
Pasemos al método. gj. Tenga en cuenta que el primer argumento es el byte en el desplazamiento 2 (es decir, 0x1F en nuestro caso), y el segundo es la respuesta del servidor sin
primeros 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, y el mismo byte que está en nuestro
caso igual a 0x1F, denota una de tres opciones posibles.

Seguimos analizando el código. Después de un par de saltos nos encontramos en un método con un nombre que se explica por sí mismo. descifrarBytesPorClave.

Aquí se separan dos bytes más de nuestra respuesta y de ellos se obtiene una cadena. Está claro que de esta forma se selecciona la clave para descifrar el mensaje.

    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 futuro, observamos que en esta etapa todavía no obtenemos una clave, sino sólo su "identificador". Obtener la clave es un poco más complicado.

En el siguiente método, se añaden dos parámetros más a los existentes, formando cuatro: el número mágico 16, el identificador de clave, los datos cifrados y una cadena incomprensible (en nuestro caso, vacía).

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

Después de una serie de transiciones llegamos al método. estáticoBinarySafeDecryptNoB64 interfaz com.alibaba.wireless.security.open.staticdataencrypt.IStaticDataEncryptComponent. No hay clases en el código de la aplicación principal que implementen esta interfaz. Existe tal clase en el archivo. lib/armeabi-v7a/libsgmain.so, que en realidad no es un .so, sino un .jar. El método que nos interesa se implementa de la siguiente 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í nuestra lista de parámetros se complementa con dos números enteros más: 2 y 0. A juzgar por
todo, 2 significa descifrado, como en el método hacerfinal clase de sistema javax.crypto.Cipher. Y todo esto se transfiere a un determinado enrutador con el número 10601; aparentemente este es el número de comando.

Después de la siguiente cadena de transiciones encontramos una clase que implementa la interfaz. Componente IRouter y metodo hacercomando:

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

Y también clase Biblioteca JNIC, en el que se declara el método nativo hacerComandoNativo:

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

Esto significa que necesitamos encontrar un método en el código nativo. hacerComandoNativo. Y aquí es donde comienza la diversión.

Ofuscación del código de máquina

En archivo libsgmain.so (que en realidad es un .jar y en el que encontramos la implementación de algunas interfaces relacionadas con el cifrado justo arriba) hay una biblioteca nativa: libsgmainso-6.4.36.so. Lo abrimos en IDA y obtenemos un montón de cuadros de diálogo con errores. El problema es que la tabla de encabezados de sección no es válida. Esto se hace intencionadamente para complicar el análisis.

Buscando vulnerabilidades en UC Browser

Pero no es necesario: para cargar correctamente un archivo ELF y analizarlo, basta con una tabla de encabezados del programa. Por lo tanto, simplemente eliminamos la tabla de secciones, poniendo a cero los campos correspondientes en el encabezado.

Buscando vulnerabilidades en UC Browser

Abra el archivo en IDA nuevamente.

Hay dos formas de indicarle a la máquina virtual Java dónde exactamente en la biblioteca nativa se encuentra la implementación de un método declarado en código Java como nativo. La primera es darle un nombre de especie. Java_nombre_paquete_NombreClase_NombreMétodo.

El segundo es registrarlo al cargar la biblioteca (en la función JNI_OnLoad)
usando una llamada de función RegistrarseNativas.

En nuestro caso, si utilizamos el primer método, el nombre debería ser así: Java_com_taobao_wireless_security_adapter_JNICLibrary_doCommandNative.

No existe tal función entre las funciones exportadas, lo que significa que debe buscar una llamada RegistrarseNativas.
vamos a la función JNI_OnLoad y vemos esta imagen:

Buscando vulnerabilidades en UC Browser

¿Que está pasando aqui? A primera vista, el inicio y el final de la función son típicos de la arquitectura ARM. La primera instrucción en la pila almacena el contenido de los registros que la función utilizará en su operación (en este caso, R0, R1 y R2), así como el contenido del registro LR, que contiene la dirección de retorno de la función. . La última instrucción restaura los registros guardados y la dirección del remitente se coloca inmediatamente en el registro de la PC, regresando así de la función. Pero si miras de cerca, notarás que la penúltima instrucción cambia la dirección del remitente almacenada en la pila. Calculemos cómo será después.
ejecución de código. Se carga una determinada dirección 1xB0 en R130, se le resta 5, luego se transfiere a R0 y se le agrega 0x10. Resulta 0xB13B. Por lo tanto, IDA piensa que la última instrucción es el retorno de una función normal, pero en realidad va a la dirección calculada 0xB13B.

Vale la pena recordar aquí que los procesadores ARM tienen dos modos y dos conjuntos de instrucciones: ARM y Thumb. El bit menos significativo de la dirección le dice al procesador qué conjunto de instrucciones se está utilizando. Es decir, la dirección es en realidad 0xB13A y uno en el bit menos significativo indica el modo Thumb.

Se ha agregado un "adaptador" similar al comienzo de cada función en esta biblioteca y
código basura. No nos detendremos más en ellos, solo recordamos
que el comienzo real de casi todas las funciones está un poco más lejos.

Dado que el código no salta explícitamente a 0xB13A, el propio IDA no reconoció que el código estaba ubicado en esta ubicación. Por la misma razón, no reconoce la mayor parte del código de la biblioteca como código, lo que dificulta un poco el análisis. Le decimos a IDA que este es el código y esto es lo que sucede:

Buscando vulnerabilidades en UC Browser

La tabla comienza claramente en 0xB144. ¿Qué hay en sub_494C?

Buscando vulnerabilidades en UC Browser

Al llamar a esta función en el registro LR obtenemos la dirección de la tabla mencionada anteriormente (0xB144). En R0 - índice en esta tabla. Es decir, el valor se toma de la tabla, se suma a LR y el resultado es
la dirección a la que acudir. Intentemos calcularlo: 0xB144 + [0xB144 + 8* 4] = 0xB144 + 0x120 = 0xB264. Vamos a la dirección recibida y vemos literalmente un par de instrucciones útiles y nuevamente vamos a 0xB140:

Buscando vulnerabilidades en UC Browser

Ahora habrá una transición en el desplazamiento con el índice 0x20 de la tabla.

A juzgar por el tamaño de la tabla, habrá muchas transiciones de este tipo en el código. Surge la pregunta de si es posible solucionar esto de alguna manera de forma más automática, sin calcular direcciones manualmente. Y los scripts y la capacidad de parchear código en IDA vienen en nuestra ayuda:

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 el cursor en la línea 0xB26A, ejecute el script y vea la transición a 0xB4B0:

Buscando vulnerabilidades en UC Browser

Una vez más, IDA no reconoció esta área como un código. La ayudamos y vemos otro diseño allí:

Buscando vulnerabilidades en UC Browser

Las instrucciones después de BLX no parecen tener mucho sentido, es más como una especie de desplazamiento. Veamos sub_4964:

Buscando vulnerabilidades en UC Browser

De hecho, aquí se toma una palabra dword en la dirección que se encuentra en LR, se agrega a esta dirección, después de lo cual se toma el valor en la dirección resultante y se coloca en la pila. Además, se agrega 4 a LR para que después de regresar de la función, se omita este mismo desplazamiento. Después de lo cual el comando POP {R1} toma el valor resultante de la pila. Si observas lo que se encuentra en la dirección 0xB4BA + 0xEA = 0xB5A4, verás algo similar a una tabla de direcciones:

Buscando vulnerabilidades en UC Browser

Para parchear este diseño, necesitará obtener dos parámetros del código: el desplazamiento y el número de registro en el que desea colocar el resultado. Para cada registro posible, deberá preparar un fragmento de código con antelación.

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 el cursor al inicio de la estructura que queremos reemplazar – 0xB4B2 – y ejecutamos el script:

Buscando vulnerabilidades en UC Browser

Además de las estructuras ya mencionadas, el código también contiene lo siguiente:

Buscando vulnerabilidades en UC Browser

Como en el caso anterior, tras la instrucción BLX hay un offset:

Buscando vulnerabilidades en UC Browser

Tomamos el desplazamiento a la dirección de LR, lo agregamos a LR y vamos allí. 0x72044 + 0xC = 0x72050. El guión para este diseño es bastante simple:

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 de la ejecución del script:

Buscando vulnerabilidades en UC Browser

Una vez que todo esté parcheado en la función, puede indicarle a IDA su comienzo real. Reunirá todo el código de función y se puede descompilar usando HexRays.

Cadenas de decodificación

Hemos aprendido a lidiar con la ofuscación del código de máquina en la biblioteca. libsgmainso-6.4.36.so de UC Browser y recibió el 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;
}

Echemos un vistazo más de cerca a las siguientes líneas:

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

En función sub_73E24 el nombre de la clase claramente se está descifrando. Como parámetros de esta función, se pasa un puntero a datos similares a los datos cifrados, un determinado búfer y un número. Obviamente, después de llamar a la función, habrá una línea descifrada en el búfer, ya que se pasa a la función. Findclass, que toma el nombre de la clase como segundo parámetro. Por lo tanto, el número es el tamaño del búfer o la longitud de la línea. Intentemos descifrar el nombre de la clase, debería decirnos si vamos en la dirección correcta. Echemos un vistazo más de cerca a lo que sucede 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 una instancia de un contenedor para matrices de bytes del tamaño especificado (no nos detendremos en estos contenedores en detalle). Aquí se crean dos contenedores de este tipo: uno contiene la línea "DcO/lcK+h?m3c*q@" (es fácil adivinar que se trata de una clave), la otra contiene datos cifrados. A continuación, ambos objetos se colocan en una determinada estructura, que se pasa a la función. sub_6115C. Marquemos también un campo con el valor 3 en esta estructura. Veamos qué pasa con esta estructura a continuación.

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ámetro switch es un campo de estructura al que previamente se le asignó el valor 3. Mire el caso 3: a la función sub_6364C Los parámetros se pasan desde la estructura que se agregaron allí en la función anterior, es decir, la clave y los datos cifrados. Si miras de cerca sub_6364C, puedes reconocer el algoritmo RC4 en él.

Tenemos un algoritmo y una clave. Intentemos descifrar el nombre de la clase. Esto es lo que pasó: es/taobao/wireless/security/adapter/JNICLibrary. ¡Excelente! Vamos por buen camino.

árbol de comandos

Ahora necesitamos encontrar un desafío. RegistrarseNativas, que nos señalará la función hacerComandoNativo. Veamos las funciones llamadas desde JNI_OnLoad, y lo encontramos 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;
}

Y de hecho, aquí está registrado un método nativo con el nombre hacerComandoNativo. Ahora sabemos su dirección. Veamos qué hace.

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

Por el nombre se puede adivinar que aquí está el punto de entrada de todas las funciones que los desarrolladores decidieron transferir a la biblioteca nativa. Estamos interesados ​​en la función número 10601.

Puede ver en el código que el número de comando produce tres números: comando/10000, comando % 10000/100 и comando % 10, es decir, en nuestro caso, 1, 6 y 1. Estos tres números, además de un puntero a JNIEnv y los argumentos pasados ​​a la función se agregan a una estructura y se pasan. Utilizando los tres números obtenidos (llamémoslos N1, N2 y N3), se construye un árbol de comandos.

Algo como esto:

Buscando vulnerabilidades en UC Browser

El árbol se llena dinámicamente. JNI_OnLoad.
Tres números codifican el camino en el árbol. Cada hoja del árbol contiene la dirección de bolsillo de la función correspondiente. La clave está en el nodo principal. Encontrar el lugar en el código donde se agrega al árbol la función que necesitamos no es difícil si comprende todas las estructuras utilizadas (no las describimos para no abultar un artículo que ya es bastante extenso).

Más ofuscación

Recibimos la dirección de la función que debería descifrar el tráfico: 0x5F1AC. Pero es demasiado pronto para alegrarnos: los desarrolladores de UC Browser nos han preparado otra sorpresa.

Después de recibir los parámetros de la matriz que se formó en el código Java, obtenemos
a la función en la dirección 0x4D070. Y aquí nos espera otro tipo de ofuscación de código.

Ponemos dos índices en R7 y R4:

Buscando vulnerabilidades en UC Browser

Cambiamos el primer índice a R11:

Buscando vulnerabilidades en UC Browser

Para obtener una dirección de una tabla, use un índice:

Buscando vulnerabilidades en UC Browser

Después de ir a la primera dirección, se utiliza el segundo índice, que está en R4. Hay 230 elementos en la tabla.

¿Qué hacer al respecto? Puede decirle a IDA que se trata de un modificador: Editar -> Otro -> Especificar idioma del modificador.

Buscando vulnerabilidades en UC Browser

El código resultante es terrible. Pero, al atravesar su jungla, notarás una llamada a una función que ya conocemos. sub_6115C:

Buscando vulnerabilidades en UC Browser

Hubo un cambio en el que en el caso 3 hubo un descifrado utilizando el algoritmo RC4. Y en este caso, la estructura pasada a la función se completa con los parámetros pasados ​​a hacerComandoNativo. Recordemos lo que tuvimos allí. magiaInt con el valor 16. Observamos el caso correspondiente y después de varias transiciones encontramos el código mediante el cual se puede identificar el algoritmo.

Buscando vulnerabilidades en UC Browser

¡Esto es AES!

El algoritmo existe, solo queda obtener sus parámetros: modo, clave y, posiblemente, el vector de inicialización (su presencia depende del modo de funcionamiento del algoritmo AES). La estructura con ellos debe formarse en algún lugar antes de la llamada a la función. sub_6115C, pero esta parte del código está especialmente bien ofuscada, por lo que surge la idea de parchear el código para que todos los parámetros de la función de descifrado se vuelquen en un archivo.

Parche

Para no escribir todo el código de parche en lenguaje ensamblador manualmente, puede iniciar Android Studio, escribir allí una función que reciba los mismos parámetros de entrada que nuestra función de descifrado y escribir en un archivo, luego copiar y pegar el código que el compilador generar.

Nuestros amigos del equipo de UC Browser también se ocuparon de la conveniencia de agregar código. Recordemos que al inicio de cada función tenemos código basura que puede ser reemplazado fácilmente por cualquier otro. Muy conveniente 🙂 Sin embargo, al comienzo de la función de destino no hay suficiente espacio para el código que guarda todos los parámetros en un archivo. Tuve que dividirlo en partes y usar bloques de basura de funciones vecinas. En total fueron cuatro partes.

Первая часть:

Buscando vulnerabilidades en UC Browser

En la arquitectura ARM, los primeros cuatro parámetros de función se pasan a través de los registros R0-R3, el resto, si los hay, se pasan a través de la pila. El registro LR lleva la dirección del remitente. Todo esto debe guardarse para que la función pueda funcionar después de que eliminemos sus parámetros. También necesitamos guardar todos los registros que usaremos en el proceso, entonces hacemos PUSH.W {R0-R10,LR}. En R7 obtenemos la dirección de la lista de parámetros pasados ​​a la función a través de la pila.

Usando la función abierto abramos el archivo /datos/local/tmp/aes en modo "ab"
es decir, para sumar. En R0 cargamos la dirección del nombre del archivo, en R1, la dirección de la línea que indica el modo. Y aquí termina el código basura, así que pasamos a la siguiente función. Para que siga funcionando, ponemos al principio la transición al código real de la función, evitando la basura, y en lugar de la basura agregamos una continuación del parche.

Buscando vulnerabilidades en UC Browser

Nosotros llamamos abierto.

Los primeros tres parámetros de la función. Aes tener tipo int. Como guardamos los registros en la pila al principio, simplemente podemos pasar la función escribir sus direcciones en la pila.

Buscando vulnerabilidades en UC Browser

A continuación tenemos tres estructuras que contienen el tamaño de los datos y un puntero a los datos para la clave, el vector de inicialización y los datos cifrados.

Buscando vulnerabilidades en UC Browser

Al final, cierre el archivo, restaure los registros y transfiera el control a la función real. Aes.

Recopilamos un APK con una biblioteca parcheada, lo firmamos, lo subimos al dispositivo/emulador y lo ejecutamos. Vemos que se está creando nuestro volcado y se escriben muchos datos allí. El navegador utiliza cifrado no sólo para el tráfico, sino que todo el cifrado pasa por la función en cuestión. Pero por alguna razón los datos necesarios no están ahí y la solicitud requerida no es visible en el tráfico. Para no esperar hasta que UC Browser se digne realizar la solicitud necesaria, tomemos la respuesta cifrada del servidor recibida anteriormente y parcheemos la aplicación nuevamente: agreguemos el descifrado a onCreate de la actividad 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, firmamos, instalamos, lanzamos. Recibimos una NullPointerException porque el método devolvió nulo.

Durante un análisis más profundo del código, se descubrió una función que descifra líneas interesantes: “META-INF/” y “.RSA”. Parece que la aplicación está verificando su certificado. O incluso genera claves a partir de él. Realmente no quiero ocuparme de lo que sucede con el certificado, así que simplemente le pasaremos el certificado correcto. Parcheemos la línea cifrada para que en lugar de "META-INF/" obtengamos "BLABLINF/", creemos una carpeta con ese nombre en el APK y agreguemos el certificado del navegador squirrel allí.

Montamos, firmamos, instalamos, lanzamos. ¡Bingo! ¡Tenemos la llave!

MitM

Recibimos una clave y un vector de inicialización igual a la clave. Intentemos descifrar la respuesta del servidor en modo CBC.

Buscando vulnerabilidades en UC Browser

Vemos la URL del archivo, algo similar a MD5, “extract_unzipsize” y un número. Comprobamos: el MD5 del archivo es el mismo, el tamaño de la biblioteca descomprimida es el mismo. Estamos intentando parchear esta biblioteca y entregársela al navegador. Para mostrar que nuestra biblioteca parcheada se ha cargado, lanzaremos un Intent para crear un SMS con el texto "¡PWNED!" Reemplazaremos dos respuestas del servidor: puds.ucweb.com/upgrade/index.xhtml y descargar el archivo. En el primero reemplazamos MD5 (el tamaño no cambia después de descomprimirlo), en el segundo entregamos el archivo con la biblioteca parcheada.

El navegador intenta descargar el archivo varias veces y luego muestra un error. aparentemente algo
no le gusta. Como resultado del análisis de este formato turbio, resultó que el servidor también transmite el tamaño del archivo:

Buscando vulnerabilidades en UC Browser

Está codificado en LEB128. Después del parche, el tamaño del archivo con la biblioteca cambió un poco, por lo que el navegador consideró que el archivo se descargó de manera torcida y después de varios intentos arrojó un error.

Ajustamos el tamaño del archivo... ¡Y – victoria! 🙂 El resultado está en el vídeo.

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

Consecuencias y reacción de los desarrolladores.

De la misma manera, los piratas informáticos podrían utilizar la característica insegura de UC Browser para distribuir y ejecutar bibliotecas maliciosas. Estas bibliotecas funcionarán en el contexto del navegador, por lo que recibirán todos los permisos del sistema. Como resultado, es posible mostrar ventanas de phishing, así como acceder a los archivos de trabajo de la ardilla china naranja, incluidos nombres de usuario, contraseñas y cookies almacenadas en la base de datos.

Contactamos a los desarrolladores de UC Browser y les informamos sobre el problema que encontramos, intentamos señalar la vulnerabilidad y su peligro, pero no discutieron nada con nosotros. Mientras tanto, el navegador seguía haciendo alarde de su característica peligrosa a plena vista. Pero una vez que revelamos los detalles de la vulnerabilidad, ya no fue posible ignorarla como antes. El 27 de marzo fue
Se lanzó una nueva versión de UC Browser 12.10.9.1193, que accedía al servidor a través de HTTPS: puds.ucweb.com/upgrade/index.xhtml.

Además, después de la “solución” y hasta el momento de escribir este artículo, al intentar abrir un PDF en un navegador aparecía un mensaje de error con el texto “¡Ups, algo salió mal!” No se realizó una solicitud al servidor al intentar abrir un PDF, pero sí cuando se inició el navegador, lo que insinúa la capacidad continua de descargar código ejecutable en violación de las reglas de Google Play.

Fuente: habr.com

Añadir un comentario