Java Card平台安全容器的逻辑攻击
摘要
Java Card平台为程序员提供了作为加密密钥和PIN码容器的API类。本文首次对来自不同制造商的多张智能卡上的这些容器在抵御逻辑攻击方面的安全性进行了系统性评估。
我们调查的大多数智能卡似乎都没有为这些容器实施任何完整性与机密性保护。对于那些已实施保护措施的智能卡,本文提出了可绕过这些安全措施的新逻辑攻击。特别是,我们发现平台对密钥和个人识别码的加密可以通过该平台自身提供的解密功能被攻破,从而使得逻辑攻击仍能获取明文密钥和PIN。
我们还研究了利用类型混淆访问全局APDU缓冲区的可能性以及是否存在未记录的字节码指令。
1 引言
与任何智能卡一样,Java卡智能卡可能遭受利用软件漏洞的逻辑攻击,或遭受诸如功耗分析、毛刺攻击或其他形式故障注入的侧信道攻击[1,12,15]。除了可能存在的敌对环境带来的常规安全风险外,卡本身还可能存在恶意Java Card代码的风险。由于资源限制,许多Java卡没有配备卡上字节码验证器,这使得畸形代码可能被安装。此类代码可能会破坏Java(卡)平台的安全保证,尤其是内存安全。即使在配备字节码验证器的卡上,格式正确的字节码仍可能因平台实现中的漏洞(例如事务机制[11])或通过故障注入[2]而表现出非预期行为。
一些智能卡会执行运行时检查以防御格式错误的代码。原则上,进行大量运行时检查——实际上就是执行完整的运行时类型检查——可以防止所有格式错误的代码执行,但其开销过高,难以接受。
Java卡应用程序接口提供了多个用于安全敏感数据的类,特别是用于存储加密密钥的Key子类以及OwnerPIN用于PIN对象的类。该平台可以——甚至可能应该——针对逻辑和/或物理攻击对这些对象实施安全措施,以保护密钥和PIN的机密性与完整性,以及PIN尝试计数器的完整性。实际上,应用程序可能会故意将数据存储在Key对象中,即使该数据并非密钥,只是为了利用平台层面对这些容器提供的任何保护[16]。
本文研究了各种智能卡上针对这些安全容器所提供的安全措施,以防御逻辑攻击。
我们分析的大多数智能卡似乎并未提供任何此类措施。在那些确实保护密钥和PIN的智能卡上(特别是通过加密方式),我们提出了新的攻击方法,可破坏这种保护。
第2节介绍关于Java Card逻辑攻击的相关工作。第3节展示了我们对密钥和PIN安全容器在面对使用恶意代码的逻辑攻击时的安全性分析。同时介绍了一种利用类型混淆攻击全局APDU缓冲区的新方法,以及对未文档化操作码的研究结果。第4节总结了对来自不同制造商的五款Java卡的评估。有关攻击的更多细节可参见第一作者的硕士学位论文[17]。最后,第5节讨论了某些智能卡上可能已实施的防御措施,并提出了改进建议。
2 背景与相关工作
在逻辑攻击中,向Java Card小程序发送恶意输入,以利用小程序或底层平台实现中的缺陷。逻辑攻击也可通过恶意代码而非恶意输入实现:安装恶意的、类型错误的代码可能会破坏Java Card平台提供的安全保证,尤其是类型安全和内存安全;这样一来,恶意(或存在缺陷)的小程序就可能危及其他小程序或平台本身数据的完整性或机密性,甚至可能破坏其代码的完整性。
自从第一篇讨论Java Card平台上的逻辑攻击的论文[18],以及第一篇关于各种智能卡所采用的逻辑攻击和防御措施的系统性综述[11],发表以来,现在已有大量关于Java卡上逻辑攻击的文献,例如[1,6,7,9]。我们在此不讨论所有这些文献,而是重点关注那些介绍了我们在攻击中同样使用的技巧的相关工作。
最简单的逻辑攻击仅依赖于类型错误的代码,并利用由此产生的类型混淆来非法访问内存。字节码验证器是可选的(除了Java Card 3连接版之外),在没有字节码验证器的智能卡上,原则上可以安装类型错误的代码。然而,这实际上对攻击者和智能卡做出了非常强的假设:许多智能卡确实包含字节码验证器,即使某张智能卡没有,通过全球平台使用数字签名也会严格控制小程序的安装。如果类型错误的代码能够被安装,就可能破坏平台在类型安全和内存安全方面的安全保证。请注意,防火墙的运行时机制也是如此
可能会被类型错误的代码完全绕过。通过防御性平台实现增加运行时检查可以缓解此问题,一些智能卡确实采取了此类措施,这一点已通过智能卡实验得到证实[11]。
一种更为隐蔽的类型错误代码变体是使用不兼容的可共享接口 [18];这涉及一对小程序,每个小程序单独来看都是类型正确的。另一种造成类型混淆的方法是利用平台实现中的缺陷,例如事务机制 [11], 实现中的缺陷或卡上字节码验证器 [10] 实现中的缺陷。
一旦实现非法内存访问,修改对象的元数据就是扩大攻击影响的标准技巧。
经典示例是更改数组的长度字段,该字段作为元数据记录在数组对象的表示中,从而获得超出允许范围的数据访问权限。其他技巧包括使用指针运算来破坏或伪造引用,以及使用getstatic b指令。最后一条指令允许恶意代码访问任意内存位置[8],,因为它绕过了Java Card防火墙的运行时检查。内存攻击的最终目标是获取完整的内存转储[4,7]。
一种更高级的扩大非法内存访问影响的方法是修改其他小程序的代码。井口‐卡蒂尼和拉内提出了一种特洛伊小程序,该程序扫描内存并替换其他小程序代码中的指令,以修改其行为[9]。为了绕过字节码验证,作者使用了一个字节数组,该数组包含带有恶意代码的方法的方法体的正确字节级表示。由于该字节数组是数据而非代码,其内容不会被字节码验证器检查。然而,通过获取该数组的地址并修改CAP文件,链接器可以被引导将方法调用解析为执行该数据作为代码。
布法尔和拉内提出了一种在卡上执行“shellcode”并访问卡的只读存储器(ROM)内存的方法[4]。他们还提供了一种工具,用于识别内存转储中的原生代码和Java卡字节码。
除了攻击堆内存外,攻击还可能试图破坏栈。福热隆提出了一种针对Java Card 虚拟机的栈下溢攻击 [6]。该攻击使用指令 dup x,该指令会从栈顶复制最多四个字。如果该指令的实现未检查栈下溢情况(某些智能卡正是这种情况),则该指令可能读取当前栈帧之外的内容,从而可以访问位于栈上其他方法的数据。
文献中唯一一篇研究安全容器的论文来自法尔哈迪和拉内 [5]。该论文提出了一种攻击方法,可访问属于另一个小程序的3DES密钥的明文值。作者使用了一种新颖的方法来利用类型混淆,借助应用程序接口方法 arrayCopyNonAtomic实现攻击。作者在论文中提出了若干防御措施,特别是建议使用未存储在非易失性内存中的secretkey对密钥容器进行加密。作者指出,某些智能卡实际上确实采用了这种保护机制。这一点正如本文所证实的那样,但我们同时表明,此类保护机制仍有可能被绕过。
3 对安全容器的逻辑攻击
本节介绍了针对Java Card平台为加密密钥和PIN对象提供的安全容器的新逻辑攻击。这些攻击的目标是使一个(恶意)小程序非法访问另一个小程序的此类安全容器内容,最终目的是获取明文密钥和个人识别码,或重置PIN 重试计数器,从而实现对个人识别码的暴力猜测。
这些攻击假设攻击者能够在卡上安装自己的小程序。利用第2节中讨论的技术,攻击者随后可能试图非法访问内存,但此处攻击者可能会被运行时防御措施阻止。对于安全容器,攻击者可能会遇到旨在保护密钥和PIN的机密性、PIN尝试计数器完整性,以及可能还包括密钥和PIN完整性的防御措施。
下面描述的攻击要求攻击者获取有关安全容器实现及其在内存中表示方式的知识。为此,攻击者可以在卡上安装一个小程序,使用他想要研究的容器,然后采用第2节中描述的技术访问原始内存,从而揭示这些容器的内部内存表示。随后,攻击者可以对容器的内存表示进行逆向工程,并尝试在属于其他小程序的内存中定位此类对象。
当然,如果平台未提供任何防御措施来保护PIN和个人识别码的机密性——例如以明文形式存储——或未保护重试计数器的完整性,则显然此类攻击是可能的。事实上,许多智能卡以明文格式存储PIN和密钥,且未加密,同时重试计数器也未采用任何冗余或完整性检查。
第3.1–3.3节中介绍的攻击绕过了我们在智能卡中发现的防御措施,即对重试计数器进行简单的完整性检查,以及使用未知的平台密钥对密钥和PIN进行加密。
第3.4节介绍了针对APDU缓冲区的攻击,该攻击允许攻击者存储并重用对全局数组的引用;第3.5节讨论了对某些虚拟机实际支持的未指定操作码的调查。
第4节给出了针对不同智能卡的所有攻击的概述。
3.1 更改PIN尝试计数器
API 类 OwnerPIN 为小程序开发者提供了 PIN 对象的标准实现,用于存储 PIN 码,并提供所有相关功能,包括管理猜测 PIN 的剩余尝试次数,即所谓的尝试计数器。
在平台上提供这种PIN码的实现不仅方便,而且避免了不安全的临时实现所带来的风险。此外,应用程序接口的实现可以考虑到平台级别的弱点或对策,包括针对物理攻击的对策。事实上,API规范指出,OwnerPIN类的实现应能抵御某些攻击,包括试图滥用事务机制的攻击。
在我们评估的五张卡中,只有一张卡,即卡a,可以确认其对OwnerPIN尝试计数器实施了完整性检查。然而,如下所述,该完整性检查可以被逆向工程并绕过。
攻击的实现. 我们的攻击使用了一个恶意小程序,通过操纵数组对象的元数据来访问任意内存位置,如[11]中所述。然后我们可以观察到OwnerPIN对象的原始内存表示,如图1所示。
显然,该数据结构不包含以明文形式存储的PIN。由于Java Card平台未对此作出规定,因此很难确定PIN实际存储的位置。然而,可以很容易地在数据结构中看到四个字节,用于记录最大尝试次数和剩余尝试次数。
该卡对这些计数器使用了冗余表示,每个计数器使用两个字节以实现完整性检查。第二个字节存储计数器本身的值,第一个字节存储其位级补码,因此对这两个字节进行异或操作始终得到0xFF。
这种技术被广泛用于防止故障注入。对于PIN对象中使用的这些计数器值而言,将内存单元更改为随机值的故障显然是危险的,因为将其设置为随机值可能导致尝试次数超过预期。事实上,我们发现,如果更改了这些字节中的任何一个并破坏了完整性检查,卡将永久停止响应。
这些完整性检查对故障攻击非常有效,但对我们所考虑的逻辑攻击无效:一旦攻击者了解了这些检查的工作原理,就很容易通过逻辑攻击来破坏计数器的值。事实上,通过修改我们的恶意小程序以将尝试计数器重置为最大值,我们就可以轻易地暴力破解PIN码,即反复猜测PIN码,然后调用我们的恶意小程序重置尝试计数器。创建一个带有OwnerPIN对象且提供输入PIN码接口的小程序,四位PIN码可在15分钟内被暴力破解。
3.2 检索明文DES密钥
Java Card API 还提供了用于存储和使用加密密钥的类,例如用于 DES 密钥的 DESKey 类。该类的方法包括 setKey(byte[]keyData, short kOff) 和 getKey(byte[] keyData, short kOff),用于设置和获取密钥的值。在设置密钥时,可以提供以加密形式的密钥1。请注意,提供以加密形式的密钥并不能说明密钥是否以加密形式存储:API 实现是否选择以加密形式存储密钥并未指定,实际上 API 的使用者也无法得知。
方法 getKey 始终返回明文密钥。从安全角度来看,支持此操作本身就可被批评[16],,事实上,我们在攻击中就利用了这一点。
使用恶意小程序,可以检查原始内存,以查看密钥是否以明文形式存储。在我们评估的智能卡中,只有一张卡carda将密钥以加密形式存储。然而,我们可以设计一种攻击,使恶意小程序能够获取另一个小程序的明文DES密钥,如下所述。
攻击的实现。 为了执行该攻击,恶意小程序从另一个小程序的内存中读取加密的DES密钥的原始字节表示,并将其复制到自身的一个DES密钥对象中。一旦加密密钥被复制,攻击者就可以简单地调用DESKey.getKey()方法来获取明文。
用于创建密钥对象的工厂方法KeyBuilder.buildKey为此提供了支持,适用于那些实现 javacardx.crypto.KeyEncryption接口的Key子类。
一种——相对简单——的防御此攻击的措施是对不同小程序用于密钥加密的密钥进行多样化处理。一个简单且低成本的多样化操作,例如将主密钥与小程序的应用标识符(AID)进行异或操作,就足以使该攻击无法实施。我们已确认同类型的智能卡使用不同的加密密钥,因此至少该平台实现并未在所有卡上硬编码相同的密钥。
如 [5],所述,理想情况下,加密密钥应存储在攻击者难以或无法访问的内存中(例如只读存储器(ROM)或密码协处理器的内存)。我们并未对卡进行逆向工程以确定该密钥确实未存储在任何非易失性内存中,但通过上述攻击,我们无论如何都不需要知道此 Key。
3.3 检索明文个人识别码
如第3.1节所述,通过非法内存访问,我们可以修改尝试计数器,然后通过暴力猜测找出PIN,即使PIN看似是加密存储的。然而,这种攻击速度相当慢。
类OwnerPIN未提供类似getPIN()的方法,该方法类似于DESKey类中的 getKey(),用于检索明文PIN。从安全角度来看,提供此类方法显然是一个糟糕的主意,而从功能角度来看,也没有合理的理由需要它。如果存在这样的getPIN()方法,我们可能会像在第3.2节中解除DES密钥加密那样,通过将PIN的原始加密字节表示复制到另一个PIN对象并调用getPIN()来解除PIN的加密。
然而,由于存储在卡上的PIN和密钥对象的内容都是加密的,因此有可能两种情况下使用了相同的算法。事实上,很容易注意到8字节长DES密钥和8字节长PIN都以10字节的形式存储,这表明情况可能确实如此。
这表明了一种攻击方式,即利用getKey()操作从DESKey类中获取明文 PIN:一个恶意小程序将其所属的另一个小程序的PIN加密表示复制到自己的 DES密钥对象中,然后调用getKey()来获取明文。图3描述了这种攻击。
我们发现这种攻击是有效的。显然,相同的加密密钥不仅用于所有密钥,还用于所有个人识别码(PINs)和所有小程序。这使得攻击者能够使用该密钥对任意值进行加密和解密,而无需实际知晓该密钥。同样,采用密钥分散即可阻止此类攻击:只需使用与加密密钥不同的密钥来加密PIN即可。
3.4 对APDU缓冲区数组引用的非法访问
作为防火墙功能的一部分,Java Card运行环境限制存储对所谓全局数组的引用,因为它们可能在小程序之间共享。APDU缓冲区就是一个这样一个全局数组,且运行环境规范指出:
“所有全局数组都是临时全局数组对象。这些对象由JCRE上下文拥有,但可以从任何上下文中访问。然而,不允许将这些对象的引用存储在类变量、实例变量或数组组件中。JCRE会检测并限制尝试存储这些对象引用的行为,这是防火墙功能的一部分,旨在防止未经授权的重复使用。”
[14, Sect.6.2.2]
这意味着对全局数组的引用不能存储在堆上的持久内存中(类和实例变量存储在此处)。这类引用只能存储在局部变量中,即存储在临时内存的栈上。(此外,为了避免小程序之间的数据泄漏,JCRE 和 API 规范规定,在选择小程序时必须将 APDU 缓冲区清零 [14, Sect.6.2.2]。)
然而,如果攻击者能够(非法)将一个引用转换为短整型值,然后再转换回引用,那么通过存储该短整型值,他就可以绕过对全局数组引用的存储限制。我们发现,在我们分析的所有智能卡上,这种攻击都是可行的。这实际上破坏了上述引用的要求。
这种对APDU缓冲区的非法访问以及由此引发的攻击被Barbu等人[2]进行了广泛讨论。
攻击的实现. 为了绕过对存储全局对象引用的限制,开发了一个畸形小程序。该小程序的核心功能由以下函数给出:
public static 短整型 addr( byte[] ptr) {
return (短整型)ptr; } public static byte[] ptr( 短整型 addr) {return (byte[]) addr; }
上面列表中的代码明显类型错误,因此会被任何编译器拒绝,但可以通过手动创建此代码的CAP文件,并且如果卡上没有卡上字节码验证器,则可以将该CAP文件安装到卡上。
代码安装后,addr() 方法可将对任意数组(包括全局数组)的引用转换为 短整型,且平台不会阻止将该短整型存储在类或实例变量中。在之后的任何时间点,均可使用 ptr() 方法将存储的短整型重新转换回引用,因此这在功能上等同于将对全局数组的引用存储在类或实例变量中。下文的代码示例对此进行了更详细的说明。
首先,以下代码说明了防火墙将不允许将对全局数组(即APDU缓冲区)的引用存储在类变量buffClassCopy中,但允许将其存储在局部变量buffLocalCopy中:
public class App extends 小程序 {
byte[] instance;
...
public void process(APDU apdu) {
byte[] local;
byte[] buffer = apdu.getBuffer();
local = buffer; // 防火墙允许
instance = buffer; // 防火墙禁止
}
}
然而,通过使用操作 addr() 和 ptr(),我们可以在不违反防火墙规则的情况下获得等效的功能:
public class App extends 小程序 {
短整型 instanceShort;
...
public void process(APDU apdu) {
byte[] buffer = apdu.getBuffer();
instanceShort = addr(buffer); // 防火墙允许
}
}
在任何我们希望将 instanceShort 用作字节数组引用的方法中,都可以声明一个局部字节数组变量,并使用 ptr() 将短整型转换回引用:
byte[] localVar;
localVar = ptr(实例短整型);//允许
...// 现在可以使用localVar,且指向全局数组
值得一提的是,我们观察到一些非平凡的防御措施来卡上,以防止对全局数组的引用被存储在类或实例变量中。如果我们尝试将ptr(实例短整型) 存储到类型为byte[],的实例变量中,卡会终止该会话。似乎存在一个运行时检查机制,当任何类或实例变量被设置为指向APDU缓冲区时,卡就会终止会话。
显然,这样的检查会消耗资源,并且对于此处介绍的攻击完全无效:将引用存储为类变量中的短整型与将其存储为引用效果相同。
3.5 非法操作码的执行
根据Java Card虚拟机规范,[13]正确的Java字节码必须仅包含来自0x00到 0xB8、0xFE和0xFF的指令字节。第一个操作码范围保留用于标准字节码操作。最后两个操作码保留由虚拟机实现在内部使用,用于任何特定于实现的功能。
在智能卡上执行非法代码发现,所研究的其中一张卡(即卡a)支持超出上述范围的操作码,我们将这些操作码称为“非法操作码”,用于Java卡虚拟机未指定的操作。
与已知且已指定的操作码类似,非法操作码后面也可能跟随零个或多个字节参数(即在CAP文件中),并且它们可以从操作数栈顶部弹出参数。对于合法操作码,代码和栈参数的数量在规范中有明确定义。确定每个非法操作码所需的参数数量是理解其用途的第一步挑战。我们通过执行一个非法操作码,后跟若干条将常量压入操作数栈的指令,并分析执行后的栈状态来应对这一挑战。
可以通过从栈中弹出剩余的字并保存到局部变量中,来分析非法操作码执行后的栈状态。然而,仅仅知道非法操作码使用的参数数量并不能为理解其用途提供太多线索。
幸运的是,卡的制造商提供了一个仿真器,该仿真器也支持非法操作码的执行。通过比较仿真器在执行非法字节码和合法字节码时的执行轨迹,可以理解这些操作码的作用,因为某些非法操作码产生的执行轨迹与合法操作码完全相同或非常相似。表1列出了与部分非法指令对应的合法代码。这表明这些额外的操作码只是便捷的缩写,可能是为了生成更紧凑的字节码。使用未使用的操作码作为常见模式的缩写这一想法已在 [3]中讨论过。
对非法操作码的研究似乎很有前景,但事实证明这非常耗时,并未导致成功的攻击。然而,单个字节码指令可能非常危险,正如 getstatic b 赋予恶意代码的能力所示,因此任何额外的操作码在任何安全评估中都值得密切关注
| 非法操作码 | 等效指令 |
|---|---|
| 0xBD | sload 4 |
| 0xBE | sload 5 |
| 0xBF | sload 6 |
| 0xC0 | sload 7 |
注意。仿真器的使用结果表明,它是攻击者手中一个极佳的工具,有助于理解 Java卡 实现的内部设计。
4 评估
我们对来自不同制造商的五张不同的智能卡进行了攻击测试。表2列出了这些智能卡提供的Java卡和全球平台的版本号。攻击结果总结在表3中。其中,✓表示攻击成功,✗表示攻击失败。因此,一张智能卡的✗越少,其安全性就越高。
我们在攻击中使用的技术,除了第3节讨论的技术外,均为第2节所述的已知技术,即
– 修改元数据(特别是数组的元数据),
– 通过指针运算“伪造”引用,
– 使用getstatic b,
– 使用“非法”操作码,作为对[9]的攻击的一种变体,
以及 – 将任意对象当作数组进行访问(实质上是对arrayCopyNonAtomic在[5]中所用攻击方法的泛化)。
对于卡a,这是我们发现的唯一一个具有防御措施来保护安全容器的卡,我们使用第3节中描述的技术来绕过这些防御措施。
在卡b和卡e上未实施任何防御措施,所有数据和元数据均以明文形式存储。因此,无需使用第3节中描述的更复杂攻击,即可轻松恢复密钥和个人识别码。
大多数攻击在卡c和卡d上均未成功,因为无法对其他小程序的内存进行未经授权访问。尽管可以安装和运行恶意代码,但无法实现类型混淆,这可能是由于这些卡执行了全面的防御性运行时类型检查所致。
APDU缓冲区攻击在卡b上失败,因为该卡拒绝安装包含库包的CAP文件,而我们在此次攻击中使用了库包。显然,APDU缓冲区引用攻击在最多数量的智能卡上成功奏效。这印证了[2]中的观察:小程序开发者应意识到APDU缓冲区可能被其他小程序攻击的风险。
| Card | GlobalPlatform | Java卡 |
|---|---|---|
| 卡 a | GP 2.1.1 | JC 2.1.2 |
| 卡 b | GP 2.1.1 | JC 2.1.1 |
| 卡 c | GP 2.2.1 | JC 2.2.1 |
| 卡 d | GP 2.1.1 | JC 3.0.4 |
| 卡 e | GP 2.1.1 | JC 3.0.1 |
| 攻击 | 卡 a | 卡 b | 卡 c | 卡 d | 卡 e |
|---|---|---|---|---|---|
| 更改PIN尝试计数器 | ✓ | ✓ | ✗ | ✗ | ✓ |
| 检索明文PIN | ✓ | ✓ | ✗ | ✗ | ✓ |
| 检索明文DES Key | ✓ | ✓ | ✗ | ✗ | ✓ |
| APDU缓冲区数组引用 | ✓ | ✗ | ✓ | ✓ | ✓ |
5 防护措施
抵御带有恶意代码的逻辑攻击,最明显的对策当然是配备卡上字节码验证器。
请注意,即使在具有卡上字节码验证器的卡上,平台中的漏洞仍可能使攻击者能够绕过其提供的保护(如[10,11]所示)。
除了字节码验证器之外,如果卡的硬件提供了用于控制内存区域访问的内存管理单元,则可以将其用作抵御逻辑攻击的额外防御层。
运行时检查可使逻辑攻击更难实施。在运行时进行完整类型检查将阻止任何类型错误的代码,但这成本过高,难以实现。实际上,智能卡会在运行时执行一些检查。在我们研究的两张卡上,卡c和卡d,由于平台实施的防御性运行时检查,我们所有试图通过恶意代码访问安全容器的尝试均告失败。在试验攻击过程中,智能卡有时会因完整性检查被破坏而进入静音状态。在全部实验过程中,共有24张卡永久性静音。当然,具体执行了哪些完整性检查是未知的,只能通过反复试验来推断:Java Card规范并未规定此类防御措施,这完全取决于具体实现。第2节提到的许多论文对所遇到的防御措施进行了推测。
我们发现,使用arrayCopyNonAtomic的攻击在我们拥有的任何智能卡上都不起作用,因此这似乎是[5]中研究的特定卡的一种实现相关的弱点。
在对格式错误代码的能力具有重大影响的平台实现选择中,一个关键因素是引用的表示方式。一些实现直接使用指针(即内存地址)来表示引用。然而,大多数现代智能卡采用通过间接表进行间接引用的方式。在这种方式中,引用是某个表中的索引,该表存储了真正的指针值(即数据所在的实际内存地址)。
使用间接表使得破坏或伪造引用的攻击更难实施:它可防止攻击者对引用执行算术运算以访问内存中的任意位置。一些实现间接引用的制造商将数组元数据与数据本身相邻存储,而不是将其存储在引用表中。由于数组元数据具有固定大小,因此似乎并不困难将其存入表中。若将元数据存储在引用表中,则攻击者将更难篡改这些数据。例如,超出数组边界修改内存时,不会影响到内存中相邻数组对象的元数据。
根据Java卡虚拟机规范,虚拟机的内存地址为16位。这意味着使用Java卡指令的小程序只能寻址64千字节的非易失性内存。如今许多Java卡的非易失性内存大于64千字节。这意味着存在无法寻址的内存,除非恶意小程序能够执行本机代码,否则无法访问这些内存。尽管已经证明在某些Java卡上执行本机代码是可能的[4],但这并非易事。显然,将包含对象元数据的引用表存储在虚拟机上运行的(非本机)代码无法寻址的内存中,将是增加逻辑攻击难度的一种良好对策。
我们能够检索到卡a上安全容器中数据的明文,其原因在于所有容器(包括密钥和PIN)以及所有小程序都使用了相同的加密密钥。通过采用密钥分散机制可以避免此漏洞,例如为每个小程序甚至每个对象使用不同的密钥。此类密钥分散方案有多种选择。例如,可以利用小程序的应用标识符(AID)对主加密密钥进行分散,从而获得用于保护该小程序对象的密钥。当然,这种方案假设攻击者无法篡改其攻击小程序的AID。更强大的解决方案是使用对象的内存地址作为参数来分散密钥,以获得保护特定对象的唯一密钥。然而,这一措施将使对象的重新分配变得复杂;因此,如果希望平台在垃圾回收过程中支持内存压缩,则该方案不是一个理想的选择。
保护重试计数器的完整性免受逻辑攻击似乎比保护密钥或PIN的机密性更困难。无论实施何种完整性措施(例如使用加密PIN的同一密钥进行基于密钥的消息认证码的密码学完整性检查),能够访问内存的攻击者都可能简单地恢复重试计数器的早期值以及所有正确的完整性措施。当然,可以使得完整性检查更难被逆向工程,同时将涉及的数据分散开来表示这些计数器以及内存中的任何相关完整性检查都会使攻击者的生活变得更加困难。
全局数组引用攻击在大多数智能卡上可能实现的原因是,卡上的操作数栈是无类型的,且在没有运行时类型检查的情况下很难阻止此类攻击。一种可能的解决方案是使用有类型操作数栈(但这会带来较大的性能开销),或为引用和其他数据类型分别设置独立的栈。
正如Barbu等人所指出的[2],他们广泛讨论了涉及非法访问APDU缓冲区的攻击,在此情况下,小程序开发者应考虑到APDU缓冲区可能被其他小程序破坏。在我们看来,这种风险(如果存在的话,因为它假设存在恶意小程序)在新的JavaCard 3 连接版上更大,因为该版本支持多线程,从而增加了另一个小程序同时访问共享APDU缓冲区的可能性。
6 结论
除了明确指定的安全机制(如小程序防火墙和事务机制)之外,还可以在Java Card智能卡上实施多种额外的防御措施。但是,正如本文所示,并非所有防御措施都同样有效。这凸显了字节码验证器以及使用全球平台的数字签名对小程序安装进行控制作为防御手段的重要性。
我们的研究表明,我们分析的大多数智能卡并未实施任何特殊保护机制来防御针对安全容器的恶意逻辑攻击,因为它们只是以明文形式存储密钥和PIN。
即使在对密钥进行加密的智能卡上,我们发现攻击者仍可通过滥用应用程序接口自身通过getKey()方法提供的解密功能,轻松解密密文。我们用于评估的智能卡为工程样品,因此我们不能排除实际应用中的智能卡可能具备不同的防御措施。
值得一提的是,我们遇到的密钥和个人识别码的加密以及对重试计数器的简单完整性检查,可能是为了防止侧信道攻击而非逻辑攻击。对于故障注入,这些机制将非常有效,既能抵御攻击者试图破坏重试计数器的故障攻击,也能抵御攻击者试图将PIN或密钥修改为可预测值的故障攻击。(后者需要stuck-at故障攻击,即攻击者可以预测攻击后某个内存位置的值将变为固定值,例如全零或全一。) 如第5节所述,对加密密钥和PIN的方案进行一个简单的修改——即增加一些密钥分散——将使保护强度大大提高。相比之下,保护PIN尝试计数器免受逻辑攻击似乎是一个更困难的问题。
1246

被折叠的 条评论
为什么被折叠?



