当前位置:安全行业动态 → 正文

从0到TrustZone第三篇:从QSEE劫持Linux内核

责任编辑:editor005 作者:安小白 |来源:企业网D1Net  2016-05-23 15:12:01 本文摘自:Freebuf

在前一篇文章中,我们介绍了QSEE的漏洞及利用,接下来让我们将重点转移到QSEE shellcode。

之前讨论过,QSEE可以被提权——这里的提权不仅包含直接与TrustZone内核交互并访问硬件——安全的TrustZone文件系统(SFS),也包括一些系统内存的直接访问形式。

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

  与QSEE交互

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

当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,所以运行缓慢,欢迎提供改善意见(例如,同时读取较大的内存块会提速)。

关键字:QSEELinux内核函数指针

本文摘自:Freebuf

x 从0到TrustZone第三篇:从QSEE劫持Linux内核 扫一扫
分享本文到朋友圈
当前位置:安全行业动态 → 正文

从0到TrustZone第三篇:从QSEE劫持Linux内核

责任编辑:editor005 作者:安小白 |来源:企业网D1Net  2016-05-23 15:12:01 本文摘自:Freebuf

在前一篇文章中,我们介绍了QSEE的漏洞及利用,接下来让我们将重点转移到QSEE shellcode。

之前讨论过,QSEE可以被提权——这里的提权不仅包含直接与TrustZone内核交互并访问硬件——安全的TrustZone文件系统(SFS),也包括一些系统内存的直接访问形式。

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

  与QSEE交互

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

当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,所以运行缓慢,欢迎提供改善意见(例如,同时读取较大的内存块会提速)。

关键字:QSEELinux内核函数指针

本文摘自:Freebuf

电子周刊
回到顶部

关于我们联系我们版权声明隐私条款广告服务友情链接投稿中心招贤纳士

企业网版权所有 ©2010-2024 京ICP备09108050号-6 京公网安备 11010502049343号

^