联系我们
  • 联系人: 侯女士?

    电 话: 025-58630787

    邮 箱: [email protected]?

    手 机: 18052008777

    公司地址:南京市雨花台区雨花大道2号邦宁科技园1-4层



从0到TrustZone第三篇:从QSEE劫持Linux内核
2016-5-23
来源:freebuf
点击数: 4398          作者:江苏天网
  • 之前讨论过,QSEE可以被提权——这里的提权不仅包含直接与TrustZone内核交互并访问硬件——安全的TrustZone文件系统(SFS),也包括一些系统内存的直接访问形式。

    本文我们要讨论在不需要内核漏洞的情况下,如何利用“安全世界”的内存访问权限劫持“普通世界”中运行的Linux内核。

    war_of_worlds.png

    与QSEE交互

    在上一篇文章中,当用户控件的Android应用与QSEE中运行的trustlet进行交互时,必须通过一个特殊的Linux 内核设备“qseecom”,该设备发送由QSEOS处理的SMC调用,并传递到请求的trustlet中,以便被处理:

    Screenshot from 2016-04-29 01-09-43.png

    每个发送到trustlet的命令都有一对对应的输入和输出缓冲区,用于传递“普通世界”和trustlet之间的通信信息。

    但是,有一些更快的通信模式所必需的特殊用例——例如,当解密较大的DRM保护的媒体文件时,为了保证顺利播放,需要使用尽量少的通信消耗。

    另外,有一些设备中包含trustlet是为了确保设备的完整性。例如,三星提供了一个“TrustZone-based Integrity Measurement Architecture (TIMA)”框架来保证设备完整性,TIMA会对“普通世界”内核定期检查,验证是否与原厂内核相匹配。

    因此,Trustlet需要与“普通世界”进行快速通信,同时需要具备一定的检验系统内存的能力——听起来有些危险!下面让我们来深入分析。

    继续对“widevine”trustlet的研究,以下代码为用于DRM加密内存块的命令:

    Screenshot from 2016-05-05 16-28-21.png

    该函数接收表示输入和输出缓冲区的指针,这两个缓冲区可以是用户提供的任意缓冲区。因此,如果想要访问他们需要一些准备。该函数通过调用cacheflush_register完成准备,一旦加密进程完成,通过调用cacheflush_deregister释放缓冲区。

    分析发现,cacheflush_register和cacheflush_deregister都是围绕QSEE系统调用的简单的封装程序:

    cacheflush_register cacheflush_deregister
    qsee_register_shared_buffer qsee_prepare_shared_buf_for_nosecure_read
    qsee_prepare_shared_buf_for_secure_read qsee_deregister_shared_buffer

    那么这些系统调用的作用是什么呢?

    查看QSEOS相关代码发现这些调用的名字是有些误导性的——实际上,qsee_prepare_shared_buf_for_secure_read只能使数据缓存中的给定范围无效(QSEE会查看更新的数据),qsee_prepare_shared_buf_for_nosecure_read可以删除数据缓存中给定的范围(“普通世界”可以收到QSEE做出的更改)

    至于qsee_register_shared_buffer——该系统调用主要用于将给定范围实际映射到QSEE。其工作原理如下:

    Screenshot from 2016-05-05 19-44-43.png

    经过完整性检测,该函数会验证给定的内存区域是否位于“安全世界”。如果这就是问题所在,那是因为trustlet正在试图通过映射和修改TZBSP或QSEOS使用的内存区域攻击TrustZone内核。由于这一行为十分危险,“安全世界”中只有少数特定的区域可以映射到QSEE。如果给定的地址范围没有在特定的区域中,该操作就会被拒绝。

    然而,对于“普通世界”中的任意地址,系统不会做任何额外的检查。这就意味着QSEOS允许使用qsee_register_shared_buffer将物理地址映射到“普通世界”。

    劫持Linux内核

    由于QSEE拥有所有“普通世界”内存的读写权限,理论上我们可以直接在物理内存中定位“普通世界”运行的Linux内核并注入代码。

    让我们来创建一个不需要内核符号的QSEE shellcode——该方法可以用在所有的QSEE环境中,定位并劫持运行的Linux内核。

    启动设备后,引导程序使用Android引导镜像中指定的数据,将Linux内核提取到给定的物理地址并执行:

    Screenshot from 2016-05-05 20-23-47.png

    Linux内核的物理加载地址就可以通过全局可读的/proc/iomem文件用于任意进程:

    Screenshot from 2016-05-05 20-51-08.png

    然而,简单地获取内核加载地址并不是全部——系统中存在大量的内核镜像和内核符号。因此,我们需要找到所有动态使用运行时内核内存的符号。要知道,Linux内核在内部维护着一个内核符号列表,允许内核函数使用特殊的搜索函数kallsyms_lookup_name查找这些符号。

    内核符号列表中的名称使用build时生成的256为霍夫曼编码进行压缩,霍夫曼表存储在内核镜像中,在相同的位置还有代表索引的相应的描述符,用于解压名称,当然还包含符号的实际地址。

    Screenshot from 2016-05-05 21-15-50.png

    为了访问符号表中的所有信息,我们首先需要在内核镜像中找到它。

    如果幸运的话,符号表的******个区域——SymbolAddress Table,通常由两个指向内核虚拟加载地址(由于没有内核地址空间随机分配KASLR机制,可通过对物理加载地址计算得出)的指针开始。另外,该符号地址为内核虚拟地址范围内的单调非递减地址——以此来确定指向内核虚拟加载地址的连个连续指针。符号地址表如下:

    Screenshot from 2015-08-25 16-45-56 (1).png

    既然已经找到了内核镜像中的符号表,接下来需要做的就是解压该表,来遍历并查找任何符号。

    使用上述方法找到内核中的符号表后,我们就可以定位并从QSEE中劫持内核函数。根据以往的内核利用经验,我们可以从一个很少用到的网络协议PPPOLAC中劫持一个函数指针。

    该函数指针存储在以下内核结构体中:

    Screenshot from 2015-08-16 02-07-43 (1).png

    当PPPOLAC套接字关闭时,覆盖其中的release指针会导致内核执行用户提供的函数指针。

    总结

    综上所述,获取Linux内核中的代码执行权限需要执行以下步骤:

    1、获取QSEE代码执行权限

    2、使用qsee_register_shared_buffer映射QSEE中的所有内核地址

    3、找到内核符号表

    4、在符号表中查找“pppolac_proto_ops”符号

    5、覆盖指向用户提供的函数地址的指针

    6、使用qsee_prepare_shared_buf_for_nosecure_read清除QSEE中的改变

    7、使用PPPOLAC套接字使内核调用用户提供的函数

    完整利用代码传送门

    注意,该代码目前只能一次读取一个DWORD,所以运行缓慢,欢迎提供改善意见(例如,同时读取较大的内存块会提速)。

版权所有 Copyright(C)2009-2013 江苏天网计算机技术有限公司 苏ICP备16029720号

玄机二句是2019