Skip to content

Instantly share code, notes, and snippets.

@adrianbn
Last active August 29, 2015 14:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save adrianbn/1352e8ecb46c1df60a1f to your computer and use it in GitHub Desktop.
Save adrianbn/1352e8ecb46c1df60a1f to your computer and use it in GitHub Desktop.
A PoC for a Linux kernel module performing transparent IO encryption.
/*
* 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");
# 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