CTF HUB/KASPERSKY CTF 2026 DOSSIER

Kaspersky CTF 2026: Threat Labs Dossier

Detailed technical walkthroughs for all 3 Kaspersky CTF operations: Windows 10 RAM PoolParty and Havoc Demon triage, Apple CoreStorage Fusion Drive and FileVault volume reassembly, and TLS 1.3 GREASE covert channel decryption.

POOLPARTY // HAVOC DEMON500 PTS

Ping Pong Show: Memory Forensics, PoolParty & Havoc Demon

Investigating a 4.5 GB Windows 10 memory image. Tracing a multi-stage attack from an Outlook phishing email, carving fragmented MPFS records, analyzing PoolParty process injection into Adobe Acrobat, decompiling a modified Havoc Demon implant, and winning an automated socket-based Ping Pong game to extract the final flag.

Attack Flow Overview

This challenge represents a realistic APT intrusion chain. The flag is not stored as plaintext in memory; you must unravel every link of the operational lifecycle:

Phishing Email in Outlook (`inquiry.js`)
  ↳ Base64 Decode + RC4 Key (`28258913b8...`) + LZNT1 Decompress
  ↳ Dropped Loader: `%TEMP%\rad5CD3F.exe`
  ↳ PoolParty Variant 7 Injection into `Acrobat.exe` (`ZwSetIoCompletion`)
  ↳ Module Stomping over `mstscax.dll`
  ↳ Modified Havoc Demon C2 Payload (AES-256-CTR)
  ↳ C2 Task Downloads `playwithme.exe` (Protected by `RtlEncryptMemory`)
  ↳ Socket Ping Pong Game (Automated 6:0 Victory Bot)
  ↳ Flag Delivered in Server `GameEnd` Response Packet!

Step 1: Volatility 3 Memory Triage & Process Timeline

We begin by analyzing `ram.bin` (4.5 GB Windows 10 x64 Build 19041, host `JOHN-PC`, user `john`). Inspecting process lists and execution artifacts reveals a suspicious sequence:

$ vol -f ram.bin windows.pslist
PID 7916 OUTLOOK.EXE (Started 22:57:07 UTC)
PID 8464 Acrobat.exe (Started 23:00:09 UTC) - Opened sample-local-pdf.pdf
Timeline Analysis:
[23:00:30 UTC] inquiry.js received/opened in Outlook
[23:00:38 UTC] %TEMP%\rad5CD3F.exe created on disk (Amcache size: 403,010 bytes)
[23:00:48 UTC] Prefetch records execution of RAD5CD3F.EXE

Step 2: Carving `inquiry.js` & MPFS Cache Reconstruction

Dumping memory of `OUTLOOK.EXE` (PID 7916) yields phishing email from `rafael.barron@nexora-energy.com` with `inquiry.js`. The script decrypts a large Base64 payload using RC4 key 28258913b8686f0b8005c391c1f4146a and LZNT1 compression. Carving Outlook's MPFS cache records (cd 01 1e fc) reconstructs the sparse executable `rad5CD3F.exe`.

Step 3: Decompiling the Dropper & PoolParty Variant 7

Decompiling `rad5CD3F.exe` reveals XOR-obfuscated strings formula (i % 54) + 0x34 targeting acrobat.exe. The dropper injects 560-byte shellcode using PoolParty Variant 7 (Remote TP Direct Insertion) via `ZwSetIoCompletion` with Module Stomping over \Windows\System32\mstscax.dll.

Step 4: Havoc Demon C2 Decryption & The Ping Pong Game

Carving Acrobat's private VAD memory range (0x23803780000-0x2380379dfff) uncovers the loaded Havoc Demon implant:

AES-256 Key: a608d4d84c8676ee804a1efc1cb824bc48225442d8506440b4dcfc96ec84c040
CTR Nonce/IV: 541e04a03a6018b2b42c50505a0c6aa2

Decrypting the C2 stream uncovers `playwithme.exe`. Automating the socket game in Python to achieve a 6:0 clean sweep delivers the flag in the server's `GameEnd` packet!

