前情提要
在 上篇 中,我们给出了大致的漏洞原因和攻击链,在本篇中,我们将给出具体的实验思路和步骤。出于对法律风险的考虑,我们不会给出源码和具体的参数。有兴趣的读者可以根据思路和论文,自行摸索。如有意探讨更多技术细节,请联系我们:zheng.liu@osr-tech.com 。
以下实验步骤在 AArch64 上适用。
LKM 的使用
由于实践环节大量依赖 LKM (Loadable Kernel Module) 作为注入脚本。在此为不熟悉 Linux 内核编程的小伙伴简要介绍一下要点。
LKM 是能够在内核态运行的程序,由 insmod
接口在 root 权限下加载。
如何使用 LKM
内核头文件
LKM 的编译过程需要运行 LKM 平台的内核信息。主要是一些模块符号和内核头文件中函数的格式,以便 LKM 在运行时正确地找到所需的内核接口。一般而言,x86 平台 Debian 系 mainline 的头文件通过
apt install linux-headers-$(uname -r)
可以自动获得。但与 x86 平台基本就 I 家和 A 家设计厂商不同,以嵌入式设备为主的 ARM 平台,各个设计厂商对自家 SoC 设计方案五花八门,使得 Linux 内核对不同 SoC 驱动支持程度不一;或者由于设备上资源所限,OEM 对内核进行了定制化修改,使得该设备的内核头文件无法通过apt
等主流渠道获得。此时要找到内核编译 LKM 所需的内核头文件,建议以下方式:
- 查看该 SoC 是否有开发板,因为开发板往往会提供开源的内核源码,有内核源码也就有了内核头文件
- 查看该 SoC 是否被某个版本的 Linux 内核 mainline 所接受,尝试更换内核到该版本
- OEM 原则上来说应该对所使用的内核开源(比如安卓手机厂商),找到该内核源码
- 逛逛爱好者论坛,寻找第三方做好的 ROM 或 kernel,再依此寻找第三方内核源码
加载 LKM
通过
insmod
和rmmod
可以方便的加载和卸载 LKM。需要注意的是 LKM 的编写与平常的程序代码有所不同,内核函数与 libc 的差异自不必说,LKM 需要有特定的加载和退出函数,请自行查阅。做成驱动
如果想更方便的运行 Nailgun 攻击,可以考虑把内核态的代码做成驱动的方式,提供给用户态的程序调用。在用户态下执行代码一是写起来方便,二是加载驱动后就不再需要 root 权限。
如何观察 LKM 运行结果
通过 printk
在内核态输出的结果,只能通过 dmesg
或 cat /var/log/kern.log
查看。
动手
经过上面的准备,刀已磨好,下面开始动手。
查看调试接口的开放情况
翻阅 ARM 架构参考手册(DDI0487),DBGAUTHSTATUS_EL1
寄存器标注了低八位寄存器的意义。
- SNID [7:6],安全非侵入式调试
- SID [5:4],安全侵入式调试
- NSNID [3:2],非安全非侵入式调试
- NSID [1:0],非安全侵入式调试
在论文 [1] 中,作者详细描述了这四种调试方式的差异,简单来说,如果低八位的值为 0xFF...
读取该寄存器的方法,可以通过 inline assembly 的方式,直接读这个寄存器的值就好了。
翻找调试寄存器基址
如果 DBGAUTHSTATUS_EL1
的值是 0xFF,下一步就是找到 Externel Debug Registers 和 Cross Trigger Interface registers 正式进行 Nailgun 实验。
从软件上看,EDR 和 CTI 是两组地址编码为 0x1000 大小的寄存器。访问该寄存器组中的独立寄存器,是通过基址 + 偏移的方式。偏移在 ARM 手册中都给出了准确值,困难的是这两个模块并不属于开放给第三方的外设,因此在公开的 SoC 手册里几乎不可能找到准确的寄存器基址(极大概率在 SoC 手册上被划入了 reserved 或语焉不详)。对此,只能来硬的。
饱和式搜索——穷举!
幸运的是,在 CTIDEVID 寄存器中,对于 Cortex-A53 而言,它低几位值是固定的,为 0xnn040800。因此在一个合理的地址范围内,搜索一个以 0xFC8 结尾,0x1000 为间隔的地址,其值低 24 位为 0x040800 的匹配并不算太难。需要注意的是,ARM 手册中的这个地址是物理地址,在 LKM 中进行编码的时候需要通过 ioremap() 进行地址映射。
如果幸运女神真的瞄了你一眼,获得了匹配,请进入下一步。
如果在扫描的过程中频繁遇到内核崩溃,系统假死,芯片烫烫烫得要命... 我们可以通过一个工具缩小范围。
ARM 的工作小组提供了一个工具,帮助通过查看 ROM Table 中特定位置的值,协助确定搜索的范围。他们的工具开源在这里(https://github.com/ARM-software/CSAL) 。使用方法请自行阅读。
通常来说 SoC 中每一个核心对应一对 EDR 和 CTI,其用来调试该核心,所以八核 A53 会找到八组对应寄存器,不过也有例外……(坑可真多)
让代码在 Core 1 上运行
由于我们要停住 SoC 的一个核心,将代码在另一个核心上执行,因此需要告诉系统,指定代码运行的核心编号。
smp_call_function_single(1, payload_func, arg_struct, 1);
以上从 Nailgun 论文里抠出的代码,意思是:将 payload_func 在 Core 1 上执行,其中 payload_func 有一个参数叫 arg_struct。所以,把需要实现的功能写到 payload_func,把需要的地址放到 arg_struct 以指针传入,运行这条函数,不出意外的话可以得到正确执行。
通过核间调试访问 Core 0
如果以上步骤都成功完成,那么按照论文作者的 PoC [2] 就可以直接通过调试接口操作 Core 0 核心进行读写,读写方式就是最基础的 value = ioread(addr)
和 iowrite(addr, value)
。步骤如下:
输入密码
开启调试都有密码的,写默认密码 0xc5acce55 (CS Access) 到指定寄存器
暂停 Core 0
通过 EDR 的一个寄存器,给 Core 0 一个外部调试的中断,使得 Core 0 能够保存现场,清空流水线,等待开启调试
开启调试
通过 CTI 的寄存器一系列操作,使得通过 CTI 可以发送指令给 Core 0
Core 0 确认状态
检查各个标志位后,写一个 ack 返回给 Core 0 的 CTI,使得 CTI 可以接受 Core 1 的新指令
保存栈,通过人脑翻译将汇编指令转换成机器码,写到 EDR 的寄存器里
请开始你的表演
还原栈,第五步的逆操作
停止调试
Core 1 通过 CTI 重启 Core 0
检查重启标志位后,发送 ack 给 CTI,以便 CTI 完成使命
用一个不严谨的流程图来表示,就是 Core 1 通过 CTI 和 EDR 来操作 Core 0 的运行。
在 Core 0 上执行代码
通过调试寄存器让 Core 0 执行代码,其根本方式就是将待执行的指令翻译成机器码,将机器码写到 EDITR 寄存器(32位),通过查询 EDSCR 标志位,得知指令的执行情况。遇到权限错误无法执行的指令,EDSCR 会在特定标志位给出指示。原作者的代码如下:
static void execute_ins_via_itr(void __iomem *debug, uint32_t ins) {
uint32_t reg;
// clear previous errors
iowrite32(CSE, debug + EDRCR_OFFSET);
// Write instruction to EDITR register to execute it
iowrite32(ins, debug + EDITR_OFFSET);
// Wait until the execution is finished
reg = ioread32(debug + EDSCR_OFFSET);
while ((reg & ITE) != ITE) {
reg = ioread32(debug + EDSCR_OFFSET);
}
if ((reg & ERR) == ERR) {
printk(KERN_ERR "%s failed! instruction: 0x%08x EDSCR: 0x%08x\n",
__func__, ins, reg);
}
}
(左右滑动查看完整代码)
最终效果,是在内核不崩溃的情况下读出 SCR_EL3 寄存器的值。
因为 ARM 手册写着,只有当前状态处于 EL3 时,才允许读取该寄存器。
内核崩溃的处置
由于 Nailgun Attack 是涉及未知硬件实现的软件代码,因此在实验当中一定会频繁遇到内核崩溃的情况。在此有几条建议,帮助大家趟坑。
- 由于 Nailgun Attack 的根源在 SoC 硬件,所以运行什么版本的 Linux 内核或发行版都没有差别。实验的时候尽量找功能少(对,就是别带 GUI)、驱动少、稳定的系统为上。然而一旦在某个系统上攻击成功,同款芯片在任何软件系统中均可能依葫芦画瓢受到影响
- 内核不论出现严重错误(SError)无法动弹或者只是啊哦(Oops)抱怨几句,都请拔电重启
- 从内核 dump 的 backtrace 中可以找到导致内核崩溃的原因,但总的来说大多数是由于黑盒状态的硬件设计差异导致,在确认逻辑正确的情况下,修改代码收效甚微,只能尽力找到绕开的办法
总结
Nailgun Attack 是一个可能很早以前就存在,却没有被大家认识的硬件实现漏洞。这种漏洞可能缘于 ARM、SoC 设计方、OEM 产品方等多方在产品设计时因考虑问题的角度不同,因而对调试功能的态度和能力不同所导致。一个本应是方便开发和调试的功能,最终成为一个终端产品上广泛存在的漏洞,如何防止未来出现类似多方安全性问题,值得大家思考。安全,作为一个贯穿整个产业链的的概念,在产品的设计、生产、销售、使用和回收等各个环节都需要有所准备。
最后,围绕 Nailgun Attack,我们开发了一个自动化的检测工具,将针对 Nailgun 漏洞对目标设备进行远程扫描,快速得知设备被漏洞影响的程度。相关信息我们将在近期公布,欢迎垂询。
OSR安全检测实验室服务万物互联的物联网时代更需要安全护航。物联网的蓬勃发展衍生出许多创新商业模式和应用,一些新的安全漏洞和隐私泄露等问题往往也会因此暴露。OSR 安全检测实验室通过不断吸收并快速转化前沿技术成果,为各方提供以下服务:
- 物联网设备安全性评估
- 系统级安全架构培训
- 行标级安全认证辅导
- 定制化安全方案咨询
联系 Email:shiqi.li@osr-tech.com
参考资料
[1] Understanding the Security of ARM Debugging Features http://www.cs.wayne.edu/fengwei/paper/nailgun-sp19.pdf
[2] https://github.com/ningzhenyu/nailgun/blob/master/PoC/Read_SCR/
万物互联 安全赋能扫码关注最新动态关于纽创信安OSR
深圳市纽创信安科技开发有限公司(Open Security Research Inc.,OSR),成立于2014年,总部在深圳,在北京设有研发中心。是一家专注于安全攻防技术的国家高新技术企业,为客户提供高安全IP和攻击测试服务。
纽创信安以领先的硬件安全攻防技术和创新的硬件安全方法学,致力于把金融级别的安全攻击与防御技术自动化、智能化、产品化、可视化,形成开放的方法学,为万物智联的各垂直行业做安全赋能,并致力于成为新时代安全技术的领跑者。
OSR产品和服务涵盖硬件安全检测设备、安全算法加速、硬件安全解决方案和安全咨询服务,典型客户与合作伙伴包括国家电网、南方电网、泰尔实验室、汇顶、泰凌微电子、得一微电子、云天励飞、C-Sky、Synopsys等。
戳