tee漏洞学习-翻译-2:探索 Qualcomm TrustZone的实现

原文:http://bits-please.blogspot.com/2015/08/exploring-qualcomms-trustzone.html

获取 TrustZone image

从两个不同的位置提取image

  • 从手机设备本身
  • 从google factory image

已经root的Nexus 5设备,image存储在eMMC芯片上,并且eMMC芯片的分区在/dev/block/platform/msm_sdcc.1下,可以通过dd命令进行复制 。

此外,在/dev/block/platform/msm_sdcc.1/by-name分区下,包含tztzb这些有意义的名称:
在这里插入图片描述

tz(TrustZone 的缩写),另一个名为tzb,作为tz映像的备份映像,并且与tz映像相同。

直接从手机内部提取,可能存在两个问题:

  • 尽管 TrustZone 映像存储在 eMMC 芯片上,但“正常世界”很容易无法访问它(通过要求设置系统总线上的 AxPROT 位),或者它的多个部分可能会丢失。
  • 拉取整个分区的数据不会显示有关image真实(逻辑)边界的信息,因此需要一些额外的工作来确定image实际结束的位置。 (实际上,由于“tz”image是 ELF 二进制文件,因此它的大小包含在 ELF 标头中)。

因此,从设备中提取了一个image后,让我们看一下google factory image。

Nexus 5 的出厂镜像均可从 Google 下载。出厂映像包含一个包含所有默认映像的 ZIP,另外还包含引导加载程序映像。KTU84P

下载工厂映像并查找与 TrustZone 相关的字符串后,很快就发现bootloader包含所需的代码。

然而,这里仍然有一个小问题需要解决 - 引导加载程序image的格式未知。无论如何,使用十六进制编辑器打开该文件并猜测其结构实际上非常简单:
在这里插入图片描述

引导加载程序文件具有以下结构:

  • magic值(“BOOTLDR!”)- 8 个字节
  • image数量 - 4 字节
  • 从文件开头到image数据开头的偏移量 - 4 个字节
  • image中包含的数据的总大小 - 4 字节
  • 一个数组,其中包含与上面的“image数量”字段匹配的多个条目。数组中的每个条目都有两个字段:
    • image名称 - 64 字节(零填充)
    • image长度 - 4 字节

正如您在上图中看到的,引导加载程序映像包含一个名为“tz”的映像,这就是我们要查找的映像。为了解压该文件,我编写了一个小型 python 脚本(可在此处获取),该脚本接收引导加载程序映像并解压其中包含的所有文件。

提取图像并将其与之前从设备中提取的image进行比较后,我验证它们确实是相同的。所以我想这意味着我们现在可以继续检查 TrustZone image。

import sys, struct, osdef main():#Reading the commandline argumentsif len(sys.argv) != 3:print "USAGE: %s <BOOTLOADER_IMAGE> <OUTPUT_DIR>" % sys.argv[0]returnbootloader_path = sys.argv[1]output_path = sys.argv[2]#Verifying the magicbootloader_file = open(bootloader_path, 'rb')magic = bootloader_file.read(8)if magic != "BOOTLDR!":print "[-] Read incorrect magic: %s" % magic.encode("hex")returnprint "[+] Read correct magic"#Reading in the metadata blockimage_count,data_start_addr,total_size = struct.unpack("<III", bootloader_file.read(12))print "[+] Found %d images, starting at %08X, total size: %08X" % (image_count, data_start_addr, total_size)image_metadata = []for i in range(0, image_count):image_name = bootloader_file.read(64).strip('\x00')image_len = struct.unpack("<I", bootloader_file.read(4))[0]image_metadata.append((image_name, image_len))print "[+] Images: %s" % str(image_metadata)#Dumping each imagebootloader_file.seek(data_start_addr, 0)for image_name, image_len in image_metadata:print "[+] Dumping %s" % image_namedata = bootloader_file.read(image_len)open(os.path.join(output_path, image_name), 'wb').write(data)print "[+] Done"if __name__ == "__main__":main()

