如何获取JavaCard栈信息
继综合评估JavaCard虚拟机文章发布后,跟网友也在讨论获取栈的问题,今天也就此问题提出自己的分析方法,欢迎拍砖。
JavaCard虚拟机的栈的实现结构,每个厂商的可能并不一致,有些可能是是将局部变量与栈帧(frame)分开,有两个不同的栈顶;有些可能是一个大的RAM区,局部变量与frame从两端开始向中间增加,直到两端数据在中间相会,则栈满。
如下图所示:
那我们如何得知每个帧的大小,以及每个局部变量存储几个字节呢?在图中我们分别以x、y来表示栈中Frame的尺寸与short类型局部变量的尺寸。为此我们可以设计一个函数来获取这些信息,我们设计几个函数,分别为只有0、1、2、3个参数的函数,进行递归调用,使用全局变量记录调用次数,得出每个函数调用调用深度,然后再解一个方程就可以获得以上信息了。为了方便后面的表述,我们假定手里拿到的卡的栈RAM的可使用RAM大小为tSize.
具体的实现方式如下:
没有参数的函数递归调用
/** * 没有参数的递归调用,获取调用深度 */ public static void sgetJavaCardStack() { sCount1++; sgetJavaCardStack(); }
对应的字节码:
P:0 7d 0 7 getstatic_s sCount1 (S) P:3 4 sconst_1 P:4 41 sadd P:5 81 0 7 putstatic_s sCount1 (S) P:8 8d 0 8 invokestatic sgetJavaCardStack; ()V P:b 7a return
从字节码中可以看到该函数的确没有局部变量(参数与局部变量在JCA中都叫local变量)
执行结果:
cm> /send 8000000000 => 80 00 00 00 00 ..... (335762 usec) <= 00 2B 90 00 .+.. Status: No Error
那我们可以得出一个方程式1:
0x2B*x = tSize
有一个参数的函数递归调用:
/** * 有一个参数的递归调用,获取调用深度 */ public static void sgetJavaCardStack(short s0) { sCount1++; sgetJavaCardStack(s0); }
对应字节码:
P:0 7d 0 7 getstatic_s sCount1 (S) P:3 4 sconst_1 P:4 41 sadd P:5 81 0 7 putstatic_s sCount1 (S) P:8 1c sload_0 s0 (S) P:9 8d 0 b invokestatic sgetJavaCardStack; (S)V P:c 7a return
执行结果:
cm> /send 8001000000 => 80 01 00 00 00 ..... (279340 usec) <= 00 23 90 00 .#.. Status: No Error
得出方程式2:
0x23*x + 0x23*(y*1) = tSize – n; (n<x+y)
有两个参数的递归调用
/** * 有二个参数的递归调用,获取调用深度 */ public static void sgetJavaCardStack(short s0, short s1) { sCount1++; sgetJavaCardStack(s0, s1); }
对应的字节码
P:0 7d 0 7 getstatic_s sCount1 (S) P:3 4 sconst_1 P:4 41 sadd P:5 81 0 7 putstatic_s sCount1 (S) P:8 1c sload_0 s0 (S) P:9 1d sload_1 s1 (S) P:a 8d 0 c invokestatic sgetJavaCardStack; (SS)V P:d 7a return
执行结果
cm> /send 8002000000 => 80 02 00 00 00 ..... (230688 usec) <= 00 1D 90 00 .... Status: No Error
得出方程式3:
0x1D*x +0x1D*(y*2) = tSize – n; (n<x+2y)
有三个参数的递归调用
/** * 有三个参数的递归调用,获取调用深度 */ public static void sgetJavaCardStack(short s0, short s1, short s2) { sCount1++; sgetJavaCardStack(s0, s1, s2); }
对应的字节码
P:0 7d 0 7 getstatic_s sCount1 (S) P:3 4 sconst_1 P:4 41 sadd P:5 81 0 7 putstatic_s sCount1 (S) P:8 1c sload_0 s0 (S) P:9 1d sload_1 s1 (S) P:a 1e sload_2 s2 (S) P:b 8d 0 d invokestatic sgetJavaCardStack; (SSS)V P:e 7a return
执行结果
cm> /send 8003000000 => 80 03 00 00 00 ..... (197498 usec) <= 00 18 90 00 .... Status: No Error
得出方程式4:
0x18*x +0x18*(y*3) = tSize – n; (n<x+3y)
在解方式的时候,可以把n忽略,通过以上几个方式式解出的结果可能是个小数,但不影响,因为这本身就是一个不严谨的分析方式。
通过计算得到的结果为:
X = 8;
Y = 2;
tSize = 344;
即每一帧使用8字节,每个局部变量存储为2字节。
这里的tSize就是你调用函数时的可用栈空间,但不是虚拟机的全部栈空间,因为API以及JCRE的实现还会占用一部分。
如果感兴趣的话,还可以分析一下其它数据类型的栈使用使用情况,如引用类型,通过实例函数实现等。事实上除了int类型外,其它类型在栈上都使用两个字节存储。关于栈的结构,本博客在深入JavaCard虚拟机系列中还会进一步探讨。
以下是本文分析使用的完整源代码:
1 package GetJavaCardStackInfoPkg; 2 3 import javacard.framework.APDU; 4 import javacard.framework.ISO7816; 5 import javacard.framework.Applet; 6 import javacard.framework.ISOException; 7 import javacard.framework.Util; 8 9 /** 10 * @author SCPlatform@outlook.com 11 */ 12 public class GetJavaCardStackInfoApp extends Applet { 13 static short sCount1 = 0; 14 public static void install(byte[] bArray, short bOffset, byte bLength) { 15 new GetJavaCardStackInfoApp().register(bArray, (short) (bOffset + 1), 16 bArray[bOffset]); 17 } 18 19 public void process(APDU apdu) { 20 if (selectingApplet()) { 21 return; 22 } 23 byte[] buffer = apdu.getBuffer(); 24 sCount1 = 0; 25 switch (buffer[ISO7816.OFFSET_INS]) { 26 case (byte) 0x00: 27 sCount1 = (short) 0; 28 try { 29 sgetJavaCardStack(); 30 } catch (Exception e) { 31 } finally { 32 apdu.setOutgoingAndSend((short) 0, Util.setShort(buffer, (short) 0, sCount1)); 33 } 34 break; 35 36 case (byte) 0x01: 37 sCount1 = (short) 0; 38 try { 39 sgetJavaCardStack((short) 0); 40 } catch (Exception e) { 41 } finally { 42 apdu.setOutgoingAndSend((short) 0, Util.setShort(buffer, (short) 0, sCount1)); 43 } 44 break; 45 46 case (byte) 0x02: 47 sCount1 = (short) 0; 48 try { 49 sgetJavaCardStack((short) 0, (short) 0); 50 } catch (Exception e) { 51 } finally { 52 apdu.setOutgoingAndSend((short) 0, Util.setShort(buffer, (short) 0, sCount1)); 53 } 54 break; 55 case (byte) 0x03: 56 sCount1 = (short) 0; 57 try { 58 sgetJavaCardStack((short) 0, (short) 0, (short) 0); 59 } catch (Exception e) { 60 } finally { 61 apdu.setOutgoingAndSend((short) 0, Util.setShort(buffer, (short) 0, sCount1)); 62 } 63 break; 64 case (byte) 0x04: 65 sCount1 = (short) 0; 66 try { 67 sgetJavaCardStack((short) 0, (short) 0, (short) 0, (short) 0); 68 } catch (Exception e) { 69 } finally { 70 apdu.setOutgoingAndSend((short) 0, Util.setShort(buffer, (short) 0, sCount1)); 71 } 72 break; 73 default: 74 ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED); 75 } 76 } 77 78 /** 79 * 没有参数的递归调用,获取调用深度 80 */ 81 public static void sgetJavaCardStack() { 82 sCount1++; 83 sgetJavaCardStack(); 84 } 85 86 /** 87 * 有一个参数的递归调用,获取调用深度 88 */ 89 public static void sgetJavaCardStack(short s0) { 90 sCount1++; 91 sgetJavaCardStack(s0); 92 } 93 94 /** 95 * 有二个参数的递归调用,获取调用深度 96 */ 97 public static void sgetJavaCardStack(short s0, short s1) { 98 sCount1++; 99 sgetJavaCardStack(s0, s1); 100 } 101 102 /** 103 * 有三个参数的递归调用,获取调用深度 104 */ 105 public static void sgetJavaCardStack(short s0, short s1, short s2) { 106 sCount1++; 107 sgetJavaCardStack(s0, s1, s2); 108 } 109 110 /** 111 * 有四个参数的递归调用,获取调用深度 112 */ 113 public static void sgetJavaCardStack(short s0, short s1, short s2, short s3) { 114 sCount1++; 115 sgetJavaCardStack(s0, s1, s2, s3); 116 } 118 }
作者:SCPlatform
Email:SCPlatform@outlook.com
本文旨在学习、交流使用,任何对文章内容的出版、印刷,对文章中提到的技术的商业使用时,请注意可能存在的法律风险。