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

2024-02-06 08:12

本文主要是介绍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 内核中实现完整的代码执行。我还将发布完整的漏洞利用代码,敬请期待!

这篇关于tee漏洞学习-翻译-2:探索 Qualcomm TrustZone的实现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/683669

相关文章

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

Ilya-AI分享的他在OpenAI学习到的15个提示工程技巧

Ilya(不是本人,claude AI)在社交媒体上分享了他在OpenAI学习到的15个Prompt撰写技巧。 以下是详细的内容: 提示精确化:在编写提示时,力求表达清晰准确。清楚地阐述任务需求和概念定义至关重要。例:不用"分析文本",而用"判断这段话的情感倾向:积极、消极还是中性"。 快速迭代:善于快速连续调整提示。熟练的提示工程师能够灵活地进行多轮优化。例:从"总结文章"到"用

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

学习hash总结

2014/1/29/   最近刚开始学hash,名字很陌生,但是hash的思想却很熟悉,以前早就做过此类的题,但是不知道这就是hash思想而已,说白了hash就是一个映射,往往灵活利用数组的下标来实现算法,hash的作用:1、判重;2、统计次数;

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

深入探索协同过滤:从原理到推荐模块案例

文章目录 前言一、协同过滤1. 基于用户的协同过滤(UserCF)2. 基于物品的协同过滤(ItemCF)3. 相似度计算方法 二、相似度计算方法1. 欧氏距离2. 皮尔逊相关系数3. 杰卡德相似系数4. 余弦相似度 三、推荐模块案例1.基于文章的协同过滤推荐功能2.基于用户的协同过滤推荐功能 前言     在信息过载的时代,推荐系统成为连接用户与内容的桥梁。本文聚焦于

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo