利用BCV漏洞攻击Java卡

从字节码验证小程序攻击Java卡虚拟机

摘要

字节码验证器(BCV)是Java Card环境中最重要的安全元素之一。实际上,嵌入的小程序在安装前必须经过验证,以防止格式错误的小程序加载。在本文中,我们披露了Oracle字节码验证器(Oracle BCV)中存在的一个影响小程序链接过程的缺陷,并且该缺陷可在实际的Java Card智能卡上被利用。我们描述了在一种Java Card实现上对该缺陷的利用,该利用使得从一个已验证小程序能够在通信缓冲区中注入并执行任意本地恶意代码。这种本地执行允许以操作系统权限对智能卡内存进行快照。

关键词 :Java 卡 · 软件攻击 · BCV漏洞

1 引言

开发智能卡应用程序是一个漫长而复杂的过程。尽管已经存在标准化的努力,例如关于电源、输入和输出信号的规范,但智能卡开发过去一直依赖于各制造商提供的专有API。这种开发方式的主要缺点是应用程序代码只能在特定平台上运行,从而降低了互操作性。

为了提高嵌入式软件的互操作性和安全性,Java卡技术于1997年被设计出来,以允许基于Java的应用程序在智能卡及类似资源受限的设备上安全运行。由于该类设备的资源限制,Java卡技术仅保留了Java技术的一个子集。为使Java卡虚拟机(JCVM)能够在资源受限的设备上嵌入而对Java架构所做的权衡,涉及功能和安全方面。

1.1 Java Card 安全模型

在Java领域,软件安全的某些方面依赖于字节码验证器(BCV)。BCV保证代码的类型正确性,从而确保了Java在内存访问方面的特性。例如,在Java中无法对引用执行算术运算。因此,在执行任何算术运算之前,必须证明栈顶的两个元素是字节、短整型或整数。由于Java Card不支持动态类加载,字节码验证在加载时进行,即在将转换后的应用包(CAP)文件安装到卡之前。此外,大多数Java Card平台未嵌入卡上BCV,因为这在内存消耗方面代价较高。因此,字节码验证在卡外进行,要么由发卡机构直接完成(如果其控制加载链),要么由可信第三方进行并通过对应用程序签名作为验证证明。

除了BCV强制执行的静态卡外验证外,Java Card防火墙还执行运行时检查以确保应用隔离。防火墙将Java Card平台划分为称为上下文的独立受保护的对象空间。每个包都关联到一个上下文,从而防止某个包的实例访问(读取或写入)其他包的数据,除非它通过可共享接口对象显式暴露功能。

尽管Java Card环境强制实施了所有安全特性,但Java Card安全社区已发现若干攻击路径 [1,2,4–6,10,11,13–15,19,22] 可被利用。

1.2 研究现状:JavaCard 字节码验证器漏洞

BCV 是 Java Card 平台安全性的关键组件。CAP文件中一个看似微不足道的未检查元素,也可能在智能卡中引入严重安全漏洞,如 [11] 所示。

尽管完全测试一段软件是一个复杂的问题,但已有多个尝试从功能和安全角度对Java标准版的BCV进行表征。在[24]中,作者依靠通过代码变异实现的自动测试用例生成,并使用包含BCV的参考虚拟机(VM)实现作为oracle。在[8]中,设计了一个包含BCV的虚拟机的形式化模型,然后采用基于模型的测试来生成测试用例并评估其对模型的符合性。

在Java Card社区中,一些研究致力于提供卡外[16]或卡上[3,9] Java Card BCV的参考实现。这些实现主要基于形式化模型设计,可用于测试甲骨文提供的BCV实现。对于虚拟机,已采用基于模型的测试方法[7,23]来评估Java Card BCV的实现。截至目前,尚未提出完整的Java Card BCV参考实现或模型。

福热龙等人[11]分析了版本2.2.2中甲骨文的BCV实现。在此实现中,作者发现了BCV在执行类型级抽象解释时对分支指令解释存在的一个问题。作者利用此问题在局部变量中执行了类型混淆,且未被甲骨文的BCV检测到。该BCV中的问题已在版本3.0.3及之后被甲骨文修补。

自版本3.0.3以来,尚未有公开报道指出在Java Card BCV中发现安全漏洞或被利用的情况。本文中,我们展示了从版本2.2.2到3.0.5的Java Card BCV中存在的一个新漏洞,并描述了对该漏洞的利用方法。

2 BCV中的一个缺陷

2.1 BCV职责