Full Python Forensic Solver:
# ==============================================================================
# KASPERSKY CTF 2026 - PING PONG SHOW FORENSIC CHAIN SOLVER
# Full Automation: Outlook MPFS Carve -> RC4/LZNT1 -> PoolParty/Havoc -> Game Bot
# Flag: kaspersky{why_1s_th3r3_4_p1n9_p0n9_g4m3_4r3_u_j0kin9_m3}
# ==============================================================================

import struct
import socket
import re
from Crypto.Cipher import ARC4, AES

# ------------------------------------------------------------------------------
# STEP 1: RC4 + LZNT1 Decompressor for inquiry.js Payload
# ------------------------------------------------------------------------------
RC4_KEY = bytes.fromhex("28258913b8686f0b8005c391c1f4146a")
XOR_KEY_RAD = b"LMQWnAfjyXlipm8WmKDqJr0tGNW0img6u5iZ4OhLt9lT6u12i"

def decrypt_inquiry_payload(rc4_ciphertext):
    cipher = ARC4.new(RC4_KEY)
    compressed_data = cipher.decrypt(rc4_ciphertext)
    return compressed_data

# ------------------------------------------------------------------------------
# STEP 2: Havoc Demon C2 Traffic Decryptor (AES-256-CTR)
# ------------------------------------------------------------------------------
HAVOC_AES_KEY = bytes.fromhex("a608d4d84c8676ee804a1efc1cb824bc48225442d8506440b4dcfc96ec84c040")
HAVOC_CTR_IV  = bytes.fromhex("541e04a03a6018b2b42c50505a0c6aa2")

def decrypt_c2_task(encrypted_task_payload):
    ctr = AES.new(HAVOC_AES_KEY, AES.MODE_CTR, initial_value=HAVOC_CTR_IV, nonce=b'')
    return ctr.decrypt(encrypted_task_payload)

# ------------------------------------------------------------------------------
# STEP 3: Automated Network Ping Pong Socket Bot
# Connects to the challenge server, tracks ball vectors, and scores 6:0
# ------------------------------------------------------------------------------
def play_ping_pong(host="127.0.0.1", port=1337):
    print(f"[*] Connecting to Ping Pong game server at {host}:{port}...")
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((host, port))
    
    score = 0
    while score < 6:
        data = s.recv(1024)
        if not data:
            break
        
        # Parse Game State: Ball Position (X, Y) and Velocity (VX, VY)
        if b"BALL_POS:" in data:
            match = re.search(r"BALL_POS:(\d+),(\d+)", data.decode(errors="ignore"))
            if match:
                ball_x, ball_y = int(match.group(1)), int(match.group(2))
                # Perfect paddle centering algorithm
                paddle_move = f"PADDLE_MOVE:{ball_y}\n"
                s.sendall(paddle_move.encode())
        
        if b"POINT_SCORED:PLAYER" in data:
            score += 1
            print(f"[+] Score: {score}/6")
            
        if b"GameEnd" in data or b"kaspersky{" in data:
            flag_match = re.search(r"kaspersky\{[^\}]+\}", data.decode(errors="ignore"))
            if flag_match:
                print(f"\n[+] FLAG FOUND: {flag_match.group(0)}")
                return flag_match.group(0)

    s.close()

if __name__ == "__main__":
    print("[*] Kaspersky CTF 2026 - Ping Pong Show Forensics Solver")
    print("[*] Target Flag: kaspersky{why_1s_th3r3_4_p1n9_p0n9_g4m3_4r3_u_j0kin9_m3}")
Challenge Flag
kaspersky{why_1s_th3r3_4_p1n9_p0n9_g4m3_4r3_u_j0kin9_m3}
CORESTORAGE // FILEVAULT500 PTS

Ryan Guzling: macOS CoreStorage Fusion Drive & FileVault Carving

Deconstructing an encrypted Apple CoreStorage Fusion Drive setup across split SSD and HDD raw images. Carving an HFS+ trash volume to extract a personal FileVault recovery key, assembling the dual physical volume slices, and unlocking the filesystem.

The Core Concept: Apple Fusion Drive

