Descripción
Hello, we have discovered that one of our ex-employees was leaking information to another company. Before he left, he cleaned everything in his computer to wipe any traces but we have found that one of his virtual machines in our servers is still running, it’s locked but we have been able to get a memory dump, the problem is that we haven’t been able to get any useful information from it, we have been told that you are an expert in this field, could you try to find something in it?
Write-Up
Nos han dado un volcado de memoria de una máquina virtual (memory_dump.raw), para analizarlo vamos a utilizar la herramienta volatility. Primero debemos localizar el perfil a utilizar para el análisis, para ello debemos ejecutar volatility con el plugin imageinfo que analizará el volcado y nos indicará que perfil usar en caso de localizarlo.
pwnshadow@hackiit:Memory_dump# volatility -f memory_dump.raw imageinfo
Da error, ningún perfil de los que dispone volatility es válido para este volcado, vamos a intentar sacar con strings información sobre el sistema al que pertenece:
pwnshadow@hackiit:Memory_dump# strings memory_dump.raw | grep GNU/Linux Welcome to Ubuntu 14.04.4 LTS (GNU/Linux 4.2.0-27-generic x86_64) GNU/Linux
Podemos ver que se trata de la versión de Ubuntu 14.04.4 de 64 bits, buscamos por internet a ver si localizamos un perfil para esta versión, en caso de no localizarlo debemos descargar una iso de esa versión de Ubuntu (o derivados como Xubuntu, Lubuntu…), montar una maquina virtual y generamos un perfil tal y como se indica en la wiki de volatility:
https://github.com/volatilityfoundation/volatility/wiki/Linux#creating-a-new-profile
Hemos creado el perfil, lo hemos añadido a la carpeta volatility/plugins/overlays/linux/ y hemos verificado que se ha añadido correctamente.
pwnshadow@hackiit:Memory_dump# volatility --info | grep 14.04 Volatility Foundation Volatility Framework 2.6 LinuxUbuntu14_04_4x64 - A Profile for Linux Ubuntu14.04.4 x64
Con el perfil añadido correctamente empezamos a analizar.
pwnshadow@hackiit:Memory_dump# volatility --profile=LinuxUbuntu14_04_4x64 -f memdump.raw linux_pslist
Obtenemos una lista de los procesos en ejecución en el momento del volcado de memoria, los más interesantes parecen ser los últimos, tiene en ejecución un navegador web (firefox), un editor de texto (leafpad), un terminal (xterm), bash y python.
0xffff88007075e040 firefox 1585 1276 1000 1000 0x0000000070fa1000 2019-02-21 21:31:41 UTC+0000 0xffff8800707f3700 leafpad 1638 1276 1000 1000 0x000000005840a000 2019-02-21 21:31:51 UTC+0000 0xffff880058515280 x-terminal-emul 1665 1426 1000 1000 0x000000005853d000 2019-02-21 21:33:06 UTC+0000 0xffff880058513700 gnome-pty-helpe 1666 1665 1000 1000 0x00000000707e8000 2019-02-21 21:33:06 UTC+0000 0xffff8800585144c0 bash 1667 1665 1000 1000 0x0000000058428000 2019-02-21 21:33:06 UTC+0000 0xffff8800707a44c0 python2 1683 1667 1000 1000 0x000000005852c000 2019-02-21 21:34:49 UTC+0000
Revisamos el historial de bash
pwnshadow@hackiit:Memory_dump# volatility --profile=LinuxUbuntu14_04_4x64 -f memory_dump.raw linux_bash Volatility Foundation Volatility Framework 2.6 Pid Name Command Time Command -------- -------------------- ------------------------------ ------- 1667 bash 2019-02-21 21:34:24 UTC+0000 python2 decrypt.pyc Hackiit_CTF
Tiene una llamada a python2 para ejecutar decrypt.pyc con el argumento Hackiit_CTF. Vamos a intentar recuperar el fichero decrypt.pyc para ver que contiene, para ello primero extraemos la lista de ficheros en memoria y buscamos en ella el fichero con el fin de obtener el inode.
pwnshadow@hackiit:Memory_dump# volatility --profile=LinuxUbuntu14_04_4x64 -f memory_dump.raw linux_find_file -L > files Volatility Foundation Volatility Framework 2.6 pwnshadow@hackiit:Memory_dump# grep decrypt.pyc files 409388 0xffff880060db84c8 /home/kshywonis/decrypt.pyc
El fichero se encuentra en la carpeta /home/kshywonis/ y el inode correspondiente es 0xffff880060db84c8. Recuperamos el fichero haciendo uso del plugin de volatility linux_find_file
pwnshadow@hackiit:Memory_dump# volatility --profile=LinuxUbuntu14_04_4x64 -f memory_dump.raw linux_find_file -i 0xffff880060db84c8 -O decrypt.pyc
Una vez tenemos el fichero decrypt.pyc recuperamos el original haciendo uso de la herramienta uncompyle6.
pwnshadow@hackiit:Memory_dump# uncompyle6 decrypt.pyc > decrypt.py
El código recuperado es el siguiente:
from Crypto.Cipher import AES import os, random, struct from hashlib import sha256 from sys import argv password = 'This is a nice password :D' def decrypt_file(key, in_filename, out_filename=None, chunksize=24576): if not out_filename: out_filename = os.path.splitext(in_filename)[0] with open(in_filename, 'rb') as (infile): origsize = struct.unpack('<Q', infile.read(struct.calcsize('Q')))[0] iv = infile.read(16) decryptor = AES.new(key, AES.MODE_CBC, iv) with open(out_filename, 'wb') as (outfile): while True: chunk = infile.read(chunksize) if len(chunk) == 0: break outfile.write(decryptor.decrypt(chunk)) outfile.truncate(origsize) def main(): key1 = raw_input('Enter the key1: ') key2_location = raw_input('Enter the location of key2: ') key2_file = open(key2_location, 'r') key2 = key2_file.read() number = str(int(raw_input('Enter your magic number: ')) % 10) filename = raw_input('Enter the location of the file: ') password = key1 + number + key2 + argv[1] enc_key = sha256(password.encode('utf-8')).digest() decrypt_file(enc_key, filename) key2_file.close() if __name__ == '__main__': main()
Analizando el código vemos que lo que hace es pedir una key1, luego la localización de un fichero que contiene key2 que abre y lee, posteriormente pide un número (number) del 0 al 9 y finalmente solicita la localización de un fichero (filename) que posteriormente intentará descifrar, la variable password se modifica concatenando key1, number, key2 y el argumento con el que se ha llamado al programa, a continuación le calcula el sha256 y lo utiliza para llamar a decrypt_file junto con filename, finalmente cierra el fichero key2 y finaliza. Respecto a la función decrypt_file, esta consiste en una implementación de AES que, llamandola con la localización de un fichero cifrado y la clave lo descifra.
Dado que el fichero key2 permanece abierto durante toda la ejecución del programa, intentamos recuperarlo, para ello miramos los filleros abiertos por el proceso 1683 (python2).
pwnshadow@hackiit:Memory_dump# volatility --profile=LinuxUbuntu14_04_4x64 -f memory_dump.raw linux_lsof -p 1683 Volatility Foundation Volatility Framework 2.6 Offset Name Pid FD Path ------------------ ------------------------------ -------- -------- ---- 0xffff8800707a44c0 python2 1683 0 /dev/pts/0 0xffff8800707a44c0 python2 1683 1 /dev/pts/0 0xffff8800707a44c0 python2 1683 2 /dev/pts/0 0xffff8800707a44c0 python2 1683 3 /tmp/key2 pwnshadow@hackiit:Memory_dump# grep "/tmp/key2" files 1203 0xffff880060d91cf8 /tmp/key2 pwnshadow@hackiit:Memory_dump#volatility --profile=LinuxUbuntu14_04_4x64 -f memory_dump.raw linux_find_file -i 0xffff880060d91cf8 -O key2
El contenido de key2 es }just a random string
El valor de number podemos buscarlo en memoria aunque no estamos seguros de que se haya introducido, por lo que podemos probar con los 10 posibles valores 0,1,2,3,4,5,6,7,8,9.
Ya tenemos 3 partes de password, key2, el argumento y los 10 posibles valores de number. Como el fichero key2 se encontraba abierto, analizando el flujo del programa vemos que cuando key2 se abre, la variable key1 ya ha sido introducida y por tanto almacenada en memoria, aquí surgen dos posibles formas de obtener el valor de key1:
- Volcar la memoria del proceso, analizar en busca de la variable key1 y obtener el valor de la variable.
- Sacar las strings de proceso python2, para ello podemos utilizar el plugin de volatility python_string
Nos decantamos por la segunda opción.
pwnshadow@hackiit:Memory_dump# volatility --profile=LinuxUbuntu14_04_4x64 -f memory_dump.raw linux_python_strings -p 1683 --dump-dir .
Con esto ya tenemos la lista de posibles valores de key1. Pasamos a buscar el fichero cifrado.
Analizando la lista de ficheros abiertos no encontramos nada que parezca el fichero, sin embargo, en la lista de procesos se encontraba abierto firefox (PID 1585), quizás iba a descargar el fichero, intentamos extraer las webs haciendo uso del plugin de yarascan para volatility:
pwnshadow@hackiit:Memory_dump# volatility --profile=LinuxUbuntu14_04_4x64 -f memory_dump.raw linux_yarascan -Y "https://" -p 1585 > websites
Buscamos en el fichero generado y todo parece normal a excepción de links a mega, buscamos uno que corresponda un enlace de descarga:
Task: firefox pid 1585 rule r1 addr 0x7f78128b7560 0x7f78128b7560 68 74 74 70 73 3a 2f 2f 6d 65 67 61 2e 6e 7a 2f https://mega.nz/ 0x7f78128b7570 23 21 57 4b 59 41 7a 59 7a 54 21 48 2d 39 35 55 #!WKYAzYzT!H-95U 0x7f78128b7580 46 57 59 61 6d 37 75 64 52 70 68 6a 33 51 70 32 FWYam7udRphj3Qp2 0x7f78128b7590 36 53 52 4f 74 74 67 6c 6e 63 77 56 4f 78 5f 78 6SROttglncwVOx_x 0x7f78128b75a0 50 54 76 30 7a 59 00 e5 e5 e5 e5 e5 e5 e5 e5 e5 PTv0zY.......... 0x7f78128b75b0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0x7f78128b75c0 00 76 8b 12 78 7f 00 00 00 76 8b 12 78 7f 00 00 .v..x....v..x... 0x7f78128b75d0 00 76 8b 12 78 7f 00 00 00 76 8b 12 78 7f 00 00 .v..x....v..x... 0x7f78128b75e0 00 00 00 00 00 00 00 00 00 00 00 00 e5 e5 e5 e5 ................ 0x7f78128b75f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0x7f78128b7600 70 b2 8b 14 78 7f 00 00 3c 00 00 00 3c 00 00 00 p...x...<...<... 0x7f78128b7610 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0x7f78128b7620 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0x7f78128b7630 00 00 00 00 00 00 00 00 00 00 00 00 ff ff ff ff ................ 0x7f78128b7640 ff ff ff ff ff ff ff ff 32 00 00 00 00 00 00 e5 ........2....... 0x7f78128b7650 60 93 6d 54 7b 7f 00 00 b0 fd 6d 54 7b 7f 00 00 `.mT{.....mT{...
El enlace es https://mega.nz/#!WKYAzYzT!H-95UFWYam7udRphj3Qp26SROttglncwVOx_xPTv0zY y nos lleva a la descarga del fichero just_a_photo.jpeg.enc
Probamos a mirar si el fichero tiene algo con binwalk y file:
pwnshadow@hackiit:Memory_dump# binwalk just_a_photo.jpeg.enc DECIMAL HEXADECIMAL DESCRIPTION -------------------------------------------------------------------------------- pwnshadow@hackiit:Memory_dump# file just_a_photo.jpeg.enc just_a_photo.jpeg.enc: data
No hay nada, parece el fichero cifrado que buscabamos.
Vamos a probar cada una de las strings de python obtenidas anteriormente (key1) con los 10 posibles valores de number, para ello, modificamos el código decrypt.py. El fichero cifrado tiene la extensión .jpeg antes de .enc por lo que parece que el fichero original se trata de una imagen en formato JPEG, es por ello que vamos a comprobar que el número mágico del fichero descifrado sea el de jpeg FF D8, así agilizaremos el proceso de descifrado ya que solo hace falta descifrar los 16 primeros bytes para cada posibilidad. Además, cada fichero descifrado lo vamos a guardar con el nombre original quitándole la extensión .enc y concatenando los valores de key1 y number
El programa queda tal que así:
from Crypto.Cipher import AES import os, random, struct from hashlib import sha256 from multiprocessing import Pool def decrypt(key_and_number): key = sha256((key_and_number+password).encode('utf-8')).digest() decryptor = AES.new(key, AES.MODE_CBC, iv) out_filename = os.path.splitext(in_filename)[0]+'.'+key_and_number dec_data = decryptor.decrypt(data[0:16]) if dec_data[0:2] == b'\xff\xd8': #Check if the magic number is from jpeg with open(out_filename, 'wb') as (outfile): outfile.write(dec_data + decryptor.decrypt(data[16:])) outfile.truncate(origsize) return True else: return False key2 = '}just a random string' arg = 'Hackiit_CTF' password = key2 + arg in_filename='just_a_photo.jpeg.enc' infile = open(in_filename, 'rb') origsize = struct.unpack('<Q', infile.read(struct.calcsize('Q')))[0] iv = infile.read(16) data = infile.read() key1_number = [] with open('1683.python2.strings','r') as strings: for key1 in strings: key1 = key1[1:-2] for number in range(0,10): key1_number.append(key1.replace("\n", "")+str(number)) pool = Pool(processes=8) pool.map(decrypt, key1_number)
Debemos asegurarnos de tener la librería pycrypto que es la encargada del descifrado. Lo ejecutamos y obtenemos el fichero just_a_photo.jpeg.unexpected end of pattern7 lo que quiere decir que key1 es unexpected end of pattern y number 7. El fichero es el siguiente:

