Listado de la etiqueta: Binarios

Introducción a Frida

¿Qué es Frida?

Frida es un framework de instrumentación de binarios multiplataforma: en el momento de escribir este artículo, Frida puede ser usado en Windows, Mac, Linux, iOS y Android.

Frida funciona inyectando el motor de JavaScript V8 en aplicaciones nativas. De esta manera, Frida puede ejecutar código JavaScript en el contexto de la aplicación en la que está inyectado, pudiendo así acceder a la memoria, hookear funciones, llamarlas, etc.

Frida no es sólo una librería. Frida trae consigo un conjunto de herramientas que te pueden ayudar a la hora de realizar una análisis de un aplicación.

En este post os mostraré algunas de las capacidades de Frida mediante la realización de una serie de ejercicios prácticos en los que analizaremos un binario compilado para OSx (Mach-O 64-bit executable x86_64).

Nota: el código del programa utilizado en estos ejercicios es el que aparece a continuación de estas lineas. Pero para intentar darle un poco de realismo, se va a tratar el binario como si fuera una caja negra

Analizando el binario

Analizando el binario con radare2 (o haciendo trampa y mirando el código) se puede ver que el programa pide una contraseña y luego comprueba si esta es buena
o no.
Si la contraseña es correcta devuelve el mensaje «Yey!», y si no es buena «Nice try, but nope».

$ r2 poc
 -- Experts agree, security holes suck, and we fixed some of them!
[0x100000e60]> pd 60 @ main
    ;-- main:
    ;-- entry0:
    ;-- _main:
    ;-- func.100000e60:
            0x100000e60      55             push rbp
            0x100000e61      4889e5         mov rbp, rsp
            0x100000e64      4883ec50       sub rsp, 0x50
            0x100000e68      c745fc000000.  mov dword [rbp - 4], 0
            0x100000e6f      897df8         mov dword [rbp - 8], edi
            0x100000e72      488975f0       mov qword [rbp - 0x10], rsi
            0x100000e76      c745ec000000.  mov dword [rbp - 0x14], 0
        .-> 0x100000e7d      488d3d080100.  lea rdi, [rip + 0x108]     ; 0x100000f8c ; section.3.__cstring ; "Password: " @ 0x100000f8c
        |   0x100000e84      b000           mov al, 0
        |   0x100000e86      e88f000000     call sym.imp.printf
        |   0x100000e8b      488d3d050100.  lea rdi, [rip + 0x105]     ; 0x100000f97 ; str._10s ; "%10s" @ 0x100000f97
        |   0x100000e92      488d75cc       lea rsi, [rbp - 0x34]
        |   0x100000e96      8945c8         mov dword [rbp - 0x38], eax
        |   0x100000e99      b000           mov al, 0
        |   0x100000e9b      e880000000     call sym.imp.scanf
        |   0x100000ea0      488d7dcc       lea rdi, [rbp - 0x34]
        |   0x100000ea4      c645dd64       mov byte [rbp - 0x23], 0x64 ; [0x64:1]=0 ; 'd'
        |   0x100000ea8      c645d949       mov byte [rbp - 0x27], 0x49 ; [0x49:1]=0 ; 'I'
        |   0x100000eac      c645de65       mov byte [rbp - 0x22], 0x65 ; [0x65:1]=0 ; 'e'
        |   0x100000eb0      c645d653       mov byte [rbp - 0x2a], 0x53 ; [0x53:1]=0 ; 'S'
        |   0x100000eb4      c645df00       mov byte [rbp - 0x21], 0
        |   0x100000eb8      c645d765       mov byte [rbp - 0x29], 0x65 ; [0x65:1]=0 ; 'e'
        |   0x100000ebc      c645da6e       mov byte [rbp - 0x26], 0x6e ; [0x6e:1]=0 ; 'n'
        |   0x100000ec0      c645dc69       mov byte [rbp - 0x24], 0x69 ; [0x69:1]=0 ; 'i'
        |   0x100000ec4      c645d863       mov byte [rbp - 0x28], 0x63 ; [0x63:1]=0 ; 'c'
        |   0x100000ec8      c645db73       mov byte [rbp - 0x25], 0x73 ; [0x73:1]=69 ; 's' ; "EXT" @ 0x73
        |   0x100000ecc      8945c4         mov dword [rbp - 0x3c], eax
        |   0x100000ecf      e8fcfeffff     call sym._encrypt_arg
        |   0x100000ed4      488d75d6       lea rsi, [rbp - 0x2a]
        |   0x100000ed8      488945e0       mov qword [rbp - 0x20], rax
        |   0x100000edc      488b7de0       mov rdi, qword [rbp - 0x20]
        |   0x100000ee0      e847000000     call sym.imp.strcmp
        |   0x100000ee5      83f800         cmp eax, 0
       ,==< 0x100000ee8      0f8416000000   je 0x100000f04
       ||   0x100000eee      488d3da70000.  lea rdi, [rip + 0xa7]      ; 0x100000f9c ; str.Nice_try__but_nope_n ; "Nice try, but nope." @ 0x100000f9c
       ||   0x100000ef5      b000           mov al, 0
       ||   0x100000ef7      e81e000000     call sym.imp.printf
       ||   0x100000efc      8945c0         mov dword [rbp - 0x40], eax
      ,===< 0x100000eff e911000000 jmp 0x100000f15 |`--> 0x100000f04      488d3da50000.  lea rdi, [rip + 0xa5]      ; 0x100000fb0 ; str.Yey__n ; "Yey!." @ 0x100000fb0
      | |   0x100000f0b      b000           mov al, 0
      | |   0x100000f0d      e808000000     call sym.imp.printf
      | |   0x100000f12      8945bc         mov dword [rbp - 0x44], eax
      `-`=< 0x100000f15      e963ffffff     jmp 0x100000e7d

El mensaje «Yey!» se puede ver en la linea 46 y «Nice try, buy nope.»en la linea 42.
Analizando cómo llegar a «Yey!», vemos la siguiente parte del código, dónde se llama a strcmp y si las dos cadenas son iguales, salta a la parte del código que hará que se escriba «Yey!»:

        |   0x100000ee0      e847000000     call sym.imp.strcmp
        |   0x100000ee5      83f800         cmp eax, 0
       ,==< 0x100000ee8      0f8416000000   je 0x100000f04

Analicemos el resto de llamadas que ocurren en el main del programa:
r2VV
Primero hay una llamada a printf y luego una a scanf. Sin saber nada de ingeniería inversa, sólo ejecutando el binario podemos imaginar que se trata de la petición de contraseña que aparece al ejecutar el programa:

Después de eso se llama a la función encrypt_arg y por ultimo el resultado de esa función se pasa a strcmp, que lo compara con la cadena que se forma con la ejecución de las siguientes instrucciones:

        |   0x100000ea4      c645dd64       mov byte [rbp - 0x23], 0x64 ; [0x64:1]=0 ; 'd'
        |   0x100000ea8      c645d949       mov byte [rbp - 0x27], 0x49 ; [0x49:1]=0 ; 'I'
        |   0x100000eac      c645de65       mov byte [rbp - 0x22], 0x65 ; [0x65:1]=0 ; 'e'
        |   0x100000eb0      c645d653       mov byte [rbp - 0x2a], 0x53 ; [0x53:1]=0 ; 'S'
        |   0x100000eb4      c645df00       mov byte [rbp - 0x21], 0
        |   0x100000eb8      c645d765       mov byte [rbp - 0x29], 0x65 ; [0x65:1]=0 ; 'e'
        |   0x100000ebc      c645da6e       mov byte [rbp - 0x26], 0x6e ; [0x6e:1]=0 ; 'n'
        |   0x100000ec0      c645dc69       mov byte [rbp - 0x24], 0x69 ; [0x69:1]=0 ; 'i'
        |   0x100000ec4      c645d863       mov byte [rbp - 0x28], 0x63 ; [0x63:1]=0 ; 'c'
        |   0x100000ec8      c645db73       mov byte [rbp - 0x25], 0x73 ; [0x73:1]=69 ; 's' ; "EXT" @ 0x73

Dependiendo del resultado de esa comparación, podremos saltaremos a «Yey!», o no.

Para resolver este ejercicio, necesitamos primero saber cual es la cadena contra la compara strcmp y luego saber que es lo que hace la función encrypt_arg con la password que introducimos antes de pasársela a strcmp. En ese momento podremos generar una cadena que tras ser procesada por encrypt_arg sea igual que el segundo argumento de strcmp.

Solucionando nuestros problemas usando Frida

Normalmente este tipo de cosas se pueden solucionar leyendo ensamblador y reformando la cadena, este es el caso de nuestro binario. Pero otras veces, esta cadena se forma dinámicamente en tiempo de ejecución hay que ir paso a paso con el debugger mirando la cadena que se forma.
O, si lo que queremos es sólo llegar a «Yey!» podríamos parchear la instrucción je 0x100000f04 para convertirla en un salto incondicional. Pero esto se trata de utilizar Frida, así que veamos que podemos hacer con Frida.
Frida nos permite interceptar la función y mandarnos un mensaje con el contenido de los argumentos de la función.
Para ello basta con escribir un modulo en python de este estilo:

import frida
import sys

session = frida.attach(int(sys.argv[2]))
script = session.create_script("""
Interceptor.attach(ptr("%s"), {
    onEnter: function (args) {
        send("arg[0]: " + Memory.readCString(args[0]));
        send("arg[1]: " + Memory.readCString(args[1]));
    }
});
""" % int(sys.argv[1], 16))

def on_message(message, data):
    print message['payload']

script.on('message', on_message)
script.load()
sys.stdin.read()

Este script va a interceptar la llamada en la dirección que le pasemos como primer parámetro, del proceso cuyo PID pasemos como segundo parámetro.
La dirección de la llamada función se puede sacar de muchas maneras, pero en el video de demostración que podéis ver a continuación, he usado radare2 para depurar el proceso y encontrar la dirección.

Ya sabemos entonces que la cadena que introducimos se procesa de alguna manera y se compara con SecInside. Ahora podríamos analizar la función que «cifra» la cadena (que en este caso es muy fácil, o usar Frida para hacer que strcmp siempre se cumpla.

Para eso tenemos dos opciones: hookear la llamada y modificar los argumentos, o modificar el valor de retorno. Para ambos casos la API de Frida nos lo pone fácil con sus «onEnter» y «onLeave».
En el primer caso querremos que al entrar el primer argumento sea igual que el segundo. Este se consigue muy fácil modificando un poco el script anterior:

import frida
import sys

session = frida.attach(int(sys.argv[2]))
script = session.create_script("""
Interceptor.attach(ptr("%s"), {
    onEnter: function (args) {
	args[0] = args[1]
        send("arg[0]: " + Memory.readCString(args[0]));
        send("arg[1]: " + Memory.readCString(args[1]));
    }
});
""" % int(sys.argv[1], 16))

def on_message(message, data):
    print message['payload']

script.on('message', on_message)

script.load()
sys.stdin.read()

En este caso la parte de los mensajes no es necesaria, pero la he dejado para que se vea claramente lo que está pasando. La clave está en la linea 8, dónde le decimos que al entrar en la función args[0] = args[1].
En el segundo caso, lo que haremos será aprovechar onLeave para modificar el valor de salida de strcmp que lo modificaremos para que siempre diga que las cadenas son iguales. Es decir, que siempre devuelva 0:

import frida
import sys

session = frida.attach(int(sys.argv[2]))
script = session.create_script("""
Interceptor.attach(ptr("%s"), {
    onEnter: function (args) {
        send("arg[0]: " + Memory.readCString(args[0]));
        send("arg[1]: " + Memory.readCString(args[1]));
    },
    onLeave: function (retval) {
	retval.replace(0);
    }
});
""" % int(sys.argv[1], 16))

def on_message(message, data):
    print message['payload']

script.on('message', on_message)

script.load()
sys.stdin.read()

Como podéis ver, he utilizado también onEnter, pero la única razón por la que aparece es para registrar la actividad y que se vea un poco mejor lo que está pasando.
A continuación un video con estos dos scripts en acción:

Esto son sólo unos ejemplos simples de las cosas que se pueden hacer con Frida, pero vale que para lo que no lo conocíais, tengáis una idea general de las cosas que permite automatizar con unas pocas lines de JavaScript. Si te ha llamado la atención, pruébalo y cuéntanos que tal te fue en los comentarios. ¡Hasta la próxima!

Amenaza Silenciosa I

Cuando trabajo cazando malware, una de las cosas que suelen cantar a la hora de encontrar artefactos maliciosos es la aparición de ejecutables extraños en los sistemas de persistencia más comunes, como, por ejemplo, en algunas claves de registro. Un día me pregunté, ¿es posible generar una amenaza silenciosa, que no cante mucho en los sistemas de persistencia, sin utilizar técnicas extrañas? Veamos como de fácil o difícil sería.

Dridex

No es la primera vez que hablo de temas relacionados con Dridex en este blog. En este caso lo hablaré de su mecanismo de persistencia. Es un sistema simple pero efectivo que da muchos dolores de cabeza a los administradores de sistemas a la hora de eliminarlo de una red.
Una vez infectado con Dridex, al ejecutarse, este se inyecta en el proceso Explorer.exe y se borra del sistema. Pero, ¿cómo logra entonces persistir?
Cuando Dridex detecta que Windows se va a cerrar se escribe a si mismo con la estructura <nombre aleatorio>.TMP y añade a la clave de registro HKEY_CURRENT_USER\ Software\Microsoft\Windows\CurrentVersion\Run el valor:

"rundll32.exe C:\DOCUME~1\\APPLIC~1\.tmp NfInitialize"

Cuando se vuelve a ejecutar el reiniciar el sistema, el fichero .TMP se vuelve a borrar y la clave del registro desaparece.
Esto no sólo hace su detección muy complicada, ya que la mayoría de los antivirus funcionan analizando los ficheros en disco y los que analizan la memoria no analizan todas la páginas, si no que la manera más fácil y barata de desinfectar las maquinas es desenchufándolas y quitándoles la batería si son ordenadores portátiles, lo cual complica la desinfección. ¿Os imagináis que gracia le hará al que le toque ir desenchufando dos mil ordenadores?
Sin utilizar ninguna técnica nueva, esto consigue que la detección en base a la aparición de artefactos sospechosos en sistemas de persistencia de la que hablaba en la introducción sea imposible una vez Windows ha arrancado: ni vamos a encontrar un ejecutable en %TMP% o %APPDATA% , ni vamos a ver nada en las claves del registro.

Alternate Data Streams

Alternate Data Stream (ADS) es una característica de NTFS en principio diseñada para almacenar información sobre un ficheros sin tener que utilizar otros ficheros. Estos metadatos se escriben en el disco duro a continuación de los datos del fichero.
Si miramos un registro de la Master File Table (MFT) de NTFS, se puede ver la diferencia entre un fichero sin ADS y uno con ADS.
Registro de fichero en MFT:
Screen Shot 2015-08-12 at 10.44.54 PM
Registro de fichero en MFT con ADS:
Screen Shot 2015-08-12 at 10.44.38 PM

Images extraídas del documento: Alternate Data Streams: Out of the Shadows and into the Light de Ryan L. Means (2003).

Como se puede ver, en el registro con ADS aparece un segundo stream de datos después de los datos del fichero.
En el pasado se han utilizado ADS para ocultar malware y ejecutarlo desde ahí.
Con la llegada de Windows 7, Microsoft deshabilitó la posibilidad de ejecutar directamente nada que estuviera almacenado en ADS. Aun así, hay formas indirectas de ejecutar estos binarios.

Infección de ficheros

Una de las cosas que me lleva a escribir esta serie de artículos es la siguiente hipótesis: ¿y si en lugar de añadir un nuevo binario a cualquiera de los sistemas de persistencia infectamos un binario que ya esté en uno de estos mecanismos?
Es decir, aprovechando la estructura de los ficheros PE, ¿puedo inyectar mi programa en un ejecutable que previamente he detectado que persiste?
La respuesta es, en principio, que sí, puedo. La realidad es que no sé cual será la dificultad de realizar esto en sistemas operativos modernos, pero lo que sí que sé es que en muchos proyectos de “compromise discovery” * pasaría desapercibido.

Objetivos

Este post es sólo la introducción a un viaje en el que sólo he empezado a dar los primeros pasos en el que voy a tratar de replicar las técnicas comentadas en el post y del que espero volver con los siguientes resultados:

  • Aumentar mi conocimiento de inyección de procesos en Windows.
  • Mejorar y terminar de comprender cómo funcionan el hooking de funciones en Windows.
  • Conocimiento profundo de los diferentes métodos de persistencia en sistemas Windows.
  • Comprender el sistema de ficheros NTFS.
  • Ver como funciona la infección de ficheros a nivel práctico.
  • Aplicación de las técnicas clásicas de infección en un entorno moderno.
  • Profundizar en la API de Windows.

¿Qué puede esperar el lector?

  • Un montón de referencias y enlaces a recursos que utilice.
  • Pruebas de concepto (al menos de las partes relevantes).
  • Los resultados explicados tan bien como pueda.
  • Largas esperas y poca periodicidad :P.

Mientras sigo pasito a pasito, ¿se os ocurre alguna otra técnica o forma de hacer que un bicho pase desapercibido y se me haya pasado?

* Compromise discovery es el nombre que reciben los proyectos en los que un equipo con conocimiento en respuesta ante incidentes se presenta en una compañía, toma un “snapshot” de los sistemas de la compañía y analiza esa información en busca de evidencias de compromiso.