BCV强制执行多种安全性和一致性检查,以确保每个嵌入式应用程序都限制在其自身的沙箱中。这些验证是在CAP文件上进行的,CAP文件是加载到卡上的类的二进制表示。BCV强制执行两种主要类型的检查:类型正确性检查代码,以及结构验证检查CAP文件。第一种旨在对每个方法代码进行抽象解释,以识别禁止的类型转换。第二种则是分析CAP文件结构,以验证其与Java Card规范[20]的一致性,详细内容将在下一节中介绍。

2.2 CAP文件结构的验证

CAP文件由十二个不同的组件构成,这些组件具有内部和外部依赖,在CAP文件验证期间会进行检查。内部依赖验证旨在根据Java Card规范验证组件的特性。外部依赖检查则验证在不同组件中指定的冗余信息是否相互一致。例如,每个组件都有一个大小字段,该字段必须与目录组件中包含的组件大小数组一致,该数组指定了每个组件的大小。图1中总结了CAP文件中各组件之间所有外部依赖关系的概览,该图引自[12]。

在CAP文件存储的十二个组件中,我们将重点关注以下组件:
- 方法组件以字节集的形式存储包中所有方法的代码;
- 常量池组件包含对方法组件中引用的每个类、方法和字段的条目;
- 类组件描述在包中定义的每个类和接口,以便能够对该类或接口执行操作;
- 描述符组件提供了解析和验证CAP文件所有元素所需的足够信息。该组件是字节码验证的主要入口点。

示意图0

描述符组件是BCV操作的关键,但对于卡的处理而言却几乎没有重要性,因此在小程序加载期间可选择性提供。由于其用途,描述符组件引用了其他组件中的多个元素,甚至提供了与这些组件相关的冗余信息。相反,没有任何组件引用描述符组件。

对BCV在外部依赖检查方面的行为,特别是组件之间冗余信息的分析,使我们发现类组件与描述符组件之间存在缺失的外部依赖检查。我们将在接下来的章节中介绍此BCV漏洞的详情及相应的利用方法。

2.3 BCV中的缺失检查

我们发现的BCV中的缺失检查涉及基于令牌的链接机制。该机制允许下载的软件与已嵌入卡中的API进行链接。因此,包中每个外部可见的项目都会被分配一个公开令牌,可从另一个包中引用。

有三种可以分配公共令牌的项目:类、字段和方法。方法组件中的字节码引用常量池组件中的项目,其中指定了执行字节码操作(例如方法调用所需的类和方法令牌)所需的令牌。

当CAP文件加载到卡上时,令牌会与API进行链接,并从令牌形式解析为虚拟机使用的内部表示。链接过程作用于字节码,并分多个步骤执行:
1. 每个令牌是常量池组件中的一个索引。位于指定索引处的项定义了所需项的公共令牌(例如,方法调用中的类令牌和方法令牌);
2. 令牌被解析为JCVM内部表示。对于方法调用,类令牌标识类组件中的一个class info元素;
3. 在class info元素中,公共虚拟方法表数组存储了方法的内部表示。方法令牌是公共虚拟方法表数组的索引;
4. 位于方法令牌索引处的公共虚拟方法表中的元素是方法组件中指向待执行方法头部及其字节码的绝对偏移量。

示意图1

方法组件中指向要执行的方法头和字节码的绝对偏移量在CAP文件中属于冗余信息,因为它同时存储在类组件的公共虚拟方法表元素和描述符组件的方法描述符信息元素中。描述符组件中的偏移量信息仅由BCV在加载前使用,而类组件中的偏移量信息则仅由卡上的JCVM链接器使用。因此,类组件中任何格式错误的偏移量信息都会在BCV检查中未被检测到,但仍会被卡上的JCVM链接器所使用。

2.4 利用BCV漏洞

到目前为止,我们发现的BCV漏洞允许在保持与BCV检查一致的同时,操纵CAP文件中类组件的方法偏移信息。该漏洞的利用方式是删除public虚拟方法表中class info元素的一个条目。在JCVM链接过程中,对应方法偏移的解析会导致类组件发生溢出,如图3所示。此溢出使得JCVM将卡上类组件之后的内存区域内容解释为方法索引。

示意图2

CAP组件的加载顺序由Java Card规范定义。该顺序规定方法组件在类组件之后立即加载。因此,方法组件很可能被存储在卡内存中紧邻类组件的位置。这样一来,类组件溢出很可能会影响到方法组件。在这种情况下,

示意图3

3 从字节码验证小程序进行代码注入

在上一节中,我们展示了在存在有利的内存映射的情况下,攻击者如何利用 BCV 漏洞在 BCV 验证的小程序中指定任意方法偏移。在本节中,我们将介绍在实际产品上对该漏洞的利用,该利用使我们能够从 BCV 验证的小程序向通信缓冲区注入并执行本地代码。

