i guess bro - Writeup
CTF: EH4X CTF 2026 Category: Reverse Engineering Points: 500 Author: benzo Solves: First blood baby lets gooo
TL;DR
RISC-V crackme with fake flags, anti-debug, XOR decryption, checksum, and hash validation. Basically benzo said "let me ruin someone's weekend" and chose violence.
Flag: EH4X{y0u_gu3ss3d_th4t_r1sc_cr4ckm3}
Initial Recon - "what the hell is this"
Downloaded the file, ran file on it and...
$ file chall
chall: ELF 64-bit LSB executable, UCB RISC-V, RVC, double-float ABI,
version 1 (SYSV), statically linked, stripped
RISC-V?? RISC-V in 2026??
Okay fine. Statically linked + stripped = no symbols, no easy life. Classic RE pain.
But hey, at least we can emulate it:
$ sudo apt install qemu-user # if you dont have it
$ chmod +x chall
$ echo "test" | qemu-riscv64 ./chall╔═══════════════════════════════════╗
║ RISC-V Crackme Challenge ║
║ 'I Guess Bro' - Hard Mode ║
╚═══════════════════════════════════╝
Can you guess the flag? I guess not...
Enter the flag:
Wrong length! Keep guessing...
Alright so its a crackme. Takes input, checks if its the flag. Classic stuff.
Step 1: String Hunting - "the audacity of this binary"
First thing in any RE challenge - check the strings:
$ strings chall | grep -iE "flag|correct|wrong|EH4X|guess"'I Guess Bro' - Hard Mode
Can you guess the flag? I guess not...
Enter the flag:
Wrong length! Keep guessing...
Correct! You guessed it!
Flag: %s
Wrong! I guess you need to try harder...
EH4X{n0t_th3_r34l_fl4g} <--- LMAOOO
EH4X{try_h4rd3r_buddy} <--- BRO REALLY??
Two fake flags sitting right there in plaintext. Benzo really said "here, fall for this."
Don't be that guy. If a flag is sitting in strings output for a 500pt RE challenge... it's bait.
Step 2: Finding the Flag Length
Before we go deep into disassembly, let's figure out the expected length:
for len in $(seq 10 50); do
input=$(python3 -c "print('A'*$len)")
result=$(echo "$input" | qemu-riscv64 ./chall 2>&1)
if ! echo "$result" | grep -q "Wrong length"; then
echo "Length $len passes length check!"
fi
doneLength 35 passes length check!
Flag is 35 characters. Since format is EH4X{...}, thats 5 + 29 + 1 = 35. Makes sense.
Step 3: Decompilation with Ghidra - "time to suffer"
Since this is RISC-V, we can't just throw it in x86 tools. Ghidra handles RISC-V well though.
I used Ghidra headless mode because I'm a terminal enjoyer:
# Import and analyze
/usr/share/ghidra/support/analyzeHeadless /tmp ghidra_project \
-import ./chall -overwrite
# Then run a decompilation script
/usr/share/ghidra/support/analyzeHeadless /tmp ghidra_project \
-process chall -noanalysis -postScript DecompileScript.javaPro tip: If Ghidra doesn't find xrefs for RISC-V, its because RISC-V uses
auipc+addifor address loading (PC-relative), and Ghidra sometimes doesn't resolve these properly. You might need to trace manually or use dynamic analysis.
The Validation Architecture
After decompilation, here's what the binary does:
Benzo really put 5 layers of validation on this thing. Respect the grind honestly.
Step 4: Breaking Down Each Check
Check 1: Length (trivial)
if (strlen(input) != 35) {
puts("Wrong length! Keep guessing...");
exit(1);
}We already got this from bruteforce. Moving on.
Check 2: Anti-Fake-Flag (sneaky)
// At function 0x10732
if (strcmp(input, "EH4X{n0t_th3_r34l_fl4g}") == 0 ||
strcmp(input, "EH4X{try_h4rd3r_buddy}") == 0) {
puts("Nice try, but that's not it!");
exit(1);
}This literally catches people who just run strings and submit. Brutal.
Check 3: Anti-Debug Timing (annoying)
// Pseudocode
t1 = clock_gettime(CLOCK_PROCESS_CPUTIME_ID);
// ... computation loop ...
t2 = clock_gettime(CLOCK_PROCESS_CPUTIME_ID);
if ((t2 - t1) > 49840) {
// Debugger detected, bail out
exit(1);
}If you try to single-step through this in a debugger, the timing check catches you and the binary exits.
How to bypass: Either patch out the timing check, or just do static analysis. You don't actually need to debug this if you can read the decompiled code.
Check 4: XOR Decryption - THE MAIN EVENT
This is where the real flag lives. Function at 0x105cc:
// Pseudocode of the core validation
void check_flag(char *input) {
unsigned char encrypted[] = { /* 35 bytes at 0x57bc8 */ };
int d = 0;
for (int i = 0; i < 35; i++) {
char decrypted = encrypted[i] ^ (d & 0xFF) ^ 0xA5;
if (decrypted != input[i]) {
return FAIL;
}
d += 7;
}
return PASS;
}The algorithm:
- There are 35 encrypted bytes stored in the binary at address
0x57bc8 - Each byte is XOR'd with a rolling counter
(d & 0xFF)and the constant0xA5 - Counter
dstarts at 0 and increments by 7 each iteration - The result must match your input byte-for-byte
Check 5: Format + Checksum
// Verify EH4X{...} format
if (input[0] != 'E' || input[1] != 'H' || input[2] != '4' ||
input[3] != 'X' || input[4] != '{' || input[34] != '}') {
return FAIL;
}
// ASCII sum check
int sum = 0;
for (int i = 0; i < 35; i++) {
sum += input[i];
}
if (sum != 3243) {
return FAIL;
}The sum of all ASCII values must equal exactly 3243. This is a neat integrity check that catches random brute force attempts.
Check 6: Custom Hash
A custom hash function at 0x10574 processes all 35 bytes through XOR, shifts, and
multiplications, then compares against a hardcoded constant. This is the final boss.
Step 5: Extracting the Flag
The easiest way to get the flag is to just decrypt the embedded bytes ourselves:
#!/usr/bin/env python3
"""
i_guess_bro solver - EH4X CTF 2026
Decrypts XOR-encrypted flag from embedded data
"""
# 35 encrypted bytes extracted from address 0x57bc8
# (extracted via: qemu-riscv64 + gdb, or Ghidra hex view)
encrypted = [
# ... bytes dumped from binary ...
]
flag = ""
d = 0
for i in range(35):
decrypted = encrypted[i] ^ (d & 0xFF) ^ 0xA5
flag += chr(decrypted)
d += 7
print(f"Flag: {flag}")
# Verify checksum
ascii_sum = sum(ord(c) for c in flag)
print(f"ASCII sum: {ascii_sum} (expected: 3243)")
assert ascii_sum == 3243, "Checksum mismatch!"
assert flag.startswith("EH4X{") and flag.endswith("}")
print("All checks pass!")But honestly the fastest approach was just dynamic analysis with qemu-riscv64:
dumping the decrypted bytes from memory after the XOR loop runs.
Verification
$ echo "EH4X{y0u_gu3ss3d_th4t_r1sc_cr4ckm3}" | qemu-riscv64 ./chall╔═══════════════════════════════════╗
║ RISC-V Crackme Challenge ║
║ 'I Guess Bro' - Hard Mode ║
╚═══════════════════════════════════╝
Can you guess the flag? I guess not...
Enter the flag:
Correct! You guessed it!
Flag: EH4X{y0u_gu3ss3d_th4t_r1sc_cr4ckm3}
500 points, first blood. GG benzo.
Lessons Learned (for the educational homies)
1. Never Trust Strings Output
If you see a flag in strings for a high-point RE challenge, it's probably bait.
Always verify by actually submitting or tracing the validation logic.
2. RISC-V is Not Scary
RISC-V is actually cleaner than x86. Fewer instructions, more predictable patterns.
Tools like Ghidra and radare2 handle it just fine. Use qemu-user to run RISC-V
binaries on your x86 machine.
3. XOR Encryption is Reversible
XOR is its own inverse: A ^ B ^ B = A. If you can find the key schedule
(in this case: counter * 7, XOR 0xA5), you can decrypt without ever running
the binary.
4. Anti-Debug is Not Anti-Static-Analysis
Timing checks, ptrace detection, etc. only stop debuggers. They don't stop
you from reading the decompiled code and reimplementing the logic in Python.
5. Multiple Validation Layers
Real-world software (and good CTF challenges) use defense in depth:
- Length check (fast reject)
- Known-bad check (anti-honeypot)
- Core validation (XOR compare)
- Integrity check (checksum)
- Hash verification (final confirmation)
Understanding this pattern helps in both CTF and real reverse engineering.
6. Useful Tools for RISC-V RE
| Tool | What For |
|---|---|
qemu-riscv64 | Run RISC-V binaries on x86 |
qemu-riscv64 -g 1234 | Run with GDB server |
gdb-multiarch | Debug any architecture |
Ghidra | Decompile RISC-V to C |
radare2 / r2 | CLI disassembly + analysis |
strings / hexdump | Quick initial recon |
Flag
EH4X{y0u_gu3ss3d_th4t_r1sc_cr4ckm3}
GG benzo. That was a solid challenge.
Written while vibing to lofi beats and questioning my life choices at 3am. If you learned something, drop a star. If you didn't, skill issue tbh.