HTB: Strike Back Challenge

Strike Back - HackTheBox Challenge Writeup

Challenge Information

FieldValue
NameStrike Back
CategoryForensics
DifficultyMedium
Authord3vn0mi

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 capture
  • freesteam.dmp (10 MB) — a process minidump of the infected freesteam.exe

2. Pcap triage

Extracting HTTP objects from the pcap showed the classic Cobalt Strike beacon lifecycle:

Terminal window
# Pull out HTTP request metadata and any exported objects
tshark -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 checkin
    • POST /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 = key
for 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:

Terminal window
pip install "dissect.cobaltstrike[full]"
beacon-dump stage.bin
C2 Server : 192.168.1.9
HTTP GET : /match
HTTP POST : /submit.php?id=1909272864
User-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 tag
import 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 IV
from Crypto.Cipher import AES
IV = b"abcdefghijklmnop" # Cobalt Strike's known fixed IV
for 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())
break

This recovered both session keys resident in memory:

  • HMAC key bf2d35c0… at offset 0x44b2a1
  • AES key 3ae7f995… at offset 0x447f81 (validated against the fixed CS IV abcdefghijklmnop)

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, hashlib
from 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:

Terminal window
pip install pdfminer.six
pdftotext orders.pdf -

The recovered document — the “attack orders” referenced by the challenge fluff — contained the flag.

Flag: HTB{REDACTED}

Key Steps

  1. Recovered the real challenge archive (the local pcap placeholder was a 0-byte stub) and unzipped it with password hackthebox.
  2. Extracted HTTP objects from the pcap with tshark and identified a Cobalt Strike stager/beacon lifecycle (/iVd9 stage download → /match checkins → /submit.php exfil).
  3. Decoded the chained-XOR stager wrapper to recover the raw beacon DLL, then confirmed the C2 config with dissect.cobaltstrike’s beacon-dump.
  4. 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.
  5. Wrote a decryptor to validate HMACs and AES-CBC decrypt every beacon packet in the capture.
  6. 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 parsing
  • pdfminer.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.