ciruit map-wp

Zengan 2026-08-24 16:39:45 18 0 返回题目详情


ciruit map题目构成

  • block_cipher.py: 24-bit SPN。encrypt_data = 3 轮 (XOR key→S(6×4bit)→P(24bit)) + 末 XOR key。encrypt(m,k1,k2)=Ed(Ed(m,k1),k2)(k1 内层、k2 外层);decrypt 相应。

  • yao_circuit.py: 每 wire 两个 24-bit label;garble_label(k0,k1,k2)=(encrypt(k2,k0,k1), encrypt(0,k0,k1)),第二项 validation 行 = Ed(Ed(0,k0),k1) —— 是本解法的 oracle。

  • circuit_map.json: AND(1,2)->5; AND(3,4)->6; AND(5,6)->7; XOR(7,4)->9; out=[9]。

  • public_data.py: 真实 G_Table(gate 5,6,7,9 各 4 行 (gl, validation))。

  • task.py: the_chaos = concat(long_to_bytes(sum(keys[i])) for i in keys);mask=md5(the_chaos);flag_ct = xor(mask,flag).hex() = 1661fe85c7b01b3db1d432ad3c5ac83a;private_data.py(keys/flag)未下发。

攻击步骤

  1. 向量化 SPN(numpy int32,全空间 2^24 单遍 ~5-6s)。T[a]=Ed(0,a) 全局一次;U_r[b]=Dd(v_r,b) 每 gate 每行一次。

  2. 结构搜索(每 gate 6 个规范行配对模式 (i,j,k,l):i,j 共享 a0/t0,k,l 共享 a1/t1,b0 跨 (i,k)、b1 跨 (j,l)):key=(U_i[b0]<<24 | U_k[b0]) 与 (U_j[b1]<<24 | U_l[b1]) 交集 ⇒ t0=U_i[b0]=U_j[b1]、t1=U_k[b0]=U_l[b1];t0,t1 必须在 imT 内 ⇒ a0=preimage(t0)、a1=preimage(t1);b0,b1 即 wire-b 两标签(任取 witness)。单 witness 方案不可行(preimage 类平均 1.58,碰撞丢真结构);排序连接一次覆盖全部 witness。

  3. gl 校验:decrypt(gl,k0,k1) 必须符合 AND/XOR 真值表值模式,角色无关地给出wire_a/wire_b=(FALSE,TRUE)、out=(k2_0,k2_1)。AND 唯一;XOR 有 4 重真值歧义。

  4. 跨 gate 拼接:wire5=gate5.out==gate7.wa;wire6=gate6.out==gate7.wb;wire7=gate7.out==gate9.wa;wire4=gate6.wb==gate9.wb —— 得到唯一全局 keys。

  5. flag:keys 迭代顺序 = gen_key.py set 构造(CPython 3.12 实测 [1,2,3,4,5,6,7,9]);the_chaos/md5/xor 解明文;若顺序不符则按 SCTF{} 可打印枚举 9!。

