diff --git a/c/linux_loader_poc/Makefile b/c/linux_loader_poc/Makefile new file mode 100644 index 0000000..1ce2082 --- /dev/null +++ b/c/linux_loader_poc/Makefile @@ -0,0 +1,87 @@ +## Proof-of-concept to show different methods to load executables in the Linux kernel +# +# @copyright 2016 Steffen Vogel +# @license http://www.gnu.org/licenses/gpl.txt GNU Public License +# @author Steffen Vogel +# @link http://www.steffenvogel.de +######################################################################################### + +TARGETS = demo-interpreter demo-binfmt_misc proxy proxy-static + +# We need to know absolute paths to our interpreters / loaders +MKDIR := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST)))) +PROXY = $(MKDIR)/proxy + +CC = gcc +CFLAGS = -g -std=c11 -fPIC +LDFLAGS = + +# We are patching the OS/ABI field of the ELF header +ELF_OSABI_OFFSET = 7 +ELF_OSABI = "\\xa1" + +# We are registering a new binary format in the kernel +BINFMT_MISC_PATH = /proc/sys/fs/binfmt_misc +BINFMT_MISC_NAME = hermit +BINFMT_MISC_FILE = $(BINFMT_MISC_PATH)/$(BINFMT_MISC_NAME) + +.PHONY: all clean binfmt_misc + +all: $(TARGETS) + +binfmt_misc: $(BINFMT_MISC_PATH)/$(BINFMT_MISC_NAME) + +# Register a new binary format within the kernel binfmt subsystem +# binfmt_misc is a kernel module which allows us to register new formats +# based on magic numbers and filename extension matching. +# Loading is then performed by custom interpreters in the userspace. +$(BINFMT_MISC_FILE): + # Mount binfmt_misc pseudo FS ########################################################## + test -d $(BINFMT_MISC_PATH) || mount binfmt_misc -t binfmt_misc $(BINFMT_MISC_PATH) + + # Remove old entry ##################################################################### + test -f $(BINFMT_MISC_FILE) && echo -1 > $(BINFMT_MISC_FILE) + + # Register new format ################################################################## + echo ":$(BINFMT_MISC_NAME):M:$(ELF_OSABI_OFFSET):$(ELF_OSABI)::$(PROXY):" > $(BINFMT_MISC_PATH)/register + + # Test for success and show result ##################################################### + test -f $(BINFMT_MISC_FILE) && cat $(BINFMT_MISC_FILE) + +# We have to versions of our demo application: +# +# 1. We use a custom dynamic linker (instead of the usual dynamic linker: ld-linux.so) +# For some reason our interpreter must be statically linked or we get a segfault. +demo-interpreter: demo.o + # Link demo_interpreter ################################################################ + $(CC) $(LDFLAGS) -Wl,-dynamic-linker,$(PROXY)-static -o $@ $? + + # Verify ############################################################################### + readelf -l $@ | grep -A 2 INTERP + +# 2. We register a new binary format within the Linux kernel +# And patch our binary in a way it get's recognized by the new format +# binfmt_misc succeeds with starting our dynamically linked interpreter "proxy" :-) +demo-binfmt_misc: demo.o $(BINFMT_MISC_FILE) + # Link demo_binfmt_misc ################################################################ + $(CC) $(LDFLAGS) -o $@ $< + + # Patch OS/ABI field in ELF header to match the binfmt_misc format ##################### + printf $(ELF_OSABI) | dd of=$@ bs=1 seek=$(ELF_OSABI_OFFSET) count=1 conv=notrunc + + # Verify ############################################################################### + readelf -h $@ | grep "OS/ABI" + +# This is the loader / proxy which is executed by the binfmt_misc subsystem or the 1st stage loader "loader" +proxy: proxy.o + # Link proxy ########################################################################### + $(CC) $(LDFLAGS) -o $@ $? + +proxy-static: proxy.o + # Link proxy ########################################################################### + $(CC) $(LDFLAGS) -static -o $@ $? + +clean: + rm -rf $(TARGETS) + rm -rf *.o + make -C kernel clean diff --git a/c/linux_loader_poc/README.md b/c/linux_loader_poc/README.md new file mode 100644 index 0000000..3c9d63d --- /dev/null +++ b/c/linux_loader_poc/README.md @@ -0,0 +1,8 @@ +# Custom ELF interpreter under Linux + +This is proof-of-concept shows how to use a custom + + +#### Credits + +Steffen Vogel \ No newline at end of file diff --git a/c/linux_loader_poc/demo.c b/c/linux_loader_poc/demo.c new file mode 100644 index 0000000..eb65393 --- /dev/null +++ b/c/linux_loader_poc/demo.c @@ -0,0 +1,16 @@ +/** Proof-of-concept to show different methods to load executables in the Linux kernel + * + * @copyright 2016 Steffen Vogel + * @license http://www.gnu.org/licenses/gpl.txt GNU Public License + * @author Steffen Vogel + * @link http://www.steffenvogel.de + */ + +#include + +int main() +{ + printf("Hello world\n"); + + return 0; +} diff --git a/c/linux_loader_poc/kernel/.tmp_versions/binfmt_hermit.mod b/c/linux_loader_poc/kernel/.tmp_versions/binfmt_hermit.mod new file mode 100644 index 0000000..798760f --- /dev/null +++ b/c/linux_loader_poc/kernel/.tmp_versions/binfmt_hermit.mod @@ -0,0 +1,2 @@ +/custom_loader_poc/kernel/binfmt_hermit.ko +/custom_loader_poc/kernel/binfmt_hermit.o diff --git a/c/linux_loader_poc/kernel/Makefile b/c/linux_loader_poc/kernel/Makefile new file mode 100644 index 0000000..b90e3f2 --- /dev/null +++ b/c/linux_loader_poc/kernel/Makefile @@ -0,0 +1,18 @@ +MODULE = binfmt_hermit + +obj-m += $(MODULE).o + +.PHONY: all clean install reload + +all: + make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules + +clean: + make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean + +install: all + make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules_install + +reload: install + modprobe -r $(MODULE) + modprobe $(MODULE) diff --git a/c/linux_loader_poc/kernel/Module.symvers b/c/linux_loader_poc/kernel/Module.symvers new file mode 100644 index 0000000..e69de29 diff --git a/c/linux_loader_poc/kernel/binfmt_hermit.c b/c/linux_loader_poc/kernel/binfmt_hermit.c new file mode 100644 index 0000000..803e78a --- /dev/null +++ b/c/linux_loader_poc/kernel/binfmt_hermit.c @@ -0,0 +1,125 @@ +/** Proof-of-concept to show different methods to load executables in the Linux kernel + * + * @copyright 2016 Steffen Vogel + * @license http://www.gnu.org/licenses/gpl.txt GNU Public License + * @author Steffen Vogel + * @link http://www.steffenvogel.de + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* Needed by all modules */ +#include /* Needed for KERN_INFO */ +#include /* Needed for the macros */ + +#include + +static int load_binary(struct linux_binprm *bprm) +{ + int retval; + const char *i_name, *i_arg; + char interp[BINPRM_BUF_SIZE]; + struct file *file; + struct elfhdr *hdr = (struct elfhdr *) bprm->buf; + + /* Check if this is an ELF file */ + if (memcmp(hdr->e_ident, ELFMAG, SELFMAG) != 0) + return -ENOEXEC; + if (hdr->e_type != ET_EXEC && hdr->e_type != ET_DYN) + return -ENOEXEC; + if (!elf_check_arch(hdr)) + return -ENOEXEC; + if (!bprm->file->f_op->mmap) + return -ENOEXEC; + + printk(KERN_INFO "Got ELF file in binfmt_hermit\n"); + + if (hdr->e_ident[EI_OSABI] != 0xa1) + return -ENOEXEC; + + /* Hardcoded for now */ + i_name = "/custom_loader_poc/proxy"; + i_arg = NULL; + + strcpy (interp, i_name); + + printk(KERN_INFO "It's a hermit one! Start the interpreter\n"); + + /* + * OK, we've parsed out the interpreter name and + * (optional) argument. + * Splice in (1) the interpreter's name for argv[0] + * (2) (optional) argument to interpreter + * (3) filename of shell script (replace argv[0]) + * + * This is done in reverse order, because of how the + * user environment and arguments are stored. + */ + retval = remove_arg_zero(bprm); + if (retval) + return retval; + + retval = copy_strings_kernel(1, &bprm->interp, bprm); + if (retval < 0) + return retval; + + bprm->argc++; + + retval = copy_strings_kernel(1, &i_name, bprm); + if (retval) + return retval; + + bprm->argc++; + + retval = bprm_change_interp(interp, bprm); + if (retval < 0) + return retval; + + /* + * OK, now restart the process with the interpreter's dentry. + */ + file = open_exec(interp); + if (IS_ERR(file)) + return PTR_ERR(file); + + bprm->file = file; + retval = prepare_binprm(bprm); + if (retval < 0) + return retval; + + return search_binary_handler(bprm); +} + +static struct linux_binfmt script_format = { + .module = THIS_MODULE, + .load_binary = load_binary, +}; + +static int init(void) +{ + printk(KERN_INFO "Loaded binary format for HermitCore\n"); + + register_binfmt(&script_format); + + return 0; +} + +static void cleanup(void) +{ + printk(KERN_INFO "Un-loaded binary format for HermitCore\n"); + + unregister_binfmt(&script_format); +} + +module_init(init); +module_exit(cleanup); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Steffen Vogel "); diff --git a/c/linux_loader_poc/proxy.c b/c/linux_loader_poc/proxy.c new file mode 100644 index 0000000..c1b8852 --- /dev/null +++ b/c/linux_loader_poc/proxy.c @@ -0,0 +1,18 @@ +/** Proof-of-concept to show different methods to load executables in the Linux kernel + * + * @copyright 2016 Steffen Vogel + * @license http://www.gnu.org/licenses/gpl.txt GNU Public License + * @author Steffen Vogel + * @link http://www.steffenvogel.de + */ + +#include +#include + +int main(int argc, char *argv[]) +{ + printf("This is the dynamically-linked proxy: %s\n", argv[0]); + printf(" Running now /usr/bin/objdump -dS %s\n\n", argv[1]); + + execl("/usr/bin/objdump", "objdump", "-dS", argv[0], NULL); +}