CTF Challenge: RISC It for the Biscuit
I have developed a RISC-V 64bit (RV64IM) virtual machine you can download here. I decided to keep it simple, and didn't add an MMU, so it uses the host process memory addresses. I doubt thats going to cause any issues. There is a flag.txt the in same directory as the VM that you need to read by providing the VM with a binary to execute.
You can compile C code for the virtual machine like so:
#include <stdint.h>
extern void syscall_exit(void);
extern void print_char_string(char* str);
extern void print_u64_hex(uint64_t val);
extern void print_u64(uint64_t val);
extern void print_new_line(void);
// Return the n-th prime (n >= 1). For n == 1, returns 2.
uint64_t find_nth_prime(uint64_t n) {
if (n == 0) {
return 0; // or handle as error
}
uint64_t t0 = 1; // current number being tested
uint64_t t3 = 0; // count of primes found
while (1) {
t0 += 1; // next candidate
int t2 = 0; // 0 = assume prime, 1 = not prime
// special case: 2 is prime
if (t0 == 2) {
t3 += 1;
if (t3 == n) {
return t0;
}
continue;
}
// check divisors from 2 up to t4*t4 > t0
uint64_t t4 = 2;
while (1) {
uint64_t t1 = t4 * t4;
if (t1 > t0) {
break; // no divisor found
}
if (t0 % t4 == 0) { // found a divisor
t2 = 1;
break;
}
t4 += 1;
}
if (t2 == 0) { // still marked as prime
t3 += 1;
if (t3 == n) {
return t0; // t0 is the n-th prime
}
}
}
}
void run(void) {
uint64_t nth_prime_number_to_find = 10000;
uint64_t nth_prime = find_nth_prime(nth_prime_number_to_find);
print_char_string("The ");
print_u64(nth_prime_number_to_find);
print_char_string("th prime number is: ");
print_u64(nth_prime);
print_new_line();
syscall_exit();
}
This uses a couple syscall's the VM exposes, those are defined like so:
.section .text
.globl _start, syscall_exit, print_char_string, print_u64_hex, print_u64, print_new_line
_start:
call run
syscall_exit:
li a7, 0x5d
ecall
print_char_string:
li a7, 100
ecall
ret
print_u64_hex:
li a7, 101
ecall
ret
print_u64:
li a7, 102
ecall
ret
print_new_line:
li a7, 103
ecall
ret
Lastly, you need a linker script like:
ENTRY(_start)
SECTIONS {
. = 0x80000000;
.text : {
*(.text*)
*(.rodata*)
}
.data : {
*(.data*)
}
.bss : {
*(.bss*)
*(COMMON)
}
}
To build the example, use Developer Command Prompt for VS or Developer PowerShell for VS. In the Visual Studio Installer, you will need C++ Clang tools for Windows.
clang -target riscv64 -march=rv64im -mcmodel=medany -nostdlib -Wl,-T,link.ld stub.s prime.c -o prime.o
llvm-objcopy -O binary prime.o prime.bin
# Look at the disassembly
llvm-objdump --disassemble prime.o
You should see:
PS Release> .\RV64VMv4.exe .\prime.bin
The 10000th prime number is: 104729
When running the VM, the exit code indicates the following:
SUCCESS(0): It exited successful using the exit syscall.
INVALID_INSTRUCTION(1): You give the VM bad machine code.
INVALID_SYSCALL(2): You called something other than the 5 syscalls in the example.
Submitting a Solution
You can give the binary to acebond on Discord, and I'll run it on the latest version of Windows 11 25H2 and give you the output, and add you to the list of solvers if it prints the flag.
Smart People Who Have Solved It
:(