【2026】ISCC 长虹守卫

发布时间:2026/5/25 2:01:26

【2026】ISCC 长虹守卫 长虹守卫题目类型:杂项拿到这道题的时候我第一反应是飞行日志 pcap这两个东西放在一起能有什么关系带着这个问题开始看。先摸清楚题目在说什么LX517.txt打开73 行每行是一条飞行记录。飞机叫 AURORA-ER航班号 LX-517从太平洋中部向东飞。我注意到经度在 idx0026 附近发生了一次跳变0025: lon179.1578° 0026: lon179.2429° 0027: lon-179.9142°经度从正数跳成负数这是穿越了国际日期变更线IDL经度 180°。然后我注意到一件奇怪的事——idx0026 和 idx0027 的时间0026: 2026-01-19 12:27:33 0027: 2026-01-19 11:28:06时间倒退了将近一个小时但日期没变还是 1月19日。正常来说飞机向东穿越 IDL本地时间应该减一天。时钟往回拨了但日期没跟着变——这是个 bug。idx0026 的 detail 字段直接写明了DATE_CORR-1DAY;bugFDR-227(date_not_applied)应该减一天但没减。这个 bug 是整道题的核心。文件末尾藏了什么cat LX517.txt的时候最后一行之后有一段乱码。放大看tail -c 600 LX517.txt | xxd | head -20\x00之后全是A-Za-z0-9/末尾有补位是 Base64。Tail -c 600 LX517.txt | tr -d ‘\0\r\n\f\t’ | grep -a -oP ‘[A-Za-z0-9/]{80,}’ | base64 -d 2/dev/null解出来这是加密的说明书藏在文件末尾。读完之后整个题目的逻辑就清晰了密钥由三个飞行事件的 UTC 时间戳拼成用这个密钥生成 SHA256 流密码去解密 pcap 里的密文。三个事件一个陷阱根据 NOTE 找三类事件grep -E EVT_A|EVT_B|EVT_C LX517.txtEVT_A 和 EVT_B 各只有一条没有歧义。EVT_C 出现了三次idx0033 markerX9 descnoise idx0049 markerA2 desctiming_inconsistency_suspected idx0056 markerZ1 descdecoynoise和decoy是题目故意放的干扰NOTE 里说的是ANOMALY_MARKER_A2所以目标是 idx0049。但我在写脚本的时候没有直接排除 X9因为noise这个词本身也可能是迷惑性的——万一 A2 解不出来X9 也要试。事实证明这个保留是对的脚本把 X9 和 A2 都纳入候选最终在第 167 次尝试时命中。三个关键事件事件idx本地时间经度EVT_ATCP SYN00412026-01-19 11:54:12-173.7042°EVT_BIDL 跨越00262026-01-19 12:27:33179.2429°EVT_C标记 A200492026-01-19 11:56:21-173.3478°IDL bug 对时间戳的影响这是最需要想清楚的地方。hint 里的 IDRULE 说IDRULE:idx26_applied_to_idx27_only意思是 idx0026 记录的日期修正-1天只影响 idx27 的行idx0026 本身不受影响。所以EVT_Bidx0026不需要日期修正直接用本地时间算 UTCEVT_Aidx0041和 EVT_Cidx0049idx 26需要考虑日期修正考虑的意思是由于 bug 的存在这些行的本地时间日期可能是错的多了一天也可能是对的如果不应用修正。两种情况都要试。时间转换公式UTC 本地时间 日期修正0天或-1天- 时区偏移时区偏移根据经度估算经度 / 15取整但靠近 IDL 的区域时区不规则要多试几个值经度 -173.7° → 可能是 -11 或 -12 经度 179.2° → 可能是 12、13 或 14每个事件生成一组候选时间戳三组取笛卡尔积逐一尝试解密。pcap 里的密文strings -n 4 LX517.pcap | grep -a -i “CIPH”76 个十六进制字符38 字节。ISCC{是 5 字节32 位 hex 是 32 字节}是 1 字节合计 38 字节——密文长度和 flag 格式完全吻合说明密文就是直接加密的 flag。用 tshark 也可以看到同样的内容tshark -r LX517.pcap -T fields -e data.data 2/dev/null解密原理SHA256 CTR 流密码每个块用计数器区分密钥流[0] SHA256(keymat_bytes \x00\x00\x00\x00) 密钥流[1] SHA256(keymat_bytes \x00\x00\x00\x01) ... 取前 38 字节 明文 密文 XOR 密钥流XOR 的性质A XOR K XOR K A加解密用同一套代码。运行脚本:hint: KEYMAT:AURORA-ER|flightLX-517|synEVT_A_UTC|idlEVT_B_UTC|a2EVT_C_UTC ALGO:SHA256|XOR|CTR REASM:SEQ_ORDER|frag3 IDRULE:idx26_applied_to_idx27_only UTC:localdate_corr-timezone NOTE:EVT_ATCP_SYN_SENT,EVT_BIDL_CROSS,EVT_CANOMALY_MARKER_A2 ciphertext: 6bcc44455892278c6577691d39eef5806754e33feecb956e9546fe8ca331966d2e0683c054c6 keymat: AURORA-ER|flightLX-517|syn1768863252|idl1768775253|a21768862526 flag: ISCC{df155a9a410be4e7b14a6a191bb5eddc} tried: 167验证成功的时间戳python3 -c from datetime import datetime, timezone pairs [(EVT_A, 1768863252), (EVT_B, 1768775253), (EVT_C, 1768862526)] for name, ts in pairs: print(name, datetime.fromtimestamp(ts, tztimezone.utc)) EVT_A 2026-01-19 23:54:1200:00 EVT_B 2026-01-19 00:27:3300:00 EVT_C 2026-01-18 22:56:0600:00反推EVT_A本地11:54:12时区-12→ UTC23:54:12日期 1月19日未应用修正✓EVT_B本地12:27:33时区12→ UTC00:27:33日期 1月19日idx26 不修正✓EVT_CUTC22:56:06反推本地11:56:06日期2026-01-18应用了 -1天修正时区-11EVT_C 的本地时间反推是11:56:06而 idx0049 记录的是11:56:21差了 15 秒。这说明暴力搜索命中的候选值并不是从 idx0049 精确推算出来的而是候选集足够宽覆盖了正确答案。这也是为什么要把 X9 也纳入候选——扩大搜索空间容错。回头看这道题的设计整道题围绕一个现实中真实存在的问题飞行数据记录仪FDR在跨越 IDL 时的日期处理 bug。题目把这个 bug 做成了密钥的一部分——因为 bug 的存在时间戳有歧义所以不能精确计算只能枚举。这个设计让暴力搜索成为必要而不是偷懒。三个 EVT_C 的设置也很有意思noise、A2、decoy中间夹着真正的目标两侧都是干扰。如果只看名字就排除可能会漏掉 X9 这个候选导致搜索空间不够宽最终找不到答案。FlagEXP:import base64 import csv import hashlib import io import re import struct from datetime import datetime, timedelta, timezone from itertools import product from pathlib import Path import dpkt WORK_DIR Path(__file__).resolve().parent LOG_FILE WORK_DIR / LX517.txt CAP_FILE WORK_DIR / LX517.pcap FLAG_RE re.compile(rb^ISCC\{[0-9a-fA-F]{32}\}$) # ── 1. 从日志末尾提取并解码 Base64 hint ─────────────────────────────────── def load_hint(path: Path) - str: raw path.read_text(encodingutf-8, errorsignore) for token in re.findall(r[A-Za-z0-9/]{80,}, raw): try: s base64.b64decode(token).decode() if KEYMAT: in s and ALGO: in s: return s except Exception: pass raise ValueError(hint not found) # ── 2. 用 csv 模块解析飞行日志 ──────────────────────────────────────────── def load_log(path: Path) - list[dict]: lines [ ln for ln in path.read_text(encodingutf-8, errorsignore).splitlines() if re.match(r^\d{4},, ln) ] reader csv.reader(io.StringIO(\n.join(lines)), skipinitialspaceTrue) records [] for row in reader: if len(row) 10: continue # csv 把 detail 字段含逗号也正确处理了但我们用 maxsplit9 的原始行更安全 # 这里直接用 rowdetail 是 row[9] try: records.append({ seq: int(row[0]), dt: datetime.strptime(row[1].strip(), %Y-%m-%d %H:%M:%S), lat: float(row[2]), lon: float(row[3]), evt: row[8].strip(), note: row[9].strip(), }) except (ValueError, IndexError): continue return records # ── 3. 用 dpkt 解析 pcap提取 TCP 载荷中的密文 ────────────────────────── def extract_ct(path: Path) - bytes: payload_buf bytearray() with open(path, rb) as f: for _, pkt in dpkt.pcap.Reader(f): try: eth dpkt.ethernet.Ethernet(pkt) if not isinstance(eth.data, dpkt.ip.IP): continue ip eth.data if not isinstance(ip.data, dpkt.tcp.TCP): continue tcp ip.data if tcp.data: payload_buf tcp.data except Exception: continue hit re.search(rbCIPH:([0-9a-fA-F]), bytes(payload_buf)) if not hit: hit re.search(rbCIPH:([0-9a-fA-F]), path.read_bytes()) if not hit: raise ValueError(ciphertext not found) return bytes.fromhex(hit.group(1).decode()) # ── 4. 时区候选经度附近所有合理偏移 ──────────────────────────────────── def tz_candidates(lon: float) - list[int]: base round(lon / 15) extra [] if lon 150: extra [12, 13, 14] elif lon -150: extra [-10, -11, -12] return sorted({base, *extra}) # ── 5. 生成候选 UTC epoch用生成器避免预先展开 ─────────────────────────── def epoch_gen(rec: dict): for tz, dc in product(tz_candidates(rec[lon]), (0, -1)): t rec[dt] timedelta(daysdc) - timedelta(hourstz) yield int(t.replace(tzinfotimezone.utc).timestamp()) # ── 6. SHA256 流密码用 bytearray 原地 XOR ─────────────────────────────── def stream_decrypt(ct: bytes, key: str) - bytes: key_b key.encode() out bytearray(len(ct)) block_idx 0 pos 0 while pos len(ct): ks_block hashlib.sha256(key_b struct.pack(I, block_idx)).digest() for byte in ks_block: if pos len(ct): break out[pos] ct[pos] ^ byte pos 1 block_idx 1 return bytes(out) # ── 7. 主逻辑 ───────────────────────────────────────────────────────────── def main(): hint load_hint(LOG_FILE) log load_log(LOG_FILE) ct extract_ct(CAP_FILE) tmpl re.search(r^KEYMAT:(.)$, hint, re.MULTILINE).group(1).strip() syn_pool [e for e in log if e[evt] EVT_A] idl_pool [e for e in log if e[evt] EVT_B] a2_pool [e for e in log if e[evt] EVT_C and (markerA2 in e[note] or markerX9 in e[note])] count 0 for ea, eb, ec in product(syn_pool, idl_pool, a2_pool): seen set() for syn, idl, a2 in product( set(epoch_gen(ea)), set(epoch_gen(eb)), set(epoch_gen(ec)), ): key (syn, idl, a2) if key in seen: continue seen.add(key) km (tmpl .replace(EVT_A_UTC, str(syn)) .replace(EVT_B_UTC, str(idl)) .replace(EVT_C_UTC, str(a2))) count 1 plain stream_decrypt(ct, km) if FLAG_RE.match(plain): print(hint:) print(hint) print(ciphertext:, ct.hex()) print(keymat:, km) print(flag:, plain.decode()) print(tried:, count) return raise RuntimeError(fnot solved after {count} attempts) if __name__ __main__: main()

相关新闻