700 words
4 minutes
EHAX CTF 2026 - i guess bro - Reverse Engineering Writeup

Category: Reverse Engineering
Flag: EH4X{y0u_gu3ss3d_th4t_r1sc_cr4ckm3}

Challenge Description#

meh yet another crackme challenge

Analysis#

file "chall"
chall: ELF 64-bit LSB executable, UCB RISC-V, RVC, double-float ABI, version 1 (SYSV), statically linked, ... stripped
checksec "chall"
Arch:       riscv64-64-little
RELRO:      Partial RELRO
Stack:      No canary found
NX:         NX enabled
PIE:        No PIE (0x10000)

The first pass showed a static, stripped RISC-V binary. Since qemu-riscv64 was not present in this environment, I treated it as a static reversing problem and leaned on r2 decompilation plus constant extraction instead of runtime execution.

strings -a "chall" | rg -i "I Guess Bro|Wrong length|Correct!|Flag: %s|EH4X\{"
'I Guess Bro' - Hard Mode
Wrong length! Keep guessing...
Correct! You guessed it!
Flag: %s
EH4X{n0t_th3_r34l_fl4g}
EH4X{try_h4rd3r_buddy}

Seeing two ready-made EH4X{...} candidates this early looked suspicious, and the challenge style screamed decoy checks.

r2 -e scr.color=0 -A -q -c "s 0x1037e; af; pdg" "chall" | rg "fcn.00010732|Wrong length|Correct!|Flag: %s|== 0x23|Input error"
fcn.000151c4("Input error!");
if (iVar1 == 0x23) {
    iVar1 = fcn.00010732(auStack_90);
        fcn.000151c4("\nšŸŽ‰ Correct! You guessed it!\n");
        fcn.000110d4("Flag: %s\n",auStack_90);
    fcn.000151c4("Wrong length! Keep guessing...");
r2 -e scr.color=0 -A -q -c "s 0x10732; af; pdg" "chall" | rg "n0t_th3_r34l_fl4g|try_h4rd3r_buddy|Debugger detected|0xc351|fcn.000105cc|fcn.00010622|fcn.00010574|0x1fb53791|0xcab|-0x7e30f90b5f734a11"
iVar1 = fcn.0001eaf0(param_1,"EH4X{n0t_th3_r34l_fl4g}");
iVar1 = fcn.0001eaf0(param_1,"EH4X{try_h4rd3r_buddy}");
if (iVar2 - iVar1 < 0xc351) {
    iVar1 = fcn.000105cc(param_1);
    if ((iVar1 != 0) && (iVar1 = fcn.00010622(param_1), iVar1 != 0)) {
        iVar1 = fcn.00010574(param_1,0x23);
        return iVar1 == -0x7e30f90b5f734a11;
    }
    fcn.000151c4("Debugger detected! Exiting...");

That decompilation confirmed the trick: both visible flags are explicitly rejected first, then real validation happens through three helper checks. So the shortest path was to reverse those helpers and recover the expected 35-byte input directly.

r2 -e scr.color=0 -A -q -c "s 0x105cc; af; pdg" "chall" | rg "0x57bc8|0x57beb|\^ 0xa5|uVar4 = uVar4 \+ 7"
puVar5 = 0x57bc8;
*puVar3 = uVar1 ^ uVar4 ^ 0xa5;
uVar4 = uVar4 + 7;
} while (puVar5 != 0x57beb);
r2 -q -c "pxj 35 @ 0x57bc8" "chall"
[224,234,159,232,194,255,191,225,194,253,150,219,130,141,244,168,138,166,179,20,93,105,77,53,126,105,76,123,19,90,20,23,40,113,54]
# decode_table.py
data = [
    224,234,159,232,194,255,191,225,194,253,150,219,130,141,244,168,138,
    166,179,20,93,105,77,53,126,105,76,123,19,90,20,23,40,113,54
]

out = []
k = 0
for b in data:
    out.append((b ^ k ^ 0xA5) & 0xFF)
    k = (k + 7) & 0xFF

print(bytes(out).decode())
data=[224,234,159,232,194,255,191,225,194,253,150,219,130,141,244,168,138,166,179,20,93,105,77,53,126,105,76,123,19,90,20,23,40,113,54]
out=[]
k=0
for b in data:
    out.append((b^k^0xa5)&0xff)
    k=(k+7)&0xff
print(bytes(out).decode())
EH4X{y0u_gu3ss3d_th4t_r1sc_cr4ckm3}

The decoded string looked right, but I still verified it against every remaining constraint from fcn.00010622 and fcn.00010574 so it was not just a plausible-looking decode.

r2 -e scr.color=0 -A -q -c "s 0x10622; af; pdg" "chall" | rg "param_1\[0x22\] == 0x7d|iVar3 == 0xcab|0x3b9aca07|0x1fb53791|aiStack_18\[0\] = 5|aiStack_18\[5\] = 0x1e"
((param_1[4] == 0x7b && (param_1[0x22] == 0x7d)))) {
if (iVar3 == 0xcab) {
aiStack_18[0] = 5;
aiStack_18[5] = 0x1e;
uVar5 = (param_1[iVar3] * uVar5) % 0x3b9aca07;
return uVar5 == 0x1fb53791;
# verify_constraints.py
flag = b"EH4X{y0u_gu3ss3d_th4t_r1sc_cr4ckm3}"

print("len", len(flag))
print("prefix", flag[:5] == b"EH4X{" and flag[0x22] == 0x7D)
print("sum", hex(sum(flag)))

idx = [5, 10, 15, 20, 25, 30]
mod = 0x3B9ACA07
prod = 1
for i in idx:
    prod = (prod * flag[i]) % mod
print("prod", hex(prod))

def mix(x: int, i: int) -> int:
    x = ((0x5851F42D4C957F2D >> (i & 0x3F)) ^ x) & 0xFFFFFFFFFFFFFFFF
    return (((x >> 0x33) + ((x * 0x2000) & 0xFFFFFFFFFFFFFFFF)) ^ 0xEBFA848108987EB0) & 0xFFFFFFFFFFFFFFFF

x = 0xDEADBEEF
for i, b in enumerate(flag):
    x = mix(x ^ ((b << ((i & 7) << 3)) & 0xFFFFFFFFFFFFFFFF), i)

print("hash", hex(x))
print("target", hex((-0x7E30F90B5F734A11) & 0xFFFFFFFFFFFFFFFF))
flag=b'EH4X{y0u_gu3ss3d_th4t_r1sc_cr4ckm3}'
print('len',len(flag))
print('prefix', flag[:5]==b'EH4X{' and flag[0x22]==0x7d)
print('sum', hex(sum(flag)))
idx=[5,10,15,20,25,30]
mod=0x3b9aca07
prod=1
for i in idx:
    prod=(prod*flag[i])%mod
print('prod', hex(prod))
def mix(x,i):
    x=((0x5851f42d4c957f2d >> (i & 0x3f)) ^ x) & 0xffffffffffffffff
    return (((x >> 0x33) + ((x * 0x2000)&0xffffffffffffffff)) ^ 0xebfa848108987eb0) & 0xffffffffffffffff
x=0xdeadbeef
for i,b in enumerate(flag):
    x=mix(x ^ ((b << ((i & 7)<<3)) & 0xffffffffffffffff), i)
print('hash', hex(x))
print('target', hex((-0x7e30f90b5f734a11) & 0xffffffffffffffff))
len 35
prefix True
sum 0xcab
prod 0x1fb53791
hash 0x81cf06f4a08cb5ef
target 0x81cf06f4a08cb5ef

Finally I re-extracted from raw file bytes at the decoded table offset to make sure the recovered flag came straight from the challenge artifact, not from any decompiler artifact.

smug

# extract_flag.py
from pathlib import Path

p = Path("/home/rei/Downloads/chall").read_bytes()
enc = p[0x47BC8:0x47BC8 + 35]
flag = bytes((b ^ ((i * 7) & 0xFF) ^ 0xA5) & 0xFF for i, b in enumerate(enc))
print(flag.decode())
from pathlib import Path
p=Path('/home/rei/Downloads/chall').read_bytes()
enc=p[0x47bc8:0x47bc8+35]
flag=bytes((b ^ ((i*7)&0xff) ^ 0xa5) & 0xff for i,b in enumerate(enc))
print(flag.decode())
EH4X{y0u_gu3ss3d_th4t_r1sc_cr4ckm3}

Solution#

# solve.py
from pathlib import Path


def decode_flag(binary_path: str) -> str:
    data = Path(binary_path).read_bytes()
    enc = data[0x47BC8:0x47BC8 + 35]
    out = bytes((b ^ ((i * 7) & 0xFF) ^ 0xA5) & 0xFF for i, b in enumerate(enc))
    return out.decode()


if __name__ == "__main__":
    print(decode_flag("chall"))
python3.12 solve.py
EH4X{y0u_gu3ss3d_th4t_r1sc_cr4ckm3}
EHAX CTF 2026 - i guess bro - Reverse Engineering Writeup
https://blog.rei.my.id/posts/61/ehax-ctf-2026-i-guess-bro-reverse-engineering-writeup/
Author
Reidho Satria
Published at
2026-03-01
License
CC BY-NC-SA 4.0