Description
RISC-V binary analysis.
Setup
Download the RISC-V binary from the challenge page.
Install RISC-V cross-compilation tools if needed: `sudo apt install binutils-riscv64-linux-gnu`.
wget <challenge_url>/riscy-business # download the binarySolution
- Step 1Identify the architecture and disassembleRun `file` to confirm the binary is RISC-V ELF. Use `riscv64-linux-gnu-objdump` to disassemble, or load the binary into Ghidra with the RISC-V processor module to get decompiled output.
file riscy-businessriscv64-linux-gnu-objdump -d riscy-business | head -80strings riscy-businessLearn more
RISC-V is an open-source instruction set architecture (ISA) based on reduced instruction set computing (RISC) principles. Unlike x86, RISC-V uses a small, fixed set of simple instructions. The standard integer register file has 32 registers named
x0–x31, with conventional aliases:a0–a7for function arguments,s0–s11for saved registers, andrafor the return address.Ghidra has built-in RISC-V support. When importing the binary, select the correct language - RISCV:LE:64:default for a 64-bit little-endian binary. The decompiler will produce pseudo-C output that is much easier to read than the raw assembly.
- Step 2Trace the character-by-character comparison logicIn Ghidra, locate the main or check function. The binary reads a string from the user and compares each character individually against hardcoded expected values. Note each expected byte in order.
Learn more
Character-by-character comparison is a classic pattern in CTF reverse engineering challenges. In RISC-V assembly, this typically manifests as a loop with a
lb(load byte) instruction reading from the input buffer, followed by a comparison instruction (beq/bne) against an immediate value or a loaded constant.Key RISC-V instructions to recognize:
lb rd, offset(rs1)- load byte from memorybeq rs1, rs2, label- branch if equalbne rs1, rs2, label- branch if not equalli rd, imm- load immediate constant into register
When Ghidra decompiles these patterns, it produces
if (input[i] != expected[i]) fail(). Read the expected values in loop order to reconstruct the flag. - Step 3Extract all expected bytes and form the flagCollect every expected byte value from the comparison chain, convert from hex/decimal to ASCII characters, and assemble them in index order to form the complete flag string.
# Use Python to convert extracted byte values to characterspython3 -c "bytes_list = [0x70,0x69,0x63,0x6f]; print(''.join(chr(b) for b in bytes_list))"# Or use GDB with QEMU emulation to trace executionqemu-riscv64 ./riscy-businessLearn more
If the binary cannot run natively on x86, use QEMU user-mode emulation:
qemu-riscv64 ./riscy-businesslets you run RISC-V binaries on an x86 host without a full VM. Combined withqemu-riscv64 -g 1234 ./riscy-businessand a RISC-V-aware GDB, you can set breakpoints and single-step through the comparison logic dynamically.Dynamic analysis (running the binary under a debugger) is often faster than purely static analysis for character-by-character checks, because you can watch registers change in real time as each character is validated. A wrong character causes an early exit - binary search strategies can be used to efficiently find each correct character.
Flag
picoCTF{...}
The RISC-V binary validates the flag one byte at a time using lb + beq/bne instructions - read the expected immediate values from each comparison in Ghidra and assemble them into the flag string.