[+] Recon

This post spoils a CTF challenge … Don’t read if you want to try it !

SantHackLaus is a Jeopardy CTF challenge. It is organized by IMT Lille Douai. I had a great time solving these challenges :D

Mission Impossible 2 is a forensic challenge. We need to recover the flag that has been deleted from the machine by the attacker.
You can get the archive here.

We just have two file : challenge.raw and challenge.pcapng.

challenge.pcapng is a network capture. Let’s analyze it with Wireshark.

Filter : ICMP requests and HTTP requests only

The network capture contains a lot of base64 encoded data. It seems to be a data exfiltration through HTTP and ICMP requests…
I dumped the interesting filtered packets as JSON in “exported.json”.

challenge.raw is a Linux memory dump. Let’s analyze it with Volatility.

I didn’t have a profile for this Linux version (Debian8-3_16_0-amd64x64), so I created one with the following tutorial.

First, let’s enumerate the processes in this memory dump with pstree.

volatility --plugins='./volatility_plugins/' -f challenge.raw --profile=Linuxdebian8-3_16_0-amd64x64 linux_pstree

We mainly notice that bash and an apache server are running. Let’s look if there is something interesting in bash history.

volatility --plugins='./volatility_plugins/' -f challenge.raw --profile=Linuxdebian8-3_16_0-amd64x64 linux_bash

Sounds great ! We can notice two things :
1- The attacker’s working directory was /opt/DET. The flag.zip archive (protected by password ‘IMTLD{N0t_Th3_Fl4g}') contains flag.jpg, which is probably our flag.
2- The attacker used this Data Exfiltration Toolkit (DET) to exfiltrate flag.zip through HTTP et ICMP. The config file used by the attacker is config.json, which he created a few minutes ago (nano config.json).

The network capture is the dump of this exfiltration.

[+] Recovery

So, to get our flag, we need to understand the DET’s code, and create a script which recover the exfiltrated file from the network capture.
Then, we just have to unzip the archive with password ‘IMTLD{N0t_Th3_Fl4g}’ and read flag.jpg !

Let’s recover config.json, which contains the AES secret key used to encrypt the network traffic. File enumeration :

volatility --plugins='./volatility_plugins/' -f challenge.raw --profile=Linuxdebian8-3_16_0-amd64x64 linux_find_file -L |grep 'config.json'

File extraction : volatility --plugins='./volatility_plugins/' -f challenge.raw --profile=Linuxdebian8-3_16_0-amd64x64 linux_find_file -i 0xffff88003c16ec80 -O config.json.

Here is config.json contents :

cat config.json

We have the attacker’s AES key !

Here is script I made to recover the encrypted exfiltrated data from the network capture (exported as JSON).

import os,binascii
import base64
import random
import urllib
import hashlib
import sys
import string
import time
import json
import signal
import struct
import tempfile
from random import randint
from os import listdir
from os.path import isfile, join
from Crypto.Cipher import AES
from zlib import compress, decompress

KEY = "IMTLD{This_is_just_a_key_not_the_flag}"
files = {}

def retrieve_file(jobid):
        fname = files[jobid]['filename']
        filename = "%s.%s" % (fname.replace(
            os.path.pathsep, ''), time.strftime("%Y-%m-%d.%H:%M:%S", time.gmtime()))
        content = ''.join(str(v) for v in files[jobid]['data']).decode('hex')
        content = aes_decrypt(content, KEY)
        if COMPRESSION:
        	content = decompress(content)
        f = open(filename, 'w')
        print("File %s recovered" % (fname))

def register_file(message):
        global files
        jobid = message[0]
        if jobid not in files:
            files[jobid] = {}
            files[jobid]['checksum'] = message[3].lower()
            files[jobid]['filename'] = message[1].lower()
            files[jobid]['data'] = []
            files[jobid]['packets_number'] = []
            print("Register packet for file %s with checksum %s" %
                    (files[jobid]['filename'], files[jobid]['checksum']))

def retrieve_data(data):
	message = data
	if (message.count("|!|") >= 2):
		print("Received {0} bytes".format(len(message)))
		message = message.split("|!|")
		jobid = message[0]

                # register packet
		if (message[2] == "REGISTER"):
			print("[+] NEW FILE REGISTERED")
                # done packet
		elif (message[2] == "DONE"):
			print("[+] FILE RECEIVED")
             	  # data packet
                    # making sure there's a jobid for this file
			if (jobid in files and message[1] not in files[jobid]['packets_number']):

def aes_decrypt(message, key=KEY):
        # Retrieve CBC IV
        iv = message[:AES.block_size]
        message = message[AES.block_size:]

        # Derive AES key from passphrase
        aes = AES.new(hashlib.sha256(key).digest(), AES.MODE_CBC, iv)
        message = aes.decrypt(message)

        # Remove PKCS5 padding
        unpad = lambda s: s[:-ord(s[len(s) - 1:])]

        return unpad(message)
        return None

f = open('exported.json','r')
mydata = json.load(f)
for packet in mydata:
	line = b''
	if 'icmp' in packet['_source']['layers']:
		print('[+] ICMP Packet Received !')
		line = binascii.unhexlify(''.join(packet['_source']['layers']['icmp']['data']['data.data'].split(':')))
	if 'http' in packet['_source']['layers']:
		print('[+] HTTP Packet Received !')
		line = packet['_source']['layers']['http']['http.file_data'][5:]
	line = urllib.unquote(line).decode('utf8') 

The biggest part of this code is just adapted from DET original code.

After running the script and unzipping the resulting archive with python solve.py && unzip -P 'IMTLD{N0t_Th3_Fl4g}' flag.zip, we have our flag.jpg !!

Flag is : IMTLD{Th3_d4t4_3xf1ltr4Ti0n_T00lK1t_r0x}.

[+] Bye

Feel free to tell me what you think about this post :)