Loading [MathJax]/jax/output/CommonHTML/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >CVE-2017-5123 漏洞利用全攻略

CVE-2017-5123 漏洞利用全攻略

作者头像
Seebug漏洞平台
发布于 2018-03-16 04:16:02
发布于 2018-03-16 04:16:02
1.7K0
举报
文章被收录于专栏:Seebug漏洞平台Seebug漏洞平台

原文:https://salls.github.io/Linux-Kernel-CVE-2017-5123/

译者:hello1900@知道创宇404实验室

本文介绍如何利用Linux内核漏洞CVE-2017-5123提升权限,突破SEMP、SMAP、Chrome沙箱全方位保护。

背景

在系统调用处理阶段,内核需要具备读取和写入触发系统调用进程内存的能力。为此,内核设有copy_from_userput_user等特殊函数,用于将数据复制进出用户区。在较高级别,put_user的功能大致如下:

代码语言:txt
AI代码解释
复制
put_user(x, void __user *ptr)
    if (access_ok(VERIFY_WRITE, ptr, sizeof(*ptr)))
        return -EFAULT
    user_access_begin()
    *ptr = x
    user_access_end()

access_ok() 调用检查ptr是否位于用户区而非内核内存。如果检查通过,user_access_begin()调用禁用SMAP,允许内核访问用户区。内核写入内存后重新启用SMAP。需要注意的一点是:这些用户访问函数在内存读写过程中处理页面错误,在访问未映射内存时不会导致崩溃。

漏洞

某些系统调用要求多次调用put/get_user以实现内核与用户区之间的数据复制。为避免重复检查和SMAP启用/禁用的额外开销,内核开发人员将缺少必要检查的不安全版本_put_userunsafe_put_user涵盖进来。这样一来,忘记额外检查就在意料之中了。CVE-2017-5123就是一个很好的例子。在内核版本4.13中,为了能够正常使用unsafe_put_user,专门对waitid syscall进行了更新,但access_ok检查仍处于缺失状态。漏洞代码如下所示。

代码语言:txt
AI代码解释
复制
SYSCALL_DEFINE5(waitid, int, which, pid_t, upid, struct siginfo __user *,
                                  infop, int, options, struct rusage __user *, ru)
{
    struct rusage r;
    struct waitid_info info = {.status = 0};
    long err = kernel_waitid(which, upid, &info, options, ru ? &r : NULL);
    int signo = 0;

    if (err > 0) {
        signo = SIGCHLD;
        err = 0;
        if (ru && copy_to_user(ru, &r, sizeof(struct rusage)))
            return -EFAULT;
        }
        if (!infop)
            return err;

        user_access_begin();
        unsafe_put_user(signo, &infop->si_signo, Efault);    <-    no access_ok call
        unsafe_put_user(0, &infop->si_errno, Efault);
        unsafe_put_user(info.cause, &infop->si_code, Efault);
        unsafe_put_user(info.pid, &infop->si_pid, Efault);
        unsafe_put_user(info.uid, &infop->si_uid, Efault);
        unsafe_put_user(info.status, &infop->si_status, Efault);
        user_access_end();
        return err;
Efault:
        user_access_end();
        return -EFAULT;
}

原语

缺少access_ok检查意味着允许提供内核地址并将其作为waitid syscall的infop参数。syscall将使用unsafe_put_user覆盖内核地址,因为此项操作可以逃避检查。该原语的棘手部分在于无法对写入内容(6个不同字段中的任何1个)施与足够控制。info.status 是32位int,但被限制为0 < status < 256。info.pid可在某种程度上通过重复fork操作进行控制,但最大值为0x8000。

以下是漏洞利用阶段将引用到的写入字段概况。

代码语言:txt
AI代码解释
复制
struct siginfo {
    int si_signo;
    int si_errno;
    int si_code;
    int padding;   // this remains unchanged by waitid
    int pid;       // process id
    int uid;       // user id
    int status;    // return code
}

谷歌Chrome沙箱

该漏洞的特色在于可从Chrome浏览器沙箱内部实现提权。首先介绍Chrome沙箱概况与工作原理。

谷歌Chrome采用沙箱保护浏览器,即便成功利用漏洞实现代码执行也无法touch系统其它部分。沙箱分两层:第一层通过改变user id与chroot限制资源访问;第二层尝试通过seccomp filter限制内核攻击面,阻止沙箱进程中不必要的系统调用。通常情况下,Chrome沙箱行之有效,因为Linux内核漏洞多位于syscall,由seccomp沙箱拦截。

然而,waitid syscall在seccomp沙箱中普遍存在,当然也包括Chrome沙箱(chrome seccomp source)。也就是说,可以通过攻击内核实现Chrome沙箱逃逸!

沙箱的局限性在于不允许使用fork,只能创建新线程而非进程。如果无法进行fork操作,waitid就会无法发挥作用,只能将0写入内核内存。

喘口气,进行 infoleak

所有困难都是暂时的,但无论采取哪种方式,都需要先获取内核基地址。 unsafe_put_user的一个优秀属性是在访问无效内存地址时不会崩溃,仅返回-EFAULT。因此,我们仅需猜测内核数据段潜在地址,直至显示不同错误代码、找到内核地址。有了内核地址就可以攻破KASLR了, 但注意不要覆盖任何重要信息 :)

我们可以用相同做法查找内核堆栈地址或内核内存其他区域。

SMAP绕过存在的局限性

现在,我想看看是否可以利用该漏洞突破所有防线。 结果发现目前能做的事情相当有限:

  • 只能写0;
  • 写24个字节的0,破坏附近内存;
  • 少量信息渗出,包括内核基地址与堆栈位置,但不包括堆栈目标位置。

辗转思考多种漏洞利用方法后确定了几个方向:

  • 在内核数据段找到一个对象,其索引/大小/值为零将导致超出内存访问边界;
  • 在内核中覆盖一个自旋锁,用来创建竞争条件;
  • 尝试覆盖内核堆栈上的基址指针或其他值;
  • 触发可能导致在内核堆栈上创建有用结构的操作,看看是否可以用任意写入的0命中对象。

我最终选取了第四个策略,进行堆喷射。

堆喷射

task_struct代表每个进程和线程的结构)开始部分是一些flag,其中一个flag标记是否采用seccomp过滤器。如果能够用task_structs进行堆喷射,并且只覆盖那些起始flag,则可从其中一个进程移除seccomp,从而获取更多可能。

考虑到Linux内核堆栈并非自身擅长领域,先喷射10000个线程,然后使用调试器检查任务结构在堆栈中的位置。我注意到,喷射对象达到一定数量后,大部分任务结构将在堆栈较低地址处结束。这似乎意味着随着空闲槽被用完,堆栈将向下扩展。

接下来的计划是:

  • 创建10000个线程;
  • 从堆栈最低地址起继续猜测任务结构潜在地址;
  • 让10000个线程继续自检是否仍位于seccomp沙箱;
  • 当发现某个线程不再受seccomp影响时停止。

结果竟然奏效了!这种做法虽不可靠,但作为PoC已经足够。我认为增大喷射力度能够提升可靠程度。如果先喷洒其他对象填充,再创建10000个线程释放,可以更加确定目标任务结构将位于堆栈底部。截至目前,我电脑上的运行结果已达到50%成功率,其余半数则以内核崩溃告终。

获得更佳“任意”写入效果

现在,我们面临一项seccomp沙箱外围任务,目前已从上一步获知task_struct地址,仍需弄清如何利用内核漏洞升级到root权限并移除chroot。

好在原语已得到优化,可以使用fork() 来创建子对象,然后使waitid写入非零值。尽管如此,我们仍无法控制多数siginfo结构。唯一可用值是pidstatus,两者都存在一定限制。 pid最大值是0x8000,状态是单字节。

但是,由于pid紧挨着一些未使用的填充(如前文所述),可以执行5次写入,每次都移回一个字节,构造一个任意写入的5字节。

5字节写入+ Physmap

5字节写入的使用方法并非显而易见,暂时仍无法创建任意地址。然而,我们可以创建外观类似

0x**********000000的地址,其中*可以是任意值。

在此,我从ret2dir获取灵感。有一段名为physmap的内核内存,其中内核保留一个映射到与用户区内存具有相同物理内存的“alias”(虚拟地址)。因此,在用户区创建一个填充0x41的页面后,内核中确实存在一个可以找到与该页面完全相同的网页地址。

