Muxun_programs/python/ceshi1.py
Galaxy 907bd5af0e mx init
the muxun is not operated by git,now init
2025-11-09 20:06:06 +08:00

153 lines
9.6 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.

# -*- 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()