修复 TrustZone 映像

首先,检查该文件发现它实际上是一个 ELF 文件,这是一个好消息!这意味着内存段及其映射地址应该可供我们使用。

用 IDA Pro 打开文件并让自动分析运行一段时间后,我想开始逆向文件。然而,令人惊讶的是,似乎有很多分支指向未映射的地址(或者更确切地说,未包含在“tz”二进制文件中的地址)。

仔细一看,似乎所有指向无效地址的绝对分支都在文件的第一个代码段内,并且它们指向未映射的高地址。此外,第一个代码段的地址没有绝对分支。

这看起来有点可疑…那么我们看一下 ELF 文件的结构怎么样?执行 readelf 会显示以下内容:
在这里插入图片描述

有一个 NULL 段映射到更高的地址,它实际上对应于无效绝对分支指向的地址范围!

不管怎样,我做了一个相当安全的猜测,那就是第一个代码段实际上映射到了错误的地址,实际上应该映射到更高的地址 - 0xFE840000。很自然地,我想使用 IDA 的 rebase 功能对段进行 rebase,但是你瞧!这会导致 IDA 严重崩溃:
在这里插入图片描述

在这里插入图片描述
0xFC58C48地址太低了,不在加载地址范围之内)

我实际上不确定这是否是高通的反逆向功能,或者 NULL 段是否只是其内部构建过程的结果,但这可以通过手动修复 ELF 文件轻松绕过。所需要做的就是将 NULL 段移动到未使用的地址(因为 IDA 无论如何都会忽略它)Type 类型为NULL,没啥用,所以会被忽略,除非专门为tz编写了加载器,并将第一个代码段从错误的地址 (0xFC86000) 移动到正确的地址 (0xFE840000)这个需要自己用IDA打开提取出的tz,稍微看看就能理解,如下所示:
在这里插入图片描述

现在,在 IDA 中加载镜像后,所有绝对分支都有效了!这意味着我们可以继续分析image。

分析 TrustZone image

首先,应该指出的是,TrustZone 映像是一个相当大的 (285.5 KB) 二进制文件,包含相当少量的字符串,并且没有公共文档。此外,TrustZone 系统由完整的内核组成,具有执行应用程序等功能。所以…目前还不清楚我们应该从哪里开始,因为逆向整个二进制文件可能需要太长时间。

由于我们希望从应用程序处理器攻击 TrustZone 内核,因此最大的攻击面可能是安全监视器调用,这些调用使“正常世界”能够与“安全世界”进行交互。

当然,应该指出的是,我们还可以通过其他方式与 TrustZone 进行交互,例如共享内存甚至中断处理,但由于这些攻击面要小得多,因此最好从分析 SMC 调用。

那么我们如何找到 TrustZone 内核处理 SMC 调用的位置呢?首先,我们回想一下,在执行 SMC 调用时,与处理 SVC 调用(即“正常世界”中的常规系统调用)类似,“安全世界”必须注册向量的地址。当遇到这样的指令时,处理器将跳转。

“安全世界”的等效项是MVBAR(监视器向量基地址寄存器),它提供向量的地址,该向量包含“安全世界”中处理器处理的不同事件的处理函数。

正向的MRC/MSR

MRS x0, TTBR0_EL1 // Move TTBR0_EL1 into x0
MSR TTBR0_EL1, x0 // Move x0 into TTBR0_EL1

每个系统寄存器都可看做是一个标号 正向的源码中可以写寄存器名称,编译器认识,但逆向的IDA中只能看到寄存器标号

使用任意一个插件,IDA将会识别系统寄存器
https://github.com/gdelugre/ida-arm-system-highlight
https://github.com/NeatMonster/AMIE

访问 MVBAR 是使用 MRC/MCR 操作码和以下操作数完成的:
在这里插入图片描述

因此,这意味着我们可以简单地在 TrustZone 映像中搜索具有以下操作数的 MCR 操作码,并且我们应该能够找到“监视器向量”。事实上,在 IDA 中搜索操作码会返回以下匹配项:
在这里插入图片描述

