HTB: Strike Back Challenge
Strike Back - HackTheBox Challenge Writeup
Challenge Information
| Field | Value |
|---|---|
| Name | Strike Back |
| Category | Forensics |
| Difficulty | Medium |
| Author | d3vn0mi |
Description
A fleet of steam blimps waits the final signal from their commander in order to attack Gogglestown kingdom. A recent cyber attack had us thinking if the enemy managed to discover our plans and prepare a counter-attack. Will the fleet get ambushed???
The challenge ships two artifacts: a packet capture and a process memory dump from a compromised host. The task is a classic C2-traffic-decryption exercise — recover the Cobalt Strike beacon’s keys from the memory dump, use them to decrypt the captured HTTP traffic, and read the operator’s exfiltrated commands and files to find the flag.
Solution
1. Artifact recovery
The provided capture.pcap in the working directory was a 0-byte stub. Matching the solve-directory UUID against the HTB challenge metadata’s file_name confirmed it was the correct download slot, so the real archive was re-fetched via the HTB API and unzipped with the standard challenge password hackthebox. That produced the actual working set:
capture.pcap(924 KB) — the real network capturefreesteam.dmp(10 MB) — a process minidump of the infectedfreesteam.exe
2. Pcap triage
Extracting HTTP objects from the pcap showed the classic Cobalt Strike beacon lifecycle:
# Pull out HTTP request metadata and any exported objectstshark -r capture.pcap -Y http -T fields \ -e frame.number -e http.request.method -e http.request.uri
tshark -r capture.pcap --export-objects "http,obj"Findings:
freesteam.exe(the stager) fetches a ~206 KB payload from/iVd9.- The infected host then beacons repeatedly:
GET /match— beacon checkinPOST /submit.php?id=1909272864— encrypted task output / exfil
3. Decoding the staged payload
The /iVd9 response wasn’t a raw PE — it was wrapped in a chained-XOR stager encoder, with a key and length embedded as two dwords near offset 0x39:
import struct
data = bytearray(open("obj/iVd9", "rb").read())key, length = struct.unpack_from("<II", data, 0x39)
out = bytearray(length)prev = keyfor i in range(length): b = data[0x3d + i] ^ (prev & 0xFF) out[i] = b prev = (prev >> 8) | (b << 24) # rolling chained XOR
open("stage.bin", "wb").write(out)Decoding this revealed a proper Cobalt Strike beacon DLL. Running the community dissect.cobaltstrike tooling against it confirmed the embedded config:
pip install "dissect.cobaltstrike[full]"beacon-dump stage.binC2 Server : 192.168.1.9HTTP GET : /matchHTTP POST : /submit.php?id=1909272864User-Agent : Mozilla/4.0 (compatible; MSIE 8.0; ...)This confirmed the traffic seen in the pcap was genuine Cobalt Strike beacon check-ins, and pointed to the crypto scheme (AES-CBC payload + HMAC-SHA256 integrity tag) that CS beacons use for its C2 channel.
4. Recovering the AES/HMAC keys from the minidump
Cobalt Strike beacons carry their session AES and HMAC keys resident in process memory after key exchange. Strings/heuristics against freesteam.dmp didn’t yield the keys in their raw form directly usable — the naive sha256(candidate) derivation didn’t match either. Instead, a brute-force sliding window over the dump was used, scoring each 16-byte candidate window against real captured traffic:
# Brute force 16-byte windows in the dump against a captured packet's HMAC tagimport hashlib, hmac, struct
dump = open("freesteam.dmp", "rb").read()
for offset in range(len(dump) - 16): candidate = dump[offset:offset+16] tag = hmac.new(candidate, packet_body, hashlib.sha256).digest()[:16] if tag == captured_hmac: print("HMAC key found at", hex(offset), candidate.hex()) break# Sanity-check AES-CBC candidates with the beacon's fixed IVfrom Crypto.Cipher import AES
IV = b"abcdefghijklmnop" # Cobalt Strike's known fixed IVfor offset in range(len(dump) - 16): candidate = dump[offset:offset+16] cipher = AES.new(candidate, AES.MODE_CBC, IV) pt = cipher.decrypt(encrypted_body) if looks_like_valid_task_packet(pt): print("AES key found at", hex(offset), candidate.hex()) breakThis recovered both session keys resident in memory:
- HMAC key
bf2d35c0…at offset0x44b2a1 - AES key
3ae7f995…at offset0x447f81(validated against the fixed CS IVabcdefghijklmnop)
5. Decrypting the beacon traffic
With both keys in hand, a small decryptor (support_scripts/cs_decrypt.py) was written to walk every GET /match and POST /submit.php body in the pcap, verify the HMAC, then AES-CBC decrypt the payload:
# cs_decrypt.py (core logic)import hmac, hashlibfrom Crypto.Cipher import AES
AES_KEY = bytes.fromhex("3ae7f995...")HMAC_KEY = bytes.fromhex("bf2d35c0...")IV = b"abcdefghijklmnop"
def decrypt_packet(raw: bytes) -> bytes: body, tag = raw[:-16], raw[-16:] expected = hmac.new(HMAC_KEY, body, hashlib.sha256).digest()[:16] assert hmac.compare_digest(tag, expected), "HMAC mismatch" return AES.new(AES_KEY, AES.MODE_CBC, IV).decrypt(body)All packet HMACs verified cleanly against the recovered keys, confirming the correct keys had been recovered. Decrypting the full session reconstructed the operator’s interactive session: recon commands (getuid, net user), credential/hash dumping, and — critically — file exfiltration, including a submit.php upload containing a PDF (orders.pdf) that had been staged out of the target host.
6. Extracting the flag
The exfiltrated PDF was pulled out of the decrypted POST bodies and read:
pip install pdfminer.sixpdftotext orders.pdf -The recovered document — the “attack orders” referenced by the challenge fluff — contained the flag.
Flag: HTB{REDACTED}
Key Steps
- Recovered the real challenge archive (the local pcap placeholder was a 0-byte stub) and unzipped it with password
hackthebox. - Extracted HTTP objects from the pcap with
tsharkand identified a Cobalt Strike stager/beacon lifecycle (/iVd9stage download →/matchcheckins →/submit.phpexfil). - Decoded the chained-XOR stager wrapper to recover the raw beacon DLL, then confirmed the C2 config with
dissect.cobaltstrike’sbeacon-dump. - Brute-forced 16-byte windows of the process minidump against a real HMAC tag and an AES-CBC IV sanity check to recover the live session’s HMAC and AES keys.
- Wrote a decryptor to validate HMACs and AES-CBC decrypt every beacon packet in the capture.
- Extracted an exfiltrated PDF from the decrypted traffic and pulled the flag from its text.
Tools Used
tshark— HTTP object/traffic extraction from the pcap- Python (
struct,hashlib,hmac,pycryptodome) — chained-XOR stage decoding, key brute-forcing, AES-CBC decryption dissect.cobaltstrike(beacon-dump) — Cobalt Strike beacon config parsingpdfminer.six/pdftotext— text extraction from the exfiltrated PDF
Key Learnings
- Cobalt Strike stagers often wrap the actual beacon payload in a simple chained-XOR encoder with the key/length embedded near a fixed offset — worth checking before assuming a downloaded blob is directly loadable.
- Beacon AES/HMAC session keys are resident in process memory in their raw usable form (not merely a hash of some seed), so a windowed brute-force against the memory dump, validated against real captured HMAC tags or the beacon’s fixed CBC IV, is a reliable recovery technique when the keys aren’t handed to you directly.
- Always verify the HMAC before trusting a decrypted AES-CBC plaintext — it’s a cheap, unambiguous correctness check that confirms key recovery before spending time parsing the reconstructed session.
- Don’t assume provided artifacts are complete — a 0-byte capture file was a broken placeholder, and re-fetching the original challenge archive from the platform was necessary to get a usable pcap.