hitrugan
hitrugan
Platform: rev_kids20 | Category: Reversing | Type: Challenge | Difficulty: Medium | OS: Linux | Author: XESXOR | Date: 2026-06-15 | Status: Solved Techniques: conditional_jump_patch, jne_to_nop, static_binary_patch, win_branch_force
Summary
Task: 64-bit PIE keygen crackme that compares hex serial input against transform_rand_value(rand()) where the PRNG is time-seeded, giving one attempt per run so the leaked serial cannot be replayed. Solution: NOP the jne (75 18 -> 90 90) at file offset 0x10f9 that guards the print-flag function, then run the patched binary under Docker amd64.
hitrugan — rev_kids20
Description
Survival of the hitriest. Enter the correct serial to get the flag.
A 64-bit x86-64 PIE ELF hitrugan.elf (not stripped), keygen-style. It prints:
[[[ GENERATING UNIQUE SERIAL NUMBER ]]]
[[[ ENTER SERIAL TO GET THE FLAG: ____ ]]]
[[[ VALIDATING ]]]
On a wrong serial it leaks the expected value:
Haha, you fail! Correct serial was %llx, you sucker!!!
Goal: get the flag by entering the correct serial.
Analysis
Relevant functions: main @0xff1, transform_rand_value @0x980, obfuscated_print_the_flag @0x1139, plus _1_stringEncoder / _3_stringEncoder.
main logic:
srand(time(NULL))— PRNG seeded with the current time.- Reads the serial with
scanf("%llx", &input)(hexadecimal input). - Computes
v = transform_rand_value(rand())and masks to 48 bits:v &= 0xffffffffffff. - Compares
input == v. If equal →obfuscated_print_the_flag(). Otherwise prints the failure message that leaks the correct%llxserial.
transform_rand_value @0x980 is a long deterministic chain of mul/add/xor/rol/ror reductions masked to 48 bits. It is fully deterministic given rand().
The trap: because the PRNG is seeded with time(NULL), the expected serial changes every run, and the program reads input once per process. So:
- You cannot leak-then-replay: the leaked "correct serial" is only printed after the (already failed) comparison, and the process exits.
- Re-running produces a different serial (new time seed), so the previously leaked value is stale.
Reversing the full PRNG (glibc random()) + transform_rand_value chain to predict the serial for the current time-seed before submitting is possible but slow and fragile.
Solution
This is a classic "win function guarded by one conditional jump" pattern. Instead of predicting the time-seeded serial, force the success branch.
The conditional jump right after the serial compare is:
jne <fail> ; opcode 75 18, at file offset 0x10f9
Patch it to two NOPs (90 90) so execution always falls through into obfuscated_print_the_flag regardless of input.
bash read-only • copy allowed# verify the guard jump xxd -s 0x10f9 -l 2 hitrugan.elf # -> 7518 (jne +0x18)
python read-only • copy allowed#!/usr/bin/env python3 # Patch the jne guarding the flag printer into two NOPs. data = bytearray(open("hitrugan.elf", "rb").read()) off = 0x10f9 assert data[off:off+2] == b"\x75\x18", "unexpected bytes at guard jump" data[off:off+2] = b"\x90\x90" # jne -> nop; nop open("hitrugan_patched.elf", "wb").write(data) print("patched", hex(off))
Run the patched binary (under Docker amd64 / qemu-user on a non-x86 host). Any input works because the comparison no longer matters:
bash read-only • copy allowedecho 0 | docker run --rm -i --platform linux/amd64 \ -v "$PWD":/w -w /w ubuntu:22.04 ./hitrugan_patched.elf
Output:
[[[ GENERATING UNIQUE SERIAL NUMBER ]]]
[[[ ENTER SERIAL TO GET THE FLAG: ____ ]]]
[[[ VALIDATING ]]]
flag: REDACTED
Note: calling obfuscated_print_the_flag directly via gdb does not work here — gdb/ptrace is unavailable under qemu-user in Docker. The 2-byte jne→nop patch is the cleanest reliable solve.