我的策略是在用户区分配大量内存,然后尝试随机覆盖内核physmap中的页面,同时检查用户区页面是否已经改变。如果发现变化,则说明我们已经找到了一个与用户区地址相对应的内核虚拟地址,可以写入用户区并在内核内存中创建有效payload。我仅对内核physmap中以6个0结尾的页面进行了尝试,一旦找到“alias”,就可以构造一个指向内核地址的指针。

这部分内容非常可靠,但在罕见情况下也可以崩溃一个随机过程。

真正的任意读/写和Root操作!

现在我覆盖task_struct中的files指针,使其指向内核中的“alias”,在用户区构造一个伪造的files_struct对象,该对象也将位于alias.file对象,好处在于它们包含函数指针,即用来控制使用函数(如readlseekioctl)的参数。通过将ioctl指向内核中的各种ROP小工具可以创建一个任意读写原语。于是,我修复了task_struct的clobbered部分,将creds结构改为root。最后,通过重置当前的fs移除chroot。现在我们已经完全实现沙箱逃逸,能够以root身份弹出一个计算器了!

完整漏洞参见https://github.com/salls/kernel-exploits/blob/master/CVE-2017-5123/exploit_smap_bypass.c

最后,感谢Chrome/Chromium安全团队对我的漏洞报告给予快速响应!

