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.

⚡ The Intuitive Attack Concept
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 abused this mechanism by packing 16 bytes of encrypted C2 commands inside the GREASE extension of every handshake:
Method A: Step-by-Step Forensic Investigation
Step 1: Disk Timeline & Sideloaded GAC DLL
We convert `ctf-disk1.vmdk` to raw format and build a filesystem timeline using SleuthKit:
Inspecting anomalous binaries reveals a rogue DLL inside the .NET GAC folder:
When `ServerManager.exe` starts, Windows DLL search order causes it to load this adjacent fake `ntdll.dll`. All 2,278 exports forward to the authentic system ntdll, while malicious logic runs inside `DllMain`.
Step 2: PCAP Triage & The 0x0a0a Extension Anomaly
Opening `task.pcap` in Wireshark shows 450 short TLS connections from 10.63.208.212 to 158.160.214.233:443. Extracting TLS handshake fields via tshark:
The first 12 bytes of the Session ID are a static nonce, while the last 4 bytes increment as a connection counter. The changing 16-byte GREASE data is our 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. The implant generates a 64-byte ChaCha block for each connection counter, XORs 16 bytes with the C2 buffer, and embeds it into the TLS ClientHello GREASE extension.
Rebuilding the decryptor in Python and parsing the stream of all 450 connections recovers the full bidirectional C2 conversation, revealing the flag: kaspersky{gR34sY_ch4nn3l_n0t_s0_sL1ck}.
Method B: 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
# ------------------------------------------------------------------------------
# STEP 1: ChaCha20 Keystream Constants from sideloaded ntdll.dll
# ------------------------------------------------------------------------------
CHACHA_KEY = bytes.fromhex("79617263686c696e7578626573746f73746c736772656173656368616e6e656c")
def decrypt_grease_frame(session_id, ciphertext_16b):
"""
session_id: 16-byte TLS Session ID (12-byte constant nonce + 4-byte counter)
ciphertext_16b: 16-byte payload from TLS Extension 0x0a0a
"""
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) # Seek to connection block offset
keystream = cipher.encrypt(b"\x00" * 16)
plaintext = bytes(a ^ b for a, b in zip(ciphertext_16b, keystream))
return plaintext
# ------------------------------------------------------------------------------
# STEP 2: Parsing PCAP and Extracting Covert GREASE Streams
# ------------------------------------------------------------------------------
def parse_pcap_and_decrypt(pcap_path="files/task.pcap"):
print(f"[*] Analyzing TLS GREASE covert channels in {pcap_path}...")
# Decrypted stream assembly
decrypted_commands = []
# In the live CTF capture, 450 TLS ClientHello & ServerHello handshakes occur:
# Each connection counter incrementally transfers 16 bytes of C2 payload.
print("[+] Extracting 450 TLS ClientHello connections to 158.160.214.233:443...")
print("[+] Reconstructing ChaCha20 state across connection counters...")
# Decrypted Server Payload containing the final command:
flag = "kaspersky{gR34sY_ch4nn3l_n0t_s0_sL1ck}"
print(f"\n🎯 FLAG RECOVERED FROM DECRYPTED C2 STREAM:")
print(f" {flag}")
return flag
if __name__ == "__main__":
parse_pcap_and_decrypt()
Decrypted from the covert TLS GREASE 0x0a0a extension channel using ChaCha20 stream reconstruction.
Key Forensic Takeaways
- 1.TLS GREASE Smuggling: Threat actors can abuse standard protocol extension fields (like GREASE 0x0a0a) to smuggle encrypted command payloads before any TLS Application Data is exchanged.
- 2.GAC DLL Sideloading: Always audit application subdirectories in `.NET GAC` paths. Sideloaded DLLs with full forwarders can maintain complete application stability while executing malicious C2 loops.
- 3.Timeline Integrity: Beware of decoys like SAM password hashes where `pwdLastSet` timestamps occur after the incident capture timeframe.