mlfreemedium

dualflow

umdctf

Task: submit a float32 window close to a provided reference so it passes three RealNVP-based acceptance checks. Solution: use a white-box adversarial perturbation, changing one coordinate of the reference by +0.03 to push the flow_1 likelihood and margin far above the required thresholds while staying inside the L-infinity budget.

$ ls tags/ techniques/
white_box_adversarial_examplecoordinate_perturbationlikelihood_margin_optimization

dualflow — UMDCTF

Description

submit base64(numpy float32 array, shape (5, 64)):

The service loads a reference window and checks whether our submitted float32 array stays within EPS = 0.08 in L-infinity distance. It then evaluates the sample under two RealNVP flows and only prints the flag if the sample looks realistic to flow_1 and strongly prefers class 1 over class 0.

Analysis

challenge.py exposes the full decision rule:

  • shape must be (5, 64)
  • values are compared to reference_window.npy
  • linf <= 0.08
  • margin = log q1 - log q0 >= 30
  • log q1 >= 932.8031
  • log_det1 must lie in [1367.1451, 1595.0465]

The important point is that this is a white-box setting. We have the reference input, the model code, and both flow checkpoints. The reference sample itself is not close to the target class boundary in the right direction: its margin is about -1.90, so we must meaningfully increase log q1 - log q0 while keeping the perturbation tiny.

In practice, no numerical trick was needed. A simple coordinate search was enough: flatten the reference array and add +0.03 at index 205, which is element [3, 13] in the original (5, 64) layout.

That single change already satisfies every condition:

  • linf = 0.03
  • margin = 105.70
  • log_q1 = 986.23
  • log_det1 = 1526.48

So the challenge is essentially a straightforward adversarial-example task against a flow-based discriminator.

Solution

Load the reference window, perturb one coordinate, save the result as a NumPy array, then base64-encode the .npy payload for submission.

#!/usr/bin/env python3 import base64 import io import numpy as np ref = np.load("reference_window.npy").astype(np.float32) sub = ref.copy().reshape(-1) sub[205] += np.float32(0.03) # same as ref[3, 13] += 0.03 sub = sub.reshape(5, 64).astype(np.float32) buf = io.BytesIO() np.save(buf, sub) print(base64.b64encode(buf.getvalue()).decode())

Submitting that array to the remote service returns the success line and the flag.

$ cat /etc/motd

Liked this one?

Pro unlocks every writeup, every flag, and API access. $9/mo.

$ cat pricing.md

$ grep --similar

Similar writeups