๐Ÿ‡ฎ๐Ÿ‡นSrdnlenCTF 2026

Writeups for all but two challenges

crypto

FHAES

Description

Service provides garbled-circuit evaluation of AES-based operations with a fixed per-connection secret key. Available circuits include encrypt, decrypt, add, multiply, and custom_circuit (wrapped as Enc_k(custom(Dec_k(ct)))). Goal: recover the 16-byte AES key and submit it.

Solution

The break is on garbling metadata, not AES cryptanalysis.

  1. Build a custom_circuit that adds exactly one attacker-controlled AND gate:

    • a = x0 XOR x0

    • b = NOT(a)

    • y0 = a AND b

    • y1..y127 = xi XOR xi

  2. In this construction, the custom AND gate leaks the global Free-XOR offset:

    • delta = gate0 XOR gate1 for that custom AND table entry.

  3. Key-schedule section of the AES circuit is fully garbled before evaluator-dependent AES rounds.

    • First 1360 AND gates are key-schedule-only.

    • We can evaluate this prefix locally as evaluator using received key_evaluator labels and AND tables.

  4. Record the first 40 key-schedule S-box input-wire sets (done locally by instrumenting circuit construction).

  5. For each of the first 16 key-schedule S-box calls (4 rounds of schedule bytes):

    • Known: active wire labels for that S-box input byte (A0..A7) and delta.

    • Unknown: semantic input byte bits.

    • For each candidate byte v in [0..255], derive candidate zero labels (Zi = Ai if bit 0 else Ai ^ delta), re-garble one optimized S-box chunk (34 ANDs), and compare with observed AND tables at that chunk offset.

    • Each chunk yields a unique byte.

  6. Reconstruct words w3, w7, w11, w15 from chunk order (chunks are on RotWord order).

  7. Recover initial words w0, w1, w2 algebraically from AES-128 schedule recurrence using:

    • w7, w11, w15 and g(w3), g(w7), g(w11).

  8. Key is w0 || w1 || w2 || w3.

  9. Send empty circuit line to exit loop, submit recovered key, receive flag.

Recovered flag: srdnlen{I_hope_you_didn't_slop_this_one...although_I_don't_know_if_you_can_slop_it...}

Solution code used:

Faulty Mayo

Description

The service exposes a one-byte fault injection in a patched MAYO-2 signer (chall) before returning (pk, sm) for a fixed secret key. The allowed patch window sits inside mayo_sign_signature, specifically in the final s = v + O*x construction. With carefully chosen one-byte patches, each signature query leaks linear equations in one row of secret matrix O over GF(16). Recovering all 64 rows of O lets us forge valid signatures for arbitrary messages and obtain the flag from option 2.

Solution

  1. Reverse chall and map patchable offsets to instructions in mayo_sign_signature.

  2. Use faulted signatures to obtain equations for unknown row entries of O.

  3. Solve each row as a 17-variable linear system over GF(16).

  4. Rebuild an equivalent signer using recovered O and public seed_pk from cpk.

  5. Forge a valid signature for the challenge message, submit as signed message hex sm = sig || msg.

Fault offsets used per row (MAYO-2):

  • row 0: patch 0x62f5 -> 0x7d

  • row 1: patch 0x630d -> 0x7d

  • rows 2..62: patch 0x6323 + 0x16*(row-2) -> 0x25

  • row 63: patch 0x6866 -> 0x25

Exploit script (solve.py):

Custom signer (forge_mayo.c):

Build and run:

Recovered flag: srdnlen{M4YO+0n3_N1bBl3_F4ulT=Br0k3N}

Lightweight

Description

We are given an oracle based on a 4-round Ascon-like permutation:

  • Secret key: key[0], key[1] (128 bits total), fixed for the session.

  • Per query we choose diff = (d0, d1).

  • Server samples random nonce (n0, n1), prints:

    • nonce

    • F_k(n0, n1) (first two 64-bit words after 4 rounds)

    • F_k(n0^d0, n1^d1)

  • After up to 2^16 queries we must guess the key.

The core weakness is reduced-round diffusion: for d0=d1=1<<i, specific output-bit differentials have strong key-dependent biases.

