riscy businesspicoMini by redpwn Solution

Published: April 2, 2026

Description

RISC-V binary analysis.

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 binary

Solution

  1. Step 1Identify the architecture and disassemble
    Run `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-business
    riscv64-linux-gnu-objdump -d riscy-business | head -80
    strings riscy-business
    Learn 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 x0x31, with conventional aliases: a0a7 for function arguments, s0s11 for saved registers, and ra for 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.

  2. Step 2Trace the character-by-character comparison logic
    In 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 memory
    • beq rs1, rs2, label - branch if equal
    • bne rs1, rs2, label - branch if not equal
    • li 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.

  3. Step 3Extract all expected bytes and form the flag
    Collect 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 characters
    python3 -c "bytes_list = [0x70,0x69,0x63,0x6f]; print(''.join(chr(b) for b in bytes_list))"
    # Or use GDB with QEMU emulation to trace execution
    qemu-riscv64 ./riscy-business
    Learn more

    If the binary cannot run natively on x86, use QEMU user-mode emulation: qemu-riscv64 ./riscy-business lets you run RISC-V binaries on an x86 host without a full VM. Combined with qemu-riscv64 -g 1234 ./riscy-business and 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.

Want more picoMini by redpwn writeups?

Useful tools for Reverse Engineering

Related reading

What to try next