Based on the recorded hands-on evidence linked below.

REA investigation / 01ELF · x86-64

A clue hidden in four bytes

The 22nd character
has to be 7.

REA reads EB 01 F2 00 from the binary. Decoded, that mask selects just one character.

DUCTF{ezzzpzzz_07bcda7bfe81faf43caa}
Check 07 / 26

Skip 21
Select 1
Skip 14

Required sum 55
Recovered ruleASCII “7” = 55 = 55
Original binary · recordedCorrect! exit 0

Try the recovered rule above. Both full-program verdicts were captured with REA; this browser demo replays those two inputs.

The question behind the prompt

A 15 KB executable asks “What is the flag?” Most inputs produce Incorrect!. We want to understand what the checker requires—and give the original program an input it accepts.

The target is masked squares flag checker, an easy reverse-engineering challenge by joseph from DownUnderCTF 2023. This is a guided reproduction of REA’s official case study. We had already read that write-up and its answer. The queries, extracted tables, solver run and process captures linked here are fresh runs of our own; this is not a blind solve or a model benchmark.

The interactive check above previews one finding. Here is how it connects to the rest of the investigation.

A string gives us a way in

The binary has no helpful original function names. Its user-facing text still gives us a starting point. Through REA’s MCP tools, we open the executable and search its defined strings:

search_strings
{"pattern":"flag|correct|wrong|input","mode":"regex","case_sensitive":false}

0x102004  What is the flag?
0x10201c  Incorrect!
0x102027  Correct!

xrefs on 0x102004 returns a reference at 0x10126d. resolve_containing_procedure places that instruction inside the function starting at 0x10124d. Instead of asking for an explanation of the entire executable, we can inspect the function that prints the prompt.

These are Ghidra analysis-image addresses. The executable is position-independent; a live process can load at a different address.

The checker is a set of sums

REA’s analyze_function result shows the checker copying character values into six rows of six integers. It then walks two tables: encoded masks starting at 0x1040e0, and required totals starting at 0x104060.

0x1012c6: LEA RBX, [0x1040e0]
0x1012cd: LEA RBP, [0x104060]
0x1012d4: LEA R13, [RBX + 0x270]
0x1012e1: CALL 0x00101189
0x1012ec: CALL 0x00101217
0x1012f1: CMP dword ptr [RBP], EAX
0x1012f4: JNZ 0x00101335
0x1012f6: ADD RBX, 0x18

The table advances 24 bytes (0x18) at a time, over 624 bytes (0x270): 26 records. Inspecting the two called helpers explains each record. The first expands signed run lengths into a selection mask; the second adds the character values at selected positions. A total that differs from the stored target reaches the failure branch.

The decoder’s pseudocode misleadingly declares a void return. The checker consumes its result in RAX, while the decoder’s instructions retain the allocated mask pointer there. We use the caller and instructions together rather than treating that recovered signature as authoritative.

Four bytes expose one character

We ask REA to read all 624 mask bytes and all 104 bytes containing the 26 integer targets. The seventh record begins at 0x104170:

Mask:    EB 01 F2 00
Target:  37 00 00 00

The decoder uses MOVSX to read mask bytes as signed values. Thus EB means −21 and F2 means −14. Negative runs skip cells; positive runs select them; zero ends the record.

Skip 21 → select 1 → skip 14. Only zero-based position 21 contributes. The target is a little-endian integer: 0x37, or 55. Within the printable ASCII inputs used here, that character must be 7.

The browser diagram highlights that position in the complete candidate. Changing it to 8 changes this sum from 55 to 56. That violates this equation. It does not establish which of the program’s 26 checks rejects the changed input first.

Solve all the equations

Most masks select several characters, so one check alone is insufficient. We decode every record and pass the overlapping sums to Z3. For each check, the solver enforces:

Sum([codes[i] for i in check["selected"]]) == check["target"]

We also assume the conventional DUCTF{...} flag shape and an alphabet of letters, digits, underscores and braces. These are analysis assumptions, not facts extracted from the checking loop.

The solver returns:

DUCTF{ezzzpzzz_07bcda7bfe81faf43caa}

An independent pass through the extracted equations confirms all 26 totals. That establishes consistency with our reconstructed rules. The original executable still has the final say.

Download the solver and all extracted checks. Save both files in one directory, install z3-solver, then run python solve.py. Our run used Z3 5.3.1. The script returns one candidate; we have not proved uniqueness.

Give the answer to the original

We use REA’s capture_process_scenario to launch the unchanged Linux executable and enter the candidate. A second capture changes only position 21 from 7 to 8.

On a small screen, scroll the table to compare →
InputOriginal program outputExit code
Solver candidateCorrect!0
Position 21 changed to 8Incorrect!255

Both captures completed normally without output truncation. They confirm these two inputs against the actual binary, beyond our browser reconstruction. They do not prove that every possible input behaves as our model predicts.

The browser does not run REA or the ELF. It calculates the seventh equation and displays the corresponding retained full-program verdict. Read the accepted run or the changed-input run.

Repeat the investigation

Download the original executable from the organizers’ pinned handout. It is not redistributed here. Its SHA-256 is:

dcd3bec4f608e11f3a12ce461100aaf5f621851ee63a1f870d7c90d4e3dd51ae

Our analysis used REA 6.7.0, Ghidra 12.1.4 and Temurin JDK 21.0.13 on Ubuntu 24.04 under WSL. The Windows Ghidra interface rejected this ELF target; the successful analysis and process capture ran in Linux. We used a local MCP recording client, without changing agent registrations.

With REA and Ghidra configured in Linux, start by asking your agent:

Use REA to inspect ms_flag_checker.
Follow the input prompt to its checker and explain the mask decoding.
Read the mask and target tables, turn the checks into equations,
and test a candidate plus a one-character change with the original binary.

This is a suggested prompt, not a transcript of an autonomous agent solve. The run record lists the actual MCP requests and environment. Public JSON has local paths replaced with [workspace]; it is a readable publication copy rather than an untouched canonical Evidence bundle.

This case demonstrates a complete route from an observable prompt to checking code, a recovered rule, a solver and original-program verification. Continue with how to check an explanation or plan your first investigation.

Recorded verification

DownUnderCTF 2023 masked squares flag checker / 2026-10-23

Environment: Ubuntu 24.04 x64 under WSL 2 on Windows, REA 6.7.0, host Node.js 24.15.0 / Ghidra 12.1.4 / Temurin JDK 21.0.13 / Python 3.14.6 / Z3 5.3.1, model Codex authoring session interpreted REA MCP results; no isolated model benchmark.

Sample SHA-256: dcd3bec4f608e11f3a12ce461100aaf5f621851ee63a1f870d7c90d4e3dd51ae

  1. Download and hash-check the unchanged executable from the organizers’ pinned revision.
  2. Use REA MCP to search strings, follow a reference, inspect the checker and its two helpers, and read both data tables.
  3. Decode all 26 masks, solve their sums with stated flag-format assumptions, and check every equation.
  4. Use REA process capture to run the candidate and a single-character change against the original executable.

Expected: A candidate accepted by the original checker and a changed input it rejects.

Observed: The candidate passes all 26 reconstructed equations. Original program captures return Correct! / exit 0 and Incorrect! / exit 255 for the position-21 change from 7 to 8, without output truncation.

  • Guided reproduction after reading the official REA showcase and its answer; not a blind discovery.
  • The browser evaluates one reconstructed equation and replays the two recorded original-program verdicts.
  • Flag alphabet and DUCTF{...} shape are analyst assumptions. Candidate uniqueness is not proved.
  • Public JSON has local paths normalized; internal canonical digests describe the original records, not these edited publication copies.

Sources & further reading

Tools in this guide

  • REA — Agent-powered workflow