本文系外文翻译,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文系外文翻译,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Linux为什么一定要copy_from_user ?
网上很多人提问为什么一定要copy_from_user,也有人解答。比如百度一下:
Linux阅码场
2020/06/28
2.3K0
Linux为什么一定要copy_from_user ?
Linux Kernel运行时安全检测之LKRG-原理篇
许庆伟:龙蜥社区eBPF技术探索SIG组 Maintainer & Linux Kernel Security Researcher
Linux阅码场
2022/09/28
1.3K0
Linux Kernel运行时安全检测之LKRG-原理篇
Linux Signal 一网打尽
Linux Signal想毕很多人都用过,比如在命令行下想要结束某个进程,我们会使用kill pid或者kill -9 pid,其实就是通过给对应的进程发送信号来完成。
扫帚的影子
2020/09/10
2.5K0
宋宝华: Linux为什么一定要copy_from_user
网上很多人提问为什么一定要copy_from_user,也有人解答。比如百度一下:
233333
2022/05/10
1.3K0
宋宝华: Linux为什么一定要copy_from_user
Linux信号处理
目前 Linux 支持64种信号。信号分为非实时信号(不可靠信号)和实时信号(可靠信号)两种类型,对应于 Linux 的信号值为 1-31 和 34-64。
用户7686797
2020/08/25
6.1K0
Linux kernel中常见的宏整理
替换列表和标识符列表都是将字符串 token 化以后的列表。区别在于标识符列表使用,作为不同参数之间的分割符。每一个参数都是一个 token 化的列表。在宏中空白符只起到分割 token 的作用,空白符的多少对于预处理器是没有意义的。
233333
2019/12/20
1.8K0
【专业技术】Linux设备驱动第六篇:高级字符驱动操作之iotcl
在之前我们介绍了如何实现一个简单的字符设备驱动,并介绍了简单的open,close,read,write等驱动提供的基本功能。但是一个真正的设备驱动往往提供了比简单读写更高级的功能。这一篇我们就来介绍一些驱动动中使用的一些高级的操作的实现。 大部分驱动除了提供对设备的读写操作外,还需要提供对硬件控制的接口,比如查询一个framebuffer设备能提供多大的分辨率,读取一个RTC设备的时间,设置一个gpio的高低电平等等。而这些对硬件操作能力的实现一般都是通过ioctl方法来实现的 1.原型介绍 Ioctl在
程序员互动联盟
2018/03/13
1.5K0
Linux kernel中常见的宏整理
替换列表和标识符列表都是将字符串 token 化以后的列表。区别在于标识符列表使用,作为不同参数之间的分割符。每一个参数都是一个 token 化的列表。在宏中空白符只起到分割 token 的作用,空白符的多少对于预处理器是没有意义的。
网e渗透安全部
2019/12/18
2.1K0
Linux进程退出详解(do_exit)--Linux进程的管理与调度(十四)
exit是c语言的库函数,他最终调用_exit。在此之前,先清洗标准输出的缓存,调用用atexit注册的函数等, 在c语言的main函数中调用return就等价于调用exit。
233333
2018/10/09
6.5K0
Android/Linux Root 的那些事儿
玩过安卓的朋友应该都对 root 这个名词不陌生,曾几何时,一台 root 过的手机是发烧友标配;对于开发者来说,root 后的手机是黑灰产外挂的温床,是想要极力避免和打击的目标;而对于安全研究人员来说,root 则意味着更多 —— Towelroot、PingPongRoot、DirtyC0w、ReVent,那些有趣的漏洞和精妙的利用,承载了不少的汗水和回忆。
evilpan
2023/02/12
1K0
【转】记一次 Redis 延时毛刺问题定位
原文 https://mp.weixin.qq.com/s/8A_y1dlZrUvpaJfbQrVK3w
保持热爱奔赴山海
2023/12/24
5240
CVE-2017-1000112-UFO 学习总结
今天看到有人发了CVE-2017-1000112-UFO的分析,就把之前的学习报告整理一下,做个对比学习。 别人的分析报告: https://securingtomorrow.mcafee.com/mcafee-labs/linux-kernel-vulnerability-can-lead-to-privilege-escalation-analyzing-cve-2017-1000112/
De4dCr0w
2019/02/27
2.4K1
Linux内核中的递归漏洞利用
6月1号,我提交了一个linux内核中的任意递归漏洞。如果安装Ubuntu系统时选择了home目录加密的话,该漏洞即可由本地用户触发。如果想了解漏洞利用代码和短一点的漏洞报告的话,请访问https:/
FB客服
2018/02/08
2.3K0
Linux内核中的递归漏洞利用
Linux驱动实践:中断处理函数如何【发送信号】给应用层?
大家好,我是道哥,今天我为大伙儿解说的技术知识点是:【中断程序如何发送信号给应用层】。
IOT物联网小镇
2021/12/20
3.7K0
Linux驱动实践:中断处理函数如何【发送信号】给应用层?
一文看懂 | fork 系统调用
Unix标准的复制进程的系统调用时fork(即分叉),但是Linux,BSD等操作系统并不止实现这一个,确切的说linux实现了三个,fork,vfork,clone(确切说vfork创造出来的是轻量级进程,也叫线程,是共享资源的进程)
用户7686797
2021/10/20
2.7K0
Linux下进程的创建过程分析(_do_fork do_fork详解)--Linux进程的管理与调度(八)
Unix标准的复制进程的系统调用时fork(即分叉),但是Linux,BSD等操作系统并不止实现这一个,确切的说linux实现了三个,fork,vfork,clone(确切说vfork创造出来的是轻量级进程,也叫线程,是共享资源的进程)
233333
2018/10/09
2.8K0
Linux下进程的创建过程分析(_do_fork do_fork详解)--Linux进程的管理与调度(八)
Linux驱动实践:驱动程序如何发送【信号】给应用程序?
大家好,我是道哥,今天我为大伙儿解说的技术知识点是:【驱动层中,如何发送信号给应用程序】。
IOT物联网小镇
2021/12/09
3K0
Linux驱动实践:驱动程序如何发送【信号】给应用程序?
从 0 开始学 Linux 内核之 android 内核栈溢出 ROP 利用
最近在研究一个最简单的android内核的栈溢出利用方法,网上的资料很少,就算有也是旧版内核的,新版的内核有了很大的不同,如果放在x86上本应该是很简单的东西,但是arm指令集有很大的不同,所以踩了很多坑
Seebug漏洞平台
2019/02/26
1.7K0
The Route to Host:从内核提权到容器逃逸
绿盟科技研究通讯曾经发表过容器逃逸的技术文章《【云原生攻防研究】容器逃逸技术概览》[1],该文中探讨了已有的容器逃逸技术。本文将沿着上文的思路,主要从Linux内核漏洞的角度对容器逃逸进行深度介绍,包括攻击原理、自动化利用和防御思路等内容。
绿盟科技研究通讯
2022/04/14
1.9K0
The Route to Host:从内核提权到容器逃逸
Linux 进程管理
在内核层面,每个进程都是由task_struct 描述的,这个结构体非常大,可以粗略看下各主要内容:
一只小虾米
2023/03/09
10.3K0
Linux 进程管理
相关推荐
Linux为什么一定要copy_from_user ?
更多 >
交个朋友
加入腾讯云官网粉丝站
蹲全网底价单品 享第一手活动信息
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档