# -*- coding: utf-8 -*- """ CTF 解密脚本:Morse + Zero-Width + Ascii85 + AES(多模式试探) 用法:python solve_flag.py """ from __future__ import annotations from typing import List, Tuple import base64, hashlib, itertools from Crypto.Cipher import AES from Crypto.Util.Padding import unpad # ---- 题面(保持 HTML 实体,不要手改)---- RAW = """...‌‌‌‌‍‍‌‌‌‌‍‬‍‍.- . --... ‌‌‌‌‍‬‌‌‌‌‌‍‬‌...‌‌‌‌‍‬ --...‌‌‌‌‍‬‍ .--- -‌‌‌‌‍‬‍‍.-. ‌‌‌‌‍‍‌-- .-. ‌‌‌‌‍‬.--‌‌‌‌‍‬‌ -.‌‌‌‌‍‌‬.. -.‌‌‌‌‍‬‍‌‌‌‌‍‌‌‌‌‌‌‍‍‌ --‌‌‌‌‍‬. -. ‌‌‌‌‍‍.‌‌‌‌‍‬--- -.‌‌‌‌‍‌‬‌‌‌‌‍‬‌.- ...‌‌‌‌‍‬‍‌.- .-. ‌‌‌‌‌‬‍‬‌‌‌‌‌‌‍..‌‌‌‌‍‍‌‍.- ‌‌‌‌‍‌‌‍-. ‌‌‌‌‍‍‬‬.‌‌‌‌‌‌‬...-‌‌‌‌‍‍‍ ‌‌‌‌‍‍‌...‌‌‌‌‍‍‬‌.- -- ..‌‌‌‌‌‌..‌‌‌‌‍‌‍‍‌‌‌‌‍‌‍‌ ‌‌‌‌‍‌‌-.‌‌‌‌‌‍‌-.‌‌‌‌‍‍‌‬‌‌‌‌‍‌‍‬ .. ‌‌‌‌‍‍‍‬.... ..‌‌‌‌‌‍‍..‌‌‌‌‍‍‍‌ .‌‌‌‌‍‌‍‌‌‌‌‍‌‌‬- ‌‌‌‌‌‍‬.. .‌‌‌‌‍‍‬‍--‌‌‌‌‍‌‬‌‌‌‌‌‍‌‬- ‌‌‌‌‌‍-‌‌‌‌‍‍‍‍-‌‌‌‌‍‌‬‬-‌‌‌‌‍‌‍-.‌‌‌‌‌‬‌ ..-. .‌‌‌‌‍‌‬‍-- -. ...‌‌‌‌‍‌‬‌‌‌‌‌‬‍ ‌‌‌‌‍‌.---‌‌‌‌‍‌‌ ‌‌‌‌‌‌‌..‌‌‌‌‍‍‌‌-‌‌‌‌‌‬‍‬ -‍‬‍‌‍‍‍‌‌‌‬‍- .-- ‍‍‌‌‌.‍‌‬‬‍‬‬.‍‍‌‬‬‬‌‌‍‍‬‌‍‬-. ‍‍‍‌‬‌‌.‌‌‌‌‍‬‌‬. ‌‌‌‌‍‬‌‍--..‌‌‌‌‍‌. -..-‌‌‌‌‍‬‍‍ ...‌‌‌‌‌‌ ...‌‌‌‌‌‌‬-- ‍‍‍‍‬‍‍-.-‌‌‌‌‍‬. .. 还真是!实际上是这样""" # ---- Step1: 提取摩斯并 Base62 → 36B 密钥素材 ---- import html as _html MORSE = {".-":"A","-...":"B","-.-.":"C","-..":"D",".":"E","..-.":"F","--.":"G","....":"H","..":"I", ".---":"J","-.-":"K",".-..":"L","--":"M","-.":"N","---":"O",".--.":"P","--.-":"Q",".-.":"R", "...":"S","-":"T","..-":"U","...-":"V",".--":"W","-..-":"X","-.--":"Y","--..":"Z", "-----":"0",".----":"1","..---":"2","...--":"3","....-":"4",".....":"5","-....":"6", "--...":"7","---..":"8","----.":"9"} decoded = _html.unescape(RAW) morse_only = ''.join(ch for ch in decoded if ch in '.- ') morse_text = ''.join(MORSE.get(tok, '?') for tok in morse_only.split()) # Base62 解码 import string B62 = string.digits + string.ascii_uppercase + string.ascii_lowercase val = 0 for ch in morse_text: val = val * 62 + B62.index(ch) b62_bytes = val.to_bytes((val.bit_length()+7)//8, 'big') or b'\x00' # ---- Step2: 解析零宽 → Ascii85 → 密文字节 ---- ZWS = (0x200C, 0x200D, 0xFEFF, 0x202C) # ZWNJ, ZWJ, ZWNBSP, PDF codes = [ord(c) for c in decoded if ord(c) in ZWS] def ascii85_candidates() -> List[bytes]: out: List[bytes] = [] for perm in itertools.permutations(ZWS, 4): mp = {perm[i]: format(i, '02b') for i in range(4)} bits = ''.join(mp[c] for c in codes) for off in range(8): bbits = bits[off:][:len(bits[off:])//8*8] if not bbits: continue data = int(bbits, 2).to_bytes(len(bbits)//8, 'big') for take in (data, data[0::2], data[1::2]): # 全量/偶/奇抽样 s = bytes(ch for ch in take if 33 <= ch <= 117 or ch == 122) # 过滤到 Ascii85 合法区 if len(s) < 20: continue try: blob = base64.a85decode(s, adobe=False) out.append(blob) except Exception: pass # 去重 uniq: List[bytes] = [] seen = set() for b in out: h = hashlib.sha1(b).hexdigest() if h not in seen: uniq.append(b); seen.add(h) return uniq a85_blobs = ascii85_candidates() assert a85_blobs, "没解析到任何 Ascii85 候选;请确认文本原样未被改动" # ---- Step3: 生成密钥候选(16/24/32)---- def key_candidates() -> List[bytes]: C: List[bytes] = [] srcs = [morse_text.encode(), b62_bytes] for s in srcs: C += [hashlib.md5(s).digest(), hashlib.sha1(s).digest()[:16], hashlib.sha256(s).digest(), hashlib.blake2b(s, digest_size=32).digest()] # 额外:直接取 b62 的前/后 16/24/32 for L in (16,24,32): C += [b62_bytes[:L].ljust(L, b'\0'), b62_bytes[-L:].rjust(L, b'\0')] # 去重 uniq: List[bytes] = [] seen = set() for k in C: h = (len(k), hashlib.sha1(k).hexdigest()) if h not in seen: uniq.append(k); seen.add(h) return uniq KEYS = key_candidates() # ---- Step4: 穷举 AES 模式与 IV/nonce 切分,命中 flag ---- def try_ctr(ct: bytes, key: bytes) -> List[Tuple[str, bytes]]: outs = [] L = len(ct) for split in range(4, min(24, L-4)): iv = ct[:split]; body = ct[split:] for nonce_len in range(0, min(15, split)+1): nonce = iv[:nonce_len]; rem = iv[nonce_len:] for name, init in (("zero", 0), ("rem_be", int.from_bytes(rem, "big") if rem else 0), ("rem_le", int.from_bytes(rem[::-1], "big") if rem else 0)): try: pt = AES.new(key, AES.MODE_CTR, nonce=nonce, initial_value=init).decrypt(body) outs.append((f"CTR split={split} nonce={nonce_len} init={name}", pt)) except Exception: pass return outs def try_stream_modes(ct: bytes, key: bytes) -> List[Tuple[str, bytes]]: outs = [] L = len(ct) # CFB/OFB 尝试 iv 在前/后 for mode_name, MODE in (("CFB", AES.MODE_CFB), ("OFB", AES.MODE_OFB)): for ivpos in ("head","tail"): if L <= 16: continue iv, body = (ct[:16], ct[16:]) if ivpos=="head" else (ct[-16:], ct[:-16]) try: pt = AES.new(key, MODE, iv=iv).decrypt(body) outs.append((f"{mode_name} iv={ivpos}", pt)) except Exception: pass # CBC(尽管不太像) for ivpos in ("head","tail"): if L <= 16: continue iv, body = (ct[:16], ct[16:]) if ivpos=="head" else (ct[-16:], ct[:-16]) body = body[:len(body)//16*16] try: raw = AES.new(key, AES.MODE_CBC, iv=iv).decrypt(body) try: pt = unpad(raw, 16) except Exception: pt = raw outs.append((f"CBC iv={ivpos}", pt)) except Exception: pass return outs def solve() -> None: # 先打印你要的两个“中间产物”,便于复核 print("[MORSE] =", morse_text) print("[B62 hex] =", b62_bytes.hex()) # 遍历所有密文候选 × 密钥候选 × 模式 for blob in a85_blobs: for key in KEYS: for tag, pt in try_ctr(blob, key) + try_stream_modes(blob, key): s = pt.decode("utf-8", "ignore") if "flag{" in s.lower(): print("[HIT]", tag, "key_len=", len(key)) print(s) return print("[X] 未命中。可再加:GCM(带不同 tag 长度)、XOR-探测、zlib 解压后再试。") if __name__ == "__main__": solve()