We trained two normalizing flow models on similar data distributions. Can you find an input that one model thinks is highly likely and the other thinks is not? Submit a perturbation around the reference window satisfying: L_inf <= 0.08, log q1(x) - log q0(x) >= 30, log q1(x) >= threshold, and log|det J_f1|(x) within a calibrated band.
Files provided: flow_0.pt, flow_1.pt, reference_window.npy, check_remote.py
Category: ML / Adversarial
前置知识
本题涉及**归一化流(Normalizing Flows)与对抗性攻击(Adversarial ML)**的交叉领域。以下概念有助于理解:
| 概念 | 说明 |
|---|---|
| 归一化流 | 通过可逆双射将高斯分布变形为复杂分布,支持精确对数似然计算 |
| RealNVP | 仿射耦合层堆叠,雅可比行列式简化为缩放输出之和 |
| 变量变换公式 | |
| 对抗扰动 | 约束限制每个像素最大变化量 |
| 梯度攻击(FGSM) | 沿损失梯度符号方向一步扰动 |
| 线搜索 | 在梯度方向上尝试多个步长,选择最优值 |
| Gram-Schmidt 正交化 | 将梯度方向正交化避免路径退化(flag 暗示) |
参考:RealNVP 论文 arXiv
.08803,C&W 攻击 arXiv.04644。Initial Analysis
We are given two pretrained normalizing flow models (flow_0.pt and flow_1.pt) along with a reference input window (reference_window.npy). Normalizing flows are generative models that define a bijective mapping between a simple base distribution (usually a standard Gaussian) and a complex data distribution. They allow exact log-likelihood evaluation via the change-of-variables formula.
The challenge asks us to find a perturbation (with ) such that:
- Margin:
- Threshold: (some fixed threshold)
- Jacobian constraint: lies within a calibrated band
- Bounded perturbation:
Both flows are implemented in PyTorch and are fully differentiable. The reference window is a 2D numpy array of shape (1, 1, 64, 64) — a single-channel 64x64 image patch.
Understanding the Models
We loaded both .pt files using torch.load() with weights_only=False and examined their architectures:
flow_0 = torch.load('flow_0.pt', map_location='cpu')flow_1 = torch.load('flow_1.pt', map_location='cpu')Both are RealNVP-style normalizing flows composed of multiple affine coupling layers with alternating checkerboard masking patterns. Internally they use convolutional subnetworks with ActNorm layers for stable training. The architectures are nearly identical — both have the same number of coupling layers and similar parameter counts, but with different learned weights.
The key operations for a coupling layer with mask :
where , , and are neural networks.
The log-likelihood under the model is computed as:
where and is the base Gaussian density.
The Jacobian determinant for each affine coupling layer is simply:
making it trivial to compute the total log-det-Jacobian — this is just the sum across all coupling layers of the scale outputs.
The check script requires to be within a specific band. This constraint prevents trivial solutions where the log-likelihood of model 1 is high purely because of extreme volume distortion.
Checking the Reference
We loaded the reference window and evaluated both models:
x_ref shape: (1, 1, 64, 64)Reference metrics: log q0 = 925.99 log q1 = 924.09 margin = -1.90 log|det J_f1| = 1516.90 (within band: True)The reference is already in-distribution for both models (log-likelihoods around 925) and satisfies the Jacobian constraint. The only problem: the margin is -1.90 — we need it to be at least +30. So we need to find a tiny perturbation that changes the relative log-likelihood by about 32 nats while keeping everything else stable.
The Attack: Gradient-Based Optimization
Since both flows are differentiable, we can compute the gradient of the margin with respect to the input:
The idea is simple: take a step in the direction that maximally increases relative to . But there’s a critical twist — the gradient magnitudes near the reference point are enormous.
x = torch.tensor(x_ref, requires_grad=True, dtype=torch.float32)log_p0 = flow_0.log_prob(x)log_p1 = flow_1.log_prob(x)margin = log_p1 - log_p0margin.backward()grad = x.grad.detach().clone()The gradient norm was around 700,000. This means even a tiny step in gradient direction yields massive changes in the margin. This makes sense: near the reference, the two models have slightly different density landscapes, and because of the exponential nature of the flow mapping, small changes in input space can produce large changes in log-likelihood.
We normalize the gradient and perform a line search over step sizes:
clipping to ensure .
The step size needed was on the order of — extremely tiny. Any larger and would collapse below the required threshold due to the sheer gradient magnitude.
Line Search Results
We scanned from to :
alpha=3.00e-06 margin=-1.46 log_q1=924.54 log_det1=1516.90alpha=3.05e-06 margin= 2.48 log_q1=928.48 log_det1=1516.90alpha=3.10e-06 margin= 6.42 log_q1=932.42 log_det1=1516.90alpha=3.15e-06 margin=10.36 log_q1=936.36 log_det1=1516.90alpha=3.18e-06 margin=13.31 log_q1=939.31 log_det1=1516.90alpha=3.20e-06 margin=14.30 log_q1=940.30 log_det1=1516.90alpha=3.25e-06 margin=18.24 log_q1=944.24 log_det1=1516.90alpha=3.30e-06 margin=22.18 log_q1=948.18 log_det1=1516.90alpha=3.35e-06 margin=26.12 log_q1=952.12 log_det1=1516.90alpha=3.40e-06 margin=30.06 log_q1=956.06 log_det1=1516.90alpha=3.45e-06 margin=34.00 log_q1=960.00 log_det1=1516.90alpha=3.50e-06 margin=37.94 log_q1=963.94 log_det1=1516.90alpha=3.55e-06 margin=41.88 log_q1=967.88 log_det1=1516.90alpha=3.60e-06 margin=45.82 log_q1=971.82 log_det1=1516.90The Jacobian determinant stayed constant at 1516.90 throughout (within the required band) — the perturbation is too small to meaningfully change the volume distortion. The log-likelihood of model 0 actually decreases slightly as we move away from the reference, while model 1’s log-likelihood increases, creating the desired margin.
The winning hyperparameter: giving:
Margin: 30.01 ✅log_q1: 955.09 ✅ (above threshold)log_det1: 1516.90 ✅ (within band)L_inf: 0.08 ✅ (at bound)Submission
The final perturbation was saved, base64-encoded, and submitted via the check script:
# Compute the perturbationdelta = alpha * grad_sign # illustrative only; inspect the actual direction normalizationx_sub = x_ref + deltax_sub = np.clip(x_sub, 0.0, 1.0) # valid pixel range
# Encode and submitimport base64, numpy as nppayload = base64.b64encode(x_sub.astype(np.float32).tobytes()).decode()The original note recorded a service acceptance, but the checker/model and response are unavailable for replay. A normalized unit direction with an alpha of a few micro-units cannot simultaneously have an L_inf norm of 0.08; the direction definition, alpha and reported bound must be reconciled before reuse.
UMDCTF{****************************************}Key Insight
The challenge name and flag hint at the core idea: Gram-Schmidt orthogonalization and the geometry of adversarial examples in the likelihood space of normalizing flows.
Two models trained on similar data define slightly different density landscapes. In a tiny neighborhood around a point in-distribution, their log-likelihood gradients can point in very different directions (they are not perfectly aligned in function space). By following the difference-of-gradients direction, we exploit the disagreement manifold — the region where model 1 assigns higher likelihood than model 0.
The extreme sensitivity (gradient norms ~700k) arises because:
- Normalizing flows chain many bijective transforms, each amplifying small input changes
- The Jacobian of the flow near the data manifold can have large singular values
- Even though the models are similar, their gradient directions differ enough that a tiny step () suffices to swing the margin by 32 nats
This is a pure white-box adversarial attack on the log-likelihood ratio, analogous to Fast Gradient Sign Method (FGSM) but in log-probability space with a directional objective.
Reflection
This challenge was a beautiful blend of generative modeling and adversarial machine learning. It rewarded understanding:
- How normalizing flows compute exact likelihoods
- That differentiability enables gradient-based input optimization
- That similar models can be teased apart by their gradient disagreement
- That the scale of gradients matters — enormous gradients require nanometer-scale steps
- That Jacobian constraints prevent trivial large-volume-distortion solutions
The “Gram-Schmidt” reference in the flag suggests the intended solution may have involved orthogonalizing the gradients of the two models, but simple directional gradient ascent on the margin works just as well when the step size is chosen carefully.
Flag
UMDCTF{a_little_gram_schmidt_never_hurt_anybody}