从cve2015-1805漏洞入门
2020-08-02 15:06:35 Author: bbs.pediy.com(查看原文) 阅读量:360 收藏

  想学习漏洞,但是博客资料看了一大堆,好像入门了,也好像没有入门,决定从一个漏洞入手,全面的分析一下。从零开始,我把所有我遇到的问题,以及解决或者绕过替代的方案跟大家分享一下。

  作为一个新手,入手一个漏洞,首先要找个环境,测试漏洞是否存在,很不幸,如果顺利,我不会写环境搭建的问题。
  首先百度搜的资料,i春情有一个讲解是,看雪也有一个大佬写过相关的资料了,我手上只有一个nexus6p,为了分析漏洞专门买了一个nexus 5x,都失败了,因为没有了最早的镜像文件,现在除非google源码编译6.0,否在,市面上的镜像基本修复过了,反正我是没有找到可以的,google官网已经把这部分有漏洞的rom下架了,而如果源码编译6.0(有驱动方面的问题),这部分的版本是要趟坑的,我嫌麻烦就没有做,而且这个漏洞是内核漏洞,不是android专有的,我完全可以编译一个linux内核啊,然后使用这个内核就可以了。
漏洞补丁日志

https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=637b58c2887e5e57850865839cc75f59184b23d1

首先下载源码,因为这个漏洞是android和linux都有,所以我们下载android内核源码也可以,linux这个网站的源码也可以,一定要git下载,因为我们通过git回退到修复前的位置。
这个漏洞据说实在3.16以前的都有,反正版本那么多,随便下载吧,我也不懂,然后下载,我下载了一个3.16的(下载错了,搞了一大堆,找了找资料才知道,才想起来git回退的办法)。

git reset --hard 637b58c2887e5e57850865839cc75f59184b23d1

  回退到这个提交的前面的版本,回退了之后,好像回退到了一个什么游标的位置,git这个我也不是很懂,然后我直接编译,最好是ubuntu14.04,我编译的是64位的,直接能编译过。(坑:我电脑是ubuntu20,gcc9,编译的时候,什么位置无光,编译选项,c语言版本什么的报错都解决了,装了一个gcc 5 ,编译过了,但是编译的内核不能跑,后来桩docker 编译过的,懂的大神,能不能解释下为什么)
内核搞定了,本想直接替换ubuntu14的内核试试,结果,系统直接崩溃了,折腾了一下,最后找了个qemu模拟内核启动的方案,然后去学了根文件系统的制作,编译了一个busybox。

    https://www.secshi.com/19607.html

  因为不太懂内核堆喷,所以去看了这个大神的博客,然后我直接把他搭建的环境,一起驱动那俩直接用了,我写的太简陋了,这里,感谢大神,非常感谢。
  漏洞代码

    https://bbs.pediycom/thread-210503.htm

  这个大神也很感谢,我看了不下20遍,我把他的代码拿过来了,可以他是32位的,而且是arm,天真的我以为小改一下就能跑

   https://github.com/mobilelinux/iovy_root_research

  还有这个代码,他们的区别我下面会讲,这个工具,好像是个著名的提权工具   

  漏洞描述什么的,不好意思,看不懂,代码修改完了,提权不了,我去,蒙b了,心碎了。
  第二天,天晴了,雨停了,我又行了。开始从头分析

  readv是什么

  readv函数,是一种高级I/O函数,就是加快读写速度的,每种I/O驱动都可以实现,也可以不实现,如果不实现,应该就没法使用readv。
  这里使用pipe管道驱动的readv函数,最后调用到了pipe_read,这个函数有堆数组越界,导致任意读写,然后在进行一系列堆喷,溢出计算,等等看不懂的操作,据说就能提权,我们先看readv函数调用顺序。

read->SYSCALL_DEFINE3(readv...)->vfs_readv->do_readv_writev-> do_sync_readv_writev/do_loop_readv_writev -> pipe_read

(do_sync_readv_writev/do_loop_readv_writev ,这两个函数我没有具体研究。。。。)

代码我还是贴上吧,里面的注释,如果看不懂,可以看完在慢慢分析

static ssize_t
pipe_read(struct kiocb *iocb, const struct iovec *_iov,
       unsigned long nr_segs, loff_t pos)
{
    struct file *filp = iocb->ki_filp;
    struct pipe_inode_info *pipe = filp->private_data;
    int do_wakeup;
    ssize_t ret;
    struct iovec *iov = (struct iovec *)_iov;
    size_t total_len;

    total_len = iov_length(iov, nr_segs);    //读取iov->leng数据的长度的总长度
    /* Null read succeeds. */
    if (unlikely(total_len == 0))
        return 0;

    do_wakeup = 0;
    ret = 0;
    __pipe_lock(pipe);
    for (;;) {
        int bufs = pipe->nrbufs;
        if (bufs) {
            int curbuf = pipe->curbuf;
            struct pipe_buffer *buf = pipe->bufs + curbuf;
            const struct pipe_buf_operations *ops = buf->ops;
            void *addr;
            size_t chars = buf->len;
            int error, atomic;

            if (chars > total_len)                  //chars 最大0x1000 也就是1页大小
                chars = total_len;

            error = ops->confirm(pipe, buf);
            if (error) {
                if (!ret)
                    ret = error;
                break;
            }
   //参数漏洞的位置主要在这里,这里有页错误然后goto的初衷,是因为高端内存映射不能一直驻留,pipe使用了高端内存,所以它本身会有页错误处理,而攻击者利用他的页错误处理
   //将本该是驱动本身的页,没有映射到高端内存的错误,变成了用户空间接收数据的页的错误,强行goto
            atomic = !iov_fault_in_pages_write(iov, chars);   //这一行也很很重要,堆喷的内存可以必须可读
redo:
            addr = ops->map(pipe, buf, atomic);
            error = pipe_iov_copy_to_user(iov, addr + buf->offset, chars, atomic);   //拷贝数据,无论怎么拷贝,抖不会超过一页的大小
                                                                                                                                                   //在第一次进入pipe_iov_copy_to_user这个函数以后,将界面释放,造成错误
            ops->unmap(pipe, buf, addr);
            if (unlikely(error)) {
                /*
                 * Just retry with the slow path if we failed.
                 */
                if (atomic) {
                    atomic = 0;
                    goto redo;                  //构造页错误,回强行执行到这个位置,导致 total_len 也就是接收数据的长度没更新
                                                                                                                     //第一次执行pipe_iov_copy_to_user,故意构造错误,返回,会执行到这里
                }
                if (!ret)
                    ret = error;
                break;
            }
            ret += chars;
            buf->offset += chars;
            buf->len -= chars;

            /* Was it a packet buffer? Clean up and exit */
            if (buf->flags & PIPE_BUF_FLAG_PACKET) {
                total_len = chars;
                buf->len = 0;
            }

            if (!buf->len) {       //这个页读完了,更新到下一个页,来接着读
                buf->ops = NULL;
                ops->release(pipe, buf);
                curbuf = (curbuf + 1) & (pipe->buffers - 1);   //从1-10的读取顺序
                pipe->curbuf = curbuf;
                pipe->nrbufs = --bufs;
                do_wakeup = 1;
            }
                total_len -= chars;             //第二次执行完 pipe_iov_copy_to_user 函数以后,会执行到这里更新长度,这个时候,
                                                                                               //iov已经消耗完毕了,但是total_len,还没有读完  溢出部分 = total_len - chars - 第一次执行时消耗的个数
            if (!total_len)
                break;    /* common path: read succeeded */
        }
        if (bufs)    /* More to do? */
            continue;
        if (!pipe->writers)
            break;
        if (!pipe->waiting_writers) {
            /* syscall merging: Usually we must not sleep
             * if O_NONBLOCK is set, or if we got some data.
             * But if a writer sleeps in kernel space, then
             * we can wait for that data without violating POSIX.
             */
            if (ret)
                break;
            if (filp->f_flags & O_NONBLOCK) {
                ret = -EAGAIN;
                break;
            }
        }
        if (signal_pending(current)) {
            if (!ret)
                ret = -ERESTARTSYS;
            break;
        }
        if (do_wakeup) {
            wake_up_interruptible_sync_poll(&pipe->wait, POLLOUT | POLLWRNORM);
             kill_fasync(&pipe->fasync_writers, SIGIO, POLL_OUT);
        }
        pipe_wait(pipe);
    }
    __pipe_unlock(pipe);

    /* Signal writers asynchronously that there is more room. */
    if (do_wakeup) {
        wake_up_interruptible_sync_poll(&pipe->wait, POLLOUT | POLLWRNORM);
        kill_fasync(&pipe->fasync_writers, SIGIO, POLL_OUT);
    }
    if (ret > 0)
        file_accessed(filp);
    return ret;
}