在接下来的章节中总结了在 Java 平台实现执行任意本地代码所需的攻击步骤。首先,我们利用第 2 节中介绍的 BCV 漏洞,在方法组件中伪造一个任意方法头。然后,该任意方法头被用来滥用平台的本地方法执行机制,从而在本地方法表中引发缓冲区溢出。最后,该缓冲区溢出使得通信缓冲区地址可被作为本地函数解引用。因此,通过通信通道发送到我们已验证小程序的数据将在 JCVM 上作为本地代码执行。

这一完整攻击是一个概念验证,旨在证明在 Oracle BCV 中发现的漏洞可能危及 Java Card 智能卡的安全。

3.1 虚拟机中的原生执行

我们在嵌入了 ARM 微控制器的开源 Java Card 平台上验证了 BCV 漏洞的利用。该 Java Card 平台是在安全评估的背景下提供的,因此 VM 的代码和内存映射均可用。

该平台的运行时环境提供了一种机制,出于性能原因,允许将执行切换到 Java 卡 API 方法的本地实现。该机制的实现类似于传统 Java 虚拟机中提供的 Java 本地接口(JNI)机制[18]。

在 JNI 方法中,本地方法通过方法头中的专用标志(ACC NATIVE)进行识别。根据 JCVM 规范,本地方法头标志仅对位于卡掩码中的方法有效。因此,在 CAP 文件中加载的本地方法不符合 Java Card 规范,会被离卡验证器拒绝。

JNI 中的本地方法解析依赖于接口指针。接口指针是一个指向指针的指针。该指针引用一个指针数组,其中每个元素都指向一个接口函数。每个接口函数在数组中存储于预定义偏移量处。图 5 说明了接口指针的组织结构。在本地方法体中提供了用于查找本地函数指针的数组内的偏移量。

示意图4

3.2 从小程序验证中实现原生执行

如第 2 节所述,BCV 中缺少检查可能导致溢出,从而使虚拟机在类组件之外解析方法偏移量。在我们用于利用攻击的虚拟机实现中,类组件溢出会落入方法组件,因此方法偏移量的值可以指定为方法组件中字节码的数值。

根据 JCVM 规范[20],方法的偏移量必须指向方法组件中的方法头结构,其后是该方法的字节码。在利用 BCV 漏洞时,开发人员可以控制该偏移量,使其指向方法组件的任意部分。这可用于使方法偏移量指向一段可被解释为方法头的字节码区域。为此目的,可使用 iipush 字节码,因为其操作数是一个 4 字节常量,而 BCV 不会解释该常量。因此,此 4 字节常量可用于编码包含 ACC NATIVE 标志和本地方法索引的方法头。该 iipush 字节码能够通过 BCV 验证,因为它构成了一条有效的字节码序列,但当该操作数被解释为本地方法头(通过类组件溢出)时,控制流将切换到本地执行。图 6 展示了从类组件溢出到 JNI 方法本地执行的攻击路径。

因此,通过为方法偏移指定适当的值,我们能够执行虚拟机在 JNI 本地函数指针数组中提供的任何本地方法。

示意图5

3.3 滥用原生执行机制进行代码注入

到目前为止,该攻击允许调用平台提供的、存储在 JNI 原生函数指针数组(或原生数组)中的 JNI 原生方法。当发生原生方法调用时,从 Java 运行时环境切换到本地执行环境需要使用原生数组中的索引以确定本地函数指针。通过对目标 JCVM 的实验,我们发现通过在原生方法体中指定相应的索引,可以实现对原生数组的溢出。因此,可将紧邻原生数组存储的任意内存内容作为本地函数指针加以利用。

对产品的内存映射分析表明,原生数组旁边的内存区域包含一个指向用于主机控制器协议(HCP)通信的通信缓冲区的指针。HCP 协议负责单线协议(SWP)协议的传输层,参与与智能卡的近场通信(NFC)。HCP 消息封装了通过 SWP 传送给智能卡的 ISO7816 应用协议数据单元(APDU)。

利用原生数组上的溢出,我们可以将 HCP 通信缓冲区指针用作本地函数指针。执行此本地函数指针将导致把 HCP 通信缓冲区的内容作为本地汇编函数来执行。

HCP 协议具有若干特性,这些特性限制了将 HCP 通信缓冲区用作原生有效载荷注入占位符的使用:
1. HCP 包以 HCP 消息头和 HCP 包头作为前缀。这些头被解释为原生汇编操作码。
2. HCP 强制执行消息分片,将包大小限制为 27 字节。因此,整个原生有效载荷必须包含在 27 字节内。

