外部有报道称出现了CPU漏洞spectre的在野利用,各项证据均指向为Immunity Canvas商业渗透框架里面的spectre CPU漏洞攻击模块被上传到了VirusTotal,由此揭开了spectre武器化工具的神秘面纱。2018年披露的spectre和meltdown CPU漏洞影响范围广泛,但由于修复困难、修复方案影响性能、没有出现武器化工具而一直被轻视。恰巧Blade Team在CPU漏洞方面有一些研究和积累,有幸获得一份Immunity Canvas,于是开始对spectre武器化工具抽丝剥茧,一探究竟,希望能够引起大家对CPU漏洞的重视。
为了讲清楚来龙去脉,在正式开始Canvas spectre分析之前,有必要对一些CPU漏洞背景做一个简单的介绍。
侧信道攻击是指通过cache等第三方介质,并不直接访问目标而实现从目标窃取数据的攻击。
由图可见,访问cache需要10-100ns,访问内存需要100ns,它们时间是有差异的,通过测量访问时间,我们可以得知数据是从内存中来或者是从cache中来。
为了提高性能,现代CPU都具有分支预测,乱序执行功能。指令的执行并不严格按照先后顺序,由于分支预测和乱序执行,靠后的指令可能提前执行,此时执行并没有将结果实际生效,而是等待前面指令生效以后,再依据前面指令的结果决定后面的指令是否生效。例如:
当指令流水线来到line1时,由于分支预测flag为真, line3优先执行了,实际结果是否生效依赖于line1的if (flag) 指令的结果,如果为真,则生效,如果为假,则丢弃line2, line3的结果,并不会实际反映在寄存器里面。
我们关注line3这条语句对应的指令对系统产生的影响:
(1) 访问arr[secret * 4096],如果此地址在cache中则从cache中读取,如果不在cache中,则需要将内存中的值放置在cache中,供下次可以快速访问。
(2) 将值取出来放在ch对应的寄存器或者栈变量中。
此时line1指令 if (flag) 生效了,flag为False,line2和line3的指令结果被丢弃。但是!CPU的设计在这里出现了一个纰漏。Line3对cache的影响没有消除!即是说,即便flag为False,line3依然可以将arr[secret * 4096]对应的内存缓存进cache中。
这里出现的4096,并不是必须的。可以是512或者其他值,但必须是cache块大小的整数倍。这样做的原因是,cache缓存的时候,并不是以字节为单位,而是以cache块为单位,cache块的大小通常是512。
还记得前面说过可以通过访问时间判断数据在内存还是在cache中吗?我们可以通过访问时间来判断arr到底是哪一个4K页的头部cache块大小的字节被缓存到了cache中,从而知道了secret的值,完成一次侧信道攻击。这就是大名鼎鼎的spectre CPU漏洞了!
考虑secret_addr为一个内核地址,正常流程line2会触发异常进入到line5。Line3永远得不到执行。但是同样,由于有乱序执行,微架构层面执行和生效是两个分开的流程。line2执行,line3执行,此时line2生效,触发异常,line3执行结果被丢弃,但是line3对cache的操作保留了下来,进而通过考查哪一块内存被cache了可以得到secret的值。这样就通过cache的侧信道泄露了内核的数据。
Meltdown和spectre对比:
花了一些心思把Canvas装好以后发现,spectre_leak_file 模块用不了,Python代码各种报错。几番折腾以后终于找到了正确的打开方式。如下:
这是一个打印调试信息的函数,不过在release版本打印功能被注释掉了。通过一个gdb小的tips可以重新让它启用。通过command命令在断点下来以后跳转到printf,这样程序就有了调试日志了,极大的方便逆向调试工作。
0x4010b0 是printf的地址
在地址0x407BA5有一个函数,我给它取名为get_kernel_address_cache_time.
rdi是一个随机的内核地址,是否有效(被映射)未知。
32至43行prefetcht0将rdi内容缓存进cache(由于rdi地址是否有效未知,能否真的被缓存进cache也未知。地址有效则缓存,无效,则什么也不做)。
45行判断恒为假,47行到52行对内核地址进行访问。在分支预测的情况下会执行,在生效环节由于条件恒为假,结果被丢弃。
54行Mm_lfence 这条指令会将CPU指令流水线清空,会等待前面所有的分支预测,乱序执行的微码处理完毕。
55行和27行统计中间这段代码的执行时间。
假设1 rdi是一个有效的内核地址,47行到52行预测执行从cache里面取数据,所花时间较短,记为t1。
假设2 rdi是一个无效的内核地址,47行到52行预测执行需要执行额外的操作,所花时间较长,记为t2。
比较t1和t2,再加上一些统计学上的方法,可以得知rdi地址是否是一个有效的内核地址。
再配合穷举代码就能得到内核加载的基址了。
从0xFFFFFFFF80000000到0xFFFFFFFFC0000000步进0x20000 尝试,找到内核基址。
获取到kernel_base以后,假设kernel_base为0xffffffff81000000,加上全局变量static LIST_HEAD(super_blocks);的偏移,ubuntu16.04 4.8.0-58-generic 内核此偏移为0xe7dc90,已经内置到二进制程序里面,0xffffffff81e7dc90为super_blocks的地址,如图:
编译的不同内核版本此偏移不同。由此获得了super_blocks 内核数据结构,进而凭借meltdown内核读取的能力,遍历内核数据结构,依次获得磁盘对应的super_block结构,文件对应的inode结构,文件映射在内核的地址,最后对文件进行读取操作,这段内容就不展开讲述了,可以参照内核源码自行理解。如下图:
整体来看,此工具利用的是meltdown读内核的能力,严格的说并不是spectre,不过学术界也有说法将meltdown认为是spectre的变种,暂且不表。
不同于善意的CPU漏洞PoC程序,此canvas武器化工具从攻击角度出发、以实际利用为目的、结合高超的利用手法,实现了完整的攻击流程,包括:
(1) 利用分支预测执行地址是否映射有执行时间差原理来攻破KASLR
(2) 利用meltdown漏洞读内核空间的能力来遍历内核文件系统数据结构
(3) 利用meltdown漏洞读内核空间的能力来获取内核映射的文件内容
微码补丁是一种修复方案。
另外一种方案是KPTI。KPTI 内核页表隔离是meltdown有效防御措施。大致思想是在用户态和内核态维护两套页表。内核态的页表则对所有地址做映射,而用户态的页表不对内核地址做映射。这样一来,meltdown乱序执行访问内核的时候,因为没有映射,所以也就取不到内核的数据了。由于页表切换涉及到cache刷新,引入KPTI是对性能有一定影响的。
PoC: https://github.com/IAIK/meltdown
可以通过此PoC验证是否受影响。请尽快打上补丁,消除影响。
MD5 :9f9b74bfc20022d3f4e265df8c280e0f
SHA-1 :ae7ce9b87a269a118aa6ec73c10060b76bc4095e
SHA-256 :6461d0988c835e91eb534757a9fa3ab35afe010bec7d5406d4dfb30ea767a62c
参考: https://meltdownattack.com/
https://therecord.media/first-fully-weaponized-spectre-exploit-discovered-online/
此次Canvas Spectre武器化事件由腾讯安平洋葱团队和Blade Team联合跟进。
由腾讯安全平台部成立,专注于人工智能,物联网,移动互联网,云虚拟化,区块链等前沿技术领域的安全研究,目前已向Apple、Amazon、Google、Microsoft、Adobe等诸多国际知名公司报告了200多个安全漏洞,并与之建立了长期友好的合作关系。近两年,团队研究成果登上了CVPR、BlackHat、DEFCON、CanSecWest、HITB、POC、XCon等顶级安全大会讲台。与此同时,Tencent Blade Team也将研究成果全方位输出到实际业务场景中,与腾讯各大业务团队均建立了紧密的合作关系,致力于提升腾讯产品的安全性、创造更安全的互联网生态。
洋葱端点检测与响应系统(XDR)是腾讯公司以腾讯百万级服务器端点防护体系为核心面向企业客户提供的一套服务器端点安全解决方案,支持集中的资产管理、安全入侵事件监测、安全审计、安全漏洞、安全基线等,支持对安全事件的精准判断,对服务器安全事件的溯源审计,并进行服务器端的脆弱性安全检测,包含安全基线,安全漏洞检测。