Hunting for the rootkit with Forensics

 By Luis Almaraz aka lehag
@lehag07

Descripción 


Este reto fue presentado durante el CTF de Insomni'hack Quals 2020 donde participé junto con mi equipo Mayas. La única descripción que nos daba el reto es la siguiente: “Oh shit! Data have been stolen from my computer... I looked for malicious activity but found nothing suspicious. Could ya give me a hand and find the malware and how it's hiding?”. En esta entrada se describe la solución que propusimos para resolver el reto, a pesar de estar a poco de resolverlo durante del CTF.

Identificando el archivo memory_inshack.zip


1. Este reto nos proporcionó de un archivo de tipo zip llamado memory_inshack.zip, ver Tabla 1.

Archivo Tipo de archivo Hash (MD5)
  memory_inshack.zip       Zip archive data       15bb69dbe9e5e1cb31b8f62b13da8628    
Tabla 1. El archivo proporcionado por el reto.

2. Cuando descomprimimos el anterior archivo, él generó los siguientes dos archivos, ver Tabla 2.

Archivo Tipo de archivo Hash (MD5)
  memory.vmem     Data     d41d8cd98f00b204e9800998ecf8427e  
  Ubuntu_4.15.0-72-generic_profile.zip     Zip archive data     2773ef2ec09e373e06e48a35a3fc3f0c  
Tabla 2. Archivos generados al descomprimir el archivo de tipo zip proporcionado.

El archivo memory.vmem representa el volcado de memoria, mientras que el archivo Ubuntu_4.15.0-72-generic_profile.zip se utiliza como perfil del Framework de Volatility para interpretar el primer archivo.

Triage


1. Una vez que el perfil de Volatility fue cargado en el directorio correspondiente [1], listamos los procesos activos contenidos en el volcado de memoria.

lehag@blackbox:~/Insomnihack/2020/Quals/RE/1. Getdents$ volatility -f memory.vmem --profile=LinuxUbuntu_4_15_0-72-generic_profilex64 linux_pstree
Volatility Foundation Volatility Framework 2.6
Name                 Pid             Uid            
systemd              1                              
.systemd-journal     326
[snipped]
...bash              1733            1000           
....sudo             1750                           
.....meterpreter     1751                           
......sh             2964
[snipped]
.[jfsCommit]         2532                           
.[jfsSync]           2533                           
lehag@blackbox:~/Insomnihack/2020/Quals/RE/1. Getdents$

Hubo un árbol de procesos que llamó nuestra atención. Fue la secuencia: 

bash->sudo->meterpreter->sh.

Lo anterior, nos sugirió que el host comprometido tenía un backdoor activo llamado meterpreter [2], asociado con Metasploit, el cual ejecutó una shell en el sistema como usuario root. Meterpreter es un tipo de ataque de Metasploit asociado con un payload que provee de una shell interactiva que permite a un atacante ejecutar código remoto, es por ello, que decidimos indagar más sobre el proceso asociado con él.

2. Luego, listamos las conexiones activas presentes en el volcado de memoria relacionadas con el PID 1751.

lehag@blackbox:~/Insomnihack/2020/Quals/RE/1. Getdents$ volatility -f memory.vmem --profile=LinuxUbuntu_4_15_0-72-generic_profilex64 linux_netstat -p 1751
Volatility Foundation Volatility Framework 2.6
TCP      192.168.180.132 :51934 192.168.180.131 : 1337 ESTABLISHED           meterpreter/1751 
lehag@blackbox:~/Insomnihack/2020/Quals/RE/1. Getdents$

Encontramos una conexión remota a un host con la IP 192.168.180.131 sobre el puerto 1337.

3. En ese momento, pensamos que sería una buena idea mirar los logs del volcado de memoria para entender mejor la ejecución del proceso malicioso.

lehag@blackbox:~/Insomnihack/2020/Quals/RE/1. Getdents$ volatility -f memory.vmem --profile=LinuxUbuntu_4_15_0-72-generic_profilex64 linux_dmesg
Volatility Foundation Volatility Framework 2.6
[0.0] Linux version 4.15.0-72-generic (buildd@lcy01-amd64-026) (gcc version 7.4.0 (Ubuntu 7.4.0-1ubuntu1~18.04.1)) #81-Ubuntu SMP Tue Nov 26 12:20:02 UTC 2019 (Ubuntu 4.15.0-72.81-generic 4.15.18)
[snipped]
 [51534295268.51] show_signal_msg: 30 callbacks suppressed
[51534297419.51] meterpreter[1743]: segfault at 0 ip 0000000000000000 sp 00007fff4ac3e110 error 14 in meterpreter[400000+1000]
[snipped]
[168682653963.168] rkit: loading out-of-tree module taints kernel.
[168682697557.168] rkit: module verification failed: signature and/or required key missing - tainting kernel
lehag@blackbox:~/Insomnihack/2020/Quals/RE/1. Getdents$

La información obtenida de los logs del volcado de memoria nos permitió entender que el sistema comprometido no sólo tenía un backdoor activo, sino también un módulo de kernel cargado, normalmente asociados con rootkits.

4. De nuevo, con la ayuda del Framework de Volatility, listamos los módulos de kernel cargados en el sistema comprometido.

lehag@blackbox:~/Insomnihack/2020/Quals/RE/1. Getdents$ volatility -f memory.vmem --profile=LinuxUbuntu_4_15_0-72-generic_profilex64 linux_check_modules
Volatility Foundation Volatility Framework 2.6
    Module Address       Core Address       Init Address Module Name             
------------------ ------------------ ------------------ ------------------------
0xffffffffc0943080 0xffffffffc0941000                0x0 rkit                    
lehag@blackbox:~/Insomnihack/2020/Quals/RE/1. Getdents$

El rootkit en cuestión fue cargado en el sistema y su nombre era rkit.

5. Encontramos más información sobre el módulo de kernel al imprimir las cadenas embebidas en el volcado de memoria.

lehag@blackbox:~/Insomnihack/2020/Quals/RE/1. Getdents$ strings memory.vmem | grep "rkit"
rkit
rkit
[snipped]
rkit.ko
insmod /home/julien/Downloads/rkit.ko hide=rJ/1g5PA5amy176A64akjuq/jryOug== hide_pid=1751
Jan 16 06:02:47   168.682697] rkication failed:  required key mid key missing - failed: signatur2697] rkit: modu6:02:47 ubuntu kut specifying a 
rkit
+module:rkit
+module:rkit
rkit: loading out-of-tree module taints kernel.
rkit: module verification failed: signature and/or required key missing - tainting kernel
lehag@blackbox:~/Insomnihack/2020/Quals/RE/1. Getdents$

Alguna de la información que encontramos sobre el archivo llamado rkit, tenía los parámetros de su ejecución y el PID del proceso que escondía (1751), asociado con el backdoor.

Un aspecto a resaltar en la ejecución del rootkit era que el valor del parametro hide estaba codificado en base 64: rJ/1g5PA5amy176A64akjuq/jryOug==. Al intentar decodificarlo, no encontramos algún texto ASCII legible, por lo que en ese momento, el parámetro no nos decía mucho sobre su finalidad.

6. Enseguida, procedimos a recuperar el artefacto tomando como referencia su número de inodo 0xffffffffc0943080 del volcado de memoria, ver Tabla 3. El número de inodo anterior, fue obtenido al listar los módulos de kernel cargados en el sistema comprometido en el punto 4 de esta sección.

lehag@blackbox:~/Insomnihack/2020/Quals/RE/1. Getdents$ volatility -f memory.vmem --profile=LinuxUbuntu_4_15_0-72-generic_profilex64 linux_moddump -b 0xffffffffc0943080 -D ./rkit
Volatility Foundation Volatility Framework 2.6
Wrote 4146551 bytes to rkit.0xffffffffc0943080.lkm
lehag@blackbox:~/Insomnihack/2020/Quals/RE/1. Getdents$ cd rkit/
lehag@blackbox:~/Insomnihack/2020/Quals/RE/1. Getdents/rkit$ mv rkit.0xffffffffc0943080.lkm rkit
lehag@blackbox:~/Insomnihack/2020/Quals/RE/1. Getdents/rkit$

En este caso, obtuvimos el mapa de memoria del rootkit en ejecución, pero también fue necesario recuperar el artefacto tal y como estaba en disco, ya que, de esta manera, conoceríamos la relación entre las direcciones de memoria del rootkit en ejecución con el nombre de las referencias a ciertas funciones y variables globales contenidas en el código del artefacto en disco.

7. Obtuvimos el archivo lógico asociado con el rootkit, ver Tabla 3.

lehag@blackbox:~/Insomnihack/2020/Quals/RE/1. Getdents/rkit$ volatility -f memory.vmem --profile=LinuxUbuntu_4_15_0-72-generic_profilex64 linux_find_file --inode=0xffff8a9dd42755e8 --outfile=./rkit/rkit.ko
[snipped]
lehag@blackbox:~/Insomnihack/2020/Quals/RE/1. Getdents/rkit$

Archivo Tipo de archivo Hash (MD5)
  rkit       ELF 64-bit, not stripped       f5fc0d81f474954d36aba38ba8318b42    
  rkit.ko       ELF 64-bit, not stripped       31ef522bb5f69a7b188d9aaad414e60b    
Tabla 3. Archivos asociados con el rootkit.

Aplicando ingeniería inversa al rootkit


Antes de comenzar esta sección, si no tienes experiencia en el funcionamiento de un rootkit, básicamente son programas cuyo propósito es ocultar la presencia de malware en un sistema comprometido y preservar su existencia privilegiada. Un rootkit ocultará archivos, procesos de malware, módulos cargados, claves de registro, cuentas de usuario o incluso registros del sistema [3]. El clásico ejemplo es cuando el rootkit instalado monitorea la ejecución del programa "ps" para que al listarse los procesos corriendo en el sistema, el proceso malicioso no se le muestre a la víctima y por consecuencia, permanezca escondido en el sistema. Normalmente son instalados en el sistema atacado como módulos de kernel.

1. Con el archivo lógico previamente recuperado, rkit.ko, procedimos a aplicar ingeniería inversa al código en ensamblador analizándolo con IDA Pro. Encontramos ciertas funciones que nos permitieron entender la lógica para obtener el flag, como se muestra en la Figura 1.

Figura 1. Funciones en el rootkit.

2. La función init_module tenía el código observado en la Figura 2:

Figura 2. Código en la función init_module.

3. Notamos que primero calcula el tamaño del argumento en base 64 (rJ/1g5PA5amy176A64akjuq/jryOug==). Luego, llama a una función renombrada aquí como decodeBase64 que decodifica la cadena en base 64, la cadena resultante es almacenada en una variable llamada ep. La función renombrada como xorOperation tenía la estructura de la Figura 3.

Figura 3. Estructura de la función xorOperation.

Como se puede observar, la función carga un conjunto de valores de la variable llamada sk, sucede lo mismo con los valores de la variable pk.

4. Conocimos los valores anteriores porque se encontraban embebidos en las direcciones de memoria del mapa de proceso del rootkit obtenido también. Dichas direcciones de memoria fueron consultadas en el volcado de memoria proporcionado con el comando volshell del Framework de Volatility.

>>> db(0x0FFFFFFFFC09433E0, 62)  <- valor de sk
0xffffffffc09433e0  e5 d1 a6 f8 c1 f0 d5 dd f9 e6 ca c6 db f4 f6 e1   ................
0xffffffffc09433f0  da d4 e7 d9 fd c7 a5 fc c8 c4 e4 de e3 a2 f7 c5   ................
0xffffffffc0943400  fe a3 ff d0 c3 e0 ab c2 a7 d8 d7 e2 df eb dc aa   ................
0xffffffffc0943410  a1 a0 d3 cb a4 f1 fa c0 fb f5 d6 f3 ea e8         ..............
>>> db(0x0FFFFFFFFC0943000, 62) <- valor de pk
0xffffffffc0943000  77 43 34 6a 53 62 47 4f 6b 74 58 54 49 66 64 73   wC4jSbGOktXTIfds
0xffffffffc0943010  48 46 75 4b 6f 55 37 6e 5a 56 76 4c 71 30 65 57   HFuKoU7nZVvLq0eW
0xffffffffc0943020  6c 31 6d 42 51 72 39 50 35 4a 45 70 4d 79 4e 38   l1mBQr9P5JEpMyN8
0xffffffffc0943030  33 32 41 59 36 63 68 52 69 67 44 61 78 7a         32AY6chRigDaxz
>>>

Luego, realiza una operación xor entre los valores de sk y los de pk. De acuerdo con el ciclo observado, la longitud de ambas cadenas era de 0x3E (62 en decimal) bytes.

5. En la Figura 4, podemos ver la implementación del Hooking de la función getdents de Linux. Hooking significa que la función original será modificada por el malware en memoria para que cada vez que dicha syscall se solicite, se ejecute el código malicioso del atacante en lugar del original.

En este caso, se puede ver cómo la dirección original del syscall se guarda en el registro rdx con la instrucción:

mov rdx, [rax+270h]

Inmediatamente después, se observa que la dirección original es modificada con la del código malicioso (etiquetado como "finalDecode" en la Figura):

mov qword ptr [rax+270h], offset finalDecode
Figura 4. Estructura de la nueva función getdents.

Allí, encontramos la función que obtenía el flag, renombrada aquí como finalDecode y que tenía las líneas de código de la Figura 5, comprendiendo que el argumento inicial codificado en base 64 era utilizado para que una vez que la función decodeBase64 decodificara dicha cadena, entonces se tomara la nueva cadena y se procediera a hacer una operación XOR con ciertos valores en memoria (mostrados en el punto 4 de esta sección) y finalmente se obtuviera el flag.

Figura 5. Código relacionado con la generación del flag.

Programando la solución

1. Al ya conocer los valores anteriores, procedimos a implementar nuestra solución.

#!/usr/bin/env python3
## Author: lehag
## Code: rkit.py
import base64

def init_module():
    sk = [0xE5, 0xD1, 0xA6, 0xF8, 0xC1, 0xF0, 0xD5, 0xDD, 0xF9, 0xE6, 0xCA, 0xC6, 0xDB, 0xF4, 0xF6, 0xE1, 0xDA, 0xD4, 0xE7, 0xD9, 0xFD, 0xC7, 0xA5, 0xFC, 0xC8, 0xC4, 0xE4, 0xDE, 0xE3, 0xA2, 0xF7, 0xC5, 0xFE, 0xA3, 0xFF, 0xD0, 0xC3, 0xE0, 0xAB, 0xC2, 0xA7, 0xD8, 0xD7, 0xE2, 0xDF, 0xEB, 0xDC, 0xAA, 0xA1, 0xA0, 0xD3, 0xCB, 0xA4, 0xF1, 0xFA, 0xC0, 0xFB, 0xF5, 0xD6, 0xF3, 0xEA, 0xE8]
    pk = b"wC4jSbGOktXTIfdsHFuKoU7nZVvLq0eWl1mBQr9P5JEpMyN832AY6chRigDaxz";
    rbx = b"rJ/1g5PA5amy176A64akjuq/jryOug==";

    ep = decodeBase64(rbx);
    ## xorOperation(sk, pk);
    getdentsHooking(ep, sk);

def decodeBase64(rbx):
    return list(base64.b64decode(rbx));

def xorOperation(sk, pk):
    for i in range(0, 62):
        sk[i] = sk[i] ^ pk[i];
    return sk;

def getdentsHooking(ep, sk):
    finalDecode(ep, sk);

def  finalDecode(ep, sk):
    for i in range(len(ep)):
        ep[i] = chr(ep[i] ^ sk[i]);

    print("".join(ep));

init_module();

2. Finalmente, ejecutamos el código anterior y obtuvimos el flag.

lehag@blackbox:~/Insomnihack/2020/Quals/RE/1. Getdents$ python3 rkit.py
INS{R00tK1tF0rRo0kies}
lehag@blackbox:~/Insomnihack/2020/Quals/RE/1. Getdents$

Flag


INS{R00tK1tF0rRo0kies}

Agradecimientos

A Tony Palma aka xbytemx, compañero de Mayas, por aportar ideas durante la solución del reto.

Go Mayas!

Referencias

[1] https://github.com/volatilityfoundation/volatility/wiki/Linux#making-the-profile
[2] https://www.offensive-security.com/metasploit-unleashed/about-meterpreter/
[3] https://www.enisa.europa.eu/topics/csirts-in-europe/glossary/rootkits


Comentarios