关键参数(真实数据)

  • T=Ed(0,a) 建表约 14s;每 gate 的 4 个 U_r=Dd(v_r,b)(各 2^24)约 20-31s。

  • 6 个规范行配对模式 sort-join(int64 打包 key=(t0<<24|t1) + 交集)每 gate 约 363-453s
    (结构候选数(真实数据):gate5=23、gate6=4、gate7=26、gate9=15;gl 校验后 AND 各 1、XOR 4(真值歧义)。

  • 跨 gate 拼接唯一全局解;总求解 1645-1778s。

  • 单行 b→Dd(v,b) 像覆盖率约 62%(Poisson(1) preimage 类),故单 witness 方案必失败。

结果

  • 解密明文:#@DE-is-not-EZ@#(md5(mask) 直接还原可打印文本)

  • 最终 flag:SCTF{#@DE-is-not-EZ@#}

  • 迭代顺序:CPython set 构造实测 [1,2,3,4,5,6,7,9](9! 枚举无 SCTF 形态,确认该格式)

  • 恢复 keys(wire: (FALSE标签, TRUE标签)):1:(8343801,13675268) 2:(10251687,12870274) 3:(6827786,12490757) 4:(2096572,3391233)5:(15707475,4567418) 6:(14095476,3648155) 7:(14409690,8680011) 9:(9376523,2504390)

  • 校验: validate_the_circuit(G_Table[9], keys7, keys4)==keys9通过;4 gate × 4 真值组合用恢复 labels 重放 (gl,v) 与 public_data.py 完全一致。

复现

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
solve_yao.py — 恢复 Yao 混淆电路全部 wire labels(24-bit SPN 双重加密),解出 flag。

挑战: ciruit map/
  block_cipher.py : 24-bit SPN; encrypt(m,k1,k2)=Ed(Ed(m,k1),k2); 同理 decrypt
  yao_circuit.py  : garble_label(k0,k1,k2) = ( encrypt(k2,k0,k1), encrypt(0,k0,k1) )
                    第二项即 validation 行 => 可由 MITM 分解 (k0,k1)
  public_data.py  : 真实 G_Table (gate 5,6,7 AND; gate 9 XOR) 每 gate 4 行 (gl, validation)
  task.py         : the_chaos = concat(long_to_bytes(sum(keys[i])) for i in keys)
                    mask = md5(the_chaos); flag_ct = xor(mask,flag) = 1661fe85...

攻击:
  对每个 gate 每行 validation v = Ed(Ed(0,k0),k1):
    T[a] = Ed(0,a) 一次建表
    U_r[b] = Dd(v_r,b) 每行一次
    bp_r[a] = 能使 Dd(v_r,b)=Ed(0,a) 的 b (witness)
  结构搜索: 4 行按 (a0,w1),(a0,w2),(a1,w1),(a1,w2) 配对, x=U_r3[b1]==y=U_r4[b2] 且
    Ed(0,a1)=x => 候选 (a0,a1,w1,w2)
  gl 校验: decrypt(gl, ka, kb) 必须符合 AND/XOR 真值表模式 => 唯一确定 labels
  跨 gate: wire4/5/6/7 共享标签拼接 => keys
  flag: 按 gen_key.py 的 set 迭代顺序 [1..9] 重建 the_chaos -> md5 -> xor
"""
import os, sys, json, time, hashlib, random
import numpy as np

HERE = os.path.dirname(os.path.abspath(__file__))
CDIR = os.path.join(HERE, "ciruit map")
sys.path.insert(0, CDIR)

from block_cipher import encrypt_data, decrypt_data, encrypt, decrypt, \
    SBoxes, SInvBoxes, PBox, PInvBox
from Crypto.Util.number import long_to_bytes

SB  = np.array(SBoxes,    dtype=np.int32)   # (6,16)
SIB = np.array(SInvBoxes, dtype=np.int32)
PB  = np.array(PBox,      dtype=np.int32)
PIB = np.array(PInvBox,   dtype=np.int32)

SPACE = 2**24  # 24-bit label space


def S_arr(x, tab=SB):
    out = np.zeros(x.shape[0], dtype=np.int32)
    for i in range(6):
        out |= tab[i][(x >> (4 * i)) & 15] << (4 * i)
    return out


def P_arr(x, pb=PB):
    out = np.zeros(x.shape[0], dtype=np.int32)
    for i in range(24):
        out |= ((x >> pb[i]) & 1) << i
    return out


def Ed_arr(x, k):
    """encrypt_data vectorized over block array x, scalar key k."""
    b = x
    for _ in range(3):
        b = P_arr(S_arr(b ^ k))
    return b ^ k


def Dd_arr(x, k):
    """decrypt_data vectorized."""
    b = x ^ k
    for _ in range(3):
        b = S_arr(P_arr(b, PIB), SIB)
        b = b ^ k
    return b


# ---- global T-table: T[a] = Ed(0, a), and its inverse ----
def build_T():
    t0 = time.time()
    a = np.arange(SPACE, dtype=np.int32)
    T = Ed_arr(np.zeros(SPACE, dtype=np.int32), a)   # T[a] = Ed(0, a): block 0, key a
    order = np.argsort(T, kind="stable").astype(np.int32)
    sT = T[order]
    print(f"[T] Ed(0,a) for a in [0,{SPACE}) done in {time.time()-t0:.1f}s")
    return T, order, sT


def preimages(sT, order, t):
    lo = int(np.searchsorted(sT, t, "left"))
    hi = int(np.searchsorted(sT, t, "right"))
    return [int(x) for x in order[lo:hi]]


def gl_check(gtype, G, a0, a1, w1, w2, r1, r2, r3, r4):
    """combo->row map: r1=(a0,w1), r2=(a0,w2), r3=(a1,w1), r4=(a1,w2).
    角色无关: 由 gl 实际解密值判定 TRUE/FALSE 标签, 不预设 w1/w2 与 a0/a1 的真假。
    返回 wire_a/wire_b = (FALSE标签, TRUE标签); out = (k2_0, k2_1)。"""
    rows = {r1: (a0, w1), r2: (a0, w2), r3: (a1, w1), r4: (a1, w2)}
    k2 = {}
    for r, (ka, kb) in rows.items():
        k2[r] = decrypt(G[r][0], ka, kb)
    v1, v2, v3, v4 = k2[r1], k2[r2], k2[r3], k2[r4]
    if gtype == "AND":
        # 唯一异值行 = (TRUE,TRUE) 组合
        agg = {}
        for val, r in ((v1, r1), (v2, r2), (v3, r3), (v4, r4)):
            agg.setdefault(val, []).append(r)
        if len(agg) != 2:
            return None
        gs = sorted(agg.items(), key=lambda kv: len(kv[1]))
        if len(gs[0][1]) != 1 or len(gs[1][1]) != 3:
            return None
        diff_val, common_val = gs[0][0], gs[1][0]
        diff_row = gs[0][1][0]
        a_true, b_true = rows[diff_row]
        a_false = a1 if a_true == a0 else a0
        b_false = w2 if b_true == w1 else w1
        return [{"wire_a": (a_false, a_true), "wire_b": (b_false, b_true),
                "out": (common_val, diff_val)}]
    else:  # XOR: 反斜线等值: (r1==r4, r2==r3) 且两组不同
        if v1 == v4 and v2 == v3 and v1 != v2:
            # 4 重真值歧义: a 真值 2 选 x b 真值 2 选, 全部与 gl 值自洽;
            # 由跨 gate 拼接与 flag 可打印性消解。out=(k2_0,k2_1) 随 (ta0==tw1) 翻转。
            out = []
            for t_a0 in (0, 1):
                for t_w1 in (0, 1):
                    wire_a = (a0, a1) if t_a0 == 0 else (a1, a0)
                    wire_b = (w1, w2) if t_w1 == 0 else (w2, w1)
                    k20, k21 = (v1, v2) if (t_a0 == t_w1) else (v2, v1)
                    vv = {"wire_a": wire_a, "wire_b": wire_b, "out": (k20, k21)}
                    if vv not in out:
                        out.append(vv)
            return out
    return []


def solve_gate(gid, gtype, G, T, order, sT, sImT=None):
    """结构搜索: 对 gate 的 4 行做 (a,b) 标签恢复。

    原理: 行 r 的 validation v_r 满足 Dd(v_r, b) = Ed(0, a) 当且仅当 (a,b) 是该行 combo。
    对每行 r 预计算 U_r[b] = Dd(v_r, b) (b in [0,2^24))。
    真结构 4 行 = (A0,B0),(A0,B1),(A1,B0),(A1,B1) 的某个置换。
    6 个规范行配对模式 (i,j,k,l) (i,j 共享 a0/t0, k,l 共享 a1/t1, b0 跨 (i,k), b1 跨 (j,l)):
      包 key = (U_i[b0]<<24 | U_k[b0]), B 侧 (U_j[b1]<<24 | U_l[b1]);
      交集 => t0=U_i[b0]=U_j[b1] (t0 须在 imT 内以保证 a0=preimage(t0)) 且 t1 同理。
    排序连接处理全部 (b0,b1) witness 分类, 不受单 witness 碰撞影响。
    """
    t0t = time.time()
    vals = np.array([v for _gl, v in G], dtype=np.int32)
    a = np.arange(SPACE, dtype=np.int32)
    Us = [Dd_arr(np.full(SPACE, int(v), dtype=np.int32), a) for v in vals]
    t1t = time.time()
    print(f"[gate {gid}] U_r built in {t1t-t0t:.1f}s, sort-join...")
    if sImT is None:
        sImT = np.sort(np.unique(T)).astype(np.int32)
    n = len(sImT) - 1

    def inT(x):
        p = np.searchsorted(sImT, x)
        pc = np.clip(p, 0, n)
        return sImT[pc] == x

    cands = {}
    # 6 canonical patterns: partitions of rows into {i,j}|{k,l}
    patterns = [(0, 1, 2, 3), (0, 1, 3, 2),
                (0, 2, 1, 3), (0, 2, 3, 1),
                (0, 3, 1, 2), (0, 3, 2, 1)]
    for (i, j, k, l) in patterns:
        u0, u1 = Us[i], Us[k]
        msk = inT(u0) & inT(u1)
        b0s = np.nonzero(msk)[0].astype(np.int32)
        if len(b0s) == 0:
            continue
        keyA = (u0[b0s].astype(np.int64) << 24) | u1[b0s].astype(np.int64)
        u2, u3 = Us[j], Us[l]
        msk2 = inT(u2) & inT(u3)
        b1s = np.nonzero(msk2)[0].astype(np.int32)
        if len(b1s) == 0:
            continue
        keyB = (u2[b1s].astype(np.int64) << 24) | u3[b1s].astype(np.int64)
        orderA = np.argsort(keyA, kind="stable")
        sortA = keyA[orderA]
        b0A = b0s[orderA]
        orderB = np.argsort(keyB, kind="stable")
        sortB = keyB[orderB]
        b1B = b1s[orderB]
        pos = np.searchsorted(sortB, sortA)
        posc = np.clip(pos, 0, len(sortB) - 1)
        m = sortB[posc] == sortA
        for q in np.nonzero(m)[0]:
            key = int(sortA[q])
            t0v, t1v = key >> 24, key & 0xFFFFFF
            b0v = int(b0A[q])
            qb = int(np.searchsorted(sortB, key, "left"))
            b1v = int(b1B[qb])
            for a0v in preimages(sT, order, t0v):
                for a1v in preimages(sT, order, t1v):
                    cands[(a0v, a1v, b0v, b1v)] = (i, j, k, l)
    t2t = time.time()
    print(f"[gate {gid}] structure candidates: {len(cands)} ({t2t-t1t:.1f}s), gl check...")
    results = []
    for (a0v, a1v, b0v, b1v), (i, j, k, l) in cands.items():
        res = gl_check(gtype, G, a0v, a1v, b0v, b1v, i, j, k, l)
        for r in (res or []):
            if r not in results:
                results.append(r)
    print(f"[gate {gid}] gl-consistent label sets: {len(results)} ({time.time()-t2t:.1f}s)")
    return results


def load_public():
    import importlib.util
    spec = importlib.util.spec_from_file_location("public_data", os.path.join(CDIR, "public_data.py"))
    mod = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(mod)
    return mod.G_Table


# ----------------- flag decode -----------------
def decode_flag(keys, order=None):
    if order is None:
        order = [1, 2, 3, 4, 5, 6, 7, 9]   # CPython set 迭代顺序(已在本地复现验证)
    chaos = b"".join(long_to_bytes(int(keys[i][0]) + int(keys[i][1])) for i in order)
    mask = hashlib.md5(chaos).digest()
    ct = bytes.fromhex("1661fe85c7b01b3db1d432ad3c5ac83a")
    flag = bytes(a ^ b for a, b in zip(mask, ct))
    return flag, chaos


def printable_check(flag):
    return all(32 <= c < 127 for c in flag) and b"SCTF{" in flag[:6] and flag.endswith(b"}")


def try_order_enumeration(keys):
    """fallback: 枚举 9! 迭代顺序, 校验 flag 可打印 / SCTF{..} 格式"""
    wires = [1, 2, 3, 4, 5, 6, 7, 9]
    import itertools
    sums = {i: keys[i][0] + keys[i][1] for i in wires}
    ct = bytes.fromhex("1661fe85c7b01b3db1d432ad3c5ac83a")
    for perm in itertools.permutations(wires):
        chaos = b"".join(long_to_bytes(sums[i]) for i in perm)
        mask = hashlib.md5(chaos).digest()
        flag = bytes(a ^ b for a, b in zip(mask, ct))
        if printable_check(flag):
            return flag, perm
    return None, None


# ----------------- self-test -----------------
def self_test():
    print("=" * 60)
    print("SELF-TEST: 生成合成 private keys/G_Table, 验证恢复")
    print("=" * 60)
    import random as rnd
    rnd.seed(20240824)
    circuit = json.load(open(os.path.join(CDIR, "circuit_map.json")))
    keys_truth = {}
    for w in [1, 2, 3, 4, 5, 6, 7, 9]:
        keys_truth[w] = (rnd.randrange(SPACE), rnd.randrange(SPACE))
    from yao_circuit import GarbledGate
    G_Table = {g["id"]: GarbledGate(g, keys_truth) for g in circuit["gates"]}
    myflag = b"SCTF{s3lfT3st!!}"  # 16 bytes
    order = [1, 2, 3, 4, 5, 6, 7, 9]
    chaos = b"".join(long_to_bytes(keys_truth[i][0] + keys_truth[i][1]) for i in order)
    ct = bytes(a ^ b for a, b in zip(hashlib.md5(chaos).digest(), myflag))
    print("synthetic ct:", ct.hex())
    keys = solve_all(G_Table, T, order, sT)
    if not keys:
        print("SELF-TEST FAILED: no consistent keys"); return False
    ok = keys == keys_truth
    flag = decode_flag(keys)[0]
    print("recovered keys == truth:", ok)
    print("decoded flag:", flag)
    return ok and flag == myflag


def solve_all(G_Table, T, order, sT):
    gtype = {5: "AND", 6: "AND", 7: "AND", 9: "XOR"}
    R = {}
    for gid in [5, 6, 7, 9]:
        R[gid] = solve_gate(gid, gtype[gid], G_Table[gid], T, order, sT)
    # dedupe per gate
    for gid in R:
        seen = []
        for d in R[gid]:
            if d not in seen:
                seen.append(d)
        R[gid] = seen
    print("gate candidates:", {g: len(R[g]) for g in R})
    sols = []
    for c5 in R[5]:
        for c6 in R[6]:
            for c7 in R[7]:
                if c7["wire_a"] != c5["out"] or c7["wire_b"] != c6["out"]:
                    continue
                for c9 in R[9]:
                    if c9["wire_a"] != c7["out"] or c9["wire_b"] != c6["wire_b"]:
                        continue
                    keys = {1: c5["wire_a"], 2: c5["wire_b"], 3: c6["wire_a"],
                            4: c6["wire_b"], 5: c5["out"], 6: c6["out"],
                            7: c7["out"], 9: c9["out"]}
                    sols.append(keys)
    print("cross-gate consistent global key sets:", len(sols))
    return sols


if __name__ == "__main__":
    mode = sys.argv[1] if len(sys.argv) > 1 else "real"
    T, order, sT = build_T()
    if mode == "selftest":
        ok = self_test()
        sys.exit(0 if ok else 1)
    # real
    import json as _json
    G_Table = load_public()
    t0 = time.time()
    sols = solve_all(G_Table, T, order, sT)
    print(f"solve finished in {time.time()-t0:.1f}s")
    if not sols:
        print("NO SOLUTION"); sys.exit(1)
    keys = sols[0]
    with open(os.path.join(HERE, "keys-recovered.json"), "w") as f:
        _json.dump({str(w): list(v) for w, v in keys.items()}, f, indent=1)
    print("keys:", keys)
    print("saved -> keys-recovered.json")
    for perm in ([1,2,3,4,5,6,7,9],):
        flag, chaos = decode_flag(keys, perm)
        print(f"flag(order={perm}):", flag, "printable:", printable_check(flag))
        with open(os.path.join(HERE, "flag.txt"), "w") as f:
            f.write(flag.decode("latin-1", "replace") + "\n")
    # iteration-order fallback
    flag, perm = try_order_enumeration(keys)
    if flag:
        print("order-enumeration flag:", flag, "order:", perm)
分类:Crypto
image
作者:Zengan

3

提交

0

收入

相关WriteUP

问题反馈