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}

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *