CTF Writeup

TheRustyFrame

Reverse engineering a PIE-enabled Rust binary to expose hidden execution branches via Ghidra.

Suraj Vishwanath
3 min read

Welcome to my writeup for the TheRustyFrame binary exploitation challenge from Breachpoint Online 2026!

The challenge provided a 64-bit ELF binary compiled in Rust. The description hinted at Rust's "safety boundaries" and runtime behavior, suggesting that the solution would require reverse engineering rather than simple memory corruption/exploitation.

Initial Reconnaissance

First, I inspected the binary to understand its basic architecture:

Binary Architecture
┌──(surajv5@kali)-[~]
└─$file rusty_frame_S6NV0Zx
rusty_frame_S6NV0Zx: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=..., for GNU/Linux 3.2.0, Rust-compiled, stripped
┌──(surajv5@kali)-[~]
└─$

The output confirmed several key details:

  • 64-bit ELF
  • PIE enabled
  • Dynamically linked
  • Rust-compiled

Next, I checked the binary's security protections using checksec:

Security Protections
┌──(surajv5@kali)-[~]
└─$checksec --file=rusty_frame_S6NV0Zx
[*] '/root/rusty_frame_S6NV0Zx'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
┌──(surajv5@kali)-[~]
└─$

Based on the protections (Full RELRO, NX, and PIE), there was no obvious zero-day stack vulnerability. Memory corruption was highly unlikely to be the intended path.


Runtime Behavior

I started by running the program normally to observe its standard execution flow:

Execution Tracing
┌──(surajv5@kali)-[~]
└─$./rusty_frame_S6NV0Zx
b
┌──(surajv5@kali)-[~]
└─$strace ./rusty_frame_S6NV0Zx
...
read(0, ...
┌──(surajv5@kali)-[~]
└─$

The final syscall in strace showed read(0, ..., which confirmed the binary was halting and waiting for user input from stdin.

I tested the input handling by piping some junk data:

Input Fuzzing
┌──(surajv5@kali)-[~]
└─$echo AAAA | ./rusty_frame_S6NV0Zx
┌──(surajv5@kali)-[~]
└─$

The program exited completely silently. This behavior proved a few crucial things:

  • No crash
  • No buffer overflow
  • No simple string comparison
  • Controlled logical branching

So this was definitively not a buffer overflow challenge, but rather a logic-based reverse engineering task.


Static Analysis (Ghidra)

Knowing the runtime behavior, I imported the binary into Ghidra to analyze the logical flow.

Locate the Entry Point

Rust binaries wrap main() inside a massive layer of runtime initialization. By looking inside the entry function, I found the core execution hook:

c
__libc_start_main(FUN_0010f7d0, ...)

This revealed that FUN_0010f7d0 was the real, underlying program logic isolated from the Rust wrappers.

Analyze the Target Function

Opening FUN_0010f7d0 in the decompiler showed the following control flow:

  • Input is read from stdin.
  • A logical condition is evaluated against the input.
  • Two execution paths branch out:
    1. The default/fail path prints "b".
    2. The hidden path prints the flag.

Crucially, the flag was not stored as plain text during standard strings output analysis, indicating it was either constructed dynamically at runtime or located in a deeply non-obvious memory reference.

By following the cross-references (XREFs) attached to the conditional branches, I was led directly to the function responsible for printing the flag string. The program contained a hidden execution branch that is completely bypassed under normal incorrect input.

Once decompiled, the true hidden string construction was revealed!

The Flag

This challenge successfully demonstrated Rust's heavy runtime wrapping of main, hidden conditional tracking, and how modern protections (PIE, NX) force attackers to rely on deep reverse engineering over basic exploitation.

BPCTF{XXXXXXXXXXXXXXXX}
Login