archlinux / r3t0x@portfolio
ALL SYSTEMS NOMINAL /
← Back to archive · Misc

Securinets Friendly CTF 2026 / Misc WRITEUP

Human After All

Haters gonna say it s AI

EVENTSecurinets Friendly CTF 2026CATEGORYMiscDIFFICULTYmediumPOINTS400
misc

Walkthrough

The service puts the player's note in the last comment of Submission.java and runs the supplied byte classifier. Notes are printable ASCII, at most 72 bytes, and cannot contain */.

The model has a byte embedding, four convolution widths (3, 5, 7, 11), ReLU, global max pooling, and one linear output. The source, model, threshold and weights given to players are the exact files used by the server.

The local detector, threshold, preprocessing, source template, and weights are all public and deterministic.

Intended solution

Look at channels with negative output weights. For each position in one of those filters, try every printable byte and keep the embedding with the largest dot product. This gives one good string per filter.

Not every recovered n-gram is useful:

  • several negative channels are already saturated by Submission.java;
  • some attractive negative channels also activate positive channels;
  • all recovered strings do not fit in the note.

Score those strings locally and keep the best score reduction per byte. Some negative filters are already active in the Java file, and a few candidate strings also activate positive filters, so blindly joining all of them does not work. See solver.py for the short reference implementation.

Reference solver

The reference solver supplied with the writeup:

#!/usr/bin/env python3
import socket
import sys

from attachments.detector import Detector, PRINTABLE


def solve():
    detector = Detector()
    words = []

    for branch in detector.branches:
        for channel, head in zip(branch["weight"], branch["head"]):
            if head >= 0:
                continue
            word = bytes(
                max(
                    PRINTABLE,
                    key=lambda char: sum(
                        a * b for a, b in zip(detector.embedding[char], weights)
                    ),
                )
                for weights in channel
            )
            if b"*/" not in word and word not in words:
                words.append(word)

    payload = b""
    score = detector.score(payload)

    while score >= detector.threshold:
        best = None
        for word in words:
            for separator in ((b"",) if not payload else (b" ", b"_", b"~", b"|")):
                extra = separator + word
                trial = payload + extra
                if len(trial) > detector.max_note or b"*/" in trial:
                    continue
                new_score = detector.score(trial)
                gain = score - new_score
                if gain <= 0:
                    continue
                key = (gain / len(extra), gain)
                if best is None or key > best[0]:
                    best = (key, word, trial, new_score)
        if best is None:
            raise RuntimeError("no payload found")
        _, word, payload, score = best
        words.remove(word)

    return detector, payload


def send(host, port, payload):
    with socket.create_connection((host, port)) as sock:
        data = b""
        while b"note> " not in data:
            data += sock.recv(4096)
        sock.sendall(payload + b"\n")
        while True:
            block = sock.recv(4096)
            if not block:
                break
            sys.stdout.buffer.write(block)


def main():
    detector, payload = solve()
    print(payload.decode(), flush=True)
    print(
        f"{len(payload)}/{detector.max_note} bytes, logit {detector.score(payload):.4f}",
        flush=True,
    )

    if len(sys.argv) == 3:
        send(sys.argv[1], int(sys.argv[2]), payload)
    elif len(sys.argv) != 1:
        raise SystemExit(f"usage: {sys.argv[0]} [host port]")


if __name__ == "__main__":
    main()

Flag

Securinets{c0mm3nts_4r3_f34tur3s_t00}
Published 2026-09-28 · by r3t0x← Back to archive · Misc
0xSEARCH_INDEX

Trace a memory.

INDEX ONLINE0 addresses
↑↓ TRACEENTER OPENESC DISMISS