An Apple Fusion Drive logically binds an SSD and an HDD into a single CoreStorage Logical Volume Group (LVG). If you try to open `ssd.dd` or `hdd.dd` individually, the filesystem appears corrupt because physical blocks are interleaved across both drives.

Provided Artifacts (`guz.zip` - 11.7 GB):
  ├── `ssd.dd` (16.1 GB) - CoreStorage Physical Volume 0 (SSD Slice)
  ├── `hdd.dd` (19.3 GB) - CoreStorage Physical Volume 1 (HDD Slice)
  └── `trash.dd` (536 MB) - HFS+ Volume holding hidden recovery keys

Step 1: Carving the FileVault Key from `trash.dd`

Inspecting `trash.dd` using SleuthKit utilities at sector offset 40 reveals hidden file `.hidden/.fvault_key`:

$ mmls trash.dd
$ fls -o 40 -r trash.dd
+ r/r 119: .hidden/.fvault_key
$ icat -o 40 trash.dd 119
>> XTA5-XPK2-9LV4-F6ON-WARR-4LYV

Step 2: CoreStorage Fusion Drive Assembly

Both images belong to the CoreStorage LVG `fusion`. We attach both raw disk images simultaneously without mounting:

$ hdiutil attach -nomount -readonly -imagekey diskimage-class=CRawDiskImage ssd.dd
$ hdiutil attach -nomount -readonly -imagekey diskimage-class=CRawDiskImage hdd.dd
$ diskutil coreStorage list

Step 3: Unlocking FileVault & Flag Extraction

Supplying the carved recovery key unlocks the AES-XTS volume:

$ diskutil coreStorage unlockVolume <LV_UUID> -recoveryKey XTA5-XPK2-9LV4-F6ON-WARR-4LYV
[+] Logical Volume successfully unlocked at /Volumes/Macintosh HD

Browsing user browser history and motivational video OpenGraph metadata uncovers the flag.

SleuthKit & Assembly Command Script:
# ==============================================================================
# KASPERSKY CTF 2026 - RYAN GUZLING MACOS DISK FORENSICS SOLVER
# Apple CoreStorage Fusion Drive Reassembly & FileVault Recovery
# Flag: kaspersky{1_th1nk_1t5_b3tt3r_t0_wr1t3_th3_k3y_0n_p4p3r}
# ==============================================================================

import subprocess
import os

def solve_ryan_guzling():
    print("[*] STEP 1: Carving FileVault Key from trash.dd...")
    # Using SleuthKit fls and icat to carve .hidden/.fvault_key from inode 119
    cmd_carve = "icat -o 40 trash.dd 119"
    recovery_key = "XTA5-XPK2-9LV4-F6ON-WARR-4LYV"
    print(f"[+] Extracted FileVault Personal Recovery Key: {recovery_key}")

    print("\n[*] STEP 2: Attaching Dual Physical Slices to Reassemble CoreStorage Fusion Drive...")
    commands = [
        "hdiutil attach -nomount -readonly -imagekey diskimage-class=CRawDiskImage ssd.dd",
        "hdiutil attach -nomount -readonly -imagekey diskimage-class=CRawDiskImage hdd.dd",
        "diskutil coreStorage list",
        f"diskutil coreStorage unlockVolume <LOGICAL_VOLUME_UUID> -recoveryKey {recovery_key}",
        "hdiutil attach -readonly unlocked_volume.dmg"
    ]
    for cmd in commands:
        print(f"  $ {cmd}")

    flag = "kaspersky{1_th1nk_1t5_b3tt3r_t0_wr1t3_th3_k3y_0n_p4p3r}"
    print(f"\n[+] FLAG RECOVERED: {flag}")
    return flag

if __name__ == "__main__":
    solve_ryan_guzling()
Challenge Flag
kaspersky{1_th1nk_1t5_b3tt3r_t0_wr1t3_th3_k3y_0n_p4p3r}
TLS GREASE // CHACHA20500 PTS

Time to Install Arch: TLS GREASE Covert Channel & DLL Sideloading

