Last active
August 29, 2015 14:23
-
-
Save adrianbn/1352e8ecb46c1df60a1f to your computer and use it in GitHub Desktop.
A PoC for a Linux kernel module performing transparent IO encryption.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* encrypted_io.c | |
* | |
* usage: insmod encrypt_io.ko uid=<user ID> dir=<folder> | |
* | |
* If no uid is specified, -1 is used and no user will be affected. | |
* If no dir is specified, /tmp/ is used. | |
* | |
* For more information on theory of operation of kretprobes, see | |
* Documentation/kprobes.txt | |
* | |
*/ | |
#include <linux/kernel.h> | |
#include <linux/module.h> | |
#include <linux/kprobes.h> | |
#include <linux/ktime.h> | |
#include <linux/limits.h> | |
#include <linux/sched.h> | |
/* To access user info (UID) */ | |
#include <linux/cred.h> | |
/* To access file table info */ | |
#include <linux/fdtable.h> | |
#include <linux/fs.h> | |
#include <linux/limits.h> | |
#include <linux/gfp.h> | |
/* Global defines */ | |
#define KEY "AAAAAAAA" | |
#define KEYLENGTH 8 | |
static unsigned int target_uid = -1; | |
static char target_dir[PATH_MAX] = "/tmp/"; | |
module_param_named(uid, target_uid, int, S_IRUGO); | |
module_param_string(dir, target_dir, PATH_MAX, S_IRUGO); | |
MODULE_PARM_DESC(uid, "UID of the user for whom to encrypt/decrypt IO operations"); | |
MODULE_PARM_DESC(dir, "Folder under which IO operations are encrypted on the fly"); | |
/* Helper Functions */ | |
/* Returns the filename given the file descriptor */ | |
char* get_filename(long fd, char* buf){ | |
struct file *file; | |
char *pathname; | |
char* tmp; | |
struct path *path; | |
// Am I allowed to preempt? | |
rcu_read_lock(); | |
file = fcheck(fd); | |
if (!file) { | |
rcu_read_unlock(); | |
printk(KERN_INFO "[!] Failed to acquire file. This is gonna hurt."); | |
return NULL; | |
} | |
path = &file->f_path; | |
path_get(path); | |
tmp = kmalloc(PATH_MAX * sizeof(char), GFP_ATOMIC); | |
if (!tmp) { | |
path_put(path); | |
printk(KERN_INFO "[!] Failed allocating space for pathname."); | |
return NULL; | |
} | |
pathname = d_path(path, tmp, PAGE_SIZE); | |
path_put(path); | |
if (IS_ERR(pathname)){ | |
kfree(tmp); | |
printk(KERN_INFO "[!] Failed retrieving dentry."); | |
return NULL; | |
} | |
kfree(tmp); | |
strncpy(buf, pathname, PATH_MAX); | |
rcu_read_unlock(); | |
return buf; | |
} | |
/* Mangles data to simulate encryption */ | |
int mangle(char* src){ | |
int i; | |
char* tmp; | |
tmp = kmalloc((strlen(src) + 1)*sizeof(char), GFP_ATOMIC); | |
for (i=0; src[i] != '\0'; i++){ | |
tmp[i] = src[i] ^ KEY[i % KEYLENGTH]; | |
} | |
tmp[i] = '\0'; | |
strncpy(src, tmp, strlen(src)); | |
kfree(tmp); | |
return 0; | |
} | |
/* per kretprobe-instance private data */ | |
struct my_data { | |
long rsi; // for sys_read this holds the address of the read buffer | |
long fd; // for sys_read this holds the fd to the opened file | |
}; | |
/* | |
* sys_read entry handler. Here we store parameters we'll modify at return hook | |
* such as the address of the buffer with the contents and the fd | |
*/ | |
static int sys_read_entry_handler(struct kretprobe_instance *ri, struct pt_regs *regs) { | |
struct my_data *data; | |
if (!current->mm) | |
return 1; /* Skip kernel threads */ | |
if (current_uid() == target_uid) { | |
data = (struct my_data *)ri->data; | |
data->rsi = regs->si; | |
data->fd = (long)((void*)regs->di); | |
} | |
return 0; | |
} | |
/* | |
* sys_read Return-probe handler: modify the contents of the buffer read | |
* for the given user and folder | |
*/ | |
static int sys_read_ret_handler(struct kretprobe_instance *ri, struct pt_regs *regs) { | |
struct my_data *data = (struct my_data *)ri->data; | |
if (current_uid() == target_uid) { | |
char* pbuf; | |
char* fname; | |
fname = kmalloc(PATH_MAX*sizeof(char), GFP_ATOMIC); | |
fname = get_filename(data->fd, fname); | |
if (strncmp(fname, target_dir, strlen(target_dir)) == 0) { | |
pbuf = (char*)(data->rsi); | |
printk(KERN_INFO "[+] - READ - Data before mangle at 0x%p: %s\n", pbuf, pbuf); | |
mangle(pbuf); | |
printk(KERN_INFO "[+] - READ - Data after mangle at 0x%p: %s\n", pbuf, pbuf); | |
} | |
kfree(fname); | |
} | |
return 0; | |
} | |
/* | |
* sys_write pre_handler: called at the beginning of sys_write. Modifies the data to be written | |
* for the given UID and folder. | |
*/ | |
int sys_write_handler_pre(struct kprobe *p, struct pt_regs *regs) { | |
/* Check that the UID matches the intended target */ | |
if (current_uid() == target_uid) { | |
char *buf; | |
char *fname; | |
fname = kmalloc(PATH_MAX*sizeof(char), GFP_ATOMIC); | |
fname = get_filename((long)((void*)regs->di), fname); | |
/* Check that the target folder matches the intended one | |
* TODO: Make this check robust (think ../../../tmp) | |
*/ | |
if (strncmp(fname, target_dir, strlen(target_dir)) == 0){ | |
/* | |
* Store second argument of write() in buf | |
* ssize_t write(int fd, const void *buf, size_t count); | |
*/ | |
buf = (char*)regs->si; | |
printk(KERN_INFO "[+] - WRITE - Data before mangle at 0x%p: %s\n", buf, buf); | |
/* "Encrypt" data to be written */ | |
mangle(buf); | |
printk(KERN_INFO "[+] - WRITE - Data after mangle at 0x%p: %s\n", buf, buf); | |
} | |
kfree(fname); | |
} | |
return 0; | |
} | |
/* kprobe post_handler: called after the probed instruction is executed. Not necessary for our purposes. */ | |
void sys_write_handler_post(struct kprobe *p, struct pt_regs *regs, unsigned long flags) { | |
/* For debugging purposes only | |
* printk("post_handler: p->addr=0x%p, rflags=0x%lx\n", p->addr, regs->flags); | |
*/ | |
} | |
/* fault_handler: this is called if an exception is generated for any | |
* instruction within the pre- or post-handler, or when Kprobes | |
* single-steps the probed instruction. | |
*/ | |
int sys_write_handler_fault(struct kprobe *p, struct pt_regs *regs, int trapnr) { | |
/* TODO: consider handling some faults? */ | |
printk("fault_handler: p->addr=0x%p, trap #%dn", | |
p->addr, trapnr); | |
/* Return 0 because we don't handle the fault. */ | |
return 0; | |
} | |
/* Definition of the return probe used to hook sys_read */ | |
static struct kretprobe sys_read_kretprobe = { | |
.handler = sys_read_ret_handler, | |
.entry_handler = sys_read_entry_handler, | |
.data_size = sizeof(struct my_data), | |
/* Probe up to 30 instances concurrently. | |
* FIXME: tweak this. Use NRPROC*value */ | |
.maxactive = 30, | |
.kp.symbol_name = "sys_read", | |
}; | |
/* Definition of the kprobe used to hook sys_write */ | |
static struct kprobe wrprobe = { | |
.pre_handler = sys_write_handler_pre, | |
.post_handler = sys_write_handler_post, | |
.fault_handler = sys_write_handler_fault, | |
.addr = NULL, | |
.symbol_name = "sys_write", | |
}; | |
/* Init module */ | |
static int __init encrypted_io_init(void) { | |
int ret; | |
printk(KERN_INFO "[+] Starting module encrypted_io with uid=%d and dir=%s\n", target_uid, target_dir); | |
ret = register_kretprobe(&sys_read_kretprobe); | |
if (ret < 0) { | |
printk(KERN_INFO "[!] register_kretprobe failed, returned %d\n", ret); | |
return -1; | |
} | |
printk(KERN_INFO "[+] Planted return probe at %s: %p\n", | |
sys_read_kretprobe.kp.symbol_name, sys_read_kretprobe.kp.addr); | |
if((ret = register_kprobe(&wrprobe)) < 0) { | |
printk(KERN_INFO "[!] sys_write register_probe failed, returned %d\n", ret); | |
return -1; | |
} | |
printk(KERN_INFO "[+] Planted kprobe at %s:, %p\n", wrprobe.symbol_name, wrprobe.addr); | |
return 0; | |
} | |
/* Tear down module */ | |
static void __exit encrypted_io_exit(void) | |
{ | |
unregister_kretprobe(&sys_read_kretprobe); | |
printk(KERN_INFO "[+] kretprobe at %p unregistered\n", | |
sys_read_kretprobe.kp.addr); | |
/* nmissed > 0 suggests that maxactive was set too low. */ | |
printk(KERN_INFO "[++] Missed probing %d instances of %s\n", | |
sys_read_kretprobe.nmissed, sys_read_kretprobe.kp.symbol_name); | |
unregister_kprobe(&wrprobe); | |
printk("[+] kprobe sys_write unregistered"); | |
} | |
module_init(encrypted_io_init) | |
module_exit(encrypted_io_exit) | |
MODULE_LICENSE("GPL"); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Makefile | |
obj-m := encrypt_io.o | |
KDIR := /lib/modules/$(shell uname -r)/build | |
PWD := $(shell pwd) | |
default: | |
$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules | |
clean: | |
rm -f *.mod.c *.ko *.o |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment