Compare commits
12 Commits
197cc3098d
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 666511e258 | |||
| f80a532888 | |||
| 473cbf2002 | |||
| 6587b460d6 | |||
| 018262bdaa | |||
|
|
fe3992f758 | ||
|
|
5375ec0688 | ||
| 7f3d793f9c | |||
| ebbccfb7ae | |||
| 65da1f0093 | |||
| 926d7bbad8 | |||
| 69b16bf09d |
12
Dockerfile
Normal file
12
Dockerfile
Normal file
@@ -0,0 +1,12 @@
|
||||
FROM alpine:3.20
|
||||
|
||||
RUN apk add --no-cache gdb strace
|
||||
|
||||
WORKDIR /challenge
|
||||
COPY main ./main
|
||||
RUN chmod +x ./main
|
||||
|
||||
COPY license ./license
|
||||
|
||||
|
||||
CMD ["/bin/sh"]
|
||||
52
Makefile
52
Makefile
@@ -11,6 +11,7 @@ FLAG_FORMAT ?= ctf{FLAG_GOES_HERE}
|
||||
FLAG_SEED ?= false
|
||||
|
||||
PYTHON_EXE ?= python
|
||||
BUILD_DIR ?= build
|
||||
|
||||
# Compilation variables
|
||||
RISCV_CC ?= riscv64-unknown-elf-gcc
|
||||
@@ -20,17 +21,18 @@ HOST_CFLAGS ?= -Wall -Wextra -O3 -g
|
||||
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
|
||||
EMU_OBJ_FILES := $(EMU_SRC_FILES:src/emulated/%.c=$(BUILD_DIR)/%.riscv.o)
|
||||
EMU_OBJ := $(BUILD_DIR)/emulated.riscv.elf
|
||||
EMU_OBJ_LICENSE ?= $(BUILD_DIR)/license
|
||||
EMU_OBJ_LICENSE_IF_EXISTS := $(wildcard $(EMU_OBJ_LICENSE))
|
||||
|
||||
README_FILE ?= build/README.md
|
||||
ZIP_FILE ?= build/challenge.zip
|
||||
README_FILE ?= $(BUILD_DIR)/README.txt
|
||||
DOCKERFILE ?= $(BUILD_DIR)/Dockerfile
|
||||
ZIP_FILE ?= $(BUILD_DIR)/challenge.zip
|
||||
ZIP_FILE_IF_EXISTS := $(wildcard $(ZIP_FILE))
|
||||
|
||||
|
||||
GENERATED := build/include/generated.h
|
||||
GENERATED := $(BUILD_DIR)/include/generated.h
|
||||
|
||||
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)
|
||||
@@ -40,18 +42,18 @@ HOST_SRC_SPOOFER := src/spoof/spoofer.c
|
||||
|
||||
|
||||
ifeq ($(OS),Windows_NT)
|
||||
HOST_EXE ?= build/main.exe
|
||||
HOST_EXE ?= $(BUILD_DIR)/main.exe
|
||||
else
|
||||
HOST_EXE ?= build/main
|
||||
HOST_EXE ?= $(BUILD_DIR)/main
|
||||
endif
|
||||
|
||||
ifeq ($(OS),Windows_NT)
|
||||
HOST_EXE_ENCRYPTOR ?= build/encryptor.exe
|
||||
HOST_EXE_ENCRYPTOR ?= $(BUILD_DIR)/encryptor.exe
|
||||
else
|
||||
HOST_EXE_ENCRYPTOR ?= build/encryptor
|
||||
HOST_EXE_ENCRYPTOR ?= $(BUILD_DIR)/encryptor
|
||||
endif
|
||||
|
||||
HOST_EXE_SPOOFER ?= build/spoofer
|
||||
HOST_EXE_SPOOFER ?= $(BUILD_DIR)/spoofer
|
||||
|
||||
|
||||
# Disassembly
|
||||
@@ -70,40 +72,43 @@ emulated-riscv: $(EMU_OBJ_LICENSE)
|
||||
host: $(HOST_EXE) $(HOST_EXE_SPOOFER)
|
||||
|
||||
# Build the main challenge executable
|
||||
$(HOST_EXE): $(HOST_SRC) | build
|
||||
$(HOST_CC) $(HOST_CFLAGS) $(HOST_LDFLAGS) -static $(HOST_INCLUDE) $(HOST_SRC) -o $@
|
||||
$(HOST_EXE): $(HOST_SRC) | $(BUILD_DIR)
|
||||
$(HOST_CC) $(HOST_CFLAGS) $(HOST_LDFLAGS) $(HOST_INCLUDE) $(HOST_SRC) -o $@
|
||||
|
||||
# Build the spoofer
|
||||
$(HOST_EXE_SPOOFER): $(HOST_SRC_SPOOFER) | build
|
||||
$(HOST_CC) $(HOST_CFLAGS) $(HOST_LDFLAGS) $^ -o $@ -ldl
|
||||
$(HOST_EXE_SPOOFER): $(HOST_SRC_SPOOFER) | $(BUILD_DIR)
|
||||
$(HOST_CC) $(HOST_CFLAGS) $(HOST_LDFLAGS) -shared -fPIC $^ -o $@ -ldl
|
||||
|
||||
# Build the encryptor
|
||||
$(HOST_EXE_ENCRYPTOR): $(HOST_SRC_ENCRYPTOR) | build
|
||||
$(HOST_EXE_ENCRYPTOR): $(HOST_SRC_ENCRYPTOR) | $(BUILD_DIR)
|
||||
$(HOST_CC) $(HOST_CFLAGS) $(HOST_LDFLAGS) $(HOST_INCLUDE) $(HOST_SRC_ENCRYPTOR) -o $@
|
||||
|
||||
# Build emulated object files
|
||||
$(EMU_OBJ_FILES): $(EMU_SRC_FILES) $(GENERATED) | build
|
||||
$(EMU_OBJ_FILES): $(EMU_SRC_FILES) $(GENERATED) | $(BUILD_DIR)
|
||||
$(RISCV_CC) $(RISCV_ARCH_FLAGS) -I $(dir $(GENERATED)) -I src/emulated -c $(EMU_SRC_FILES) -o $@
|
||||
|
||||
|
||||
# Encrypt license file
|
||||
$(EMU_OBJ_LICENSE): $(EMU_OBJ) $(HOST_EXE_ENCRYPTOR) | build
|
||||
$(EMU_OBJ_LICENSE): $(EMU_OBJ) $(HOST_EXE_ENCRYPTOR) | $(BUILD_DIR)
|
||||
ifneq ($(EMU_OBJ_LICENSE_IF_EXISTS),)
|
||||
rm $(EMU_OBJ_LICENSE_IF_EXISTS)
|
||||
endif
|
||||
$(HOST_EXE_ENCRYPTOR) $(EMU_OBJ) $@ $(README_FILE)
|
||||
|
||||
# Build (unencrypted) license file
|
||||
$(EMU_OBJ): $(EMU_OBJ_FILES) | build
|
||||
$(EMU_OBJ): $(EMU_OBJ_FILES) | $(BUILD_DIR)
|
||||
$(RISCV_CC) $(RISCV_ARCH_FLAGS) -nostdlib -Wl,-e,entry -Wl,-Ttext=0 -Wl,--emit-relocs $(EMU_OBJ_FILES) -o $@
|
||||
|
||||
# Copy Dockerfile into build dir
|
||||
$(DOCKERFILE): Dockerfile | $(BUILD_DIR)
|
||||
cp $< $@
|
||||
|
||||
# Bundle the challenge
|
||||
$(ZIP_FILE): $(HOST_EXE) $(EMU_OBJ_LICENSE) $(README_FILE) | build
|
||||
$(ZIP_FILE): $(HOST_EXE) $(EMU_OBJ_LICENSE) $(README_FILE) $(DOCKERFILE) | $(BUILD_DIR)
|
||||
ifneq ($(ZIP_FILE_IF_EXISTS),)
|
||||
rm $(ZIP_FILE_IF_EXISTS)
|
||||
endif
|
||||
cd build && 7z a -bso0 $(CURDIR)/$@ $(notdir $^)
|
||||
cd $(BUILD_DIR) && 7z a -bso0 $(CURDIR)/$@ $(notdir $^)
|
||||
|
||||
.FORCE:
|
||||
|
||||
@@ -112,12 +117,11 @@ $(GENERATED): .FORCE
|
||||
$(PYTHON_EXE) codegen/generate.py $(FLAG_FORMAT) $(FLAG_LEN) $(FLAG_SEED) > $@
|
||||
|
||||
|
||||
build:
|
||||
$(BUILD_DIR):
|
||||
mkdir -p $@
|
||||
|
||||
clean:
|
||||
rm -rf build
|
||||
rm -rf $(BUILD_DIR)
|
||||
|
||||
disas-risc: $(_DISAS_FILES)
|
||||
$(RISCV_OBJDUMP) -d $^
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import random
|
||||
import sys
|
||||
import os
|
||||
|
||||
def generate_flag(length):
|
||||
characters = list("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
|
||||
@@ -13,7 +14,7 @@ def custom_op_xor(value, amount):
|
||||
return (((value << amount) ^ 0xFF) >> amount)
|
||||
|
||||
|
||||
manglers = [
|
||||
hard_manglers = [
|
||||
(lambda value: custom_op_xor(value, 5), lambda value: f"trunc_xor({value}, 5)"),
|
||||
(lambda value: custom_op_xor(value, 3), lambda value: f"trunc_xor({value}, 3)"),
|
||||
(lambda value: custom_op_xor(value, 2), lambda value: f"trunc_xor({value}, 2)"),
|
||||
@@ -26,7 +27,17 @@ manglers = [
|
||||
# (lambda value: value, lambda value: f"{value}")
|
||||
]
|
||||
|
||||
def mangle(value):
|
||||
easy_manglers = [
|
||||
(lambda value: value + 1, lambda value: f"{value} + 1"),
|
||||
(lambda value: value - 1, lambda value: f"{value} - 1"),
|
||||
(lambda value: value * 2, lambda value: f"{value} * 2"),
|
||||
(lambda value: value ^ 0xAB, lambda value: f"({value} ^ 0xAB)"),
|
||||
(lambda value: value, lambda value: f"{value}")
|
||||
]
|
||||
|
||||
|
||||
|
||||
def mangle(value, manglers):
|
||||
result = b""
|
||||
c_manglers = []
|
||||
|
||||
@@ -42,11 +53,18 @@ def mangle(value):
|
||||
|
||||
return (result, c_manglers)
|
||||
|
||||
def get_check_code(flag_part, user_part, c_mangler):
|
||||
def get_check_code(flag_part, user_part, c_mangler, difficulty):
|
||||
|
||||
if difficulty == "HARD":
|
||||
variations = [
|
||||
f"custom_jmp_neq({flag_part} ^ 0x7C, {c_mangler(user_part)}, label_false);",
|
||||
f"custom_jmp_neq({flag_part}, {c_mangler(user_part)} ^ 0x7C, label_false);"
|
||||
]
|
||||
else:
|
||||
variations = [
|
||||
f"if({flag_part} != {c_mangler(user_part)}) {{ return false; }}"
|
||||
]
|
||||
|
||||
return random.choice(variations)
|
||||
|
||||
def main():
|
||||
@@ -64,6 +82,8 @@ def main():
|
||||
if flag_seed.lower() != "false":
|
||||
random.seed("totallysecurepadding" + flag_seed + "sonoonecanguesstheflag!")
|
||||
|
||||
difficulty = os.getenv("DIFFICULTY", "EASY")
|
||||
|
||||
# Generate a secret
|
||||
secret = generate_flag(flag_len)
|
||||
secret = flag_format.replace("FLAG_GOES_HERE", secret)
|
||||
@@ -71,7 +91,7 @@ def main():
|
||||
eprint(f"Generated Flag: {secret}")
|
||||
|
||||
# Change the bytes of the secret
|
||||
mangled_secret, c_manglers = mangle(secret)
|
||||
mangled_secret, c_manglers = mangle(secret, easy_manglers if difficulty == "EASY" else hard_manglers)
|
||||
|
||||
# Shuffle the secret, but keep track of the order
|
||||
# We need this so we know the real location of the characters to compare
|
||||
@@ -89,7 +109,7 @@ def main():
|
||||
|
||||
checks = []
|
||||
for i, c_mangler in enumerate(c_manglers):
|
||||
checks.append(get_check_code(f"MANGLED[{i}]", f"userflag[{secret_shuffleorder[i]}]", c_mangler))
|
||||
checks.append(get_check_code(f"MANGLED[{i}]", f"userflag[{secret_shuffleorder[i]}]", c_mangler, difficulty))
|
||||
|
||||
random.shuffle(checks)
|
||||
check = "\n".join(checks)
|
||||
@@ -108,7 +128,7 @@ def main():
|
||||
static volatile uint8_t MANGLED[] = "{mangled_secret}";
|
||||
|
||||
bool check(char* userflag, uint8_t len) {{
|
||||
custom_jmp_neq(len ^ 0x7C, {len(secret)}, label_false);
|
||||
{f"custom_jmp_neq(len ^ 0x7C, {len(secret)}, label_false);" if difficulty == "HARD" else f"if (len != {len(secret)}) {{ return false; }}"}
|
||||
|
||||
{check}
|
||||
|
||||
|
||||
@@ -109,6 +109,8 @@ int main(int argc, char** argv) {
|
||||
fprintf(f_readme, "This doesn't just run on any hardware, I don't know how they did it\n");
|
||||
fprintf(f_readme, "but it only works on my %s processor\n", hwinfo.model_name);
|
||||
fprintf(f_readme, "running linux (version %s) with %s kb of RAM\n", hwinfo.os_version, hwinfo.mem_total);
|
||||
fprintf(f_readme, "I also included a dockerfile so you can run all that shi easier.\n");
|
||||
fprintf(f_readme, "Just unzip that thang and run docker build . -t local-dev and then docker run -it local-dev in the folder you unzipped to.\n");
|
||||
fprintf(f_readme, "You'll find a way, slime. I'm sorry that I can't give you any more details\n");
|
||||
fprintf(f_readme, "this is the last message I could get out before they... you know... ⛓️👮♂️\n");
|
||||
fprintf(f_readme, "\n");
|
||||
|
||||
@@ -80,6 +80,7 @@ int main(){
|
||||
if(memcmp(begin, expected, sizeof(begin)) != 0) {
|
||||
// This happens when decryption fails
|
||||
printf(MESSAGE_UNAUTHORIZED);
|
||||
fflush(stdout);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,164 +1,57 @@
|
||||
#define _GNU_SOURCE
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <dlfcn.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/ptrace.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/user.h>
|
||||
#include <sys/uio.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
static void write_mem(pid_t pid, unsigned long addr, const char *buf, size_t len)
|
||||
{
|
||||
struct iovec local = {(void *)buf, len};
|
||||
struct iovec remote = {(void *)addr, len};
|
||||
if (process_vm_writev(pid, &local, 1, &remote, 1, 0) < 0)
|
||||
{
|
||||
perror("spoofer: process_vm_writev");
|
||||
static FILE *(*real_fopen)(const char *path, const char *mode) = NULL;
|
||||
|
||||
static void init_real_fopen(void) {
|
||||
if (!real_fopen) {
|
||||
real_fopen = dlsym(RTLD_NEXT, "fopen");
|
||||
if (!real_fopen) {
|
||||
fprintf(stderr, "proc_redirect: dlsym(fopen) failed: %s\n", dlerror());
|
||||
abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int read_string(pid_t pid, unsigned long addr, char *buf, size_t max)
|
||||
{
|
||||
struct iovec local = {buf, max};
|
||||
struct iovec remote = {(void *)addr, max};
|
||||
ssize_t n = process_vm_readv(pid, &local, 1, &remote, 1, 0);
|
||||
if (n < 0)
|
||||
return -1;
|
||||
buf[n < (ssize_t)max ? (size_t)n : max - 1] = '\0';
|
||||
buf[strnlen(buf, max - 1)] = '\0';
|
||||
return 0;
|
||||
FILE *fopen(const char *path, const char *mode) {
|
||||
init_real_fopen();
|
||||
|
||||
if (path && strncmp(path, "/proc/", 6) == 0) {
|
||||
const char *rel_suffix = path + 1;
|
||||
size_t len = strlen(rel_suffix) + 4;
|
||||
char *new_path = malloc(len);
|
||||
if (!new_path) {
|
||||
fprintf(stderr, "proc_redirect: malloc failed\n");
|
||||
abort();
|
||||
}
|
||||
new_path[0] = '.';
|
||||
new_path[1] = '/';
|
||||
new_path[2] = '.';
|
||||
strcpy(new_path + 3, rel_suffix);
|
||||
|
||||
FILE *f = real_fopen(new_path, mode);
|
||||
free(new_path);
|
||||
return f;
|
||||
}
|
||||
|
||||
return real_fopen(path, mode);
|
||||
}
|
||||
|
||||
static void handle_openat(pid_t pid, struct user_regs_struct *regs)
|
||||
{
|
||||
unsigned long path_ptr = regs->rsi;
|
||||
char orig[4096] = {0};
|
||||
if (read_string(pid, path_ptr, orig, sizeof(orig)) < 0)
|
||||
return;
|
||||
if (strncmp(orig, "/proc/", 6) != 0)
|
||||
return;
|
||||
|
||||
char newpath[4096];
|
||||
snprintf(newpath, sizeof(newpath), ".%s", orig + 1);
|
||||
|
||||
/* Scratch space below the red zone, safe from any live stack frame */
|
||||
unsigned long scratch = regs->rsp - 256 - sizeof(newpath);
|
||||
write_mem(pid, scratch, newpath, strlen(newpath) + 1);
|
||||
|
||||
struct user_regs_struct r = *regs;
|
||||
r.rsi = scratch;
|
||||
if (ptrace(PTRACE_SETREGS, pid, NULL, &r) < 0)
|
||||
perror("spoofer: PTRACE_SETREGS");
|
||||
else
|
||||
fprintf(stderr, "spoofer: redirected '%s' -> '%s'\n", orig, newpath);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
if (argc < 2)
|
||||
{
|
||||
fprintf(stderr, "usage: %s <program> [args...]\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
pid_t pid = fork();
|
||||
if (pid < 0)
|
||||
{
|
||||
perror("fork");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (pid == 0)
|
||||
{
|
||||
if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) < 0)
|
||||
{
|
||||
perror("spoofer: PTRACE_TRACEME");
|
||||
_exit(1);
|
||||
}
|
||||
execvp(argv[1], argv + 1);
|
||||
perror("spoofer: execvp");
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
int status;
|
||||
if (waitpid(pid, &status, 0) < 0)
|
||||
{
|
||||
perror("waitpid");
|
||||
return 1;
|
||||
}
|
||||
if (!WIFSTOPPED(status))
|
||||
{
|
||||
fprintf(stderr, "spoofer: unexpected initial status %d\n", status);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (ptrace(PTRACE_SETOPTIONS, pid, NULL,
|
||||
(void *)(long)(PTRACE_O_TRACESYSGOOD | PTRACE_O_EXITKILL)) < 0)
|
||||
{
|
||||
perror("spoofer: PTRACE_SETOPTIONS");
|
||||
return 1;
|
||||
}
|
||||
|
||||
int in_syscall = 0;
|
||||
|
||||
while (1)
|
||||
{
|
||||
if (ptrace(PTRACE_SYSCALL, pid, NULL, NULL) < 0)
|
||||
{
|
||||
perror("spoofer: PTRACE_SYSCALL");
|
||||
break;
|
||||
}
|
||||
if (waitpid(pid, &status, 0) < 0)
|
||||
{
|
||||
perror("waitpid");
|
||||
break;
|
||||
}
|
||||
if (WIFEXITED(status) || WIFSIGNALED(status))
|
||||
break;
|
||||
if (!WIFSTOPPED(status))
|
||||
continue;
|
||||
|
||||
int sig = WSTOPSIG(status);
|
||||
|
||||
if (sig == (SIGTRAP | 0x80))
|
||||
{
|
||||
if (!in_syscall)
|
||||
{
|
||||
struct user_regs_struct regs;
|
||||
if (ptrace(PTRACE_GETREGS, pid, NULL, ®s) == 0)
|
||||
{
|
||||
if (regs.orig_rax == SYS_openat)
|
||||
{
|
||||
handle_openat(pid, ®s);
|
||||
}
|
||||
else if (regs.orig_rax == SYS_open)
|
||||
{
|
||||
/* SYS_open: rdi=path — copy to rsi so handle_openat works */
|
||||
regs.rsi = regs.rdi;
|
||||
handle_openat(pid, ®s);
|
||||
}
|
||||
}
|
||||
}
|
||||
in_syscall ^= 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Forward real signals to the tracee */
|
||||
ptrace(PTRACE_SYSCALL, pid, NULL, (void *)(long)sig);
|
||||
in_syscall = 0;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (WIFEXITED(status))
|
||||
return WEXITSTATUS(status);
|
||||
if (WIFSIGNALED(status))
|
||||
return 128 + WTERMSIG(status);
|
||||
return 0;
|
||||
FILE *tmpfile(void) {
|
||||
char path[] = "./tmpfile_XXXXXX";
|
||||
int fd = mkstemp(path);
|
||||
if (fd == -1) {
|
||||
fprintf(stderr, "proc_redirect: mkstemp failed\n");
|
||||
abort();
|
||||
}
|
||||
fprintf(stderr, "proc_redirect: tmpfile created at %s\n", path);
|
||||
FILE *f = fdopen(fd, "w+");
|
||||
if (!f) {
|
||||
close(fd);
|
||||
abort();
|
||||
}
|
||||
return f;
|
||||
}
|
||||
Reference in New Issue
Block a user