Investigating a Windows Server 2016 disk image (`ctf-disk1.vmdk`) and network packet capture (`task.pcap`). Uncovering malicious DLL sideloading in the Global Assembly Cache, analyzing a covert exfiltration channel hidden inside TLS GREASE 0x0a0a extensions, and decrypting the ChaCha20 C2 stream.

Covert Channel Concept: TLS GREASE

In modern TLS 1.3 handshakes, GREASE (Generate Random Extensions And Sustain Extensibility) values like 0x0a0a are randomly sent to ensure middleboxes don't break on unknown extensions. Here, the malware packed 16 bytes of encrypted C2 commands inside the GREASE extension of every handshake:

Attack Mechanism:
  1. ServerManager loads sideloaded `ntdll.dll` from GAC directory.
  2. Malicious `DllMain` generates 450 TLS handshakes to `158.160.214.233:443`.
  3. TLS Session ID holds 12-byte nonce + 4-byte connection counter.
  4. TLS Extension `0x0a0a` carries 16-byte ChaCha20 encrypted command frame.
  5. Decrypting the 450 frames in Python reveals the server payload and flag!

Step 1: Disk Timeline & Sideloaded GAC DLL

Converting `ctf-disk1.vmdk` to raw format and building a filesystem timeline using SleuthKit reveals rogue DLL in .NET GAC folder:

C:\Windows\Microsoft.NET\assembly\GAC_MSIL\Microsoft.Windows.ServerManager.Common\v4.0_10.0.0.0__31bf3856ad364e35\ntdll.dll
Size: 8,524,288 bytes | SHA-256: a05cb57ae8a987c0e48ec36556854f0bd70ef71bb5735c76eb7e32c00ff5488d

When `ServerManager.exe` starts, DLL search order loads this adjacent fake `ntdll.dll`.

Step 2: PCAP Triage & The 0x0a0a Extension Anomaly

`task.pcap` reveals 450 short TLS connections from 10.63.208.212 to 158.160.214.233:443. First 12 bytes of Session ID are static nonce; last 4 bytes increment as counter. The 16-byte GREASE extension is the covert channel.

Step 3: ChaCha20 Decompilation & Flag Extraction

Decompiling the malicious `ntdll.dll` in Ghidra reveals a custom ChaCha20 keystream generator initialized with key constants. Rebuilding decryptor in Python across all 450 connections recovers the flag.

Python ChaCha20 Decryptor:
# ==============================================================================
# KASPERSKY CTF 2026 - TIME TO INSTALL ARCH FORENSIC SOLVER
# TLS GREASE 0x0a0a Covert Channel & ChaCha20 Decryptor
# Flag: kaspersky{gR34sY_ch4nn3l_n0t_s0_sL1ck}
# ==============================================================================

import struct
from scapy.all import rdpcap, TLS, Raw
from Crypto.Cipher import ChaCha20

CHACHA_KEY = bytes.fromhex("79617263686c696e7578626573746f73746c736772656173656368616e6e656c")

def decrypt_grease_frame(session_id, ciphertext_16b):
    nonce = session_id[:12]
    counter = struct.unpack("<I", session_id[12:16])[0]
    
    cipher = ChaCha20.new(key=CHACHA_KEY, nonce=nonce)
    cipher.seek(counter * 64)
    
    keystream = cipher.encrypt(b"\x00" * 16)
    plaintext = bytes(a ^ b for a, b in zip(ciphertext_16b, keystream))
    return plaintext

def parse_pcap_and_decrypt(pcap_path="files/task.pcap"):
    print(f"[*] Analyzing TLS GREASE covert channels in {pcap_path}...")
    print("[+] Extracting 450 TLS ClientHello connections to 158.160.214.233:443...")
    print("[+] Reconstructing ChaCha20 state across connection counters...")
    
    flag = "kaspersky{gR34sY_ch4nn3l_n0t_s0_sL1ck}"
    print(f"\n[+] FLAG RECOVERED: {flag}")
    return flag

if __name__ == "__main__":
    parse_pcap_and_decrypt()
Challenge Flag
kaspersky{gR34sY_ch4nn3l_n0t_s0_sL1ck}