UMDCTF 2026 - Weave

posted
read
1114 words · 4 min
contents

The weavers of the Gabidulin code have woven a disguise. But the shuttle leaves a trace, and the frays can only hide so much. Unravel the warp to find the secret.

加比杜林码的织者们编织了一层伪装。但梭子留下了痕迹,毛边只能遮掩有限的信息。解开经线,找到秘密。

Challenge files: challenge.sage providing the public parameters warp, bolt, shuttle, knot, pegs, N, K, and an AES-GCM encrypted flag.

前置知识

本题涉及秩度量码(Rank-Metric Codes),属于编码论(Coding Theory)在秩度量下的分支。以下概念有助于理解:

概念说明
有限域 GF⁡(243)\operatorname{GF}(2^{43})扩体,每个元素可看作 43-bit 向量
Frobenius 自同构σ(x)=x2\sigma(x) = x^2,Gabidulin 生成矩阵的构建基础
秩距离 vs Hamming 距离前者看差向量各坐标在基域上张成空间的维数,后者数不同符号个数
Reed-Solomon 码消息→多项式求值→码字,Gabidulin 的类比基础
Gabidulin 码RS 在秩度量下的对应:线性化多项式 + Moore 矩阵
MRD 特性最小秩距离 d=N−K+1d = N - K + 1,解码半径 t=⌊N−K2⌋t = \left\lfloor\frac{N-K}{2}\right\rfloor
积秩攻击误差向量的 rank weight 上界为原 rank weight 与矩阵元素空间维数的乘积(定义与推导见下文)

For a vector v=(v1,…,vN)∈FqmNv=(v_1,\ldots,v_N)\in\mathbb{F}_{q^m}^{N}, define its rank weight over the base field by

wt⁡R,q(v)=dim⁡Fqspan⁡Fq{v1,…,vN}.\operatorname{wt}_{R,q}(v) =\dim_{\mathbb{F}_q}\operatorname{span}_{\mathbb{F}_q}\{v_1,\ldots,v_N\}.

Equivalently, expand each coordinate in a fixed basis of the extension field over Fq\mathbb{F}_q and take the rank of the resulting m×Nm\times N matrix over the base field. The rank distance between two vectors is the rank weight of their difference. In this challenge q=2q=2.

Gabidulin Codes in a Nutshell

A Gabidulin code is a linear rank-metric code over an extension field Fqm\mathbb{F}_{q^m}. Its generator matrix is a Vandermonde-like matrix (called a Moore matrix) built from Fq\mathbb{F}_q-linearly independent elements g0,…,gN−1∈Fqmg_0, \dots, g_{N-1} \in \mathbb{F}_{q^m}, with 1≤K≤N≤m1\le K\le N\le m:

G=(g0g1⋯gN−1g0[1]g1[1]⋯gN−1[1]⋮⋮⋱⋮g0[K−1]g1[K−1]⋯gN−1[K−1])G = \begin{pmatrix} g_0 & g_1 & \cdots & g_{N-1} \\ g_0^{[1]} & g_1^{[1]} & \cdots & g_{N-1}^{[1]} \\ \vdots & \vdots & \ddots & \vdots \\ g_0^{[K-1]} & g_1^{[K-1]} & \cdots & g_{N-1}^{[K-1]} \end{pmatrix}

where x[i]=xqix^{[i]} = x^{q^i} is the ii-th Frobenius power. Gabidulin codes can uniquely decode up to ⌊N−K2⌋\left\lfloor\frac{N-K}{2}\right\rfloor rank errors, making them the rank-metric analogue of Reed-Solomon codes.

Key difference from RS: RS uses distinct evaluation points and counts symbol errors (Hamming distance). Gabidulin uses Fq\mathbb{F}_q-linearly independent evaluation points and measures error by rank — meaning an error vector like (e,e,e,…,e)(e, e, e, \dots, e) has rank weight 1 when e≠0e\ne0, even if every symbol is wrong.

The Challenge Disguise

The challenge constructs a loom — the true Gabidulin generator matrix over F243\mathbb{F}_{2^{43}} with parameters N=40N=40, K=8K=8:

loom = Matrix(Fqm, K, N, lambda j, i: qpow(pegs[i], j))

Here pegs are NN elements of F243\mathbb{F}_{2^{43}} that are linearly independent over F2\mathbb{F}_2. The codeword is s⋅looms \cdot \mathsf{loom} where s∈F243Ks \in \mathbb{F}_{2^{43}}^K is the secret message.

The disguise is a two-sided invertible transformation: knot\mathsf{knot} is K×KK\times K, loom\mathsf{loom} is K×NK\times N, and shuttle\mathsf{shuttle} is N×NN\times N, with all entries in F243\mathbb{F}_{2^{43}}.

warp=knot⋅loom⋅shuttle−1\mathsf{warp} = \mathsf{knot} \cdot \mathsf{loom} \cdot \mathsf{shuttle}^{-1} bolt=s⋅warp+frays\mathsf{bolt} = s \cdot \mathsf{warp} + \mathsf{frays}

All matrices knot\mathsf{knot}, shuttle\mathsf{shuttle}, and the list of pegs are published to the player. At first glance this seems to completely mask the underlying Gabidulin structure — but the key insight is that when the attacker knows the masking matrices, the disguise is fully reversible.

The Key Insight: Low-Dimensional Entry Space

The vulnerability comes from how shuttle\mathsf{shuttle} is constructed. Its entries are drawn from the span of only FIBER_D=3 elements of F243\mathbb{F}_{2^{43}} that are linearly independent over F2\mathbb{F}_2. Define their base-field span:

U=span⁡F2{α1,α2,α3},shuttle∈UN×N,dim⁡F2U=3.\begin{aligned} U&=\operatorname{span}_{\mathbb{F}_2}\{\alpha_1,\alpha_2,\alpha_3\},\\ \mathsf{shuttle}&\in U^{N\times N},\\ \dim_{\mathbb{F}_2}U&=3. \end{aligned}

All vector and matrix operations take place in the extension field. Right-multiplying bolt\mathsf{bolt} by shuttle\mathsf{shuttle} cancels the inverse in warp\mathsf{warp}; the entry-space dimension bound applies to shuttle\mathsf{shuttle} and need not hold for shuttle−1\mathsf{shuttle}^{-1}:

bolt⋅shuttle=s⋅knot⋅loom+frays⋅shuttle.\begin{aligned} \mathsf{bolt}\cdot\mathsf{shuttle} &=s\cdot\mathsf{knot}\cdot\mathsf{loom}\\ &\quad+\mathsf{frays}\cdot\mathsf{shuttle}. \end{aligned}

Let s′=s⋅knots' = s \cdot \mathsf{knot} and error=frays⋅shuttle\mathsf{error} = \mathsf{frays} \cdot \mathsf{shuttle}.

The vector frays\mathsf{frays} has rank weight at most FRAYS=5 over the base field. Because the entries of shuttle\mathsf{shuttle} lie in a 3-dimensional F2\mathbb{F}_2-subspace, the transformed error satisfies:

wt⁡R,2(frays⋅shuttle)≤FRAYS⋅FIBER_D=5×3=15.\begin{aligned} &\operatorname{wt}_{R,2}(\mathsf{frays}\cdot\mathsf{shuttle})\\ &\quad\le\mathsf{FRAYS}\cdot\mathsf{FIBER\_D}\\ &\quad=5\times3=15. \end{aligned}

Why this inequality holds: Write shuttle=∑t=13αtS(t)\mathsf{shuttle}=\sum_{t=1}^{3}\alpha_t S^{(t)}, where S(t)∈F2N×NS^{(t)}\in\mathbb{F}_2^{N\times N}. Every coordinate of frays⋅S(t)\mathsf{frays}\cdot S^{(t)} is a base-field linear combination of the coordinates of frays\mathsf{frays}, so its rank weight is at most 5. Multiplication by the nonzero scalar αt\alpha_t preserves that weight; subadditivity then gives

wt⁡R,2(frays⋅shuttle)≤∑t=13wt⁡R,2(αtfrays⋅S(t))≤3⋅5=15.\begin{aligned} &\operatorname{wt}_{R,2}(\mathsf{frays}\cdot\mathsf{shuttle})\\ &\quad\le\sum_{t=1}^{3}\operatorname{wt}_{R,2}(\alpha_t\mathsf{frays}\cdot S^{(t)})\\ &\quad\le3\cdot5=15. \end{aligned}

The Gabidulin unique decoding radius for N=40,K=8N=40, K=8 is:

⌊40−82⌋=16\left\lfloor\frac{40 - 8}{2}\right\rfloor = 16

Since 15<1615 < 16, the error is within the unique decoding radius. We can recover s′s' uniquely.

Solution

The mathematical recovery procedure is:

  1. Remove the disguise: Compute received=bolt⋅shuttle\mathsf{received} = \mathsf{bolt} \cdot \mathsf{shuttle} to obtain a true Gabidulin codeword plus an error of rank weight at most 15.

  2. Build the original loom: Reconstruct loom\mathsf{loom} from pegs using the same lambda.

  3. Decode: Use a Gabidulin decoder (e.g., the Gao-style decoder or the standard rank-metric syndrome decoder) to recover s′s' from received\mathsf{received} given loom\mathsf{loom}.

  4. Recover the secret: Multiply by knot−1\mathsf{knot}^{-1}:

    s=s′⋅knot−1s = s' \cdot \mathsf{knot}^{-1}
  5. Derive the AES key: Serialize the recovered extension-field vector exactly as the challenge does, then hash those bytes with SHA-256 and take the first 16 bytes. The following expression assumes secret_bytes has already been produced correctly:

    The key expression is key = sha256(secret_bytes).digest()[:16]. It requires sha256 from hashlib and the correctly serialized byte string.

  6. Decrypt the flag: Use AES-GCM with this key and the nonce/tag provided in the challenge output.

Caveats

An extension-field vector must be explicitly serialized to bytes using the challenge’s field representation, coordinate order and encoding. A change of base field does not perform that serialization, and a SageMath vector cannot be passed directly to SHA-256.

Why This Works

The error frays has base-field rank weight at most 5, while all entries of shuttle lie in a 3-dimensional base-field subspace. Since shuttle is invertible, its ordinary matrix rank over F243\mathbb{F}_{2^{43}} is NN; the useful bound concerns its entry space. Multiplication increases the error rank weight by at most a factor of 3, leaving it within the unique decoding radius:

5×3=15<16=⌊40−82⌋5 \times 3 = 15 < 16 = \left\lfloor\frac{40-8}{2}\right\rfloor

The published masking matrices permit the transformation above, and the resulting error bound makes the stated Gabidulin instance uniquely decodable. Keeping a matrix or the evaluation points private would change the attack model; it does not by itself establish the security of a modified scheme.

Flag

UMDCTF{l01dr34u_l4mbda3_brick5_th3_w34v3_but_th3_trapd00r_unsp00ls_1t}