Skip to content

Instantly share code, notes, and snippets.

@lawbyte
Created August 17, 2025 18:50
Show Gist options
  • Select an option

  • Save lawbyte/bf32a4882e9365b7c38c1489adaab250 to your computer and use it in GitHub Desktop.

Select an option

Save lawbyte/bf32a4882e9365b7c38c1489adaab250 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python3
"""
pcap_flag_solver.py
-------------------
Extracts and decrypts AES/HMAC-protected C2 messages from a PCAP/PCAPNG and prints any CTF-like flags found.
Assumptions (derived from embedded PowerShell in the PCAP):
- $sharedHex is present in the capture and provides key material (32 bytes as hex).
- AES-128-CBC for confidentiality.
- HMAC-SHA256 over (IV || CT) for integrity.
- Wire format per line: Base64( IV(16) || CT || HMAC(32) ).
Usage:
python3 pcap_flag_solver.py /path/to/network-log.pcapng [-o decrypted.txt]
Requires:
- Python 3.8+
- pycryptodome (preferred) or cryptography
pip install pycryptodome
"""
import argparse, base64, hashlib, hmac, os, re, sys
from typing import Iterable, List, Optional, Tuple
# --- Crypto helpers (prefer pycryptodome) ---
_UNPAD_ERR = "Invalid PKCS#7 padding"
def pkcs7_unpad(data: bytes, block_size: int = 16) -> bytes:
if not data or len(data) % block_size != 0:
raise ValueError(_UNPAD_ERR)
pad = data[-1]
if pad == 0 or pad > block_size or data[-pad:] != bytes([pad])*pad:
raise ValueError(_UNPAD_ERR)
return data[:-pad]
try:
from Crypto.Cipher import AES as _AES # type: ignore
def aes_cbc_decrypt(key: bytes, iv: bytes, ct: bytes) -> bytes:
return _AES.new(key, _AES.MODE_CBC, iv).decrypt(ct)
_CRYPTO_BACKEND = "pycryptodome"
except Exception:
try:
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes # type: ignore
from cryptography.hazmat.backends import default_backend # type: ignore
def aes_cbc_decrypt(key: bytes, iv: bytes, ct: bytes) -> bytes:
cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())
decryptor = cipher.decryptor()
return decryptor.update(ct) + decryptor.finalize()
_CRYPTO_BACKEND = "cryptography"
except Exception:
print("[!] Install pycryptodome or cryptography (pip install pycryptodome)", file=sys.stderr)
sys.exit(2)
def find_shared_hex(blob: bytes) -> Optional[str]:
"""
Extract $sharedHex = \"...hex...\" from the PCAP bytes.
Returns the hex string without spaces if found.
"""
m = re.search(br"\$sharedHex\s*=\s*\"([0-9a-fA-F\s]+)\"", blob)
if not m:
return None
hex_s = re.sub(br"\s+", b"", m.group(1))
try:
_ = bytes.fromhex(hex_s.decode())
except Exception:
return None
return hex_s.decode()
def iter_b64_tokens(blob: bytes) -> Iterable[str]:
"""
Iterate possible base64 tokens embedded in printable regions of the blob.
"""
text = blob.decode("latin-1", errors="ignore")
pattern = re.compile(r"(?:[A-Za-z0-9+/]{24,}={0,2})")
for m in pattern.finditer(text):
yield m.group(0)
def decrypt_line(token: str, aes_key: bytes, hmac_key: bytes) -> Optional[str]:
"""
Validate HMAC and AES-decrypt a single Base64 token line.
Returns plaintext string if valid, else None.
"""
try:
raw = base64.b64decode(token)
except Exception:
return None
if len(raw) < 16 + 32:
return None
iv, tag = raw[:16], raw[-32:]
ct = raw[16:-32]
if len(ct) % 16 != 0:
return None
calc = hmac.new(hmac_key, iv + ct, hashlib.sha256).digest()
if calc != tag:
return None
try:
pt = aes_cbc_decrypt(aes_key, iv, ct)
pt = pkcs7_unpad(pt, 16)
s = pt.decode("utf-8", errors="ignore")
return s
except Exception:
return None
def find_flags(text: str) -> List[str]:
patterns = [
r"CBD\{[^\}\n]{4,200}\}", # challenge-specific prefix
r"[A-Z]{2,20}\{[^\}\n]{4,200}\}", # generic
r"flag\{[^\}\n]{4,200}\}", r"FLAG\{[^\}\n]{4,200}\}",
]
out = set()
for p in patterns:
for m in re.findall(p, text):
out.add(m)
return sorted(out)
def main():
ap = argparse.ArgumentParser(description="Decrypt C2 messages in PCAP and extract flag")
ap.add_argument("pcap", help="Path to pcap/pcapng")
ap.add_argument("-o", "--out", help="Write decrypted messages to this file")
args = ap.parse_args()
if not os.path.exists(args.pcap):
print(f"[!] File not found: {args.pcap}", file=sys.stderr)
sys.exit(1)
blob = open(args.pcap, "rb").read()
shared_hex = find_shared_hex(blob)
if not shared_hex:
print("[!] Could not locate $sharedHex in capture. Aborting.", file=sys.stderr)
sys.exit(1)
key_bytes = bytes.fromhex(shared_hex)
if len(key_bytes) < 32:
print("[!] $sharedHex must be at least 32 bytes (64 hex chars). Found:", len(key_bytes), "bytes", file=sys.stderr)
sys.exit(1)
aes_key = key_bytes[:16]
hmac_key = key_bytes[16:32]
print(f"[*] Crypto backend: {_CRYPTO_BACKEND}")
print(f"[*] Extracted sharedHex ({len(key_bytes)} bytes). Using AES key (16) + HMAC key (16).")
decrypted = []
flags = []
seen = set()
for tok in iter_b64_tokens(blob):
if tok in seen:
continue
seen.add(tok)
msg = decrypt_line(tok, aes_key, hmac_key)
if msg:
decrypted.append(msg)
flags.extend(find_flags(msg))
if args.out:
with open(args.out, "w", encoding="utf-8") as f:
f.write("=== Decrypted C2 Messages ===\n")
for i, m in enumerate(decrypted, 1):
f.write(f"[{i}] {m.strip()}\n")
f.write("\n=== Extracted Flags ===\n")
for fl in sorted(set(flags)):
f.write(fl + "\n")
print(f"[*] Wrote decrypted transcript to {args.out}")
if flags:
print("[+] Flags found:")
for fl in sorted(set(flags)):
print(" ", fl)
sys.exit(0)
else:
print("[-] No flags found. Decrypted messages:", len(decrypted))
sys.exit(3)
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment