-
-
Save Pir00t/82928425d9726398278b94b745410369 to your computer and use it in GitHub Desktop.
Script to decode final stage of SANDY, Huntress CTF 2025
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import re, base64, os | |
| OUTDIR = "decoded_b64_out" | |
| LOGPATH = os.path.join(OUTDIR, "decode_log.txt") | |
| MIN_QUADS = 3 # minimum number of 4-char groups (>=12 chars) | |
| b64_simple = re.compile( | |
| r"(?<![A-Za-z0-9+/=])" # not preceded by base64 char | |
| r"(?:[A-Za-z0-9+/]{4}){" + str(MIN_QUADS) + r",}" # at least MIN_QUADS groups | |
| r"(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{4})?" # optional padding | |
| r"(?![A-Za-z0-9+/=])" # not followed by base64 char | |
| ) | |
| def normalize_and_pad(s): | |
| s = re.sub(r'[\s\r\n]+', '', s) | |
| # If invalid chars inside, keep them for attempt but also prepare filtered fallback | |
| filtered = re.sub(r'[^A-Za-z0-9+/=]', '', s) | |
| pad = (-len(s)) % 4 | |
| if pad: | |
| s_padded = s + ("=" * pad) | |
| else: | |
| s_padded = s | |
| return s, filtered, s_padded | |
| def try_decode_b64(s): | |
| s_orig, filtered, s_padded = normalize_and_pad(s) | |
| candidates = [s_padded, s_padded + "==", s.rstrip('=') + ("=" * ((4 - (len(s) % 4)) % 4)), filtered] | |
| last_err = None | |
| for c in candidates: | |
| if len(c) < 8: | |
| continue | |
| try: | |
| # first try strict validate | |
| b = base64.b64decode(c, validate=True) | |
| return b, c | |
| except Exception as e: | |
| last_err = e | |
| try: | |
| b = base64.b64decode(c) | |
| return b, c | |
| except Exception: | |
| pass | |
| raise ValueError(f"base64 decode failed (orig len {len(s_orig)}): {last_err}") | |
| def is_text(bytestr): | |
| # prefer utf-16le then utf-8, else no | |
| for enc in ("utf-16le", "utf-8"): | |
| try: | |
| txt = bytestr.decode(enc) | |
| return True, enc, txt | |
| except Exception: | |
| pass | |
| return False, None, None | |
| def find_candidates(text): | |
| matches = [] | |
| for m in b64_simple.finditer(text): | |
| matches.append(m.group(0)) | |
| # dedupe while preserving order (noted 20 vs 11 in testing) | |
| seen = set(); uniq = [] | |
| for s in matches: | |
| if s not in seen: | |
| seen.add(s); uniq.append(s) | |
| return uniq | |
| def save_blob(idx, bytestr, textstr, enc): | |
| os.makedirs(OUTDIR, exist_ok=True) | |
| if textstr is not None: | |
| fname = f"decoded_{idx:03d}.txt" | |
| path = os.path.join(OUTDIR, fname) | |
| with open(path, "w", encoding="utf-8", errors="replace") as f: | |
| f.write(textstr) | |
| else: | |
| fname = f"decoded_{idx:03d}.bin" | |
| path = os.path.join(OUTDIR, fname) | |
| with open(path, "wb") as f: | |
| f.write(bytestr) | |
| return path, fname | |
| def main(): | |
| text = open("decoded.txt", "r", encoding="utf-8", errors="ignore").read() | |
| candidates = find_candidates(text) | |
| log_lines = [] | |
| log_lines.append(f"Found {len(candidates)} candidate base64 strings.") | |
| idx = 0 | |
| for i, cand in enumerate(candidates, start=1): | |
| try: | |
| decoded_bytes, used = try_decode_b64(cand) | |
| except Exception as e: | |
| log_lines.append(f"[{i}] FAIL decode len={len(cand)}: {e}") | |
| continue | |
| is_txt, enc, txt = is_text(decoded_bytes) | |
| idx += 1 | |
| path, fname = save_blob(idx, decoded_bytes, txt if is_txt else None, enc) | |
| desc = f"[{idx}] saved {fname} ({len(decoded_bytes)} bytes)" | |
| if is_txt: | |
| desc += f" as text (encoding guessed: {enc})" | |
| else: | |
| desc += " (binary)" | |
| desc += f" -- original_candidate_len={len(cand)} used_pattern_len={len(used)}" | |
| log_lines.append(desc) | |
| with open(LOGPATH, "w", encoding="utf-8") as lf: | |
| lf.write("\n".join(log_lines)) | |
| print(f"Done. {idx} decoded blobs saved to {OUTDIR}. See {LOGPATH}.") | |
| if __name__ == "__main__": | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment