Files
2026-03-22 21:54:32 +01:00

97 lines
3.7 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""
Solve script for DNS 0x20 exfiltration challenge.
The webshell exfiltrates data via DNS by:
1. base64-encoding the content (with + and / replaced by -)
2. prepending a 3-char MD5 checksum
3. performing a DNS_A lookup for: {base64}.{checksum}.{attacker_domain}
DNS 0x20 encoding preserves the case of query labels in the response,
meaning the mixed-case payload in the DNS log IS the original base64.
However, intermediate resolvers may have corrupted some letter cases,
so we reconstruct the correct capitalisation by:
- splitting the payload into groups of 4 base64 characters
- trying all case variants per group
- keeping only variants that decode to printable ASCII
- finding the combination whose MD5[:3] matches the checksum in the log
"""
import base64
from hashlib import md5
from itertools import product
# ---------------------------------------------------------------------------
# Paste the data label and checksum from the 0x20-encoded DNS log entry here
# ---------------------------------------------------------------------------
PAYLOAD = "Su5tE1QwdDRSBHLfBJn2zXjFAgFWcdNUzWQTAxjSLtfLodhjMtUzfq"
CHECKSUM = "190" # first 3 hex chars of md5(flag)
# ---------------------------------------------------------------------------
def reverse_strtr(s: str) -> str:
"""The PHP replaces + and / with -. Reverse where possible (no-op if no dashes)."""
# If there are no dashes the base64 had neither + nor /, nothing to do.
# If there ARE dashes we'd need to try both — handled by the case-brute-force below.
return s
def case_variants(group: str):
"""Yield every capitalisation variant of a base64 group that decodes to printable ASCII."""
letter_positions = [(i, c) for i, c in enumerate(group) if c.isalpha()]
for bits in product([True, False], repeat=len(letter_positions)):
candidate = list(group)
for (idx, _), upper in zip(letter_positions, bits):
candidate[idx] = candidate[idx].upper() if upper else candidate[idx].lower()
s = "".join(candidate)
padding = "=" * ((4 - len(s) % 4) % 4)
try:
decoded = base64.b64decode(s + padding)
if all(32 <= b < 127 for b in decoded): # printable ASCII only
yield s, decoded
except Exception:
pass
def solve(payload: str, checksum: str) -> str:
payload = reverse_strtr(payload)
# Split into groups of 4 (standard base64 block size)
groups = [payload[i:i+4] for i in range(0, len(payload), 4)]
# Collect valid byte candidates for each group
group_candidates = []
for i, g in enumerate(groups):
cands = list(case_variants(g))
if not cands:
raise ValueError(f"No printable-ASCII candidate found for group {i} ({g!r})")
group_candidates.append([dec for _, dec in cands])
print(f" group[{i:02d}] {g!r:6s}{[dec for _, dec in cands]}")
print(f"\nSearching {' × '.join(str(len(c)) for c in group_candidates)} combinations "
f"for MD5[:3] == {checksum!r}\n")
# Enumerate combinations and check MD5 checksum
for combo in product(*group_candidates):
candidate_bytes = b"".join(combo)
if md5(candidate_bytes).hexdigest()[:3] == checksum:
return candidate_bytes.decode()
raise ValueError("No combination matched the checksum — check PAYLOAD / CHECKSUM values.")
if __name__ == "__main__":
print("=" * 60)
print("DNS 0x20 Exfiltration — Solve Script")
print("=" * 60)
print(f"Payload : {PAYLOAD}")
print(f"Checksum : {CHECKSUM}\n")
print("Candidate bytes per base64 group (printable ASCII filter):")
flag = solve(PAYLOAD, CHECKSUM)
print("=" * 60)
print(f"FLAG: {flag}")
print("=" * 60)