为了获得更多的空间来注入我们的攻击载荷,我们将一个最小有效载荷注入 HCP 通信缓冲区,其唯一目的是将执行流重定向到 ISO7816 APDU 缓冲区。该最小重定向有效载荷如表 1 所示。由于 HCP 通信缓冲区指针被用作函数指针,整个 HCP 缓冲区都被解释为本地代码,包括包头、消息头以及封装的 APDU 头部。这些头部字节不会产生副作用,如表 1 所示,从而使得重定向有效载荷能够正确执行。

表1. HCP缓冲区中的原生有效载荷,用于将执行流重定向到APDU缓冲区。相关有效载荷数据已置灰。

ISO7816 协议放宽了碎片化约束,从而为完整的原生有效载荷注入提供了足够的空间。我们在表 2 中展示了一个注入到 APDU 缓冲区的完整有效载荷,该载荷会跳转到底层读/写操作系统功能。由于执行的起始地址选自 HCP 消息缓冲区的有效载荷,因此跳过了头部字节,本地执行从 push 指令开始(表 2,第 3 行)。

该载荷将源参数初始化为载荷的前 4 个字节(表 2,第 2 行),使得读取地址可以直接在 APDU 中选择。然后,它将目标地址(读取的字节被复制到的位置)初始化为载荷之后 APDU 缓冲区的地址,使得读取的字节可立即通过 APDU 返回。

表2. ISO7816 APDU缓冲区中的原生有效载荷,用于调用操作系统函数读取任意内存区域,并将结果复制到APDU缓冲区。相关有效载荷数据已置灰。

缓冲区。最后,它跳转到执行读取操作的底层操作系统功能。结果,通过此原生有效载荷可以访问卡的任何物理地址。

图 7 显示了从本地数组溢出到 HCP 消息缓冲区中的重定向载荷,再到 APDU 缓冲区中最终攻击载荷的执行流。我们能够完整地转储卡片内存,并使用商业逆向工具对其进行逆向分析。逆向得到的代码被确认为嵌入式 JCVM 的代码。

4 其他实验结果

为了评估 BCV 漏洞对更广泛范围的虚拟机实现的影响,我们在来自不同制造商的多种智能卡上测试了每种卡对格式错误小程序安装的支持程度。我们评估了来自三个不同制造商(a、b 和 c)的七张卡。每张卡的名称均关联其制造商型号及其 Java Card 规范[20]。所评估的 Java Card 智能卡列表见表 3。

所有被评估的卡均未实现嵌入式 BCV。在每张卡上,安装一个格式错误的小程序,如果安装成功,则执行该小程序。该格式错误的小程序在公共虚拟方法表中有一个被取消引用的方法。表 4 总结了各张卡的反应。

如表 4 所示,卡对格式错误的 CAP 文件安装和执行反应不同。带有符号(✓)的卡在安装期间检测到格式错误的 CAP 文件并拒绝安装。而在标有符号(✗)的其他卡上,安装和执行成功。

表3. 本次评估中使用的卡。
表4. 各评估卡的状态。

成功执行会导致意外的卡片响应或卡片静默。意外的卡片响应表明发生了意外的代码执行。卡片静默可能是由于无限循环或卡对非法代码的反应所致,这也表明发生了意外的代码执行。

这些行为证明了 JCVM 的控制流已被修改。因此我们可以得出结论,本文所介绍的 BCV 漏洞可以在多种不同的 Java Card 智能卡上被利用。

导致任意本地代码执行的完整攻击路径需要有关内存映射和 JCVM 实现的信息,而这些信息在本次测试中无法获得。因此,我们未尝试复现完整的攻击路径。

5 结论、对策与未来工作

本文展示了如何利用甲骨文的 BCV 实现中缺失检查的问题在 Java Card 上进行攻击。我们通过在 JCVM 实现上的概念验证利用,证明了该 BCV 问题对智能卡安全具有严重的影响。我们成功地在通信缓冲区中注入并执行了本地代码,最终获得了对整个卡片内存的完全读写操作系统权限。最后,我们对来自不同制造商的多种卡进行了评估,发现大多数 JCVM 实现均未对 BCV 问题的利用提供防护。

在我们向甲骨文披露 BCV 问题后,我们被允许发表本文,并且甲骨文发布了新的 BCV 版本¹。这个新版本的 BCV 能够检测类组件不一致,从而缓解我们的攻击。包含强制性字节码验证步骤的加载过程,使用最新的甲骨文 BCV,可有效防御本文提出的攻击。

随着在 Oracle 的 BCV 实现中发现了一个新的缺陷,人们认识到必须对 BCV 进行完全验证,以降低新漏洞暴露的风险。为实现这一目标,应努力明确 BCV 必须遵守的安全和功能要求,以保护 JCVM 实现免受软件攻击。

¹ Java Card SDK 3.0.5u1 中包含的 BCV 可防止此次引入的攻击。该版本发布于 2015 年 8 月 19 日。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值