type
status
date
slug
summary
tags
category
icon
password
0x01 文件级加密FBE(File-Based Encryption)1. 概念2. 应用处理3.更新处理4. 存储类别和密钥存储0x02 TrustZone与CE密钥的派生1. TrustZone 2. CE密钥派生流程#身份验证#CE密钥派生完整流程和相关文件#使用 Gatekeeper 进行身份验证#合成密码Synthetic Password0x03 漏洞利用1. 设备2. 利用过程3. Patch PreLoader4. Patching LK5. Patching TEEGRIS#TEEGRIS以及修补方案概述#Trust Applet(TA)修补#TrustZone ARchive(TZAR)修补#Trusted execution environment1 (tee1)修补6. Patching Gatekeeper7. Patch Android boot#Magisk boot设备外修补#开机自启adb shell#默认允许root权限授权#TA的动态挂载8. 利用脚本和结果0x0X 参考
本文的完整思路来源与
Quarkslab
的文章极其汇报,再次表达对http://twitter.com/DamianoMelotti 和 http://twitter.com/max_r_b出色工作的感谢!The complete source of ideas for this article is extremely reported with Quarkslab's article, expressing once again the interest in http://twitter.com/DamianoMelotti and http://twitter.com/max_r_b
0x01 文件级加密FBE(File-Based Encryption)
1. 概念
在设备重启后且并未输入密码解锁设备时文件时加密的,
ls -al /data/data
能够发现文件夹名称均为加密的并且文件不可读取。为了安全地使用 AOSP 的 FBE 实现,设备需要满足以下依赖关系:
- 对 Ext4 加密或 F2FS 加密的内核支持。
- 基于 1.0 或更高版本 HAL 的 Keymaster 支持。不支持 Keymaster 0.3,因为它既不提供必要的功能,也不能保证为加密密钥提供充分保护。
- 必须在可信执行环境 (TEE) 中实现 Keymaster/Keystore 和 Gatekeeper,以便为 DE 密钥提供保护,从而使未经授权的操作系统(刷写到设备上的定制操作系统)无法直接请求 DE 密钥。
- 硬件信任根和启动时验证需要绑定到 Keymaster 初始化进程,以确保未经授权的操作系统无法获取 DE 密钥。
凭据加密(Credential Encrypted, CE)存储空间和设备加密(Device Encrypted, DE)存储空间是Android设备上用于保护用户数据安全的两种加密存储方式。它们的主要区别在于数据可访问性的时机:
- 凭据加密(CE)存储空间:这种加密方式设计用来保护那些需要设备被用户解锁后才能访问的敏感数据。只有在用户输入正确的解锁凭据(如密码、PIN码或图案)后,加密的数据才会被解密并变得可访问,例如
/data/data
目录下内容即为凭据加密(CE)存储空间。这意味着,如果设备未解锁,即使攻击者物理获取了设备,也无法访问这部分加密的数据。这也就解释了为什么/data/data
在重启未解锁的情况下是无法访问的。
- 设备加密(DE)存储空间:DE存储空间的数据在设备启动时就被解密,因此在直接启动模式(Direct Boot mode)期间,即使设备还没有被用户解锁,这部分数据也是可访问的。这允许某些基本功能和应用(如闹钟、电话接收和紧急呼叫)在用户未解锁设备的情况下正常工作。然而,这也意味着这部分数据的安全性相对较低,因为它们在设备启动后即可访问。
2. 应用处理
为了实现系统应用的快速迁移,新增了两个可在应用级别设置的属性。
defaultToDeviceProtectedStorage
属性仅适用于系统应用,directBootAware
属性则适用于所有应用。应用级别的
directBootAware
属性的含义是将相应应用中的所有组件均标记为加密感知型组件。defaultToDeviceProtectedStorage
属性用于将默认的应用存储位置重定向到 DE 存储空间(而非 CE 存储空间)。使用此标记的系统应用必须仔细审核存储在默认位置的所有数据,并将敏感数据的路径更改为使用 CE 存储空间。使用此选项的设备制造商应仔细检查要存储的数据,以确保其中不含任何个人信息。在这种模式下运行时,以下系统 API 可在需要时用于明确管理由 CE 存储空间支持的 Context(这些 API 与设备保护存储空间适用的同类 API 相对应)。
Context.createCredentialProtectedStorageContext()
Context.isCredentialProtectedStorage()
3.更新处理
恢复分区无法访问 userdata 分区中采用 DE 保护的存储空间。强烈建议实现 FBE 的设备使用 A/B 系统更新来支持 OTA 机制。由于可以在正常操作期间安装 OTA 更新,因此恢复分区无需访问已加密存储卷中的数据。
如果使用旧版 OTA 解决方案(该解决方案要求恢复分区访问
userdata
分区中的 OTA 文件),则需要执行以下操作:- 在
userdata
分区中创建一个顶级目录(例如misc_ne
)。
- 将该顶级目录配置为非加密(请参阅排除目录)。
- 在顶级目录中创建一个用于存放 OTA 更新包的目录。
- 添加 SELinux 规则和文件环境,以便控制对该目录及其内容的访问。应当只有接收 OTA 更新的进程或应用能够对该目录进行读取和写入操作。任何其他应用或进程都不应具有访问该目录的权限。
4. 存储类别和密钥存储
如上表所示,大多数 FBE 密钥都存储在由另一个 FBE 密钥加密的目录中。只有先解锁包含这些密钥的存储类别,才能解锁这些密钥。
此外,
vold
还会对所有 FBE 密钥应用一层加密。除了用于内部存储设备的 CE 密钥之外,每个密钥都使用自己的 Keystore 密钥(该密钥不在 TEE 外部公开)以 AES-256-GCM 加密。这样一来,除非受信任的操作系统已启动(正如启动时验证所强制执行的那样),否则便无法解锁 FBE 密钥此外,Keystore 密钥还需要设置抗回滚,以便在 Keymaster 支持抗回滚的设备上也能安全地删除 FBE 密钥。为了在抗回滚不可用时能够尽力回退,系统使用存储在与密钥一起存储的 secdiscardable
文件中的 16384 个随机字节的 SHA-512 哈希作为 Keystore 密钥的应用 ID 标记。只有将这些字节全部恢复,才能恢复 FBE 密钥。用于内部存储设备的 CE 密钥将获得更高级别的保护,以确保在未掌握用户的锁屏知识因素 (LSKF)(PIN 码、图案或密码)、安全密码重置令牌或重新启动时恢复操作的客户端密钥及服务器端密钥的情况下,CE 密钥无法解锁。只允许为工作资料和完全受管设备创建密码重置令牌。
为此,
vold
会使用从用户的合成密码派生的 AES-256-GCM 密钥加密每个用于内部存储设备的 CE 密钥。合成密码是为每个用户随机生成的不可变的高熵加密密钥。system_server
中的 LockSettingsService
用于管理合成密码及其保护方式。为了使用 LSKF 保护合成密码,
LockSettingsService
首先会扩展 LSKF(方法是通过 scrypt
传递 LSKF,目标时间约为 25 毫秒且内存用量约为 2 MiB)。由于 LSKF 通常较短,因此该步骤通常无法提供多少安全性。主要的安全保障是下文所述的安全元件 (SE) 或由 TEE 强制执行的速率限制。如果设备具有安全元件 (SE),则
LockSettingsService
使用 Weaver HAL 将经过扩展的 LSKF 映射到存储在 SE 中的高熵随机密钥。然后,LockSettingsService
将对合成密码进行两次加密:第一次使用从经过扩展的 LSKF 和 Weaver 密钥派生的软件密钥,第二次使用未经身份验证绑定的 Keystore 密钥。这样即可对 LSKF 猜测施加 SE 强制速率限制。如果设备没有 SE,则
LockSettingsService
会改为使用经过扩展的 LSKF 作为 Gatekeeper 密码。然后,LockSettingsService
将对合成密码进行两次加密:第一次使用从经过扩展的 LSKF 和 secdiscardable 文件的哈希派生的软件密钥,第二次使用经过身份验证绑定至 Gatekeeper 注册的 Keystore 密钥。这样即可对 LSKF 猜测施加 TEE 强制速率限制。更改 LSKF 后,
LockSettingsService
会删除与合成密码和旧 LSKF 的绑定相关的所有信息。在支持 Weaver 或可抗回滚的 Keystore 密钥的设备上,这样做可以保证安全地删除旧绑定。因此,即使用户没有 LSKF,系统也会应用此处所述的保护措施。FBE 适用于搭载 Android 9.0 或更高版本和 Knox 3.3 或更高版本的所有三星 Galaxy 设备,可保护用户数据 分区中的文件。每个文件均使用AES-256-XTS
单独加密,并使用从主密钥派生的唯一文件加密密钥。在 FBE 中,主密钥由基于 TEE 的 Keymaster 组件随机生成和保护,类似于 FDE 实现。
0x02 TrustZone与CE密钥的派生
1. TrustZone
TrustZone是ARM针对消费电子设备设计的一种硬件架构,其目的是为消费电子产品构建一个安全框架来抵御各种可能的攻击。
TrustZone
在概念上将SoC的硬件和软件资源划分为安全(Secure World
)和非安全(Normal World
)两个世界,所有需要保密的操作在安全世界执行(如指纹识别、密码处理、数据加解密、安全认证等),其余操作在非安全世界执行(如用户操作系统、各种应用程序等),安全世界和非安全世界通过一个名为Monitor Mode
的模式进行转换处理器架构上,TrustZone将每个物理核虚拟为两个核,一个非安全核(
Non-secure Core, NS Core
),运行非安全世界的代码;和另一个安全核(Secure Core),运行安全世界的代码。两个虚拟的核以基于时间片的方式运行,根据需要实时占用物理核,并通过Monitor Mode在安全世界和非安全世界之间切换,类似同一CPU下的多应用程序环境,不同的是多应用程序环境下操作系统实现的是进程间切换,而Trustzone下的Monitor Mode实现了同一CPU上两个操作系统间的切换。
设计上,TrustZone并不是采用一刀切的方式让每个芯片厂家都使用同样的实现。总体上以AMBA3 AXI总线为基础,针对不同的应用场景设计了各种安全组件,芯片厂商根据具体的安全需求,选择不同的安全组件来构建他们的TrustZone实现。
以三星设备为例
- Trusted Execution Environment (TEE):又称为TEE,是指存在于安全世界中的应用程序、库和操作系统。TEE为敏感的计算任务提供了一个安全的执行环境,以防止来自普通世界的攻击。
- Samsung Knox:这是三星的一套安全解决方案,包括受信任的组件和普通世界的应用程序组件,如Android应用和库。Knox旨在提供一个小而安全的执行环境,即受信任的计算基础(TCB)。
- TEEgris:这是三星在一些基于Exynos的设备上提供的受信任操作系统。在过去,三星曾使用Kinibi作为不同的受信任操作系统,该系统已经成为安全研究的主题。即使是三星的模型也可能搭载高通的SoC和其自己的受信任操作系统QSEE。
虽然完全隔离的环境非常安全,但为了实际使用,它需要与 Android 中运行的其他不受信任的组件进行通信。REE 和 TEE 之间的通信是使用名为
Secure Monitor Call
(SMC
)的专用指令触发的。当 EL > 0 时,两个世界都可以调用此指令,这意味着 Android 应用程序无法直接发起与安全 TEE 的通信。通常情况下,Linux 内核充当代理并公开驱动程序,应用程序可以使用该驱动程序与 TEE 进行交互。这种设计的优势在于,可以应用访问限制策略(例如使用 SELinux)来访问驱动程序,以便只有一部分应用程序可以与 TEE 通信,从而限制攻击面。2. CE密钥派生流程
#身份验证
- 用户提供身份验证方法,然后关联的服务向关联的守护程序发出请求。
- 对于 PIN 码、解锁图案或密码,
LockSettingsService
会向gatekeeperd
发出请求。 - 基于生物识别技术的身份验证流程取决于 Android 版本。在搭载 Android 8.x 及更低版本的设备上,
FingerprintService
会向fingerprintd
发出请求。在搭载 Android 9 及更高版本的设备上,BiometricPrompt
会使用合适的Biometric
Manager
类(如FingerprintManager
或FaceManager
)向相应的生物识别守护程序发出请求(例如,若是指纹识别身份验证,则向fingerprintd
发出请求;若是人脸识别身份验证,则向faced
发出请求)。无论什么版本,生物识别身份验证都会在请求发出后异步进行。
- 守护程序将数据发至其副本,后者生成 AuthToken:
- 对于 PIN 码/解锁图案/密码身份验证,
gatekeeperd
将 PIN 码、解锁图案或密码哈希发送到 TEE 中的 Gatekeeper。如果 TEE 中的身份验证成功,TEE 中的 Gatekeeper 会将包含相应用户 SID(已使用 AuthToken HMAC 密钥签名)的 AuthToken 发送到它在 Android 操作系统中的副本。 - 对于指纹识别身份验证,
fingerprintd
会监听指纹事件并将数据发送到 TEE 中的 Fingerprint。如果 TEE 中的身份验证成功,TEE 中的 Fingerprint 会将 AuthToken(已使用 AuthToken HMAC 密钥签名)发送到它在 Android 操作系统中的副本。 - 对于其他生物识别身份验证,相应的生物识别守护程序会监听生物识别事件,并将其发送到相应的生物识别 TEE 组件。
- 守护程序收到经过签名的 AuthToken,并通过密钥库服务 Binder 接口的扩展程序将 AuthToken 传递给密钥库服务。(
gatekeeperd
还会在设备被重新锁定以及设备密码发生变化时通知密钥库服务。)
- 密钥库服务将 AuthToken 传递给 Keymaster,并使用与 Gatekeeper 和支持的生物识别 TEE 组件共用的密钥来验证这些 AuthToken。Keymaster 会将令牌中的时间戳视为最后一次身份验证的时间,并根据该时间戳做出密钥发布决定(以允许应用使用相应密钥)。
注意:设备重新启动后,AuthToken 即作废。
为了确保在各种语言和组件之间实现令牌的共用和兼容,
hw_auth_token.h
中规定了 AuthToken 的格式。该格式是一个简单序列化协议,具有以下固定大小的字段。字段 | 类型 | 必需 | 说明 |
AuthToken 版本 | 1 个字节 | 是 | 下方所有字段的组代码。 |
质询 | 64 位未签名整数 | 否 | 用于防范重播攻击的随机整数,通常是所请求的加密操作的 ID。目前由交易指纹授权使用。如果质询存在,AuthToken 将仅对包含该相同质询的加密操作有效。 |
用户 SID | 64 位未签名整数 | 是 | 不重复的用户标识符,以加密形式绑定到与设备身份验证关联的所有密钥。如需了解详情,请参阅 Gatekeeper。 |
身份验证器 ID (ASID) | 64 位未签名整数,按网络字节序保存 | 否 | 绑定到特定身份验证器政策时使用的标识符。所有身份验证器都有自己的 ASID 值,它们可以根据自己的要求更改该值。 |
身份验证器类型 | 32 位未签名整数,按网络字节序保存 | 是 | • 0x00 代表 Gatekeeper。
• 0x01 代表 Fingerprint。 |
时间戳 | 64 位未签名整数,按网络字节序保存 | 是 | 自最近一次系统启动以来已经过的时间(以毫秒为单位)。 |
AuthToken HMAC (SHA-256) | 256 位 Blob | 是 | 除 HMAC 字段以外所有字段的已加密 SHA-256 MAC。 |
#CE密钥派生完整流程和相关文件
使用
TrustZone
进行CE密钥派生的完整过程如下(以非生物特征解密为例),流程很复杂所以拆分几个部分进行说明。- 使用 Gatekeeper 进行身份验证
- 生成合成密码Synthetic Password
首先很重要的是知道与身份验证相关的文件存储在哪,这里是
/data/system_de/0/spblob
目录。下面的文件有三个比较重要,分别是<handle>.pwd
<handle>.secdis
以及<handle>.spblob
。<handle>.pwd
存储了进行scrypt哈希的N R P
以及salt
值,除此之外还存储了注册时生成的密钥handle
,其计算方式是HMAC(SHA512("user-gk-authentication" || scrypt(credentials, N, R, P, salt))
,在gatekeeper
进行密钥校验时会读取改handle
进行比较。但是需要注意这里的HMAC
并未常规的,而是使用依赖于/dev/crypto
的KDF
实现,由于是硬件相关的,其复杂程度使得爆破handle
变得不现实。
<handle>.secdis
是密钥注册时随机生成的一组值,用于加密和解密凭据<handle>.spblob
<handle>.spblob
存储了合成密码Synthetic Password
的两次AES的加密结果,只要获取合成密码就可以获取CE密钥。
#使用 Gatekeeper 进行身份验证
首先需要明确
Gatekeeper
是如何进行凭据注册和验证的。Gatekeeper
是TEE(受信任执行环境)中的一个受信任应用程序(TA
),在验证用户凭证以进行身份验证方面发挥着关键作用。它通过与相应的Android守护进程和硬件抽象层(HAL
)通信来实现这一点。需要注意的是,Gatekeeper仅涉及通过PIN、密码或图案进行的身份验证,而其他TA用于支持生物识别技术。然而,在设备启动时用户首次进行身份验证时,不能使用生物识别技术,这与数据加密直接相关。Gatekeeper实现了两个概念上简单的命令:Enroll(注册)和Verify(验证)。
- Enroll:当用户首次设置认证因素或更改时通常会调用此命令。该命令根据注册时的密码凭据创建blob并对其进行签名,并将其转换为密码句柄
<handle>.pwd
。具体的流程是首先使用scrypt
进行扩展,然后与散列字符串组合HMAC(SHA512("user-gk-authentication" || scrypt(credentials, N, R, P, salt))
。
- Verify:当用户尝试进行身份验证时调用此命令,它验证当前身份验证尝试的密码是否有效。它通过计算HMAC并将其与原始密码句柄进行比较来实现。
Scrypt是一个密钥派生函数,需要大量内存,用于减缓自定义硬件攻击。其参数与凭证的盐一起存储在文件中。这个非常简单的步骤对每次身份验证尝试造成的延迟可以忽略不计,但对需要多次进行此操作的攻击者来说,却大大减缓了速度。
下图截取自上面流程图的左上角,其包含了
Gatekeeper
的验证和applicationId
的生成。最左侧的credentials
是用户输入的凭据,Gatekeeper
读取<handle>.pwd
中的参数后计算token并进行两次散列,与原本存储在<handle>.pwd
中的散列结果比较,如果一致说明身份验证成功,不一致则校验失败,不进行后续的操作。将token
与<handle>.secdis
中的内容做哈希后得到applicationId
,这个值是随着用户输入的凭据变化而变化的。这里的 ||
操作是personalised_hash
的意思。这里还有一些细节需要了解。如果身份验证成功,Gatekeeper会创建一个认证令牌(
token
)。这是一个标准格式的签名令牌,旨在防止重放攻击。该令牌证明用户已经通过身份验证,并需要发送给Keymaster TA
以解锁绑定认证的密钥,因为这个密钥是存储在Keystore
中的SecretKey
,在一般情况下无法通过其他手段读取,后面会再次提到。如果身份验证尝试失败,Gatekeeper的节流机制将启动,使得暴力破解变得不可能。这部分对应在
/system/framework/services.jar
中的实现如下首先是
unwrapPasswordBasedSyntheticPassword
中将输入的credential
传递给gatekeeper
校验,如果校验成功则transformUnderSecdiscardable
生成applicationId
transformUnderSecdiscardable
实现就是流程图中靠右的部分,第二个参数为/data/system_de/0/<handle>.secdis
文件内容,第一个参数pwdToken
是通过computePasswordToken
计算得到的由此可见,上面的整个流程都是可以离线计算的,所以假设如果能获取到这些密钥相关文件,就可以通过任意的输入密码计算
applicationId
#合成密码Synthetic Password
一旦用户通过身份验证,系统就会获得一个有效的
applicationId
。这需要经过几个步骤,才能真正成为 CE 密钥。首先就需要生成一个合成密码Synthetic Password
,但是得到该密码后仍需执行一些其他步骤。重点关注Synthetic Password
的生成合成密码存储在 Android 文件系统中,必须使用两个不同的密钥解密。
- 第一个是存储在
Android Keystore
中的常规密钥,受TEE
保护并与身份验证绑定。由于这些密钥永远不会离开 TEE,因此它们以加密形式存储,需要在 Keymaster TA 内解密,即在上面提到的,只有在Gatekeeper
校验成功后才会解密。
- 完成第一次解密(在 TEE 中)后,将使用
applicationId
的散列值作为密钥再次解密中间值。此处的 AES 是使用GCM 模式完成的:如果密钥出现问题,会因MAC标签不匹配而失败导致异常,利用这一特性就可以进行密钥爆破。
相关的源码实现如下
decryptBlob
从DEFAULT_KEYSTORE
获取一个SecretKey
,而这个keyAlias
其实就是synthetic_password_<handle>
该key值无法直接获取,必须在Gatekeeper
校验成功后才会解密并且由system_server
获取。之后进行两次decrypt
第一次的密钥是
decryptionKey
固定的,也就是前面提到的SecretKey
的值,两次解密的iv都是<handle>.spblob
的前12个字节第二次的密钥是
applicationId
经过personalisedHash
后的哈希值,解密的密文是第一次解密的结果intermediate
。可以发现这整个环节中,第一次解密的密钥是无法获取的,必须
Gatekeeper
校验通过才会由KeyMaster
解密,那就导致无法离线计算。从而保证了密钥的安全性。为了能够绕过这一限制,有两个解决方案:- 修改
KeyMaster
使攻击者能获取到第一次解密的密钥
- 修改
Gatekeeper
使其校验永远成功,永远返回token,这样KeyMaster
也会解密密钥,只需要在第一次解密后Hook即可获取到第一次解密后的密文,进而完全解密。
这里选择第二种方案,但是无论是采取哪一种方案,攻击的前提是必须要获取这些密钥相关文件,那么设备就必须要root,除此之外还需要具备修补可信应用的能力。
0x03 漏洞利用
1. 设备
该设备关机状态下同时按住上下音量后插入usb数据线进入下载模式。
关机状态下同时按音量上+电源长按启动,到看到第一个
logo
后放手到rec
模式。使用
MTKClient
工具,首先启动脚本等待设备连接,这里直接使用Ubuntu 22.04
系统,Windows系统可能存在各种各样的问题。然后短接背板上的两个测试点进入强刷模式(BROM
),MTKClient
检测到设备并且进行指定操作。2. 利用过程
首先是
Android
的启动过程如下实现了
ATF(Arm Trust Firmware)
的启动过程如下为了发起攻击,我们需要修补以下组件:
PreLoader
,PreLoader
介于boot rom
和bootloader
之间的桥梁,主要工作是初始化环境,包括c环境,timer,gpio,pmic,uart,i2c
等以及装载lk
镜像至DRAM
中,建立起最基本的运行环境,最重要的就是初始化DRAM
。修补PreLoader
以绕过对LK等的镜像校验检查
the next bootloader BL3
,称为Little Kernel
(简称 LK),用于禁用Android
安全启动的一系列检查,因为我们想要启动修改后的 Androidboot.img
Android
系统的boot
镜像,授予root
权限,并修改位于供应商分区中的Gatekeeper
可信应用程序
TEE OS
,首先是TEEGRIS
,用于禁用受信任应用程序的验证。其次是TA,即可信Applet,目标是修补GateKeeper
应用
3. Patch PreLoader
通过
mtkclient
提取PreLoader
,然后查看mtkclient
是如何解析PreLoader
二进制的结构,发现其jump_offset
是0x30:0x34
的四字节,daaddr
是0x1C:0x20
的四字节加上jump_offset
。IDA加载时加载地址设为
0x201000
,文件偏移设为0xF0
,同时同步上下两个size
,在入口处按c将会自动开始分析,过程和下面LK的加载一致,可以看下面。在Patch LK镜像时输入发现无法开机,但是又没有日志,去查资料才发现,联发科的芯片可以直接dump
expdb
分区获取日志信息。这里还是使用mtkclient
dump,问题似乎出在sbc
校验,但是通过修改lk中相关字符串,发现这个报错不在这里,而是在preloader
,所以才需要去patch preloader
绕过相关校验。patch
sbc_img_auth
以绕过对lk镜像的校验,避免无法开机的情况使用
patch
后的二进制启动设备,发现能够成功出现三星log
说明绕过了lk
镜像校验,之后启动设备都需要使用补丁后的preloader
来启动4. Patching LK
首先需要说明IDA如何正确加载
LK
镜像并进行分析IDA如何正确加载
LK
镜像并进行分析首先LK位于
BL_xxxx.tar
解压得到lk-verified.img.lz4
再次解压得到img
。为了能让IDA正确的加载img并进行分析必须获取的值ROM Start Address/Loading Address
:对应LK镜像而言,0x002C
偏移位置是镜像的加载地址,在这里为0xFFFFFFFF
,即为-1
,所以是无效的地址,这是因为新的联发科镜像不会从这里选取地址加载,直接搜索10 FF 2F E1
十六进制字节,跳过其后四个字节,再取四字节即为Loading Address
,这里小端序,所以加载地址即为0x4C400000
File Offset
:联发科的所有分区镜像都有0x200
字节的头部,从0x200
开始为可执行的部分。
IDA 32bit
打开后选择ARM小端序架构,然后填写参数信息,其中Loading size
会根据填入的值自动调整,调整完成后将其复制到ROM size
保持同步即可。代码从文件偏移量
0x200
开始,但 IDA 不知道它是入口点。在IDA-View A中单击行DCB 7
at ROM:
4C400000
。将光标移到那里后,按键盘上的[C] 。之后IDA会自动分析出更多内容。之前查找的4字节特征即为
BX R0
指令,可以发现在后面8字节即为ROM
加载的地址- patch在
load_and_verify_vbmeta
中的avb_safe_memcmp
绕过LK镜像的Hash校验
包括patch
sub_4C4A61B4
内部的avb_safe_memcmp
patch
avb_slot_verify
中的avb_memcmp
对vbmate
的哈希校验patch
load_and_verify_vbmeta
中的另一处avb_safe_memcmp
- patch调用
show_dm_verity_error
的位置,否则会出现dm_verity
错误并且在5s后关机。
- patch
get_efuse_blow_status
,使其返回0,以使上面检测保险丝熔断状态时返回正常的状态。
- patch
do_decision
中的三个内容,保证其返回值为0,且不会出现关机的情况
- patch
avb
校验时的逻辑,NOP掉一个跳转
- patch参数数据的函数,防止意外清除用户数据(例如在测试解锁时会自动调用擦除数据的函数)
- patch
is_sbc
返回0以绕过sbc检查
- patch
set_warrant_bit
使其返回0
- patch 其他镜像的
sha256
校验
- patch启动时的安全检查
check_secure_boot
,将authinfo
的两个字段修改为0,并且将函数返回值强制设置为0。
下面的两块拓展内容可以跳过,但是如果想更全面的学习,还是需要了解一下。
由于设备是未解锁的(攻击场景就是未解锁设备),刷入boot镜像进行root会显示不允许刷入非官方的镜像。所以可以直接patch
LK
中获取设备锁状态的部分,一般的解锁后设备会自动擦除数据,为了避免数据被擦除也需要patch擦除数据的部分。但是更好的办法是直接
patch
检查镜像的部分,即上面提到的authinfo
,但是随之而来的问题是如果不采用解锁的Patch方案,刷入第三方REC镜像会导致开不了机并且也无法进入REC模式查看日志信息。所以前期调试看日志需要解锁,后期攻击不需要,而前期调试学习阶段不需要非得Patch解锁部分,完全可以解锁设备,然后刷入各种镜像调试,之后再把锁加回来即可。下面的解锁内容属于题外话,前面提到的无法开机的原因是加密分区挂载失败,加密分区挂载失败就是因为设备安全验证没通过,所以为了方便直接处理
authinfo
即可,所以这部分仅当拓展。除此之外,在解锁状态下直接刷入magisk修补的boot会开机失败并且自动进入rec
模式,原因是fs_mgr_mount_all
。尝试禁用vbmeta
也无效。刷入TWRP然后使用adb查看/proc/last_kmsg
发现其实也是挂载加密分区/data
失败导致的问题,而本质是因为检测到设备损坏,和上面的结论一致,即安全检测未通过。解决方案就是patch
掉/vendor/tee/00000000-0000-0000-0000-4b45594d5354
,使其认为设备是正常的,从而正常解密,后面修补部分详细展开。联发科机型使用
seccfg
分区存储设备BL锁等状态关键信息,分区大小一般为8MB
,实际使用空间远小于此,1024
字节都用不完。其中MTK
定义的seccfg
头部结构仅为60B
,包括28字节数据部分和32字节的校验部分。剩下的32位校验码因为使用了设备硬件相关的加密,除非设备密钥泄露否则没有办法脱机计算(实现可以参照https://github.com/bkerler/mtkclient/blob/main/mtkclient/Library/Hardware/hwcrypto_sej.py)。- 如果设备已解锁,
lock_state = 3
- 如果设备未解锁,
lock_state = 4或1
如下图提取设备中的seccfg分区,观察第
0xc
开始的四个字节,值为1符合未解锁的定义。但是mtkclient
的解锁功能是Patch
此处的两个DWORD
,也就是将dm_verity_state
Patch为1为解锁,这样就会导致dm_verity
是校验失败的,查看mtkclient
源码此处的字段被解读为critical_lock_state
,通过逆向可以发现显然是dm_verity_state
,所以认为其实现有问题。无论是通过mtkclient
解锁还是Patch LK都需要擦除用户数据,因为默认keymaster
是可信的,其检测到设备损坏后强制要求擦除数据。根据这些已知知识对LK符号进行恢复,能够发现
seccfg_get_lock_state
是获取设备锁状态的函数,通过读取的ROM内存偏移来识别。Patch该函数,使其
a1
读取的值永远为3,以实现使LK认为设备已经解锁,从而不对镜像进行检查。虽然能够进入rec
模式,但是开机出现问题,这就和前面说的加密分区挂在失败是一个事情了,不再赘述。5. Patching TEEGRIS
#TEEGRIS以及修补方案概述
首先是大致的修补方案以及TEEGRIS的介绍
TEEGRIS
分为如下几个镜像:
tee1.img
: it contains the Arm Trusted Firmware (which is executed in monitor mode with the highest privileges - EL3), the Secure World kernel, and a binary calleduserboot.so
. This last one is quite important for us, as it is used to load and verify the root filesystem of TEEGRIS.
tzar.img
: this is the root filesystem of TEEGRIS, stored in a custom archive format that has been reversed engineered. It contains libraries that can be used by other libraries but also by the TAs, and binaries including one calledroot_task
, that is in charge of verifying and running the TAs provided by Android.
super.img
which is the Android main partition containing several logical partitions. One of them is the vendor partition and contains most of the TAs, including Gatekeeper.
拓展内容TA的通信
TA与TA APITrusted Applications
- 可信应用程序(TA)是在TEE中运行的程序,向安卓客户端应用程序提供安全服务。TEE(可信执行环境)是一个安全的执行环境,与主操作系统环境(通常称为“普通世界”)隔离。TA在这个环境中运行,可以提供加密和安全相关的服务。
- 应用程序可以与TA开启一个会话,并在会话中调用命令。客户端应用程序(普通世界中的应用程序)可以与TEE中的TA建立一个会话,并在这个会话中发送命令给TA。
- 接收到命令后,TA解析命令输入,执行所需处理,并将响应发送回客户端。TA接收并解析来自客户端的命令,执行必要的处理(如加密、解密、认证等),然后将处理结果或响应发送回客户端应用程序。
- 通过专用的SMC(安全监视器调用)指令,控制权转移到TA,TA和普通世界应用程序通常使用称为世界共享内存的共享内存缓冲区交换参数和输出。SMC(Secure Monitor Call)是一种特殊的指令,用于在普通世界和TEE之间切换执行环境。TA和客户端应用程序通过一个共享的内存区域(世界共享内存)交换数据和命令参数。
- 由于执行SMCs需要EL1权限,安卓内核中的设备驱动程序处理与TA的通信,并为普通世界应用程序暴露API。在ARM架构中,EL1是一种运行级别,通常用于操作系统内核。因为执行SMC指令需要这种权限级别,所以安卓系统中有一个设备驱动程序负责与TA的通信。这个驱动程序为普通世界的应用程序提供API,使它们能够与TA进行交互,而无需直接执行SMC指令。
TEE Client API客户端应用程序调用客户端API函数,如TEEC_OpenSession
,在普通世界(EL0)中。这是客户端应用程序开始与TA交互的第一步,通过调用TEEC_OpenSession
函数来请求与TA开启一个会话。这会触发普通世界内核(EL1)中的一个SMC操作码,执行随即切换到安全监视器,安全监视器将执行切换到安全世界TZOS内核(S-EL1)。安全世界TZOS内核将调度TA并调用相应的TA API函数,如TA_CreateEntryPoint
,然后将响应返回给客户端。常见的API函数包括:
TA_CreateEntryPoint
:构造函数,TA加载时调用。
TA_OpenSessionEntryPoint
:当客户端通过TEEC_OpenSession
开启会话时调用。
TA_InvokeCommandEntryPoint
:当客户端通过TEEC_InvokeCommand
调用命令时调用。
TA_CloseSessionEntryPoint
:当客户端通过TEEC_CloseSession
关闭会话时调用。
TA_DestroyEntryPoint
:析构函数,TA卸载时调用。
采取倒着修补的方法,逐步往上
patch
·- 首先TA的修补可以通过与
magisk
的magic_mount
类似的挂载动态分区实现。可以利用到设备中的一个spu
分区,该分区是一个ext4
文件系统,一般情况下不存储内容,可以直接在该文件系统下创建同样的目录结构,然后修改Magisk源码,将spu分区提前挂载,然后动态挂载到根目录,这样就会将spu分区底下的文件挂载到对应的目录,但是还需要处理的是selinux
标签,因为magisk daemon是root权限的并且Selinux允许设置标签,所以可以通过chcon设置。
- 其次修补
tzar
,修补其中的root_task
以绕过对可信应用的校验
- 最后修补
tee1
,修补其中的userboot.so
绕过对tzar
的校验
- 需要注意的是
preloader
负责对tee1
进行加载校验,所以还是需要使用mtkclient
绕过对tee1
分区的校验(使用自定义preloader
完成分区的装载)
#Trust Applet(TA)修补
实际只需要修补一个可信应用
GateKeeper
,二进制文件均放置于/vendor/tee
下。命名如下:00000000-0000-0000-0000-474154454b45
同理后面的数字为GATEKE
的ascii码
但是在这一节不谈如何修补
GateKeeper
,放到最后一节,先按顺序介绍基本知识和修补流程TA文件格式为SEC头 + 标准ELF文件内容 + 附加信息(Metadata + Signature)。
- 头部长 8 个字节,包含 4 个字节的版本(SEC2、SEC3或SEC4)和4字节内容部分的长度。
- 在SEC4的情况下,ELF文件为加密格式,否则为明文。
- 其中附加信息包含签名信息等内容。从版本SEC3开始,有一个包含版本号的附加字段。root_task 将此版本字段与 ACSD 结合使用,以管理 TA 防回滚保护。每当加载 SEC3 或 SEC4 TA 时,都会提取版本号并将其与存储在 RPMB 存储中的版本进行比较。如果版本号较低,则无法加载 TA,并返回错误。如果版本号较高,则 RPMB 中的版本号会增加以匹配 TA 版本,以便无法再加载同一 TA 的旧副本。签名部分包含对图像其余部分的 RSA 签名。它遵循 X.509 格式,并由 ACSD 解析
在对TA进行分析的时候可以直接删去开头的前八字节拖入IDA分析,对TA的修补直接在二进制文件上直接修补即可,但是修补完直接加载肯定是失败的,这就需要patch
tzar
中的root_task
以绕过对TA的校验。在探索TA的过程中也可以直接使用下面的脚本将TA文件直接转为可以分析的二进制文件
拓展:在前期探索的过程中也动过另一个TA,就是
00000000-0000-0000-0000-4b45594d5354
即keymaster
,用于密钥分发等等一系列功能,在前期修补LK镜像时有的地方没有修补并且在解锁状态下刷入了第三方REC,导致无法开机,查看日志发现是keymaster
的问题出现了解密失败的问题,而这里的解密失败和之前的问题一致。libfs_mgr尝试挂载data分区,但是是加密的所以跳过,然后由
vdc
使用cryptfs mountFstab
命令挂载加密的文件系统。这里的/dev/block/platform/bootdevice/by-name/userdata
和/data
分别是加密分区的设备路径和挂载点。
找到第一个错误[274:tz_iwlog_thread]SW> keymaster [ERR] (tz_check_oem:72) Device is compromized: status=0, value=1
从IDA中索引找到逻辑后直接patch
check_compromized
。原本从TEES_GetIrsFlagValue
获取一个state
和一个value
,如果两者有一者为1就说明设备出现了损坏,后面的解密就会失败,直接patch,让这两个值均为0即可。这里需要注意,这两个全局变量虽然在IDA看起来没有更多引用,但是既然其作为全局变量,大概率存在其他引用,认为IDA可能出现识别问题。成功挂载加密分区
#TrustZone ARchive(TZAR)修补
首先需要了解
T
rust
Z
one
AR
chive
TZAR
分区镜像的格式提取
tzar.img
镜像后需要进行简单的处理,即去除三星镜像的固定格式,这里去除到0x204
,剩余部分即为镜像的实际内容部分,但是还需要进行一次lz4
解压才能得到真正的内容。需要注意的是这里的lz4magic number
为0x184c2102
,也就是旧(legacy
)的格式,其中0x002e2781
是压缩的字节,要从0x20C
开始(因为lz4
头部占8字节)往后数0x002e2781
结束,取这部分数据作为lz4,结尾还有一些其他数据不在解压的范畴。然后使用脚本以特定的格式解压文件能得到完整的文件目录和结构解压后即可找到
root_task
等文件内容。修改
root_task
的汇编相当于直接在特殊归档文件之上进行修改,修改完成之后重新使用lz4 -l -9 tzar tzar_new
进行压缩,然后重新打包进入tzar
分区镜像中,这里还需要处理联发科镜像的固定格式的问题。因为lz4
重新压缩后和原本的压缩件大小不一致,但是联发科镜像固定格式中存在长度字段,那么在重新打包时必须要修改分区字段,这样才能使其成功加载。联发科镜像头部定义如下,需要修改的就是
dsize
字段,这个字段指明了分区的大小,但是不包括头部的512
字节。也就是下图中的
0x2E917B
需要根据新的压缩部分的大小变化进行修改patch
的完整脚本如下,输出镜像为tzar_new.img
如果刷写后日志有 [tzar] start sp, ret=-22
就说明tzar
镜像格式有问题。然后修补tzar,主要修补其中的
root_task
中的ta_pkg_verify
调用,将其patch掉,patch为MOV W0, #0
,修补方法就是直接修补在lz4
解压后的tzar
,然后重新lz4 -l -9 tzar tzar_new
压缩,然后运行上面的脚本打包为tzar_new.img
刷入修补后的tzar开机瞬间关机,查看日志提示
hash mismatch
,就说明已经成功加载tzar
了,只是tee1
中的userboot
还对tzar
进行了校验,这就需要patch tee1。#Trusted execution environment1 (tee1)修补
接上节,已经成功加载
tzar
了,只是tee1
中的userboot
还对tzar
进行了校验。从tee1分区镜像中能够直接脱出一个ARM 64 ELF文件。查找字符串引用,patch memcmp
的参数。直接赋使第一个参数和第二个参数一致绕过检测。
修补的方法就是直接在tee1.img中找对应的汇编改掉就行,因为tee1.img中的该elf并未进行任何加密或压缩处理。
6. Patching Gatekeeper
到这里就是最关键的一步,前面提到修改
Gatekeeper
使其校验永远成功,永远返回token,这样KeyMaster
也会解密密钥,只需要在第一次解密后Hook即可获取到第一次解密后的密文,进而完全解密。找到
Gatekeeper
TA中的相关逻辑,下面的函数是在计算HMAC,查找其引用找到前面有一个
MemCompare
的调用,直接修改,使其前两个参数一致。在二进制层面的修补如下
修补完成后就可以直接使用较高的权限覆盖
/vendor/tee/
下面的二进制文件吗?答案是否定的,因为/vendor
目录是只读的,为了更加安全便捷的替换,采用动态挂载的方式。即使用mount
挂载修改后的TA到目标挂载点。这就需要探究Android boot
的修补了,顺便还可以root设备,获取密钥相关文件。7. Patch Android boot
为了实现下面两个目标,就需要
ROOT
设备:- 获取
/data/system_de/0/spblob
下的文件,并且由于需要Hook,所以采用frida
来Hooksystem_server
- 动态挂载文件以实现替换TA
为了方便直接使用成熟的ROOT方案Magisk,但是不是开箱即用的,我们需要对其进行一些修改以适应我们的攻击场景:
- 在PC设备上修补
boot.img
,Magisk原本支持在设备上的Magisk App内对boot.img的修补,但是攻击场景的设备屏幕未解锁且不能安装App,必须在外部设备实现修补。
- 必须在未解锁屏幕的情况下获取
adb shell
,一般情况下获取adb shell
需要手动打开开关并且授权相应设备访问权限,在屏幕未解锁的情况下无法开启调试选项以及点击授权。
- adb shell必须能够直接获取root权限而不需要任何点击允许,在现成的Magisk方案中,授权应用使用root权限需要用户手动点击弹窗以允许,但是在屏幕未解锁的情况下无法点击。
- 实现对TA的动态挂载
#Magisk boot设备外修补
编译
Magisk
,将编译后的部分产物移动到工作目录,除此之外还需要移动scripts
目录下的util_functions.sh
和boot_patch.sh
修改
util_functions.sh
脚本的内容,因为里面含有一些在设备上才能执行的命令,直接强制指定即可。修改
boot_patch.sh
#开机自启adb shell
在
native/src/core/daemon.cpp
的daemon_entry
设置ro.debuggable=1
以开启adb,设置ro.adb.secure=0
以禁用adb的校验,从而不需要任何授权使用adb除此之外,由于设置了
ro.adb.secure=0
,在重启adbd时会出现selinux
拒绝的问题,需要在native/src/init/selinux.cpp
的hijack_sepolicy
中添加一些权限的允许规则,否则adbd
重启会失败#默认允许root权限授权
注释掉
native/src/core/daemon.cpp
的handle_request
中部分权限拒绝代码注释掉
native/src/core/su/su_daemon.cpp
的 su_daemon_handler
中的部分代码,默认允许授权。#TA的动态挂载
首先在
native/src/init/rootdir.cpp
中的patch_ro_root
添加部分代码用于挂载SPU分区,首先makedev
和xmknod
创建设备节点,然后将其挂载到根目录,magic_mount
会暂时的将同样目录下的文件挂载到对应的位置,但是这在后面会被取消挂载,需要再次进行挂载。在
native/src/core/bootstages.cpp
的post_fs_data
中添加load_ta
的调用,其挂载/spu/vendor/tee/
下的可信二进制文件到/vendor/tee
中,并且设置权限和Selinux
标签。对应的要在spu镜像中创建指定文件夹并且复制对应的二进制文件,然后刷入
8. 利用脚本和结果
刷入各种镜像
Frida Hook脚本如下
在完成第一次解密后进行第二次解密,但是由于密码错误,出现异常,导致crash,但是已经获取到第一次解密后的结果
intermediate
爆破成功获取密钥为2356
0x0X 参考
- 作者:LLeaves
- 链接:https://lleavesg.top//article/Android-Data-Encryption
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。
相关文章