Solution

  1. Reproduce the permutation exactly (important detail: after S-box, x4 must be set to t4).

  2. Invert only the linear layer of x0 (x0 ^= rotr19(x0) ^ rotr28(x0)) using a precomputed 64x64 GF(2) inverse matrix.

  3. For each bit position i:

    • Query many times with diff=(1<<i, 1<<i).

    • Let u = Linv(x0_a) ^ Linv(x0_b) from the two outputs.

    • Measure two empirical biases:

      • e1 from bit j1=(i+1) mod 64

      • e2 from bit j2=(i+14) mod 64

  4. Classify (k0[i], k1[i]) by nearest centroid among 4 key-bit-pair classes.

    • Use two centroid tables depending on i via CMASK=0x73.

  5. Build full candidate key (k0,k1).

  6. Verify candidate in-session by issuing a few random differentials and checking predicted outputs from local ascon_eval.

  7. If verification fails, add more samples only for low-margin bit positions and reclassify.

  8. Submit recovered key, receive flag.

Recovered flag: srdnlen{https://www.youtube.com/shorts/8puNABA4rxw}

Threshold

Description

A lattice-based FROST-like threshold signature service lets us request partial signatures from signers 1..15 (we are signer 0) for any message except "give me the flag". We are given vk and our share sk[0].

The service computes each partial as:

  • z_i = r_i + lambda_i * c * s_i (mod q)

where:

  • r_i is fresh Gaussian masking noise,

  • lambda_i is the Lagrange coefficient for signer set S,

  • c is hash-derived challenge from aggregated commitment high bits.

Solution

Key observations:

  1. The preprocessing cap is queue-depth-based (<=8), not total-usage-based. By alternating menu options, we can collect many signatures.

  2. We can force a fixed challenge c by choosing our commitment w_0 each query so the aggregate commitment sum for selected signers is exactly zero before high-bit extraction.

  3. With fixed c, each coefficient becomes a 1D modular noisy equation:

    • z = lambda * u + noise (mod q), where u is a coefficient of c*s_i.

  4. We choose many signer subsets to get multiple lambda scales (small/mid/huge) for each target signer. For each coefficient, we solve via interval intersection + maximum-likelihood selection.

  5. Recover 7 signer shares (8..14), combine with our share (0), reconstruct/validate the master secret via interpolation (it must be small Gaussian), then forge a valid signature on target message offline and submit.

Full exploit code used:


misc

The Trilogy of Death Volume I: Corel

Description

Forensics challenge on a Corel Linux disk image. A WordPerfect macro file (fc.wcm) is present and contains the clue The key is in what is left plus encrypted byte arrays.

Solution

The direct image-repair path was a dead end, so I pivoted to the macro artifact.

fc.wcm contains:

  • A 4-byte key array (k1..k4) initially set to FAKE.

  • A phrase printer: The key is in what is left.

  • Two encrypted arrays (docbody, rh) decoded with:

This expression is bitwise XOR (bb ^ kb).

So the payload is XOR-encrypted with a repeating 4-byte key. Using the given fake key prints nonsense. I brute-forced the 4-byte key under a strict flag charset ([a-z0-9_{}]) against docbody.

Code used:

Output includes:

Submitted flag:

The Trilogy of Death Volume II: The Legendary Armory

Description

Forensics challenge on a Windows minidump (chall.dmp) with the hint that two relics in volatile memory must be XORed.

Solution

The visible SRDNLEN{REALLY_EASY?} image text was a decoy.

The real path came from the d.iso clue in a recovered image fragment and recovered ISO directory entries (K.;1, T.;1). The clean T payload copy in memory is at 0x7625d8b (size 176578), and the 8-byte XOR key is:

f4 14 a5 31 17 02 0b 84

XORing T with this repeating key yields a ZIP local-header stream (no central directory). Extracting entries from local headers recovers multiple ZZT files, including TOWN.ZZT.

Inside TOWN.ZZT, the Armory text is stored as repeated control triples \x01\x35<char>. Decoding that run reveals the flag.

Repro script:

Output:

The Trilogy of Death Volume III: The Poisoned Apple

Description

Given poisoned_apple.zip (contains poisoned_apple.dmg), encrypted_flag.bin, and a slow decryptor (decrypt_flag.py) with 500,000 candidate keys (keys/key_*.txt) inside APFS.

Bruteforce is intentionally impractical (PBKDF2-SHA256, 140000000 iterations).

Solution

The intended path is APFS forensics, not crypto.

  1. Extract and inspect image:

  1. Extract APFS partition and locate APFS volume superblocks (APSB):

  1. Enumerate APFS root and confirm key directory size:

  1. Parse .fseventsd and find outlier activity:

  1. Read key_449231 across APFS superblock states (history):

This shows two historical values for inode 449414 (keys/key_449231.txt):

  • old (xid <= 5526): 39f520679fd68654500f9cd44e8caed2bc897a3227dc297c4520336de2a59dd7

  • new (xid >= 5527): b1a64c6e89971c26ce98d5984ec0499756306813c692ebb26cc039ad4c9b3319

The newer one is the poisoned value; the older snapshot value is the real key.

  1. Decrypt and verify:

Recovered flag: srdnlen{b3h0ld_th3_d34dl1_APFS!}


pwn

common_offset

Description

common_offset is a 64-bit non-PIE ELF with NX, no canary, and partial RELRO. The program lets you write to one of 4 file-buffers with a shared offset.

Bug: in change_files(), index and offset overlap in stack bytes:

  • index is stored at [rsp+0x49]

  • offset is a word at [rsp+0x48]

By first setting index=0 and increasing offset by 1, then setting index=3 and increasing by 255, carry corrupts effective index to 4 and produces OOB table access into the change_files stack frame. That gives RIP control on return.

Solution

Two-stage exploit:

  1. Stage1 RIP overwrite to call read_stdin again and land on add rsp,0x28; ret.

  2. Stage2 ROP that:

  • leaks puts@got to compute libc base,

  • runs a small write-VM (get_number -> mov rdi,rax ; add rsp,0x58 ; ret -> read_stdin) to place arbitrary 8-byte chunks in .bss,

  • finally jumps to setcontext with a crafted fake ucontext.

Final payload uses:

  • fopen("/challenge/flag.txt", "r")

  • mov rdx, rax ; ret to pass returned FILE* to fgets

  • fgets(buf, 0x80, fp)

  • puts(buf)

Important gotcha: this service accepted libc symbol offsets (puts/fgets/fopen/setcontext) from the provided libc.so.6, but gadget offsets differed between Ubuntu 2.42-0ubuntu3 and 2.42-0ubuntu3.1. So the exploit tries both gadget sets:

  • set A: pop rdi=0x11b93a, mov rdx,rax=0x145f17

  • set B: pop rdi=0x11b8ba, mov rdx,rax=0x145ed7

Remote solved with set B.

Recovered flag: srdnlen{DL-r35m4LLv3}

Echo

Description

The program implements an echo loop with a custom read_stdin routine. Bug: read_stdin uses an 8-bit index and loop condition idx <= len, so for len = 0x40 it writes 65 bytes into a 64-byte buffer (1-byte overflow). That single-byte overflow hits the adjacent len variable on the stack, letting us increase future read sizes and eventually control data up to canary/saved frame/return addresses.

Solution

Key stack layout inside echo:

  • buffer: [rbp-0x50 ... rbp-0x11] (64 bytes)

  • len byte: [rbp-0x10]

  • canary: [rbp-0x8 ... rbp-0x1]

  • saved rbp: [rbp+0x0 ... rbp+0x7]

  • return address: [rbp+0x8 ... rbp+0xf]

Exploit plan:

  1. Overflow len from 0x40 to 0x48.

  2. With len=0x48, overwrite canary first byte with nonzero and leak:

  • canary bytes 1..7

  • saved rbp (stack leak)

  1. Set len=0x77, then print past many stack values to leak mainโ€™s libc return address from stack.

  2. Compute libc_base from leaked return address (ret_off = 0x2a1ca), then choose one_gadget 0xef52b.

  3. Final payload restores correct canary, sets a fake rbp into controlled stack memory, and overwrites RIP with one_gadget.

  4. one_gadget constraints are satisfied by placing NULL qwords at [rbp-0x78] and [rbp-0x60].

  5. Spawn shell and read /challenge/flag.txt.

Exploit code:

Registered Stack

Description

We get a PIE ELF that:

  • reads a hex string,

  • converts it to bytes (hex_to_bytes),

  • validates with Capstone that every instruction is only push/pop with register operands,

  • mmaps one RWX page,

  • zeros registers, sets rsp = page_base, and jumps to rsp.

Remote service: registered-stack.challs.srdnlen.it:1090.

Solution

Key points:

  1. Validation is only on initial bytes; runtime self-modification is allowed.

  2. push fs (0f a0) is validator-accepted; patching byte a0 -> 05 yields syscall (0f 05).

  3. fgets(buf, 0x200, ...) only accepts at most 511 chars, so max reliable hex payload is 510 chars = 255 bytes. Sending 256 bytes (512 hex chars) truncates and breaks stage input alignment.

  4. pop sp with seeded bytes sets rsp low16 to 0xc38f, so exploit is bucketed (works when mmap low16 bucket is c***, ~1/16).

  5. For read, rsi must be full pointer (rsp), but rdx must be small. Using rdx=rsp fails on high ASLR addresses (huge count/range issues). Use rdx=rbx=0xc305 instead.

  6. Stage-1 does read(0, stage2_buf, 0xc305), then returns to stage2 buffer.

  7. Stage-2 is shellcode for marker + /bin/sh, then send shell commands to read the flag.

Recovered flag: srdnlen{Pu5h1n6_4nd_P0pp1n6_6av3_m3_4_h34d4ch3}


rev

Artistic warmup

Description

We are given a Windows PE executable (rev_artistic_warmup.exe) and need to recover the flag.

Solution

Static reversing around the "Invalid flag."/"Valid flag!" references shows the core check at 0x1400bfb00:

  • It dynamically resolves GDI APIs.

  • It creates a 450x50 32-bit DIB (CreateDIBSection), draws user input with CreateFontA("Consolas", 24) + TextOutA.

  • It compares all 0x15f90 = 90000 raw bytes of the rendered bitmap against a blob at .rdata+0x20 (0x1400c5020) with XOR 0xAA:

    • check is ((rendered[i] ^ 0xAA) == blob[i]).

    • so expected rendered bytes are blob[i] ^ 0xAA.

That means the binary already contains the exact target rendered text image. Extract/decode it and OCR.

Code used:

OCR result is very close; using glyph consistency + CTF prefix gives the final exact flag:

srdnlen{pl5_Charles_w1n_th3_champ1on5hip}

Cornflake v3.5

Description

Given malware.exe and the hint:

The evolution of a Cereal Offender

The binary is a staged malware-like loader:

  • Stage1 checks the local username with an RC4-based check.

  • If it passes, it downloads stage2.exe (DLL) from the challenge host.

  • Stage2 reads password.txt and validates it with a custom VM.

Flag accepted by platform:

srdnlen{r3v_c4N_l0ok_l1K3_mAlw4r3}

Solution

  1. Reverse stage1 username gate:

  • RC4 key in binary: s3cr3t_k3y_v1

  • Compared hex: 46f5289437bc009c17817e997ae82bfbd065545d

  • RC4-decrypting that value gives: super_powerful_admin

  1. Download stage2 from C2 endpoint:

  • http://cornflake.challs.srdnlen.it:8000/updates/check.php?SessionID=46f5289437bc009c17817e997ae82bfbd065545d

  1. Reverse stage2.exe:

  • MainThread reads password.txt, strips CR/LF, calls VM checker, prints ez or nope.

  • VM bytecode is embedded and interpreted with opcodes 0..18.

  • Extract VM equality constraints over a 34-char srdnlen{...} string.

  1. Verify candidate against extracted VM constraints and submit.

Code used:

Dante's Trial

Description

And at last, Dante faced the Ferocious Beast. Will they be able to tr(ea)it it? Note: the submitted flag should be enclosed in srdnlen{}.

We are given a Game Boy Advance ROM (dantestrial.gba).

ROM Structure

The GBA ROM contains a custom bytecode VM that validates user input through a hash function. The game presents a text-based interface where an NPC called "G." prompts the player for input (printable ASCII, 0x20-0x7e). The input is hashed and compared against a target value; if it matches, the game displays "Thou art correcteth."

VM Architecture

The ROM loads runtime code from 0x08024100 into IWRAM (0x03000000) and executes a bytecode VM. The VM script at 0x08022654 (169 bytes) is XOR-decoded with (13*i + 0x5a) & 0xff, and opcodes are permuted through a table at 0x0802270c.

The effective execution path is a simple loop:

  1. op8: Pop next byte from input queue

  2. op11: If zero, jump to halt

  3. op10: Hash update step

  4. op12: Jump back to step 1

  5. op13: Halt

Hash Function

The hash is a modified FNV-1a with several additions:

State: hlo (32-bit), hhi (32-bit), ptr (8-bit), seeded on first character with hlo=0x84222325, hhi=0xcbf29ce4.

Per character c:

Final comparison:

Critical Discovery: VM Memory is Zeros

The tri_mix function takes two arguments: the input character c and a byte d from VM memory at position ptr. Disassembly of the ROM's VM initialization code at 0x08000784 shows it calls memset(EWRAM, 0, 256) with NO subsequent copy of user input into this memory region. The VM script contains no op3 (store) instructions, so memory stays all zeros throughout execution.

This means d=0 always, making tri_mix(c, 0) depend only on the input character. This was verified by running the full VM dispatch loop in Unicorn Engine and comparing against a corrected Python model.

Meet-in-the-Middle Attack

The hash function's forward and backward steps are invertible (using modular inverses of P and CUP mod 2^64), enabling a meet-in-the-middle attack:

  1. Forward pass: Enumerate all prefixes of length p, starting from the seed state, storing (hhi, hlo, ptr) in a hash table.

  2. Backward pass: Compute the required final state from the target hash by inverting fmix64 and the final multiply. Then enumerate all suffixes of length s, stepping backward from the required final state, and look up matches in the hash table.

Search Process

The answer turned out to be 6 characters long, containing mixed case and digits. The key insight was that prior exhaustive searches only covered [a-z0-9_] for short lengths. Running MITM with the full printable ASCII charset (95 characters) for length 6 (split 3+3) immediately found the answer:

Hash verification:

Flag

Rev Juice

Description

We are given Verilog for a vending machine. Product 8 (rev_juice) is not directly selectable (SP1..SP7 only), but selector.v has a hidden condition that sets ENABLE <= 8'h80, which enables product 8 (price 0).

The flag format is a move string:

  • I<n>C = insert n coins one-by-one

  • SPm = select product m (1..7)

  • CNL = cancel

  • Buying consumes coins.

  • Cancel refunds remaining inserted coins.

  • The challenge is based on using exactly 19 coins with reuse allowed.

Solution

  1. Reverse selector.v hidden condition. The conjunction over COINS_HISTORY[...] forces the key taps (at trigger cycle t):

  • H[0]=1

  • H[7]=4

  • H[28]=H[33]=H[38]=6

  • H[63]=H[73]=2

  • H[80]=9

  • and modular sum: (H[19]+H[21]+H[56]+H[69]) mod 32 = 0

  1. Build timing model from stable-cycle behavior. The solve uses these effective stable-to-stable durations:

  • Insert 1 coin: 3 cycles

  • Successful selection: 7 cycles

  • Failed selection: 5 cycles

  • CNL with coins inserted: 4 cycles

  • CNL at 0: 2 cycles

  1. Search for a sequence that places those history values at the required offsets and triggers product 8. The working sequence is:

srdnlen{I9C_SP6_CNL_I2C_SP2_I6C_SP6_SP6_SP5_CNL_I4C_SP1}

  1. Why this sequence aligns:

  • Creates the required high past value 9 (the H[80] tap).

  • Creates the two 2 taps (H[73], H[63]).

  • Holds 6 long enough for H[38], H[33], H[28].

  • Uses CNL timing to place required zero-sum taps.

  • Ends at 4 -> 1 (SP1) so H[7]=4 and H[0]=1 line up when hidden condition is checked.

  1. Verification helper script (timing + full selector equations):

This confirms cycles where the hidden selector condition is satisfied, which is the event that enables product 8.


web

Double Shop

Description

The site is a vending-machine frontend with two backend JSP endpoints:

  • /api/checkout.jsp (creates receipt logs)

  • /api/receipt.jsp?id=... (renders receipt file contents)

/api/manager returns 403, hinting at a hidden โ€œManagerโ€ target.

Solution

  1. Inspect frontend JS and identify backend endpoints:

  1. Confirm receipt.jsp path traversal:

  1. Read Tomcat config via traversal:

Important findings:

  • server.xml contains:

    • RemoteIpValve

    • internalProxies=".*"

    • remoteIpHeader="X-Access-Manager"

  • tomcat-users.xml contains:

    • username="adm1n"

    • password="317014774e3e85626bd2fa9c5046142c"

This means we can spoof client IP for Tomcat with:

  1. Bypass Apache block on /api/manager using path-parameter trick (;) and reach Tomcat Manager:

  1. Authenticate to Tomcat Manager with leaked creds:

  1. Read application list in Manager response. One deployed context path is:

Flag:

MSN Revive

Description

Web challenge with frontend + gateway + backend source. The backend seeds the flag into initial chat history, and an export endpoint can render any session's messages. Gateway tries to protect that endpoint as local-only.

Solution

The key bug chain:

  1. src/backend/utils.py seeds the flag in chat session 00000000-0000-0000-0000-000000000000.

  2. src/backend/api.py has POST /api/export/chat with no auth/membership check (only session_id exists).

  3. src/gateway/gateway.js blocks /api/export/chat for non-local clients.

  4. src/gateway/gateway.js rewrites Content-Length for /api/chat/event when content-type is application/x-msnmsgrp2p, deriving length from attacker-controlled MSN P2P TotalSize field.

  5. Gateway still forwards the full body buffer to backend, so backend sees fewer bytes than actually sent. Extra bytes become a smuggled second HTTP request on keep-alive connection (CL desync / request smuggling).

Exploit strategy:

  • Send POST /api/chat/event with malicious P2P header where TotalSize=0 so gateway forwards Content-Length: 48 to backend.

  • Append a full smuggled request after the first 48 bytes: POST /api/export/chat with JSON {"session_id":"000...000","format":"html"}.

  • Trigger follow-up proxied requests (/api/foo) to consume poisoned backend response queue.

  • Parse response bodies for srdnlen{...}.

Recovered flag:

srdnlen{n0st4lg14_1s_4_vuln3r4b1l1ty_t00}

Full exploit code used:

TodoList

Description

The app is client-side only and uses Handlebars templates. The admin bot:

  1. Visits the challenge page.

  2. Stores {"secret":"<FLAG>"} in app state and saves to cookie.

  3. Visits attacker-controlled URL via /report/.

Even without XSS, we can build a blind oracle against the bot by making template rendering intentionally expensive when a guessed condition is true.

Solution

The key primitive is:

  • Template condition checks secret characters via lookup secret <idx>.

  • If condition is true, render a heavy nested #each payload.

  • If false, render lightweight ok.

When sent through /report/:

  • True branch causes fast bot failure (500 {"error":"Admin failed..."}).

  • False branch reaches the bot wait path and usually returns 504 around 60s.

That gives a character-membership oracle.

This confirmed the final suffix and produced:

srdnlen{leakycstiggwp}

After Image

Description

A web challenge with three services behind afterimage-nginx:

  • PHP app (index.php, profile.php, tokens.php)

  • admin bot (Firefox) visiting attacker-supplied URLs via /report

  • internal camera at CAMERA_IP exposing MJPEG /stream with the real flag rendered on-frame

Goal: get the bot to leak the camera frame.

Solution

  1. Find initial primitive (source-based)

  • profile.php accepts file uploads and writes them to /tmp/<sanitized filename>.

  • PHP session files are also in /tmp as sess_<PHPSESSID>.

  • Uploading a file named sess_<target_sid> overwrites another session file.

  • index.php renders $_SESSION['nickname'] unsafely -> stored XSS.

  1. Exploit chain

  • Overwrite bot-target session with nickname=<script>....

  • Trigger bot with /report and url=http://afterimage-nginx/index.php?PHPSESSID=<target_sid>.

  • Stage1 redirects bot to attacker host (http://ATTACKER_IP/r?...).

  • Stage /r probes many random *-and-* 1u.ms hosts (IP1=ATTACKER_IP, IP2=CAMERA_IP) using CORS /probe.

  • On first host that resolves to attacker IP, spray several hidden iframe loads to /p (same host).

  • Stage /p sends /die to shut attacker listener, then requests same-host /stream.

  • Browser failover reaches CAMERA_IP, making /stream same-origin and readable.

  • Parse first JPEG from MJPEG stream.

  • Exfiltrate by writing JPEG_BASE64:<...> into a controlled session (loot<rand>) on afterimage.challs.srdnlen.it via POST /profile.php?PHPSESSID=<loot_sid>.

  1. Recover flag and submit

  • Pull loot session page, extract JPEG_BASE64, decode to frame_success.jpg.

  • OCR + renderer matching yielded the exact flag:

  • srdnlen{s4me_0rig1n_is_b0ring_as_h3ll}

Exploit script (run_rebind_attempt.sh)

Stage server (oneshot80.py)

Stage payload (payload.html)

Frame extraction helper used

Last updated