static int pipe_iov_copy_to_user(struct iovec *iov, const void *from, unsigned long len,
              int atomic)
{
    unsigned long copy;

    while (len > 0) {
        while (!iov->iov_len)
            iov++;
        copy = min_t(unsigned long, len, iov->iov_len);

        if (atomic) {
            if (__copy_to_user_inatomic(iov->iov_base, from, copy))
                return -EFAULT;
        } else {
            if (copy_to_user(iov->iov_base, from, copy))
                return -EFAULT;
        }
        from += copy;
        len -= copy;
        iov->iov_base += copy;
        iov->iov_len -= copy;  拷贝过的项,iov_len=0   copy的值,看上面
    }
    return 0;
}

  我们通过这个函数来分析,为什么产生漏洞,搞清楚,不是如何利用,如何进行任意地址写,只是分析为什么会产生
  如果造成 atomic=1,pipe_iov_copy_to_user执行错误返回,这个时候,iov数组就会已经copy过的项的iov_len已经等于0了,但是这个时候,函数的逻辑是将atomic=0,然后继续goto到这个函数的位置,继续copy,就从这个地方看,再次进入iov函数,只有atomic和iov的数据产生了变化,拷贝的个数chars和源地址addr + buf->offset均未发生变化,不考虑漏洞的问题,这本身就是bug了,内存拷贝,目的地址变小了,但是拷贝的个数和源缓冲区大小都没变的情况下,必然导致溢出。

  第一个点,缓冲区溢出了。

  溢出的位置是哪里,我们看这个函数,iov是否数组,但是是通过指针使用的,如果要写入的数据不为0,iov数组就会越界,直到len小于0为止

  第二个点,iov数组会一直越界,直到len小于0位置,缓冲去溢出的位置就是越界的iov数组元素的iov_base,溢出的长度是iov_len

  这个漏洞是拷贝数据函数是pipe_iov_copy_to_user里面的__copy_to_user_inatomic,要达到地任意址写入任意大小的任意数据,我们必须同时控制他的三个参数(iov->iov_base,from, copy),copy大部分情况下等于iov->iov_le,from,是我们要读的缓冲区的数据,也就是我们写进来的数据,通过readv读出去。而iov是会发生数组越界的。

  第三个点,copy函数三大参数,现在源缓冲区是我们写进来的数据,而长度和目的缓冲区都是iov,而且iov是会越界的

  我们现在需要跟踪分析这个iov的前世今生了。看看能不能控制他。

    readv ->  vfs_readv  -> do_readv_writev-> rw_copy_check_uvector

  iov数组,只要大于8组,就可以在rw_copy_check_uvector这个函数通过kmalloc分配,然后将用户的数据拷贝过来的,小于8组看上个函数

ssize_t rw_copy_check_uvector(int type, const struct iovec __user * uvector,
                  unsigned long nr_segs, unsigned long fast_segs,
                  struct iovec *fast_pointer,
                  struct iovec **ret_pointer)
{
    unsigned long seg;
    ssize_t ret;
    struct iovec *iov = fast_pointer;

    if (nr_segs == 0) {
        ret = 0;
        goto out;
    }
    if (nr_segs > UIO_MAXIOV) {
        ret = -EINVAL;
        goto out;
    }
    if (nr_segs > fast_segs) {    //如果大约8组就向堆申请,否在,不申请内存,使用上个函数的内部变量
        iov = kmalloc(nr_segs*sizeof(struct iovec), GFP_KERNEL);
        if (iov == NULL) {
            ret = -ENOMEM;
            goto out;
        }
    }
    if (copy_from_user(iov, uvector, nr_segs*sizeof(*uvector))) {   //传入的用户数据,拷贝到内核态的内存上
        ret = -EFAULT;
        goto out;
    }

    ret = 0;
    for (seg = 0; seg < nr_segs; seg++) {
        void __user *buf = iov[seg].iov_base;
        ssize_t len = (ssize_t)iov[seg].iov_len;

        /* see if we we're about to use an invalid len or if
         * it's about to overflow ssize_t */
        if (len < 0) {
            ret = -EINVAL;
            goto out;
        }
        if (type >= 0
            && unlikely(!access_ok(vrfy_dir(type), buf, len))) {
            ret = -EFAULT;
            goto out;
        }
        if (len > MAX_RW_COUNT - ret) {
            len = MAX_RW_COUNT - ret;
            iov[seg].iov_len = len;
        }
        ret += len;
    }
out:
    *ret_pointer = iov;
    return ret;
}

  64位内核,struct iovec大小=16,也就是说,我们传入多少个,iov的个数,拷贝到内核的数据,就是16*传入的个数

  第四个点,在堆内申请了一个iov数组是,而我们要控制iov数组,哪怕控制其中一个元素也行。而iov数组越界,就是堆溢出。

  我们都知道这是个堆数组越界,但是我不这么分析,漏洞手段千百中,为什么这个漏洞要用堆喷那。
  我们是要控制iov数组中的数据,让他达到任意写的目的,第一个想法,直接写进来,通过用户空间的iov传入(原谅我的天真和无知),因为这个iov是复制了我们用户空间传入的iov,我们直接在用户空间构造好iov行不行。抱歉,真不行!!!你以为写内核的大佬比你还菜吗!!
  如果你传入类内核的地址,好像也可以啊,我试了一下,pipe_read都跑不到,你就被干掉了,就在rw_copy_check_uvector,这一行

if (type >= 0 && unlikely(!access_ok(vrfy_dir(type), buf, len)))

  如果你传入内核地址,让他copy这里是过不了,所以这个方案,是不可行,也就是说,我们必须想办法,过掉这个函数的检测。而这个函数是基于数组的检测,也就是说,你传进来多少个数组,他就检测多少个,并且他的copy是基于你数组的大小的,多余的数据你是没办法传进来的,那就只能堆喷了,因为这个数组是在堆里面,而且溢出了,我们想办法让他溢出到我们的想让他溢出的数据上就行了,这样我们就能控制iov数组的数据了。

  第五个点,主动造成iov数组溢出,然后通过堆喷,构造溢出数据,达到任意地址写任意值

  原理分析完了,该干活了(我是反向分析的,也就是说,上面写的,是我代码调试中,一步一步得出的结论,最后我总结了一个这个漏洞各个知识点的串联,不只问结果,更加问过程,为什么这样做,如果不明白为什么,不能举一反三,根本就是不会)
  通过上面的知识点,我们得出结论
  1.写入的数据,就是要改变的地址的内容,是源缓冲区的内容。
  2.必须构造参数导致iov溢出,堆溢出
  3.堆喷
  太多没用的我就不写了,看雪大神另一篇博客上写的很清楚,我在写显得太罗嗦了。有些大神可能说了,有些可能觉得太简单了,就没写。我把一些边边角角的补充一下。

  1.写入数据,这个为什么可以控制,如何控制,这个可以看,pipe驱动别的相关代码,pipe缓冲区,是个环形缓冲区,申请了16页的内存,也一种16页的内存这个,这个好象是可以调整的

    ulimit -a

  这条命令可以查看自己电脑上的pipe相关信息。

  2.如何构造iov溢出,这个大神已经写过了,ummap就可以了,这个后续调试我会写遇到的问题,但是不只是ummap,必须写够4096个字节以上才行,因为,我们必须要调用三次pipe_iov_copy_to_user,如果没有读过4096可能一次就copy完了,即使中间ummap报错goto一次,也是两次,而第二次,atomic是等于0的,会导致写错误。理论上来说多几次应该也可以的。

  3.要进行内核堆喷,就要了解内核堆,了解内核内存分配机制slub这一类的,可以看下下面的博客
  https://blog.csdn.net/FreeeLinux/article/details/54754752
  我用kmalloc进行过测试,申请相同大小的内存,内存之间是没有缝隙的,只限于组与组之间。而且内存申请释放之后,如果再次申请到相同的内存,如果没有人用过这个页面,数据极有可能没有改变。所以这里就出现了两种堆喷的方式,
  (1)看雪大神,用的是,申请4k的一整个页面,数组在这个页里面全铺满,数组的溢出,就是在这一个页的相邻的下一个页,由于内核堆组内数据之间没有缝隙,可以申请很多页,总能碰到下一页是我申请的数据的情况。
  (2)iovyroot,用的是堆块使用玩后不清理,再次申请的数据跟上次一样的机制,但是他使用一页,稍微大几个字节,这样内存申请内存的时候,一样返回的是两页,但是copy到内核内存中的字节却只有一页大几个字节,然后将iov溢出的数据,填在copy到内存中的数据的大小的后面,进行堆喷,这样,如果内核刚好申请到他的堆喷的页面,只copy了1页多几个自己的数据,剩下的数据,还是原来的,就可以用来控制堆喷了。
  第二中方式,在这里可能更好一下,第一种方法,应该也会有一点点第二种方法的概率在里面,但是成功率,没有第二个高

  环境上面写了,内核下载下来以后,git回退到了这个漏洞修复前的位置,内核突然变成3.14版本了。。。。
  既然有源码,我这么菜,不如调试一下,改一下pipe驱动的代码。
  问题1:我修改了pipe驱动,在pipe_read函数中增加了printk,但是在内核跑得时候,却打印不出来,没有任何显示
  问题2:gdb调试内核跑飞,如何编译一个O0的内核,便于调试,我按照网上的单独增加pipe.o的编译标识未-O0还是没啥反映。
  方法总比困难的,希望有大神,能解释一下我上面提出的问题,后来我又写了模仿pipe直接写了一个驱动。
 
  wirte函数,我只设置了一个页的缓冲区,写入一次就行,多次使用,read函数只是实现了驱动的漏洞部分的代码,如果调试驱动有困难,可以试一试我这种方法。
  1.堆喷,iov这个内存块,如果使用第一种方式堆喷,可以看一下他是什么时候释放的,我测试的时候,iov在使用完后释放了,单线程,完全没办法造成堆喷,也是我只跑了一个内核,任务少的原因,每次iov的地址,基本上跟上次地址都是相同的,想要进行堆喷,必须进行多线程。
  2.munmap和mmap的时机问题,大神博客基本写过了,但是实际调试过程中,却发现还是有问题的,可能时间有些出入,每台机器毕竟都不相同,运行的时候
       线程1                  线程2       线程3
    iov_fault_in_pages_write
                            unmap
    pipe_iov_copy_to_user
                            mmap
    然后goto调用下一次pipe_iov_copy_to_user
                                     堆喷完成,数据布置位置准确
    调用第三次pipe_iov_copy_to_user   
  我曾经双线程,就调试这一个问题,最后还是没办法把握好时间处理成功    
  3.x86有cr0位有内核写保护,我太傻了,还以为直接能跑,第16位,数组那种,0是第一位,而且,设一次不好使,cpu环境一旦切换还是不行,这好象是个基本问题吧。。。。但是我确实在这死过好几天。
  native_write_cr0(0x80040033);  和 native_read_cr0();
  这两个我是分析内核源码找到的。。
  4.提权使用的是sys_call_table,由于是自己编译的内核,我是直接写死的地址,但是我去,调用号完全不一样,我去内核源码找,也没找到正常的,反正跟真实的都不对,最后我去查看sys_call_table的内存,然后一行一行的对函数地址才找到的。
  5.gdb调试内核,多线程不好调试,动不动飞了 最后我用了set scheduler-locking step ,然后不跑了,关于这个命令,网上说的不是很明白,有没有大神,能给我通俗易懂的解释一下
  
  我实现的驱动代码:

static int iov_fault_in_pages_write(struct iovec *iov, unsigned long len)
{
    while (!iov->iov_len)
        iov++;

    while (len > 0)
    {
        unsigned long this_len;

        this_len = min_t(unsigned long, len, iov->iov_len);
        if (fault_in_pages_writeable(iov->iov_base, this_len))
        {
            // printk(KERN_WARNING "iov_fault_in_pages_write iov_base = %p\n", iov->iov_base);
            // printk(KERN_WARNING "iov_fault_in_pages_write iov_len = %d\n", iov->iov_len);
            break;
        }

        len -= this_len;
        iov++;
    }

    return len;
}
static int
pipe_iov_copy_to_user(struct iovec *iov, const void *from, unsigned long len,
                      int atomic)
{
    unsigned long copy;

    while (len > 0)
    {
        while (!iov->iov_len)
            iov++;
        copy = min_t(unsigned long, len, iov->iov_len);
        // printk(KERN_WARNING "iov_base = %p\n", iov->iov_base);
        // printk(KERN_WARNING "iov_len = %d\n", iov->iov_len);
        // printk(KERN_WARNING "from = %x\n",from);

        if (atomic)
        {
            if (__copy_to_user_inatomic(iov->iov_base, from, copy))
            {

                return -EFAULT;
            }
        }
        else
        {
            if (copy_to_user(iov->iov_base, from, copy))
            {

                return -EFAULT;
            }
        }
        from += copy;
        len -= copy;
        iov->iov_base += copy;
        iov->iov_len -= copy;
    }
    return 0;
}

static int
pipe_iov_copy_from_user(void *to, struct iovec *iov, unsigned long len,
                        int atomic)
{
    unsigned long copy;

    while (len > 0)
    {
        while (!iov->iov_len)
            iov++;
        copy = min_t(unsigned long, len, iov->iov_len);
        if (atomic)
        {
            if (__copy_from_user_inatomic(to, iov->iov_base, copy))
                return -EFAULT;
        }
        else
        {
            if (copy_from_user(to, iov->iov_base, copy))
                return -EFAULT;
        }
        to += copy;
        len -= copy;
        iov->iov_base += copy;
        iov->iov_len -= copy;
    }
    return 0;
}

static ssize_t
pipe_write(struct kiocb *iocb, const struct iovec *_iov,
           unsigned long nr_segs, loff_t ppos)
{
    printk(KERN_WARNING "vuln_write v1\n");
    struct file *filp = iocb->ki_filp;
    struct pipe_inode_info *pipe = filp->private_data;
    ssize_t ret;
    int do_wakeup;
    struct iovec *iov = (struct iovec *)_iov;
    size_t total_len;
    ssize_t chars;
    int error, atomic = 1;
    char *src;
    struct page *page = pipe->tmp_page;
    int get_root = *((int *)(iov[0].iov_base));
    printk(KERN_WARNING "get_root = %d\n", get_root);
    printk(KERN_WARNING "buffers = %d\n", iov[1].iov_base);
    printk(KERN_WARNING "buffers = %d\n", iov[1].iov_len);
    if (!page)
    {
        page = alloc_page(GFP_HIGHUSER);
        if (unlikely(!page))
        {
            ret = ret ?: -ENOMEM;
            return ret;
        }
        pipe->tmp_page = page;
    }
    total_len = iov_length(iov, nr_segs);
    src = kmap_atomic(page);
    printk(KERN_WARNING "src = %p\n", src);
    error = pipe_iov_copy_from_user(src, iov, chars, atomic);
    kunmap_atomic(src);

    return 0;
}

static ssize_t
pipe_read(struct kiocb *iocb, const struct iovec *_iov,
          unsigned long nr_segs, loff_t pos)
{
    printk(KERN_WARNING "vuln_read\n");
    struct file *filp = iocb->ki_filp;
    struct pipe_inode_info *pipe = filp->private_data;
    struct iovec *iov = (struct iovec *)_iov;
    size_t total_len;
    size_t chars;
    int error, atomic;
    void *addr;
    ssize_t ret = 0;
    struct page *page = pipe->tmp_page;
    total_len = iov_length(iov, nr_segs);
    native_write_cr0(0x80040033);
    unsigned long cr0 = native_read_cr0();
    printk(KERN_WARNING "vuln_open cr0 = %lx\n", cr0);
    printk(KERN_WARNING "vuln_read total_len = %d\n", total_len);
    printk(KERN_WARNING "vuln_read iov = %p\n", iov);
    // struct iovec *target = (struct iovec *)kzalloc(8192, GFP_KERNEL);
    // printk(KERN_WARNING "vuln_read iov = %p\n", target);
    // target[0].iov_base = 0xffffffff81801550;
    // target[0].iov_len = 0x8;
    // target[1].iov_base = 0xffffffff81801550;
    // target[1].iov_len = 0x8;
    // target[2].iov_base = 0xffffffff81801550;
    // target[2].iov_len = 0x8;
    while (1)
    {
        chars = 4096;
        if (chars > total_len)
            chars = total_len;
        printk(KERN_WARNING "pipe_read chars = %d\n", chars);
        atomic = !iov_fault_in_pages_write(iov, chars);

    redo:
        if (atomic)
            addr = kmap_atomic(page);
        else
            addr = kmap(page);
        printk(KERN_WARNING "pipe_read atomic = %d\n", atomic);
        // mdelay(300);
        error = pipe_iov_copy_to_user(iov, addr, chars, atomic);
        printk(KERN_WARNING "pipe_read error = %d\n", error);
        if (atomic)
            kunmap_atomic(addr);
        else
            kunmap(page);
        if (unlikely(error))
        {
            if (atomic)
            {
                atomic = 0;
                goto redo;
            }
            if (!ret)
                ret = error;
            break;
        }
        ret += chars;
        total_len -= chars;
        if (!total_len)
        {
            break;
        }
    }
    // kfree(target);
    return ret;
}

static int pipe_close(struct inode *inode, struct file *filp)
{
    printk(KERN_WARNING "vuln_close\n");

    return 0;
}

static int fifo_open(struct inode *inode, struct file *filp)
{
    struct pipe_inode_info *pipe;
    pipe = kzalloc(sizeof(struct pipe_inode_info), GFP_KERNEL);
    filp->private_data = pipe;
    pipe->nrbufs = 9;
    pipe->curbuf = 8;
    pipe->buffers = 7;
    native_write_cr0(0x80040033);
    unsigned long cr0 = native_read_cr0();
    printk(KERN_WARNING "vuln_open cr0 = %lx\n", cr0);
    if (fault_in_pages_writeable(0xffffffff81801550, 8)){
            printk(KERN_WARNING "fault_in_pages_writeable ture");
    }

    return 0;
}

/**
* The operations allowed by userspace applications.
* We only really allow access through the ioctl interface.
*/
static struct file_operations vuln_ops = {
    .owner = THIS_MODULE,
    .unlocked_ioctl = do_ioctl,
    .release = vuln_release,
    .aio_write = pipe_write,
    .aio_read = pipe_read,
    .open = fifo_open,
    // .close = pipe_close,
};

static int call_kernel_kmalloc(void)   //用于堆喷,可以先调用这个函数,在readv
{

    struct iovec *trash_object = kmalloc(8192, GFP_KERNEL);
    printk(KERN_WARNING "[x] call_kernel_kmalloc [x] =%p\n",trash_object);
    trash_object[257].iov_base = 0xffffffff81801550;
    trash_object[257].iov_len = 8;
    trash_object[258].iov_base = 0xffffffff81801550;
    trash_object[258].iov_len = 8;
    kfree(trash_object);
    return 0;
}

资源:
把这个代码用linux跑就行,不过,没法提权,主要是因为x86有cr0的问题,但是漏洞利用成功是可以看到的,内核地址没法读写,它还提供了一个用户地址,用于测试,我们可以看到那个用户地址堆喷成功,数据改写了,一次可能不成功,多跑几次吧
https://github.com/mobilelinux/iovy_root_research

驱动,以及提权相关的我就不献丑了,看这个大神的吧,我也是用它的驱动改写的
https://www.secshi.com/19607.html

  我的代码我思来想去还是不放了,免得误人子弟。

【预售】看雪夏日限定 · 清爽速干T恤 开卖啦!运动速干T恤,让运动更干爽!


文章来源: https://bbs.pediy.com/thread-261165.htm
如有侵权请联系:admin#unsafe.sh