Cómo casi atrapé un virus tratando de vender botas



Soy una de esas personas que, con el inicio del otoño, intentan pasar menos tiempo en la calle. En Moscú, esto no es difícil: está limitado a la ruta desde el hogar a la oficina y viceversa. Sin embargo, el clima húmedo puede causar molestias en la habitación, especialmente si su lugar de trabajo, como el mío, está en la ventana, y cada segundo colega, quejándose de la congestión, pide que se ventile la oficina. Para no caer en el bazo, este otoño actualicé el armario.

Pensando en el destino de cosas innecesarias, me pregunté qué hacer con ellas: ¿tirarlo, cortarlo en trapos, dárselo a mi hermano menor para que lo lleve? Pero, por un lado, ninguno de estos métodos era adecuado: eran botas de cuero 44 de un tamaño decente, pero me molestaron en orden. Decidí venderlos en Avito. Subí fotos, indiqué un nombre falso (la seguridad de la información es la misma), saqué mis botas, un par de cosas más y me fui a dormir. ¿Cómo supe que esto resultaría en un largo análisis de la aplicación de amenazas ocultas?



Agradable sorpresa


El día después de un par de llamadas dudosas, recibí un interesante mensaje SMS con el siguiente contenido:



Después de un par de días, recibí otro mensaje similar:



Sorprendido de que alguien pudiera transferirme dinero de alguna manera por Internet (aparentemente, soy el único viejo, sigo usando libros de ahorro en papel), hice clic en el enlace por SMS.

Después de eso, me ofrecieron descargar una aplicación de Android (archivo apk). Felizmente descargando el archivo, vi lo siguiente:



Es creíble! Ansiosamente quería instalar rápidamente todo y terminarlo.
Pero aquí, como siempre, el molesto sistema operativo Android por alguna razón no me permitió ejecutar el archivo. "¡Dame el dinero ya!" Estaba indignado Tuve que ir a la configuración y activar alguna opción "Fuentes desconocidas", ¿es el teléfono realmente tan estúpido en 2018? Por cierto, mi teléfono es Xiaomi Remdi con Andoid 6.0.1 (nota para técnicos).



Esto fue seguido por una cadena de eventos extraños. El teléfono continuó informando fuentes poco confiables. ¡Pero Avito es una fuente confiable ! Tuve que buscarlo en Google, descubrir cómo solucionar esto y luego desactivarlo en la configuración. Pronto apareció un cierto antivirus que no instalé.



Finalmente, vi la codiciada ventana de instalación estándar: no tiene sentido mirar los permisos hoy en día , ahora incluso una computadora portátil no se iniciará hasta que le dé acceso completo al teléfono. Momento alegre de completar la instalación de la aplicación y ¡COMENZAR! Estaba esperando el dinero prometido. Además, la aplicación solicitó privilegios de administrador, con lo que acepté alegremente. Desafortunadamente, la aplicación se comportó de manera extraña y no quería pagar, y pronto desapareció por completo de la lista de aplicaciones en la pantalla común.





Spoiler
Más tarde revisé otro teléfono: Lenovo con Android 4.4.2 a bordo. La lista de permisos durante la instalación resultó ser mucho mayor. Y ninguna protección de Play y Anti-Virus no interfieren, solo necesita permitir la instalación desde fuentes poco confiables.





Altruismo



Entonces, ¿a qué hemos llegado?

  • La instalación tardó 20 minutos.
  • No he recibido el dinero.

Pensé que el problema era un error de código, como suele ser el caso con los programadores. Decidí identificar qué errores ocurren cuando la aplicación se ejecuta en mi teléfono y enviar un informe al desarrollador.

Spoiler
Ahora quedan tan pocas personas desinteresadas, una de ellas soy yo.

Está claro que este tipo de aplicación debería funcionar cuando se conecta Internet, así que para empezar intenté analizar el tráfico entre el teléfono y el servidor de aplicaciones.

Guía técnica de configuración de proxy
Puede escuchar el tráfico de Internet en cualquier punto a lo largo de la ruta desde el teléfono al servidor, ya sea un enrutador doméstico, un proveedor local de Internet, una red troncal de Internet o un servidor de aplicaciones.

Los problemas:

  • Se requiere acceso a este equipo.
  • Es necesario separar el tráfico de la aplicación deseada del resto.
  • En el caso del cifrado (y en 2018 todo ya está cifrado), es necesario conocer la clave.

Decidí ir de una manera más clásica y configurar proxies en el teléfono con un servidor proxy en mi propia computadora portátil. Para mitigar el problema de cifrado, decidí importar mi certificado y no inicié otras aplicaciones para separar el tráfico de la aplicación.
Elegí burp suite como el programa del servidor proxy. Configuré un servidor proxy y exporté un certificado a mi teléfono.





Después de configurar un software especializado, pude ver qué solicitudes envía la aplicación al servidor:



Como puede ver, los datos no se envían (el valor del host desconocido se indica en la columna IP), además, no se puede analizar debido a la presencia de cifrado adicional en el nivel de la aplicación. A juzgar por error, el teléfono no pudo determinar la dirección IP del servidor y sus subdominios https: //*.sky-sync.pw por su nombre de dominio.

Esto solo podría significar las siguientes opciones:

  • El nombre de dominio ha dejado de existir.
    • Fue bloqueado por el propio propietario.
    • Fue bloqueado por el registrador en la denuncia.
  • Problema con el servidor DNS
    • El servidor DNS no conoce la dirección, porque el desarrollador en producción implementó la dirección DNS local.
    • El servidor DNS bloqueó específicamente esta solicitud, lo que no es sorprendente en la era de la censura de Internet.

Para verificar la suposición de un problema con el servidor DNS, traté de hacer solicitudes desde diferentes servidores DNS grandes: Google, Yandex, OpenDNS (generalmente, el DNS local está censurado):



Se puede ver que ninguno de ellos sabe nada sobre este nombre. Luego, miré la información whois sobre el registro de dominio:



Curioso: el dominio está registrado, es decir, lo más probable es que no sea local, pero como el dominio no se resuelve, es posible que el registrador lo haya bloqueado por abuso. Pero para que? ¿Qué mal hizo él?

Para saber qué es esta aplicación y cómo puedo tomar mi dinero, decidí usar la magia de la ingeniería inversa.

Abismo


Si eres humanista y has leído hasta este lugar, entonces esto es bueno: para el desarrollo de lo subsiguiente, mereces un premio póstumo .

Kit de herramientas


Para saber qué hay "bajo el capó" de la aplicación, necesitamos descargar herramientas especializadas. Puedes descargarlos individualmente:

  • Desempaquetar el contenedor apk
    • Clásico - ApkTool .
    • Puede descomprimirlo con un archivador convencional, pero todos los recursos binarios, incluidas las aplicaciones y el archivo de manifiesto, serán ilegibles.
  • Descompilador de código Smali
    • El estándar es Dex2Jar , pero aprenda que este programa a menudo funciona de forma torcida.
    • Debe abordar el asunto con mucho cuidado, ya que el descompilador al descompilador es diferente, lo consideraremos más adelante.
  • Un programa para ver código descompilado , recomendaría jd-gui

O puede usar el producto, generalmente pagado, donde hay todo a la vez. Prefiero JebDecompiler : simplemente puede enviar una aplicación apk a su entrada, y organizará perfectamente todo en pestañas, además es conveniente cambiar entre código Java pequeño y descompilado en él.

Por separado, quiero señalar:

  • Si hubiera libs nativas (la carpeta / libs en la estructura de la aplicación), se necesitaría un desensamblador .
  • Es posible que necesite un conjunto de utilidades para trabajar con el código smali (dex-smali, smali-to-dex).
  • Resaltado de sintaxis en Notepad ++ para no romper los ojos.

Solo los viejos van a la batalla


Revisar


Cuando abre el código descompilado, inmediatamente queda claro que está ofuscado.



