added a PoC to show different ways to hook into the loading process of ELF binaries on Linux

This commit is contained in:
Steffen Vogel 2016-03-08 17:31:18 +01:00
parent f5e4ac0244
commit b3b3825526
8 changed files with 274 additions and 0 deletions

View file

@ -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 <post@steffenvogel.de>
# @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

View file

@ -0,0 +1,8 @@
# Custom ELF interpreter under Linux
This is proof-of-concept shows how to use a custom
#### Credits
Steffen Vogel <post@steffenvogel.de>

16
c/linux_loader_poc/demo.c Normal file
View file

@ -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 <post@steffenvogel.de>
* @link http://www.steffenvogel.de
*/
#include <stdio.h>
int main()
{
printf("Hello world\n");
return 0;
}

View file

@ -0,0 +1,2 @@
/custom_loader_poc/kernel/binfmt_hermit.ko
/custom_loader_poc/kernel/binfmt_hermit.o

View file

@ -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)

View file

View file

@ -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 <post@steffenvogel.de>
* @link http://www.steffenvogel.de
*/
#include <linux/elf.h>
#include <linux/module.h>
#include <linux/string.h>
#include <linux/stat.h>
#include <linux/binfmts.h>
#include <linux/init.h>
#include <linux/file.h>
#include <linux/err.h>
#include <linux/fs.h>
#include <linux/module.h> /* Needed by all modules */
#include <linux/kernel.h> /* Needed for KERN_INFO */
#include <linux/init.h> /* Needed for the macros */
#include <linux/fs.h>
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 <steffen.vogel@rwth-aachen.de>");

View file

@ -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 <post@steffenvogel.de>
* @link http://www.steffenvogel.de
*/
#include <unistd.h>
#include <stdio.h>
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);
}