正如您所看到的,“开始”符号的地址(顺便说一下,这是唯一导出的符号)被加载到 MVBAR 中
根据ARM文档,Monitor Vector具有以下结构:
在这里插入图片描述

这意味着,如果我们查看前面提到的“开始”符号,我们可以将以下名称分配给该表中的地址:
下图中解析的有问题,Monitor Vector的首地址是0xFE810000
在这里插入图片描述
现在,我们可以分析SMC_VECTOR_HANDLER函数。
实际上,这个函数负责很多任务;

  • 首先,它将所有状态寄存器和返回地址保存在预定义的地址中(在“安全世界”中),
  • 然后,它将堆栈切换到预分配区域(也在“安全世界”中)。
  • 最后,在进行必要的准备之后,它会继续分析用户请求的操作并据此进行操作。

由于发出 SMC 的代码存在于 Linux 内核的高通 MSM 分支中,因此我们可以看一下“正常世界”可以向“安全世界”发出的命令格式。

SMC and SCM(SCM没啥意义,就是高通自己给自己的SMC调用取了个名字)

令人困惑的是,高通选择将“正常世界”通过 SMC 操作码与“安全世界”交互的通道命名为 SCM(安全通道管理器)

无论如何,正如我在上一篇博客文章中提到的,“qseecom”驱动程序用于通过 SCM 与“安全世界”进行通信。

Qualcomm在相关源文件中提供的文档相当丰富,足以很好地掌握SCM命令的格式。

简而言之,SCM 命令分为两类:

  • 常规 SCM call - 参数很的调用方式,通过共享内存进行传参
  • Atomic SCM call - 轻量的调用方式,通过寄存器传参

常规 SCM call - 当需要将信息从“正常世界”传递到“安全世界”时使用这些call,这是为 SCM call提供服务所必需的。
内核填充以下结构:
在这里插入图片描述

TrustZone 内核在为 SCM 调用提供服务后,将响应写回“scm_response”结构:
在这里插入图片描述
为了分配和填充这些结构,内核可以调用包装函数“scm_call”,该函数接收

  • 指向内核空间缓冲区的指针,其中包含要发送的数据、数据应返回的位置
  • 以及最重要的服务标识符和命令标识符。

每个 SCM 调用都有一个类别,这意味着哪个 TrustZone 内核子系统负责处理该调用。这由服务标识符表示。命令标识符是指定在给定服务内请求哪个命令的代码。

在“scm_call”函数分配并填充“scm_command”和“scm_response”缓冲区后,它调用内部“__scm_call”函数刷新所有缓存(内部和外部缓存),并调用“smc”函数。

最后一个函数实际上执行 SMC 操作码,将控制权转移到 TrustZone 内核,如下所示:
在这里插入图片描述
请注意

  • R0 设置为 1
  • R1 设置为指向本地内核堆栈地址,该地址用作该调用的“上下文 ID”
  • R2 设置为指向分配的“scm_command”结构的物理地址。

R0 中设置的这个“神奇”值表明这是一个常规的 SCM 调用,使用“scm_command”结构。然而,对于某些需要较少数据的命令,无缘无故地分配所有这些数据结构将是相当浪费的。为了解决这个问题,引入了另一种形式的 SCM 调用。

Atomic SCM call - 对于参数数量相当低(最多四个参数)的调用,存在另一种请求 SCM 调用的方法。

有四个包装函数“scm_call_atomic_[1-4]”,它们对应于请求的参数数量。可以调用这些函数,以便使用给定的服务和命令 ID 以及给定的参数直接发出 SCM 调用的 SMC。

这是“scm_call_atomic1”函数的代码:
在这里插入图片描述

其中 SCM_ATOMIC 定义为:
在这里插入图片描述
请注意,服务 ID 和命令 ID 以及调用中的参数数量(在本例中为 1)都被编码到 R0 中。这取代了之前用于常规 SCM 调用的“神奇”值 1。
R0 中的这个不同值向 TrustZone 内核表明以下 SCM 调用是原子调用,这意味着参数将使用 R2-R5 传递(而不使用 R2 指向的结构)。

分析 SCM 调用

现在我们了解了 SCM 调用的工作原理,并且已经在 TrustZone 内核中找到了用于处理这些 SCM 调用的处理函数,我们可以开始反汇编 SCM 调用以尝试查找其中之一的漏洞。

我将跳过对 SCM 处理函数的大部分分析,因为其中大部分是用户输入的样板处理等。但是,在将堆栈切换到 TrustZone 区域并保存执行调用的原始寄存器之后,处理函数继续处理服务ID和命令ID,以便查看应该调用哪个内部处理函数。

为了轻松映射服务和命令 ID 以及相关处理函数,静态列表被编译到 TrustZone 映像的数据段中,并由 SCM 处理函数引用。以下是列表中的一小段内容:
在这里插入图片描述
如您所见,该列表具有以下结构:

  • 指向包含 SCM 函数名称的字符串的指针
  • call 类型
  • 指向处理函数的指针
  • 参数数量
  • 每个参数的大小(每个参数一个 DWORD)
  • 服务 ID 和命令 ID 连接成一个 DWORD - 例如,上面的“tz_blow_sw_fuse”函数的类型为 0x2002,这意味着它属于服务 ID 0x20,其命令 ID 为 0x02。

现在剩下的就是开始反汇编这些函数,并希望找到可利用的错误。

The Bug!

因此,在研究了所有上述 SMC 调用(全部 69 个)之后,我终于得到了以下函数:
在这里插入图片描述
通常,当使用常规 SCM 调用机制调用 SCM 命令时,R0 将包含结果地址,该地址指向由内核分配的“scm_response”缓冲区,但也由 TrustZone 内核验证以确保它实际上是“允许”范围内的物理地址 - 即对应于 Linux 内核内存的物理地址,而不是 TrustZone 二进制文件中的内存位置。

此检查是使用内部函数执行的,我将在下一篇博客文章中更详细地介绍该函数。


但是如果我们使用原子 SCM 调用来执行函数会发生什么?在这种情况下,使用的结果地址是原子调用传递的第一个参数。
在这里插入图片描述

现在 - 你能看到上面函数中的错误吗?

