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, ... strippedchecksec "chall"Arch: riscv64-64-littleRELRO: Partial RELROStack: No canary foundNX: NX enabledPIE: 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 ModeWrong length! Keep guessing...Correct! You guessed it!Flag: %sEH4X{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]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 = 0for 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=0for b in data: out.append((b^k^0xa5)&0xff) k=(k+7)&0xffprint(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;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 = 0x3B9ACA07prod = 1for i in idx: prod = (prod * flag[i]) % modprint("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 = 0xDEADBEEFfor 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=0x3b9aca07prod=1for i in idx: prod=(prod*flag[i])%modprint('prod', hex(prod))def mix(x,i): x=((0x5851f42d4c957f2d >> (i & 0x3f)) ^ x) & 0xffffffffffffffff return (((x >> 0x33) + ((x * 0x2000)&0xffffffffffffffff)) ^ 0xebfa848108987eb0) & 0xffffffffffffffffx=0xdeadbeeffor i,b in enumerate(flag): x=mix(x ^ ((b << ((i & 7)<<3)) & 0xffffffffffffffff), i)print('hash', hex(x))print('target', hex((-0x7e30f90b5f734a11) & 0xffffffffffffffff))len 35prefix Truesum 0xcabprod 0x1fb53791hash 0x81cf06f4a08cb5eftarget 0x81cf06f4a08cb5efFinally 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.

from pathlib import Path
p = Path("/home/LIGHT/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 Pathp=Path('/home/LIGHT/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
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.pyEH4X{y0u_gu3ss3d_th4t_r1sc_cr4ckm3}