Challenge
Given is a network service which simulates a tiny blockchain VM. The service prints a mined block summary and asks you to submit:
- Nonce (hex)
- A RISC-V assembly contract, terminated by an empty line
You can interact via netcat or a socket client. An ELF binary was provided for local analysis.
Step 1 --- Initial analysis
- I ran the provided ELF locally to inspect prompts and behavior. The binary prints a block summary and asks for the nonce and the RISC-V assembly contract. The service reads the assembly until it receives a completely empty line, then assembles and validates the block.
- Static inspection (strings, nm, objdump) revealed symbols for the ecall handler, block validation, hash calculation, and the RISC-V assembler/executor.
Observed prompt sequence:
Submit your new block's data.
Nonce (as hex):
Enter your RISC-V assembly contract (end with a blank line):
Step 2 --- Key reversing notes
is_block_validcontains a short-circuit: if the block's nonce equals0xdeadbeefthe function returns valid immediately, bypassing PoW and other checks.ecall_handlerchecks a register for the syscall id. If the syscall id equals1337, it prints a message and readsflag.txtthen prints it. The rodata contains the stringECALL 1337: Nice! Here's your flag!.- The assembler accepts a subset of RISC-V (including
addiandecall). The server requires the contract input to be terminated by a blank line; otherwise it waits for more input.
Attack idea: submit the magic nonce to bypass validation, then submit a tiny RISC-V contract that sets the ECALL number to 1337 and calls ecall.
Step 3 --- Exploit / manual solve
Manual steps using nc (interactive):
- Connect:
nc riskychain.challs.infobahnc.tf 1337
- When prompted, submit the nonce (hex):
deadbeef
- Then submit the contract lines, and remember to end with one extra blank line so the server knows the contract is finished:
addi x10, x0, 1337
ecall
Why this works:
addi x10, x0, 1337sets register x10 to 1337. The ECALL handler reads the syscall number from x10.ecalltriggers the handler. With the block validated via0xdeadbeef, the handler prints the flag when it sees syscall 1337.
# illustrate what to send
s.sendall(b"deadbeef\n")
s.sendall(b"addi x10, x0, 1337\n")
s.sendall(b"ecall\n\n")
Flag
infobahn{Th3_futur3_15_m3t4l1c_4nd_RISC-V}
Notes
- The intentional backdoor is the
0xdeadbeefnonce inis_block_valid. That is the designed path to the flag for this challenge. - The RISC-V interpreter executes the contract after block validation, and ECALL 1337 discloses the flag.
- Remember the final blank line to terminate the contract input.