diff --git a/.gitmodules b/.gitmodules index 97b66a1..70158fc 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "tinyriscv"] path = tinyriscv url = ssh://git@ssh.git.xn--k-0fa.se:222/sas2026/tinyriscv.git +[submodule "tiny-AES-c"] + path = tiny-AES-c + url = git@github.com:kokke/tiny-AES-c.git diff --git a/Makefile b/Makefile index 497159b..2656672 100644 --- a/Makefile +++ b/Makefile @@ -16,10 +16,14 @@ HOST_LDFLAGS ?= -Wl,-s EMU_SRC_FILES := $(wildcard src/emulated/*.c) EMU_OBJ_FILES := $(EMU_SRC_FILES:src/emulated/%.c=build/%.riscv.o) EMU_OBJ := build/emulated.riscv.elf +EMU_OBJ_LICENSE := build/license GENERATED := build/include/generated.h -HOST_SRC := src/main.c src/elf.c +HOST_SRC_EXTRA := src/elf.c src/hwinfo.c src/util.c tiny-AES-c/aes.c +HOST_SRC := src/main.c $(HOST_SRC_EXTRA) +HOST_SRC_ENCRYPTOR := src/encryptor.c $(HOST_SRC_EXTRA) +HOST_INCLUDE := -I src -I tinyriscv/src -I tiny-AES-c ifeq ($(OS),Windows_NT) HOST_EXE := build/main.exe @@ -27,6 +31,13 @@ else HOST_EXE := build/main endif +ifeq ($(OS),Windows_NT) + HOST_EXE_ENCRYPTOR := build/encryptor.exe +else + HOST_EXE_ENCRYPTOR := build/encryptor +endif + + # Disassembly RISCV_OBJDUMP ?= $(patsubst %-gcc,%-objdump,$(RISCV_CC)) _DISAS_TARGETS := $(filter disas-%,$(MAKECMDGOALS)) @@ -36,18 +47,29 @@ _DISAS_FILES := $(if $(_DISAS_TARGETS),$(filter %.elf %.o,$(MAKECMDGOALS))) .PHONY: all emulated-riscv host clean disas-risc -all: $(HOST_EXE) $(EMU_OBJ) +all: $(HOST_EXE) $(EMU_OBJ_LICENSE) $(HOST_EXE_ENCRYPTOR) -emulated-riscv: $(EMU_OBJ) +emulated-riscv: $(EMU_OBJ_LICENSE) -host: $(HOST_EXE) +host: $(HOST_EXE) $(HOST_EXE_ENCRYPTOR) -$(HOST_EXE): $(HOST_SRC) tinyriscv/src/tinyriscv.h src/config.h src/elf.h | build - $(HOST_CC) $(HOST_CFLAGS) $(HOST_LDFLAGS) -I src -I tinyriscv/src $(HOST_SRC) -o $@ +$(HOST_EXE): $(HOST_SRC) | build + $(HOST_CC) $(HOST_CFLAGS) $(HOST_LDFLAGS) $(HOST_INCLUDE) $(HOST_SRC) -o $@ -build/%.riscv.o: src/emulated/%.c $(GENERATED) | build +$(HOST_EXE_ENCRYPTOR): $(HOST_SRC_ENCRYPTOR) | build + $(HOST_CC) $(HOST_CFLAGS) $(HOST_LDFLAGS) $(HOST_INCLUDE) $(HOST_SRC_ENCRYPTOR) -o $@ + +build/%.riscv.o: src/emulated/%.c $(GENERATED) $(HOST_EXE_ENCRYPTOR) | build $(RISCV_CC) $(RISCV_ARCH_FLAGS) -I $(dir $(GENERATED)) -I src/emulated -c $< -o $@ + +EMU_OBJ_LICENSE_IF_EXISTS := $(wildcard $(EMU_OBJ_LICENSE)) +$(EMU_OBJ_LICENSE): $(EMU_OBJ) | build +ifneq ($(EMU_OBJ_LICENSE_IF_EXISTS),) + rm $(EMU_OBJ_LICENSE_IF_EXISTS) +endif + $(HOST_EXE_ENCRYPTOR) $(EMU_OBJ) $@ + $(EMU_OBJ): $(EMU_OBJ_FILES) | build $(RISCV_CC) $(RISCV_ARCH_FLAGS) -nostdlib -Wl,-e,entry -Wl,-Ttext=0 -Wl,--emit-relocs $(EMU_OBJ_FILES) -o $@ diff --git a/src/config.h b/src/config.h index b3a70a5..a4e4a6a 100644 --- a/src/config.h +++ b/src/config.h @@ -1,7 +1,7 @@ #pragma once #define EMU_MEM_SIZE 16384u -#define EMU_PROGRAM_PATH "build/emulated.riscv.elf" +#define EMU_PROGRAM_PATH "build/license" #define EMU_ENTRY_SYMBOL "entry" #define STACK_SIZE 1024 diff --git a/src/elf.c b/src/elf.c index dca16fe..02e8de5 100644 --- a/src/elf.c +++ b/src/elf.c @@ -8,45 +8,41 @@ #include -static int read_file_bytes(const char* path, uint8_t** out_buf, size_t* out_size) { - FILE* f = fopen(path, "rb"); +static int read_file_bytes(FILE* f, uint8_t** out_buf, size_t* out_size) { if (!f) { - fprintf(stderr, "Failed to open object file: %s (%s)\n", path, strerror(errno)); + fprintf(stderr, "Failed to open object file: (%s)\n", strerror(errno)); return LOAD_ERR_OPEN; } + rewind(f); + if (fseek(f, 0, SEEK_END) != 0) { - fclose(f); - fprintf(stderr, "Failed to seek object file: %s\n", path); + fprintf(stderr, "Failed to seek object file\n"); return LOAD_ERR_SMALL_FILE; } long file_size = ftell(f); if (file_size < 0) { - fclose(f); - fprintf(stderr, "Failed to read object file size: %s\n", path); + fprintf(stderr, "Failed to read object file size\n"); return LOAD_ERR_SMALL_FILE; } if (fseek(f, 0, SEEK_SET) != 0) { - fclose(f); - fprintf(stderr, "Failed to rewind object file: %s\n", path); + fprintf(stderr, "Failed to rewind object file\n"); return LOAD_ERR_SMALL_FILE; } uint8_t* file_buf = (uint8_t*)malloc((size_t)file_size); if (!file_buf) { - fclose(f); fprintf(stderr, "Failed to allocate %ld bytes for object file\n", file_size); return LOAD_ERR_OOM; } size_t read_count = fread(file_buf, 1, (size_t)file_size, f); - fclose(f); if (read_count != (size_t)file_size) { free(file_buf); - fprintf(stderr, "Object file too small or unreadable: %s\n", path); + fprintf(stderr, "Object file too small or unreadable\n"); return LOAD_ERR_SMALL_FILE; } @@ -267,29 +263,29 @@ static int apply_relocations(const uint8_t* file_buf, size_t bytes, uint8_t* mem return LOAD_OK; } -int load_elf_object_sections(const char* path, uint8_t* memory, uint32_t mem_size) { +int load_elf_object_sections(FILE* f, uint8_t* memory, uint32_t mem_size) { uint8_t* file_buf = NULL; size_t bytes = 0u; - int file_status = read_file_bytes(path, &file_buf, &bytes); + int file_status = read_file_bytes(f, &file_buf, &bytes); if (file_status != LOAD_OK) { return file_status; } if (bytes < 52u) { - fprintf(stderr, "Object file too small or unreadable: %s\n", path); + fprintf(stderr, "Object file too small or unreadable\n"); free(file_buf); return LOAD_ERR_SMALL_FILE; } if (file_buf[0] != ELF_MAGIC_0 || file_buf[1] != ELF_MAGIC_1 || file_buf[2] != ELF_MAGIC_2 || file_buf[3] != ELF_MAGIC_3) { - fprintf(stderr, "Not an ELF file: %s\n", path); + fprintf(stderr, "Not an ELF file\n"); free(file_buf); return LOAD_ERR_NOT_ELF; } if (file_buf[4] != ELFCLASS32 || file_buf[5] != ELFDATA2LSB) { - fprintf(stderr, "Unsupported ELF format in %s (need 32-bit little-endian)\n", path); + fprintf(stderr, "Unsupported ELF format (need 32-bit little-endian)\n"); free(file_buf); return LOAD_ERR_UNSUPPORTED_FORMAT; } @@ -299,13 +295,13 @@ int load_elf_object_sections(const char* path, uint8_t* memory, uint32_t mem_siz uint16_t e_shnum = read_u16_le(file_buf + 48); if (e_shoff >= bytes || e_shentsize == 0u) { - fprintf(stderr, "Invalid section header table in %s\n", path); + fprintf(stderr, "Invalid section header table\n"); free(file_buf); return LOAD_ERR_INVALID_SHT; } if ((uint32_t)e_shoff + (uint32_t)e_shentsize * (uint32_t)e_shnum > bytes) { - fprintf(stderr, "Section headers out of file bounds in %s\n", path); + fprintf(stderr, "Section headers out of file bounds\n"); free(file_buf); return LOAD_ERR_SHT_OUT_OF_BOUNDS; } @@ -389,11 +385,11 @@ int load_elf_object_sections(const char* path, uint8_t* memory, uint32_t mem_siz return reloc_status; } -int resolve_elf_symbol_memory_offset(const char* path, const char* symbol_name, +int resolve_elf_symbol_memory_offset(FILE* f, const char* symbol_name, uint32_t mem_size, uint32_t* out_offset) { uint8_t* file_buf = NULL; size_t bytes = 0u; - int file_status = read_file_bytes(path, &file_buf, &bytes); + int file_status = read_file_bytes(f, &file_buf, &bytes); if (file_status != LOAD_OK) { return file_status; } diff --git a/src/elf.h b/src/elf.h index 3f48b08..f466a2e 100644 --- a/src/elf.h +++ b/src/elf.h @@ -24,6 +24,7 @@ #define R_RISCV_RELAX 51u #include +#include enum { LOAD_OK = 1, @@ -48,7 +49,7 @@ enum { uint16_t read_u16_le(const uint8_t* data); uint32_t read_u32_le(const uint8_t* data); uint32_t align_up(uint32_t value, uint32_t align); -int load_elf_object_sections(const char* path, uint8_t* memory, uint32_t mem_size); -int resolve_elf_symbol_memory_offset(const char* path, const char* symbol_name, +int load_elf_object_sections(FILE* f, uint8_t* memory, uint32_t mem_size); +int resolve_elf_symbol_memory_offset(FILE* f, const char* symbol_name, uint32_t mem_size, uint32_t* out_offset); diff --git a/src/encryptor.c b/src/encryptor.c new file mode 100644 index 0000000..5e0a756 --- /dev/null +++ b/src/encryptor.c @@ -0,0 +1,61 @@ +#include +#include +#include +#include + +#include "hwinfo.h" +#include "util.h" + +int main(int argc, char** argv) { + if (argc != 3) { + return 1; + } + + char* input = argv[1]; + char* output = argv[2]; + + hw_info_t hwinfo = {0}; + if(get_hw_info(&hwinfo) != 0){ + return 1; + } + + uint8_t* key = hwinfo_to_key(hwinfo); + + if(key == NULL) { + return 1; + } + + fprintf(stderr, "HWINFO: %s\n", hw_info_flatten(hwinfo)); + + fprintf(stderr, "Key: "); + for (int i = 0; i < 16; i++) + fprintf(stderr, "%02X", key[i]); + fprintf(stderr, "\n"); + + struct AES_ctx ctx; + uint8_t nonce[16] = {0}; + + FILE* license = fopen(input, "rb"); + long data_start = ftell(license); + fseek(license, 0, SEEK_END); + int license_size = ftell(license) - data_start; + fseek(license, data_start, SEEK_SET); + uint8_t* buf = malloc(license_size); + fread(buf, 1, license_size, license); + + + AES_init_ctx(&ctx, key); + AES_init_ctx_iv(&ctx, key, nonce); + AES_CTR_xcrypt_buffer(&ctx, buf, license_size); + + FILE* encrypted_license = fopen(output, "wb+"); + fwrite(&nonce, 1, sizeof(nonce), encrypted_license); + fwrite(buf, 1, license_size, encrypted_license); + + fclose(license); + fclose(encrypted_license); + + fprintf(stderr, "Wrote encrypted license file to %s\n", output); + + return 0; +} diff --git a/src/hwinfo.c b/src/hwinfo.c new file mode 100644 index 0000000..8d4c92d --- /dev/null +++ b/src/hwinfo.c @@ -0,0 +1,77 @@ +#include "hwinfo.h" + +#include + +char* hw_info_flatten(hw_info_t hwinfo) { + int bufsize = strlen(hwinfo.vendor_id) + + strlen(hwinfo.model_name) + + strlen(hwinfo.os_version) + + strlen(hwinfo.mem_total) + + 1; + + char* hw_info_flat = malloc(bufsize); + + if(hw_info_flat == NULL) { + return NULL; + } + + memset(hw_info_flat, 0, bufsize); + + strcat(hw_info_flat, hwinfo.vendor_id); + strcat(hw_info_flat, hwinfo.model_name); + strcat(hw_info_flat, hwinfo.os_version); + strcat(hw_info_flat, hwinfo.mem_total); + + return hw_info_flat; +} + + +void read_cpu_info(FILE* file, hw_info_t* hwinfo) { + char line[256]; + while(fgets(line, sizeof(line), file)) { + if (strncmp(line, "vendor_id", 9) == 0) { + hwinfo->vendor_id = strdup(strchr(line, ':') + 2); + hwinfo->vendor_id[strcspn(hwinfo->vendor_id, "\r\n")] = 0; // Remove newline + } else if (strncmp(line, "model name", 10) == 0) { + hwinfo->model_name = strdup(strchr(line, ':') + 2); + hwinfo->model_name[strcspn(hwinfo->model_name, "\r\n")] = 0; // Remove newline + } + } +} + +void read_os_version(FILE* file, hw_info_t* hwinfo) { + char line[256]; + if(!fgets(line, sizeof(line), file)){ + return; + } + hwinfo->os_version = strdup(strchr(strchr(line, ' ') + 1, ' ') + 1); + hwinfo->os_version[strcspn(hwinfo->os_version, " \r\n")] = 0; +} + +void read_mem_info(FILE* file, hw_info_t* hwinfo) { + char line[256]; + while(fgets(line, sizeof(line), file)) { + if(strncmp(line, "MemTotal", 8) == 0) { + hwinfo->mem_total = strchr(line, ':') + 1; + hwinfo->mem_total = strdup(hwinfo->mem_total + strspn(hwinfo->mem_total, " ")); + *strrchr(hwinfo->mem_total, ' ') = 0; + } + } +} + +int get_hw_info(hw_info_t* hwinfo) { + FILE* f_cpuinfo = fopen("/proc/cpuinfo", "rb"); + FILE* f_osversion = fopen("/proc/version", "rb"); + FILE* f_meminfo = fopen("/proc/meminfo", "rb"); + + + if(!f_cpuinfo || !f_osversion || !f_meminfo) { + return 1; + } + + read_cpu_info(f_cpuinfo, hwinfo); + read_os_version(f_osversion, hwinfo); + read_mem_info(f_meminfo, hwinfo); + + return 0; +} diff --git a/src/hwinfo.h b/src/hwinfo.h new file mode 100644 index 0000000..7008f8c --- /dev/null +++ b/src/hwinfo.h @@ -0,0 +1,19 @@ +#pragma once + +#include +#include + +typedef struct HWInfo { + char* vendor_id; + char* model_name; + char* os_version; + char* mem_total; +} hw_info_t; + +char* hw_info_flatten(hw_info_t hwinfo); + +void read_cpu_info(FILE* file, hw_info_t* hwinfo); +void read_os_version(FILE* file, hw_info_t* hwinfo); +void read_mem_info(FILE* file, hw_info_t* hwinfo); + +int get_hw_info(hw_info_t* hwinfo); diff --git a/src/main.c b/src/main.c index 55ebc33..8dc00da 100644 --- a/src/main.c +++ b/src/main.c @@ -1,18 +1,78 @@ #define TINYRISCV_M -#include "tinyriscv.h" #include "elf.h" +#include "hwinfo.h" +#include "util.h" + +#include "aes.h" +#include "tinyriscv.h" #include "config.h" #include #include #include +#include + #define MAX_STEPS 1024u int main(){ + hw_info_t hwinfo = {0}; + if(get_hw_info(&hwinfo) != 0){ + return 1; + } + + uint8_t* key = hwinfo_to_key(hwinfo); + + if(key == NULL) { + return 1; + } + + fprintf(stderr, "HWINFO: %s\n", hw_info_flatten(hwinfo)); + + fprintf(stderr, "Key: "); + for (int i = 0; i < 16; i++) + fprintf(stderr, "%02X", key[i]); + fprintf(stderr, "\n"); + + + + struct AES_ctx ctx; + uint8_t nonce[16] = {0}; + + FILE* license = fopen(EMU_PROGRAM_PATH, "rb"); + fread(nonce, 16, 1, license); + long data_start = ftell(license); + fseek(license, 0, SEEK_END); + int license_size = ftell(license) - data_start; + fseek(license, data_start, SEEK_SET); + uint8_t* buf = malloc(license_size); + fread(buf, 1, license_size, license); + + + // void AES_init_ctx(struct AES_ctx* ctx, const uint8_t* key); + // void AES_init_ctx_iv(struct AES_ctx* ctx, const uint8_t* key, const uint8_t* iv); + // void AES_CTR_xcrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length); + + AES_init_ctx(&ctx, key); + AES_init_ctx_iv(&ctx, key, nonce); + AES_CTR_xcrypt_buffer(&ctx, buf, license_size); + + FILE* decrypted_license = tmpfile(); + fwrite(buf, 1, license_size, decrypted_license); + rewind(decrypted_license); + + char begin[8] = {0}; + char expected[] = {0x7F, 'E', 'L', 'F', 1, 1, 1, 0}; + fread(begin, 1, sizeof(begin), decrypted_license); + if(memcmp(begin, expected, sizeof(begin)) != 0) { + // This happens when decryption fails + printf("You are not authorized to use this software!\n"); + return 1; + } + const int maxninput = 255; printf("Please enter the flag: "); fflush(stdout); @@ -34,9 +94,9 @@ int main(){ 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); + int load_status = load_elf_object_sections(decrypted_license, 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); + fprintf(stderr, "ELF load failed with code %d for decrypted license\n", load_status); fflush(stderr); printf("ELF load failed with code %d\n", load_status); fflush(stdout); @@ -45,7 +105,7 @@ int main(){ uint32_t entry_offset = 0u; int symbol_status = resolve_elf_symbol_memory_offset( - EMU_PROGRAM_PATH, EMU_ENTRY_SYMBOL, cpu.mem_size, &entry_offset); + decrypted_license, EMU_ENTRY_SYMBOL, cpu.mem_size, &entry_offset); if (symbol_status != LOAD_OK) { fprintf(stderr, "Entry symbol '%s' resolve failed with code %d for '%s'\n", EMU_ENTRY_SYMBOL, symbol_status, EMU_PROGRAM_PATH); @@ -72,9 +132,9 @@ int main(){ uint32_t result = (uint32_t)cpu.x[10]; if(result == 1) { - printf("Congrats! That's the flag!"); + printf("Congrats! That's the flag!\n"); } else { - printf("Nope, sorry, that's incorrect!"); + printf("Nope, sorry, that's incorrect!\n"); } return 0; diff --git a/src/util.c b/src/util.c new file mode 100644 index 0000000..1fcd6a3 --- /dev/null +++ b/src/util.c @@ -0,0 +1,44 @@ +#include "util.h" + +#include + +/* +We do the following algorithm twice + +algorithm fnv-1a is + hash := FNV_offset_basis + + for each byte_of_data to be hashed do + hash := hash XOR byte_of_data + hash := hash × FNV_prime + + return hash +*/ +void dual_fnv_hash(char* input, uint8_t key[16]) { + uint64_t lower = 0xcbf29ce484222325; + uint64_t upper = 0xcbf29ce484222325 + 0xC0010FF5E7; // add a made-up constant + + while(*input != 0) { + lower = (lower ^ *input) * FNV_PRIME; + upper = (upper ^ *input) * FNV_PRIME; + + input += 1; + } + + memcpy(key, &lower, 8); + memcpy(key + 8, &upper, 8); +} + + +uint8_t* hwinfo_to_key(hw_info_t hwinfo) { + char* hw_info_flat = hw_info_flatten(hwinfo); + + if (hw_info_flat == NULL){ + return NULL; + } + + uint8_t* key = malloc(16); + dual_fnv_hash(hw_info_flat, key); + + return key; +} diff --git a/src/util.h b/src/util.h new file mode 100644 index 0000000..db61ccb --- /dev/null +++ b/src/util.h @@ -0,0 +1,15 @@ +#pragma once + +#include "hwinfo.h" + +#include + +#define FNV_PRIME 0x00000100000001b3 + +// Generate an aes key by hashing the input +// This hash is not safe in any way +// But it is fast and easy to implement! +// https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function +void dual_fnv_hash(char* input, uint8_t key[16]); + +uint8_t* hwinfo_to_key(hw_info_t hwinfo); diff --git a/tiny-AES-c b/tiny-AES-c new file mode 160000 index 0000000..2385675 --- /dev/null +++ b/tiny-AES-c @@ -0,0 +1 @@ +Subproject commit 23856752fbd139da0b8ca6e471a13d5bcc99a08d