feat: basic flag challenge working + fix elf loading

This commit is contained in:
2026-05-08 00:20:26 +02:00
parent e4b65d544f
commit 5ec7d85b4e
8 changed files with 279 additions and 39 deletions

View File

@@ -1,5 +1,10 @@
#pragma once
#define EMU_MEM_SIZE 4096u
#define EMU_MEM_SIZE 16384u
#define EMU_PROGRAM_PATH "build/emulated.riscv.elf"
#define EMU_ENTRY_SYMBOL "entry"
#define STACK_SIZE 1024
#define RISCV_MEM_OFFSET 0x80000000
#define SIZE_USERDATA 512
#define SECTION_USERDATA (EMU_MEM_SIZE - STACK_SIZE - SIZE_USERDATA)

230
src/elf.c
View File

@@ -67,6 +67,13 @@ uint32_t read_u32_le(const uint8_t* data) {
| ((uint32_t)data[3] << 24);
}
static void write_u32_le(uint8_t* data, uint32_t value) {
data[0] = (uint8_t)(value & 0xffu);
data[1] = (uint8_t)((value >> 8) & 0xffu);
data[2] = (uint8_t)((value >> 16) & 0xffu);
data[3] = (uint8_t)((value >> 24) & 0xffu);
}
uint32_t align_up(uint32_t value, uint32_t align) {
if (align == 0u) {
return value;
@@ -74,6 +81,192 @@ uint32_t align_up(uint32_t value, uint32_t align) {
return (value + align - 1u) & ~(align - 1u);
}
static int apply_relocations(const uint8_t* file_buf, size_t bytes, uint8_t* memory,
uint32_t mem_size, uint32_t e_shoff, uint16_t e_shentsize, uint16_t e_shnum,
const uint32_t* section_runtime, const uint32_t* section_addr,
const uint32_t* section_size, const uint8_t* section_loaded) {
for (uint16_t i = 0; i < e_shnum; ++i) {
const uint8_t* sh = file_buf + e_shoff + (uint32_t)i * e_shentsize;
uint32_t sh_type = read_u32_le(sh + 4);
uint32_t sh_offset = read_u32_le(sh + 16);
uint32_t sh_size = read_u32_le(sh + 20);
uint32_t sh_link = read_u32_le(sh + 24);
uint32_t sh_info = read_u32_le(sh + 28);
uint32_t sh_entsize = read_u32_le(sh + 36);
if (sh_type != SHT_RELA && sh_type != SHT_REL) {
continue;
}
if (sh_link >= e_shnum || sh_info >= e_shnum) {
return LOAD_ERR_RELOC_INVALID;
}
if (sh_offset >= bytes || sh_size > (uint32_t)bytes - sh_offset) {
return LOAD_ERR_RELOC_INVALID;
}
if (!section_loaded[sh_info]) {
continue;
}
const uint8_t* symtab_sh = file_buf + e_shoff + sh_link * (uint32_t)e_shentsize;
uint32_t symtab_offset = read_u32_le(symtab_sh + 16);
uint32_t symtab_size = read_u32_le(symtab_sh + 20);
uint32_t symtab_entsize = read_u32_le(symtab_sh + 36);
if (symtab_offset >= bytes || symtab_size > (uint32_t)bytes - symtab_offset) {
return LOAD_ERR_STRTAB_INVALID;
}
if (symtab_entsize == 0u) {
symtab_entsize = 16u;
}
if (symtab_entsize < 16u) {
return LOAD_ERR_STRTAB_INVALID;
}
const uint8_t* symtab = file_buf + symtab_offset;
uint32_t symtab_count = symtab_size / symtab_entsize;
uint32_t entry_size = sh_entsize;
if (entry_size == 0u) {
entry_size = (sh_type == SHT_RELA) ? 12u : 8u;
}
if (entry_size < ((sh_type == SHT_RELA) ? 12u : 8u)) {
return LOAD_ERR_RELOC_INVALID;
}
uint32_t count = sh_size / entry_size;
const uint8_t* reltab = file_buf + sh_offset;
for (uint32_t r = 0u; r < count; ++r) {
const uint8_t* rel = reltab + r * entry_size;
uint32_t r_offset = read_u32_le(rel + 0);
uint32_t r_info = read_u32_le(rel + 4);
int32_t r_addend = 0;
if (sh_type == SHT_RELA) {
r_addend = (int32_t)read_u32_le(rel + 8);
}
uint32_t sym_index = r_info >> 8;
uint32_t r_type = r_info & 0xffu;
if (sym_index >= symtab_count) {
return LOAD_ERR_RELOC_INVALID;
}
const uint8_t* sym = symtab + sym_index * symtab_entsize;
uint32_t st_value = read_u32_le(sym + 4);
uint16_t st_shndx = read_u16_le(sym + 14);
uint32_t sym_runtime = 0u;
if (st_shndx == 0u) {
sym_runtime = st_value;
} else {
if (st_shndx >= e_shnum || !section_loaded[st_shndx]) {
return LOAD_ERR_SYMBOL_INVALID_SECTION;
}
uint32_t base_runtime = section_runtime[st_shndx];
uint32_t base_elf = section_addr[st_shndx];
if (st_value < base_elf) {
return LOAD_ERR_SYMBOL_INVALID_SECTION;
}
sym_runtime = base_runtime + (st_value - base_elf);
}
uint32_t reloc_addr = 0u;
if (section_addr[sh_info] != 0u) {
reloc_addr = r_offset;
} else {
if (r_offset >= section_size[sh_info]) {
return LOAD_ERR_RELOC_INVALID;
}
reloc_addr = section_runtime[sh_info] + r_offset;
}
if (reloc_addr >= mem_size || mem_size - reloc_addr < 4u) {
return LOAD_ERR_RELOC_INVALID;
}
uint32_t inst = read_u32_le(memory + reloc_addr);
uint32_t sym_addr = sym_runtime + RISCV_MEM_OFFSET;
int32_t value = (int32_t)sym_addr + r_addend;
switch (r_type) {
case R_RISCV_NONE:
case R_RISCV_RELAX:
break;
case R_RISCV_HI20: {
uint32_t hi20 = (uint32_t)((value + 0x800) >> 12);
inst = (inst & 0x00000fffu) | (hi20 << 12);
write_u32_le(memory + reloc_addr, inst);
break;
}
case R_RISCV_LO12_I: {
uint32_t imm12 = (uint32_t)(value & 0xfffu);
inst = (inst & 0x000fffffu) | (imm12 << 20);
write_u32_le(memory + reloc_addr, inst);
break;
}
case R_RISCV_LO12_S: {
uint32_t imm12 = (uint32_t)(value & 0xfffu);
inst &= 0x01fff07fu;
inst |= (imm12 & 0x1fu) << 7;
inst |= ((imm12 >> 5) & 0x7fu) << 25;
write_u32_le(memory + reloc_addr, inst);
break;
}
case R_RISCV_BRANCH: {
uint32_t reloc_addr_cpu = reloc_addr + RISCV_MEM_OFFSET;
int32_t rel = (int32_t)(value - (int32_t)reloc_addr_cpu);
uint32_t uimm = (uint32_t)rel;
uint32_t imm12 = (uimm >> 12) & 0x1u;
uint32_t imm10_5 = (uimm >> 5) & 0x3fu;
uint32_t imm4_1 = (uimm >> 1) & 0xfu;
uint32_t imm11 = (uimm >> 11) & 0x1u;
inst &= 0x01fff07fu;
inst |= (imm11 << 7);
inst |= (imm4_1 << 8);
inst |= (imm10_5 << 25);
inst |= (imm12 << 31);
write_u32_le(memory + reloc_addr, inst);
break;
}
case R_RISCV_JAL: {
uint32_t reloc_addr_cpu = reloc_addr + RISCV_MEM_OFFSET;
int32_t rel = (int32_t)(value - (int32_t)reloc_addr_cpu);
uint32_t uimm = (uint32_t)rel;
uint32_t imm20 = (uimm >> 20) & 0x1u;
uint32_t imm10_1 = (uimm >> 1) & 0x3ffu;
uint32_t imm11 = (uimm >> 11) & 0x1u;
uint32_t imm19_12 = (uimm >> 12) & 0xffu;
inst = (inst & 0x00000fffu)
| (imm20 << 31)
| (imm19_12 << 12)
| (imm11 << 20)
| (imm10_1 << 21);
write_u32_le(memory + reloc_addr, inst);
break;
}
case R_RISCV_32: {
write_u32_le(memory + reloc_addr, (uint32_t)value);
break;
}
default:
return LOAD_ERR_RELOC_UNSUPPORTED;
}
}
}
return LOAD_OK;
}
int load_elf_object_sections(const char* path, uint8_t* memory, uint32_t mem_size) {
uint8_t* file_buf = NULL;
size_t bytes = 0u;
@@ -117,6 +310,19 @@ int load_elf_object_sections(const char* path, uint8_t* memory, uint32_t mem_siz
return LOAD_ERR_SHT_OUT_OF_BOUNDS;
}
uint32_t* section_runtime = (uint32_t*)calloc(e_shnum, sizeof(uint32_t));
uint32_t* section_addr = (uint32_t*)calloc(e_shnum, sizeof(uint32_t));
uint32_t* section_size = (uint32_t*)calloc(e_shnum, sizeof(uint32_t));
uint8_t* section_loaded = (uint8_t*)calloc(e_shnum, sizeof(uint8_t));
if (!section_runtime || !section_addr || !section_size || !section_loaded) {
free(section_runtime);
free(section_addr);
free(section_size);
free(section_loaded);
free(file_buf);
return LOAD_ERR_OOM;
}
uint32_t cursor = 0u;
for (uint16_t i = 0; i < e_shnum; ++i) {
@@ -129,6 +335,9 @@ int load_elf_object_sections(const char* path, uint8_t* memory, uint32_t mem_siz
uint32_t sh_size = read_u32_le(sh + 20);
uint32_t sh_addralign = read_u32_le(sh + 32);
section_addr[i] = sh_addr;
section_size[i] = sh_size;
if ((sh_flags & SHF_ALLOC) == 0u || sh_size == 0u) {
continue;
}
@@ -137,6 +346,10 @@ int load_elf_object_sections(const char* path, uint8_t* memory, uint32_t mem_siz
if (dst >= mem_size || sh_size > mem_size - dst) {
fprintf(stderr, "Section %u does not fit into emulator memory\n", (unsigned)i);
free(section_runtime);
free(section_addr);
free(section_size);
free(section_loaded);
free(file_buf);
return LOAD_ERR_SECTION_TOO_LARGE;
}
@@ -146,19 +359,34 @@ int load_elf_object_sections(const char* path, uint8_t* memory, uint32_t mem_siz
} else {
if (sh_offset >= bytes || sh_size > (uint32_t)bytes - sh_offset) {
fprintf(stderr, "Section %u exceeds object file bounds\n", (unsigned)i);
free(section_runtime);
free(section_addr);
free(section_size);
free(section_loaded);
free(file_buf);
return LOAD_ERR_SECTION_FILE_BOUNDS;
}
memcpy(memory + dst, file_buf + sh_offset, sh_size);
}
section_runtime[i] = dst;
section_loaded[i] = 1u;
if (sh_addr == 0u) {
cursor = dst + sh_size;
}
}
int reloc_status = apply_relocations(file_buf, bytes, memory, mem_size,
e_shoff, e_shentsize, e_shnum, section_runtime, section_addr,
section_size, section_loaded);
free(section_runtime);
free(section_addr);
free(section_size);
free(section_loaded);
free(file_buf);
return LOAD_OK;
return reloc_status;
}
int resolve_elf_symbol_memory_offset(const char* path, const char* symbol_name,

View File

@@ -8,10 +8,21 @@
#define ELFCLASS32 1
#define ELFDATA2LSB 1
#define SHT_NOBITS 8u
#define SHT_SYMTAB 2u
#define SHT_RELA 4u
#define SHT_NOBITS 8u
#define SHT_REL 9u
#define SHF_ALLOC 0x2u
#define R_RISCV_32 1u
#define R_RISCV_NONE 0u
#define R_RISCV_BRANCH 16u
#define R_RISCV_JAL 17u
#define R_RISCV_HI20 26u
#define R_RISCV_LO12_I 27u
#define R_RISCV_LO12_S 28u
#define R_RISCV_RELAX 51u
#include <stdint.h>
enum {
@@ -29,7 +40,9 @@ enum {
LOAD_ERR_STRTAB_INVALID = -11,
LOAD_ERR_SYMBOL_NOT_FOUND = -12,
LOAD_ERR_SYMBOL_INVALID_SECTION = -13,
LOAD_ERR_SYMBOL_OUT_OF_RANGE = -14
LOAD_ERR_SYMBOL_OUT_OF_RANGE = -14,
LOAD_ERR_RELOC_INVALID = -15,
LOAD_ERR_RELOC_UNSUPPORTED = -16
};
uint16_t read_u16_le(const uint8_t* data);

View File

@@ -1,13 +0,0 @@
#include "function.h"
#include "custom_instructions.h"
#include <stdint.h>
int32_t operation(int32_t a) {
return trunc_xor(a, 3);
}

View File

@@ -1,6 +0,0 @@
#pragma once
#include <stdint.h>
int32_t operation(int32_t a);

View File

@@ -1,10 +1,14 @@
#include <stdint.h>
#include "function.h"
#include "../config.h"
#include "custom_instructions.h"
#include "generated.h"
uint8_t entry(uint8_t a) {
return operation(a);
uint32_t entry(void* userdata) {
uint8_t len = (uint8_t)*((uint8_t*)userdata);
char* userflag = (char*)(userdata + 1);
return check(userflag, len);
}

View File

@@ -15,14 +15,28 @@
int main(){
const int maxninput = 255;
printf("Please enter the flag: ");
fflush(stdout);
char userinput[maxninput];
fgets(userinput, sizeof(userinput), stdin);
// Replace newline with null terminator
userinput[strcspn(userinput, "\r\n")] = 0;
uint8_t memory[EMU_MEM_SIZE]; //4kb of internal memory
tinyriscv_core cpu; //create the emulation core
memset(&cpu, 0, sizeof(cpu));
cpu.mem = memory; //use the uint8_t array as the memory
cpu.mem_size = EMU_MEM_SIZE;
memset(memory, 0, sizeof(memory));
uint8_t real_len = strnlen(userinput, maxninput);
int load_status = load_elf_object_sections(EMU_PROGRAM_PATH, memory, cpu.mem_size);
// Load user data, first, the string length
memory[SECTION_USERDATA] = real_len;
memcpy(memory + SECTION_USERDATA + 1, userinput, real_len);
// Load elf, keep 512 bytes as safe buffer
int load_status = load_elf_object_sections(EMU_PROGRAM_PATH, memory, cpu.mem_size - SIZE_USERDATA);
if (load_status != LOAD_OK) {
fprintf(stderr, "ELF load failed with code %d for '%s'\n", load_status, EMU_PROGRAM_PATH);
fflush(stderr);
@@ -43,9 +57,7 @@ int main(){
tinyriscv_init(&cpu); //initialise core for execution
cpu.pc = tinyriscv_MEM_OFFSET + entry_offset;
// Call operation(a, b) using RV32 calling convention:
// a0=x10, return value in a0, return address in ra=x1.
cpu.x[10] = TEST_A;
cpu.x[10] = tinyriscv_MEM_OFFSET + SECTION_USERDATA;
cpu.x[1] = tinyriscv_MEM_OFFSET + cpu.mem_size;
uint32_t steps = 0u;
@@ -59,15 +71,12 @@ int main(){
return 2;
}
uint32_t actual = (uint32_t)cpu.x[10];
uint32_t expected = ((TEST_A << 3) ^ 0xFF) >> 3;
printf("operation(%u) -> %u (expected %u)\n",
(unsigned)TEST_A,
(unsigned)actual, (unsigned)expected);
uint32_t result = (uint32_t)cpu.x[10];
if (actual != expected) {
fprintf(stderr, "Verification failed\n");
return 3;
if(result == 1) {
printf("Congrats! That's the flag!");
} else {
printf("Nope, sorry, that's incorrect!");
}
return 0;