与其他 SCM 处理函数相反,该函数没有验证 R0(“结果地址”)中的值,因此如果我们传入:

  • R1为非零值(为了通过第一个分支)(原文有问题,是R0为非0
    在这里插入图片描述

  • 第四个参数(在上面的 var_1C 处传入)非零

    • LDR R0,[SP, #x28+var_1C]
    • CBZ R0, loc_FE84B372
    • 进入最左侧的分支
  • R0 为任何物理地址,包括 TrustZone 地址空间范围内的地址

    • MOVS R6, R0
    • MOVS R1, #0
    • STR R1, [R6]

该函数将到达上面函数中最左边的分支,并在 R0 中包含的地址写入一个零 DWORD

What’s next? 下一步是什么?

在下一篇博文中,我将分享针对上述漏洞的详细(而且相当复杂!)利用,该漏洞可以在 TrustZone 内核中实现完整的代码执行。我还将发布完整的漏洞利用代码,敬请期待!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/455132.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【网站项目】039菜匣子优选生鲜电商系统

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

LeetCode、790. 多米诺和托米诺平铺【中等,二维DP,可转一维】

文章目录 前言LeetCode、790. 多米诺和托米诺平铺【中等&#xff0c;二维DP&#xff0c;可转一维】题目与分类思路二维解法二维转一维 资料获取 前言 博主介绍&#xff1a;✌目前全网粉丝2W&#xff0c;csdn博客专家、Java领域优质创作者&#xff0c;博客之星、阿里云平台优质…

数据库学习笔记2024/2/5

2. SQL 全称 Structured Query Language&#xff0c;结构化查询语言。操作关系型数据库的编程语言&#xff0c;定义了 一套操作关系型数据库统一标准 2.1 SQL通用语法 在学习具体的SQL语句之前&#xff0c;先来了解一下SQL语言的通用语法。 1). SQL语句可以单行或多行书写&…

MySQL学习一、库和表的基础操作

目录 一、常用数据类型 1.数值类型 2.字符串类型 3.日期类型 ​二、数据库的基础操作 三、表的基础操作 一、常用数据类型 1.数值类型 数值类型可以指定为无符号&#xff08;unsigned &#xff09;&#xff0c;但不建议取 2.字符串类型 3.日期类型 二、数据库的基础操作…

CentOS7搭建k8s-v1.28.6集群详情

文章目录 1.灌装集群节点操作系统1.1 设置hosts1.2 设置nameserver1.3 关闭防火墙1.4 关闭Selinux1.5 关闭Swap分区1.6 时间同步1.7 调整内核参数1.8 系统内核升级 2.安装Docker2.1 卸载旧Docker2.2 配置Docker软件源2.3 安装Docker 3.部署Kubernets集群3.1 设置 K8s 软件源3.2…

Node.js JSON Schema Ajv依赖库逐步介绍验证类型和中文错误提示

在构建应用程序时&#xff0c;数据的有效性是至关重要的。为了确保传入的数据符合预期的格式和规范&#xff0c;我们可以使用 Ajv&#xff08;Another JSON Schema Validator&#xff09;进行验证。在这篇博文中&#xff0c;我们将从头开始学习 Ajv&#xff0c;逐步介绍验证类型…

如何在Windows系统上部署docker

上次在Windows系统上部署成功Ubuntu系统&#xff0c;这次准备在Windows上部署docker desktop应用 这个应用软件类似于虚拟机&#xff0c;可以在该应用软件上部署多个镜像容器。其最直观的表现就是可以借用Windows和Ubuntu终端来访问docker“模拟的系统”。 Docker简介 Docke…

OCR升级版 — 微调EasyOCR实战

OCR是从图像中提取文本的有价值工具。然而&#xff0c;有时您使用的OCR在特定需求上的表现不如您所希望的那样好。如果您面临这样的问题&#xff0c;微调OCR引擎是解决的一种方法。在本教程中&#xff0c;我将向您展示如何微调EasyOCR&#xff0c;这是一个免费、开源的OCR引擎&…

Golang-Map有序输出——使用orderedmap库实现

前言 工作中遇到一个问题&#xff1a;需要导出一个MySQL表格&#xff0c;表格内容由sql查询得来。但现在发现&#xff0c;所导出的表格中&#xff0c;各列的顺序不确定。多次导出&#xff0c; 每一次的序列顺序也是不定的。 因此确定是后端&#xff0c;Map使用相关导致的问题。…

安卓动态链接库文件体积优化探索实践

背景介绍 应用安装包的体积影响着用户下载量、安装时长、用户磁盘占用量等多个方面&#xff0c;据Google Play统计&#xff0c;应用体积每增加6MB&#xff0c;安装的转化率将下降1%。 安装包的体积受诸多方面影响&#xff0c;针对dex、资源文件、so文件都有不同的优化策略&…

Python脚本之操作Elasticsearch【一】

本文为博主原创&#xff0c;未经授权&#xff0c;严禁转载及使用。 本文链接&#xff1a;https://blog.csdn.net/zyooooxie/article/details/109588072 前面刚写了 requests发请求 操作Elasticsearch - Search https://blog.csdn.net/zyooooxie/article/details/123730279&…

关于RabbitMQ面试题汇总

什么是消息队列&#xff1f;消息队列有什么用&#xff1f; 消息队列是一种在应用程序之间传递消息的通信机制。它是一种典型的生产者-消费者模型&#xff0c;其中生产者负责生成消息并将其发送到队列中&#xff0c;而消费者则从队列中获取消息并进行处理。消息队列的主要目的是…