¿Cómo entiendo eso?

  • Nombres de clase legibles por humanos
    isqpwcmx.isfdztgb.adscjobz.nxscomkr.jypbdxnt.utagwpym.wprtdznb.swldgrhm.yrbjpktq.wukovicq;
  • Código inalcanzable
     if(0 != 0) {</li> String v1 = "flnwznvh";</li> if(v1.length() != 661 && v1.charAt(0) == 104) {</li> v1.length();</li> } 
  • Cifrado de cadenas
     vcgrnfjx.execSQL(nvhdzjfo.xipswfqb(new String[]{"f741f04a4991fc2f0a0029f610bbd1c250dfe115fb7770b892f75d8718b822d273251013991b4407e224fa3f9d4e92f6","378f40211b6e32a5406cd97e85bcf9ad","6378a459b1c20edf", "gexnfwok", "meazfhdp", "bsmotaxn"}) 

Es poco probable que el programador estuviera tan loco como para desarrollar inicialmente el código. Lo más probable es que haya usado uno de los ofuscadores disponibles al público. Este paso es bastante común para complicar el análisis de código con el fin de proteger la propiedad intelectual, por ejemplo, pero qué tipo de "giro" para el investigador.

Prestemos atención a la función de cifrado principal:



La función de cifrado en sí misma acepta 3 líneas de entrada (si es más, el resto no tiene sentido):

  1. texto cifrado
  2. la llave
  3. vector de inicialización para CBC - AES

Esta función está referenciada en el programa al menos 213 veces:



Observo que es una clave importante para el análisis de código normal. A continuación , debe pensar que tenemos las siguientes formas de analizar el programa:

  1. Restaurar la lógica de la función, recopilar todas las llamadas en el análisis estático, descifrar las líneas. Puede ser difícil y largo, pero dará un resultado del 100%.
  2. Realice cambios en el código pequeño de la aplicación, vuelva a compilar, ejecute la aplicación y capture las líneas descifradas en los registros. Es fácil de hacer, pero se desconoce cómo se comportará la aplicación en un lanzamiento en particular, y es posible que no vea la imagen completa (no reciba llamadas a todas las funciones). Además, puede haber problemas con la autocomprobación mediante la aplicación del certificado y (o) integridad.
  3. Si es difícil restaurar la lógica de la función, puede recopilar todas las llamadas a funciones y extraer estas funciones con los parámetros necesarios directamente en la dinámica (utilizando, por ejemplo, el software Frida .

Elegiremos el método número 1 como el más severo confiable.

Desofuscación


Haga una reserva de inmediato, la desofuscación es a menudo un proceso largo y tedioso, por lo que debe evaluar correctamente su marco de tiempo. Para nuestro análisis, es suficiente descifrar todas las líneas al menos de alguna manera y hacerlo en el tiempo mínimo, incluso en una muleta, que perder el tiempo durante un mes en aras de una opción ideal vaga.

La desofuscación cualitativa tiene sentido en el caso de una ingeniería inversa completa, por ejemplo, los ladrones de propiedad intelectual tienen que hacer esto cuando intentan copiar la solución de un competidor, o si a menudo tiene que analizar programas procesados ​​por un ofuscador, pero este no es nuestro caso.

Código fuente después del descompilador JEB Decompiler v.1.4

Spoiler
 public static String podxiwkt(String[] args) { int v6; int v4; byte[] v2; Cipher v1; String v10 = args[0]; String v7 = args[1]; String v0 = args[2]; if(v10 == null) { goto label_9; } if(v10.length() != 0) { goto label_11; } goto label_9; label_11: IvParameterSpec v5 = new IvParameterSpec(v0.getBytes()); try { v1 = Cipher.getInstance("AES/CBC/NoPadding"); goto label_15; } catch(NoSuchPaddingException v3) { } catch(NoSuchAlgorithmException v3_1) { } String v11 = ""; goto label_10; label_15: SecretKeySpec v9 = new SecretKeySpec(v7.getBytes(), "AES"); int v11_1 = 2; try { v1.init(v11_1, ((Key)v9), ((AlgorithmParameterSpec)v5)); v2 = Base64.decode(v1.doFinal(bwdoclkr.xkvasepi(v10)), 0); if(v2.length <= 0) { goto label_48; } v4 = 0; v6 = v2.length - 1; label_29: if(v6 < 0) { goto label_38; } if(v2[v6] != 0) { goto label_33; } } catch(Exception v3_2) { goto label_51; } ++v4; label_33: --v6; goto label_29; label_38: if(v4 <= 0) { goto label_48; } try { byte[] v8 = new byte[v2.length - v4]; System.arraycopy(v2, 0, v8, 0, v2.length - v4); v2 = v8; } catch(Exception v3_2) { label_51: v11 = ""; goto label_10; } label_48: v11 = new String(v2); goto label_10; label_9: v11 = ""; label_10: return v11; } } 

Nota del descompilador
Por cierto, dex2jar a menudo se bloquea. Entonces, en la figura a continuación se puede ver que dex2jar versión 2.0 no pudo hacer frente y solo emitió un código pequeño.



Su última versión, compilada a partir de las fuentes, produjo un código descompilado para esta función, pero no pudo descompilar muchos otros (ese es el truco).





En pocas palabras: considere cuidadosamente la elección de un descompilador: esto le ahorrará mucho tiempo y será más fácil que analizar el código pequeño.


Entonces, si solo pegamos este código en el IDE ahora, entonces no funcionará debido a errores.

Es importante recordar: no se requiere que el descompilador produzca código válido escrito por el desarrollador. Es solo un salvavidas en el análisis y hace suposiciones sobre cómo se podría escribir el código. En la mayoría de los casos, después de la optimización por parte del compilador, la tarea de restaurar el código original deja de ser trivial.

Ejemplo de descompilación incorrecta:

 if(v10 == null) { goto label_9; } if(v10.length() != 0) { goto label_11; } goto label_9; … label_9: v11 = ""; return v11; 

Vemos que resultó mal e inoperante. Reescribimos:

 if ((v10 == null) || (v10.length() == 0)) { return ""; } 

Ahora es mucho más claro, aquí está la verificación de entrada habitual. En este caso, necesitamos:

  • Reemplace todo "goto" con otras construcciones de lenguaje, como "Ir a" ha sido durante mucho tiempo un operador no válido.
  • Reemplace las llamadas de la biblioteca de Android con las llamadas de la biblioteca de Java (si intentamos ejecutar código en el IDE de Java).
  • Inserte clases dependientes a las que hace referencia nuestro código.
  • Piensa por ti mismo lo que está mal.

Como resultado, obtenemos:

 package com.company; //package isqpwcmx.isfdztgb.adscjobz.nxscomkr.jypbdxnt.utagwpym.wprtdznb.swldgrhm.yrbjpktq; import java.util.Base64; //import android.util.Base64; //import bnxvhlyg.nkhoirul.zfxogwqi.mdpqejcw.srnepbly.pcbvwxrs.vixdqclm.wnuqvrhp.bnvceayd.bwdoclkr; //   import java.security.Key; import java.security.NoSuchAlgorithmException; import java.security.spec.AlgorithmParameterSpec; import javax.crypto.Cipher; import javax.crypto.NoSuchPaddingException; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; public abstract class Main { public Main() { super(); } //hex to ascii public static byte[] xkvasepi(String str) { byte[] v0 = null; if(str != null && str.length() >= 2) { int v2 = str.length() / 2; v0 = new byte[v2]; int v1; for(v1 = 0; v1 < v2; ++v1) { v0[v1] = ((byte)Integer.parseInt(str.substring(v1 * 2, v1 * 2 + 2), 16)); } } return v0; } public static String podxiwkt(String[] args) { int v6; int v4; byte[] v2; Cipher v1; String v10 = args[0]; //text String v7 = args[1]; //key String v0 = args[2]; //IV //check if ((v10 == null) || (v10.length() == 0)) { return ""; } IvParameterSpec v5 = new IvParameterSpec(v0.getBytes()); try { v1 = Cipher.getInstance("AES/CBC/NoPadding"); } catch(NoSuchPaddingException v3) { return ""; } catch(NoSuchAlgorithmException v3_1) { return ""; } SecretKeySpec v9 = new SecretKeySpec(v7.getBytes(), "AES"); int v11_1 = 2; try { v1.init(v11_1, ((Key)v9), ((AlgorithmParameterSpec)v5)); //v2 = Base64.decode(v1.doFinal(bwdoclkr.xkvasepi(v10)), 0); v2=v1.doFinal(xkvasepi(v10)); //check if(v2.length <= 0) { return new String(v2); } } catch(Exception v3_2) { return ""; } v4=0; for (v6=v2.length-1;v6>=0;v6--){ if (v2[v6]==0) ++v4; } if(v4 > 0) { try { byte[] v8 = new byte[v2.length - v4]; System.arraycopy(v2, 0, v8, 0, v2.length - v4); v2 = v8; } catch (Exception v3_2) { return ""; } } v2 = Base64.getDecoder().decode(v2); return new String(v2); } public static void main(String[] args) { // write your code here System.out.println(podxiwkt(new String[] { "b1acd584a6eae4ca6321b1f7cdf9ba9617112b4fb39e76c8def876346e3032fbd32b2d188a09715f27124c1bf9facfdc", "637904cd08aeb2d3f6a21b5c7e84f519", "8f4c796d5a3120eb", "zcmwgvdn", "mkngbsyr", "rwcdaieu" })); } } { "b1acd584a6eae4ca6321b1f7cdf9ba9617112b4fb39e76c8def876346e3032fbd32b2d188a09715f27124c1bf9facfdc", "637904cd08aeb2d3f6a21b5c7e84f519", "8f4c796d5a3120eb", "zcmwgvdn", "mkngbsyr", "rwcdaieu"})); package com.company; //package isqpwcmx.isfdztgb.adscjobz.nxscomkr.jypbdxnt.utagwpym.wprtdznb.swldgrhm.yrbjpktq; import java.util.Base64; //import android.util.Base64; //import bnxvhlyg.nkhoirul.zfxogwqi.mdpqejcw.srnepbly.pcbvwxrs.vixdqclm.wnuqvrhp.bnvceayd.bwdoclkr; //   import java.security.Key; import java.security.NoSuchAlgorithmException; import java.security.spec.AlgorithmParameterSpec; import javax.crypto.Cipher; import javax.crypto.NoSuchPaddingException; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; public abstract class Main { public Main() { super(); } //hex to ascii public static byte[] xkvasepi(String str) { byte[] v0 = null; if(str != null && str.length() >= 2) { int v2 = str.length() / 2; v0 = new byte[v2]; int v1; for(v1 = 0; v1 < v2; ++v1) { v0[v1] = ((byte)Integer.parseInt(str.substring(v1 * 2, v1 * 2 + 2), 16)); } } return v0; } public static String podxiwkt(String[] args) { int v6; int v4; byte[] v2; Cipher v1; String v10 = args[0]; //text String v7 = args[1]; //key String v0 = args[2]; //IV //check if ((v10 == null) || (v10.length() == 0)) { return ""; } IvParameterSpec v5 = new IvParameterSpec(v0.getBytes()); try { v1 = Cipher.getInstance("AES/CBC/NoPadding"); } catch(NoSuchPaddingException v3) { return ""; } catch(NoSuchAlgorithmException v3_1) { return ""; } SecretKeySpec v9 = new SecretKeySpec(v7.getBytes(), "AES"); int v11_1 = 2; try { v1.init(v11_1, ((Key)v9), ((AlgorithmParameterSpec)v5)); //v2 = Base64.decode(v1.doFinal(bwdoclkr.xkvasepi(v10)), 0); v2=v1.doFinal(xkvasepi(v10)); //check if(v2.length <= 0) { return new String(v2); } } catch(Exception v3_2) { return ""; } v4=0; for (v6=v2.length-1;v6>=0;v6--){ if (v2[v6]==0) ++v4; } if(v4 > 0) { try { byte[] v8 = new byte[v2.length - v4]; System.arraycopy(v2, 0, v8, 0, v2.length - v4); v2 = v8; } catch (Exception v3_2) { return ""; } } v2 = Base64.getDecoder().decode(v2); return new String(v2); } public static void main(String[] args) { // write your code here System.out.println(podxiwkt(new String[] { "b1acd584a6eae4ca6321b1f7cdf9ba9617112b4fb39e76c8def876346e3032fbd32b2d188a09715f27124c1bf9facfdc", "637904cd08aeb2d3f6a21b5c7e84f519", "8f4c796d5a3120eb", "zcmwgvdn", "mkngbsyr", "rwcdaieu" })); } } 

Este código se cumple con éxito. Una vez que su trabajo se aclara, puede simplificarse y simplificarse, lo que lleva al tipo supuestamente lacónico escrito por el programador (a menos, por supuesto, que sus manos no estuvieran torcidas inicialmente).

Nota
Por cierto, en este caso, se puede demostrar el descifrado de cadenas utilizando un montón de recursos en línea. Un ejemplo de llamar a una cadena encriptada dentro de un programa:



Aquí, el vector de inicialización primero debe convertirse al formato Hex:



Sustituir todos los valores:



Y al final, decodifica desde base64:



Como resultado, obtenemos la cadena habitual y la llamada se vuelve significativa.

Por lo tanto, debe revisar todo el código y recopilar todas las cadenas cifradas, ahora podemos descifrarlas de forma independiente. El punto importante aquí es que en la etapa de "modificar y comentar sobre el código" podemos trabajar tanto en el nivel smali como en el nivel Java (smali descompilado).

Ventajas de modificaciónContras Mods
SmaliPuede realizar cambios, volver a compilar en dex y descompilar con nuevas líneasNo siempre es fácil trabajar con el código smali. Si el cambio es incorrecto, la aplicación no se compilará
JavaA menudo es mucho más fácil extraer datos de operaciones de nivel superior.La mayoría de los visores de código Java no pueden editar.

Otro ejemplo de línea

 vcgrnfjx.execSQL(nvhdzjfo.xipswfqb(new String[]{"f741f04a4991fc2f0a0029f610bbd1c250dfe115fb7770b892f75d8718b822d273251013991b4407e224fa3f9d4e92f6","378f40211b6e32a5406cd97e85bcf9ad","6378a459b1c20edf", "gexnfwok", "meazfhdp", "bsmotaxn"}) 

Desde aquí es muy fácil recoger parámetros usando una expresión regular, que escribir un código regular en el siguiente código:

Código Smali ejemplo 1

 00000280 new-instance v13, Ljava/lang/StringBuilder; 00000284 invoke-direct {v13}, Ljava/lang/StringBuilder;-><init>()V 0000028A const/4 v14, 0x6 0000028C new-array v14, v14, [Ljava/lang/String; 00000290 const/4 v15, 0x0 00000292 const-string v16, "f741f04a4991fc2f0a0029f610bbd1c250dfe115fb7770b892f75d8718b822d273251013991b4407e224fa3f9d4e92f6" 00000296 aput-object v16, v14, v15 0000029A const/4 v15, 0x1 0000029C const-string v16, "378f40211b6e32a5406cd97e85bcf9ad" 000002A0 aput-object v16, v14, v15 000002A4 const/4 v15, 0x2 000002A6 const-string v16, "6378a459b1c20edf" 000002AA aput-object v16, v14, v15 000002AE const/4 v15, 0x3 000002B0 const-string v16, "gexnfwok" 000002B4 aput-object v16, v14, v15 000002B8 const/4 v15, 0x4 000002BA const-string v16, "meazfhdp" 000002BE aput-object v16, v14, v15 000002C2 const/4 v15, 0x5 000002C4 const-string v16, "bsmotaxn" 000002C8 aput-object v16, v14, v15 


Código Smali ejemplo 2

 0000008E new-array v0, v0, [Ljava/lang/String; 00000092 move-object/from16 v89, v0 00000096 const/16 v90, 0x0 0000009A const-string v91, "4500b5e2e2ad26b7545eb54d70ab360ae28c9d031e2afcc3f6a2b2ac488ea440" 0000009E aput-object v91, v89, v90 000000A2 const/16 v90, 0x1 000000A6 const-string v91, "da96f678922d4b07350b3a184ecc1f5e" 000000AA aput-object v91, v89, v90 000000AE const/16 v90, 0x2 000000B2 const-string v91, "0cf69e3d2745a1b8" 000000B6 aput-object v91, v89, v90 000000BA const/16 v90, 0x3 000000BE const-string v91, "jhiqsaoe" 000000C2 aput-object v91, v89, v90 000000C6 const/16 v90, 0x4 000000CA const-string v91, "khbqxurn" 000000CE aput-object v91, v89, v90 


Código Smali ejemplo 3

 00000D3E new-array v0, v0, [Ljava/lang/String; 00000D42 move-object/16 v298, v0 00000D48 const/4 v0, 0x0 00000D4A move/16 v299, v0 00000D50 const-string v0, "b286945744e085f4d5c19916fd261481" 00000D54 move-object/16 v300, v0 00000D5A move-object/from16 v0, v300 00000D5E move-object/from16 v1, v298 00000D62 move/from16 v2, v299 00000D66 aput-object v0, v1, v2 00000D6A const/4 v0, 0x1 00000D6C move/16 v299, v0 00000D72 const-string v0, "df6883742b2911ac5ac7b4dee065390f" 00000D76 move-object/16 v300, v0 00000D7C move-object/from16 v0, v300 00000D80 move-object/from16 v1, v298 00000D84 move/from16 v2, v299 00000D88 aput-object v0, v1, v2 00000D8C const/4 v0, 0x2 00000D8E move/16 v299, v0 00000D94 const-string v0, "90a463ce2df17b58" 00000D98 move-object/16 v300, v0 00000D9E move-object/from16 v0, v300 00000DA2 move-object/from16 v1, v298 00000DA6 move/from16 v2, v299 00000DAA aput-object v0, v1, v2 00000DAE const/4 v0, 0x3 00000DB0 move/16 v299, v0 00000DB6 const-string v0, "cupyzsgq" 00000DBA move-object/16 v300, v0 00000DC0 move-object/from16 v0, v300 00000DC4 move-object/from16 v1, v298 00000DC8 move/from16 v2, v299 00000DCC aput-object v0, v1, v2 


Como vemos, las variables internas cambian, la secuencia de comandos varía, el número de argumentos también varía, además, en el programa, la función de descifrado se llama no directamente, sino a través de las funciones de la capa. Intente escribir una regla usted mismo para buscar esta construcción, evite errores al capturar cadenas de otras funciones y haga todo esto rápidamente ( buena suerte ).

Plan de trampa:

  1. Extraeremos todos los valores del código descompilado.
  2. Descifrar.
  3. Reemplace el texto cifrado con el abierto en el código pequeño. Sustituimos, por ejemplo, en lugar del primer operador. (Sería más profesional cortar toda la llamada de función y dejar la cadena descifrada devuelta, pero de nuevo existe un gran riesgo de romper el programa).
  4. Recojamos el código smali en un archivo dex.
  5. Será conveniente buscar más en el analizador de código, donde comenzamos.

Si reúne todo el código descompilado en un solo archivo, obtiene alrededor de 20,000 líneas, lo que para el análisis manual requiere mucho tiempo, lo que cuesta claramente más que las botas que puse a la venta. Primero, recolecta todas las líneas con una expresión regular.



Vemos 593 coincidencias, más una docena que no cumplió con esta regla, la familia tiene sus ovejas negras . Un ejemplo:



Ordenar, filtrar, total 422 líneas únicas:



Pasamos por la función de descifrado que restauramos anteriormente. Resultado:



Reemplace el texto cifrado con el abierto en pequeño código usando Python:

 import os words_replace=dict() words_replace["0018aacad3d146266317d8d8c51785fd"]="imei" words_replace["016d15e4d0a72667c61428e736a6f3b8"]="WakeLock" words_replace["032c534efb6c9990cd845a08c5a08b95"]="inbox" #…  .. # smali- #      def change(path): print("file="+path) file_handle = open(path, 'r') context_full = file_handle.read() file_handle.close() for i in words_replace: context_full=context_full.replace(i, words_replace[i]) #print (i+""+words_replace[i]) file_handle = open(path, 'w') context_full = file_handle.write(context_full) file_handle.close() #      smali- for top, dirs, files in os.walk('C:\\work\\test'): for nm in files: path=os.path.join(top, nm) print (path) change(path) 

Recopilamos archivos smali en dex:



Ahora esto se puede analizar de alguna manera (leyendo el primer argumento de toda la construcción):



Análisis


Entonces, tenemos 20,000 líneas de código más o menos legibles, no necesitamos hacer un análisis completo. Es necesario comprender la funcionalidad como un todo. Aquí, de hecho, solo se requiere la capacidad de leer el código fuente de Java. Recorra el código, observe referencias cruzadas, cambie el nombre de variables y funciones.

¿Cuál es la mejor manera de analizar una aplicación de Android, especialmente una grande?

Opción 1: puede pasar del archivo de manifiesto

Por ejemplo, secuencialmente desde LAUNCHER intente desenrollar toda la cadena de llamadas. Por cierto, no olvide que todavía hay "Receptor" y "Servicio" que pueden cambiar la ejecución lineal del programa.



Archivo de manifiesto completo
 <?xml version="1.0" encoding="utf-8" standalone="no"?><manifest xmlns:android="http://schemas.android.com/apk/res/android" android:installLocation="internalOnly" package="xfmpuwon.mtnbupnc.ihqdgjal.ndgmqawx.bjunzerq.cznfpnoq.fzevcuym.jmpdiqft"> <uses-permission android:name="xfmpuwon.mtnbupnc.ihqdgjal.ndgmqawx.bjunzerq.cznfpnoq.fzevcuym.jmpdiqft.permission.C2D_MESSAGE"/> <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE"/> <uses-permission android:name="android.permission.SEND_SMS"/> <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.READ_PHONE_STATE"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <uses-permission android:name="android.permission.WAKE_LOCK"/> <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/> <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/> <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/> <uses-permission android:name="android.permission.RECEIVE_SMS"/> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/> <uses-permission android:name="android.permission.QUICKBOOT_POWERON"/> <uses-permission android:name="android.permission.READ_SMS"/> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/> <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/> <permission android:name="xfmpuwon.mtnbupnc.ihqdgjal.ndgmqawx.bjunzerq.cznfpnoq.fzevcuym.jmpdiqft.permission.C2D_MESSAGE" android:protectionLevel="signature"/> <application android:allowBackup="true" android:icon="@drawable/icon" android:label="@string/tgiwmpqy" android:noHistory="true"> <activity android:configChanges="orientation" android:excludeFromRecents="true" android:label="@string/tgiwmpqy" android:launchMode="singleTop" android:name="zemquyog.csrtmnak.xrkfygen.wkahrnjd.acnfunjh.rgipxbuf.lruiwxeg.blqndche.dcjihbou" android:screenOrientation="portrait"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> <activity android:configChanges="orientation" android:launchMode="singleTop" android:name="xbfrscou.hxrvwnoi.djvpcqri.enlnrfio.aoegxbiu.heywzmnb.znfnxcht.nazcxobq" android:screenOrientation="portrait"/> <activity android:configChanges="orientation" android:launchMode="singleTop" android:name="hcfkagds.timkagsd.oetvghzr.fcioynvl.psynofdj.slcghdjz.tapnwsdk.gzvwnban.htenafdb.qwebhzgy" android:noHistory="true" android:screenOrientation="portrait"/> <activity android:configChanges="orientation" android:excludeFromRecents="true" android:launchMode="singleTop" android:name="njfbwmre.voefarqx.ftuxvngl.wrmshxqj.zdenywgn.eiwyunlg.jysgkbam.yrijthab.vstqxpuo.iplamgxf" android:priority="2147483647" android:screenOrientation="portrait"/> <receiver android:name="gfbaznoc.asyoqtnm.kbetoqca.mqysobzu.gqwfibrv.dorxijuk.wgzkmiep.ywnnurzv.csfpqhrn" android:permission="android.permission.BIND_DEVICE_ADMIN"> <meta-data android:name="@string/pkzrlscm" android:resource="@xml/ynqukvnb"/> <intent-filter android:priority="2147483646"> <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/> </intent-filter> </receiver> <receiver android:name="ykwbodxc.gymjhibn.kgmdfqor.hbasvmfz.yegkmaif.ortzknvm.quplincn.cuxytvhs.fqonzuts.cyuoxgqi.znumwyct" android:permission="com.google.android.c2dm.permission.SEND"> <intent-filter> <action android:name="com.google.android.c2dm.intent.RECEIVE"/> <action android:name="com.google.android.c2dm.intent.REGISTRATION"/> <action android:name="com.google.android.c2dm.intent.UNREGISTRATION"/> <category android:name="xfmpuwon.mtnbupnc.ihqdgjal.ndgmqawx.bjunzerq.cznfpnoq.fzevcuym.jmpdiqft"/> </intent-filter> </receiver> <receiver android:enabled="true" android:exported="true" android:name="kqwihjot.nvkqjloc.grjnyknm.owydvckh.mugknwdx.enhcyvja.mhvbpcue.ztbwjhfo"> <intent-filter android:priority="2147483646"> <action android:name="android.intent.action.LOCKED_BOOT_COMPLETED"/> <action android:name="com.htc.intent.action.QUICKBOOT_POWERON"/> <action android:name="android.intent.action.QUICKBOOT_POWERON"/> <action android:name="android.intent.action.BOOT_COMPLETED"/> <action android:name="android.intent.action.USER_PRESENT"/> <action android:name="android.intent.action.BATTERY_OKAY"/> <action android:name="android.intent.action.BATTERY_LOW"/> <action android:name="android.intent.action.ACTION_POWER_CONNECTED"/> <action android:name="android.intent.action.ACTION_POWER_DISCONNECTED"/> <action android:name="android.intent.action.APP_ERROR"/> <action android:name="android.intent.action.HEADSET_PLUG"/> <action android:name="android.intent.action.PHONE_STATE"/> <action android:name="android.intent.action.NEW_OUTGOING_CALL"/> <action android:name="android.provider.Telephony.SMS_RECEIVED"/> <action android:name="android.intent.action.TIME_TICK"/> <action android:name="android.intent.action.SCREEN_ON"/> <action android:name="android.intent.action.SCREEN_OFF"/> <action android:name="android.net.conn.CONNECTIVITY_CHANGE"/> <action android:name="android.net.wifi.WIFI_STATE_CHANGED"/> <action android:name="android.intent.action.DREAMING_STOPPED"/> <category android:name="android.intent.category.HOME"/> </intent-filter> </receiver> <receiver android:name="btnsxnuz.wmjizbky.lynvjxqz.zinomjuv.yizlgcnf.qwoikgnc.wnrskjea.wfqgmeny.lcgvqrms.ocwkgblp"> <intent-filter android:priority="2147483646"> <action android:name="android.provider.Telephony.SMS_RECEIVED"/> </intent-filter> </receiver> <service android:name="ltvsrezg.ehxndrat.twnnyxrf.nqynefws.dhbalcnr.ynjkuxod.nhoxmsbq.nackoyhn.voycgfek.znhwkqba.taxvnfyn"/> <service android:name="rbnakfzo.qsreiubk.pwvlnngs.twoxnhfv.mftarcnd.pfioxcub.xjlaftqr.nxrqvlwh"/> <service android:enabled="true" android:name="xfmpuwon.mtnbupnc.ihqdgjal.ndgmqawx.bjunzerq.cznfpnoq.fzevcuym.jmpdiqft.ugshpjvo"/> </application> </manifest> 


Opción 2: puedes moverte desde líneas interesantes



Parte de cadenas descifradas
 system_update.apk () () , error = , unregistered = ,  .permission.C2D_MESSAGE //sky-sync.pw/ //sms/inbox /system_update.apk ALLCONTACTS ALLMSG AUTHENTICATION_FAILED Acquiring wakelock Application BLOCKER_BANKING_START BLOCKER_EXTORTIONIST_START BLOCKER_STOP BLOCKER_UPDATE_START Banking CHANGE_GCM_ID CONTACTS CONTACTS_PRO CREATE TABLE IF NOT EXISTS END Error|No process list|No access Extortionist Foreground GCM returned invalid number of GCMBaseIntentService GCMBroadcastReceiver GCMIntentService- GCMRegistrar GCM_LIB GET MESSAGE Mobile Network NEWMSG Not retrying failed operation ONLINE PAGE POST Process finished with exit code 0 RESTART Received deleted messages Registering receiver Releasing wakelock SERVICE_NOT_AVAILABLE SSL START STOP Saving regId on app version Scheduling registration retry, backoff = Setting registeredOnServer status as Stop System UNBLOCK UPDATE UPDATE_PATTERNS URL UTF-8 Update WakeLock Wakelock reference is null Wi-Fi WiMax _success add_msg_ok address android.intent.action.QUICKBOOT_POWERON answer_text answer_to api_url app appVersion application application/vnd.android.package-archive apps_list ask backoff_ms blocker blocker_banking blocker_banking_autolock blocker_banking_forced_access blocker_banking_success blocker_extortionist blocker_extortionist_autolock blocker_extortionist_forced_access blocker_extortionist_success blocker_update blocker_update_forced_access blocker_update_success body build callback cardSuccess check com.android.settings com.google.android.c2dm.intent.RECEIVE com.google.android.c2dm.intent.REGISTER com.google.android.c2dm.intent.REGISTRATION com.google.android.c2dm.intent.UNREGISTER com.google.android.gcm com.google.android.gcm.intent.RETRY com.google.android.gsf com.htc.intent.action.QUICKBOOT_POWERON command command_receive contactslist country data date delete deleted_messages device_block disableDataConnectivity enableDataConnectivity error failure file deleted. first_start force-locked gafzpjxb.cix gcm gcm_id gcm_register gcm_register_ok getITelephony get_message_list id integer primary key autoincrement, id=? imei immunity inbox init_bootable init_imei is_admin is_awake_display is_imunnity is_locked is_network_type is_top_activity job job_date job_id komgejif.hqr locked message message_delivered message_type method model msg msg_id msglist name not nypjtinq.nvp ok onServer onServerExpirationTime onServerLifeSpan operator org.android.sys.admin.disabled org.android.sys.admin.enabled org.android.sys.admin.request org.android.sys.command.receive org.android.sys.launch.first org.android.sys.sms.pro.sent org.android.sys.sms.push org.android.sys.sms.sent outbox page params pattern patterns personal phone phone_list privet process_list protocol qwertyuiopasdfghjklzxcvbnm receive regId regex register register_ok registrationId = registration_id repeat resetting backoff for ru save_contacts_list save_message_history sender sent sent_status sid ss status stop_blocker text text, text/html time token total_deleted type unknown unregistered until url useragent utf-8 value version xpls yes   !     ...                   !  ?                ! 

Opción 3: puede pasar de recursos interesantes (activos, libs)

En este caso, la opción 3 era preferible. Hay tres archivos html interesantes en la carpeta / assets (contenedor apk). Aquí está su vista en el navegador:





Parece dudoso para el programa oficial de transferencia de pagos de Avito, ¿no te parece así? Hagamos un seguimiento de lo que sucede cuando presiona la tecla para enviar datos bancarios en la página con el logotipo de Sberbank. JavaScript llama a la función sendCardData() :



Y luego se transfiere al código Java a través de la llamada ok.performClick() :



En el código Java, el procesamiento se realiza:



Además, todo esto está encriptado en la clase mcrypt :



Dentro de la función, los datos se cifran de la misma manera que se consideró anteriormente:



Pero por lo demás, las teclas están cableadas:



Intentamos descifrar a través del recurso en línea:



Y convertir de base64. Éxito! Podemos descifrar todos los datos de la aplicación: probados en el tráfico capturado anteriormente.

La aplicación informa al servidor sobre todos los eventos.
 { "sid":15, "imei":"861117030537111", "phone":"System", "message":"     22.10.2018 23:30:47", "time":"1540240247", "msg_id":1, "status":"unknown", "type":"inbox", "method":"message" } 

También transfiere periódicamente todas las aplicaciones en ejecución
 { "sid": 15, "imei": "861117030537111", "country": "ru", "operator": "MTS RUS", "phone": "", "model": "Xiaomi Redmi 3X", "version": "6.0.1", "application": "", "build": "30.0.2", "process_list": [ "Background|com.android.bluetooth|com.android.bluetooth.hid.HidService", "Background|com.android.settings:remote|com.android.settings.wifi.MiuiWifiService", "Background|com.android.phone|org.codeaurora.ims.ImsService", "Background|system|com.qualcomm.location.LocationService", ..., "Background|xfmpuwon.mtnbupnc.ihqdgjal.ndgmqawx.bjunzerq.cznfpnoq.fzevcuym.jmpdiqft|ltvsrezg.ehxndrat.twnnyxrf.nqynefws.dhbalcnr.ynjkuxod.nhoxmsbq.nackoyhn.voycgfek.znhwkqba.taxvnfyn" ], "apps_list": [ "com.introspy.config", "com.google.android.youtube", "com.google.android.googlequicksearchbox", "org.telegram.messenger", ..., "com.google.android.inputmethod.latin", "jakhar.aseem.diva" ], "method": "register" } 

Si tuviera una ventana de entrada de datos bancarios en la dinámica, entonces los datos estarían en el tráfico. Por lo tanto, podemos concluir que esta es una aplicación de "phishing".

Aquellos que fueron cuidadosos notaron que hay bastantes permisos en el archivo Manifiesto, y que la aplicación tiene una funcionalidad más rica. Llevaremos a cabo un análisis profundo de la funcionalidad en otro artículo. Mientras tanto, ¡éxito!

Conclusiones


Estoy decepcionado de no haber vendido botas. Y las conclusiones son las siguientes:

  • No vendas botas en Avito
  • No haga clic en enlaces oscuros (incluso si son de amigos e incluso si "necesita pedir prestados 100 rublos con urgencia, una cuestión de vida o muerte")
  • No descargue aplicaciones que no sean de Google Play o AppStore
    • Desconecte la instalación de "fuentes no confiables" si realmente no comprende qué es qué.
    • No desconecte "Play Protection".
    • Recuerda que puede haber malware en Google Play
  • Instale antivirus en el teléfono (realmente funciona).
  • Si eres desarrollador, no ofusques el código, deja que la gente se asegure de tus buenas intenciones ( es broma )
  • Si eres investigador, no trabajes para la comida; analiza las aplicaciones en tu tiempo libre y publica informes. Juntos haremos del mundo un lugar mejor.

PD: Traté de escribir el artículo ligeramente en un formato humorístico y presentarlo de la manera más simple posible, porque incluso yo no querría leer el viernes un largo recorrido llamado "Ingeniería inversa de una aplicación Android maliciosa ofuscada".

Source: https://habr.com/ru/post/es430074/


All Articles