Revisamos con exiftool los metadatos de la imagen:
pwnshadow@hackiit:Memory_dump# exiftool just_a_photo.jpeg.unexpected\ end\ of\ pattern7 ExifTool Version Number : 11.11 File Name : just_a_photo.jpeg.unexpected end of pattern7 Directory : . File Size : 260 kB File Modification Date/Time : 2019:03:05 23:37:24+01:00 File Access Date/Time : 2019:03:05 23:37:24+01:00 File Inode Change Date/Time : 2019:03:05 23:37:24+01:00 File Permissions : rw-r--r-- File Type : JPEG File Type Extension : jpg MIME Type : image/jpeg Comment : This is a very important messageexiftool -Comment=”””This is a very important messageexiftool just_a_photo.jpegwe have received all the information, now we need you to clean every traces that could point to us and leave the job. See you tomorrow at the meeting place, we will discuss your promotion, the access key is hackiit_flag{The_n3w_spymasteR}””” just_a_photo.jpegwe have received all the information, now we need you to clean every traces that could point to us and leave the job. See you tomorrow at the meeting place, we will discuss your promotion, the access key is hackiit_flag{The_n3w_spymasteR} Image Width : 800 Image Height : 800 Encoding Process : Progressive DCT, Huffman coding Bits Per Sample : 8 Color Components : 3 Y Cb Cr Sub Sampling : YCbCr4:2:0 (2 2) Image Size : 800x800 Megapixels : 0.640
hackiit_flag{The_n3w_spymasteR}
Recent Comments