读
memory read --size 4 --format x --count 4 me r -s4 -fx -c4 x -s4 -fx -c4
写
memory write -f x -s 1 addr 0x95 0x27 0xAB 0xCD
读
memory read --size 4 --format x --count 4 me r -s4 -fx -c4 x -s4 -fx -c4
写
memory write -f x -s 1 addr 0x95 0x27 0xAB 0xCD
UICC提示用户应该输入一段信息,用户输入的内容应该由设备透明地传给UICC.
如果UICC提供给你了默认的文字,那么终端在提示用户输入信息时,显示该文字。
用户可以接受,拒绝,编辑文字 作为 响应字符串
提示信息文字可以有3种格式
1. SMS默认字母表的 压缩格式
2. SMS默认字母表的 非压缩格式
3. UCS2 字母表 格式
UICC可以给出 期望输入的字符串的最大和最小长度
用户输入的字符串
1. 只含有数字: 0 到 9, *, # 和 +
2. SMS字符表 或 UCS2
Command Qualifier
bit 1:
0 = digits (0 to 9, *, #, and +) only;
1 = alphabet set.
bit 2:
0 = SMS default alphabet;
1 = UCS2 alphabet.
bit 3:
0 = terminal may echo user input on the display;
1 = user input shall not be revealed in any way (显示**密码).
bit 4:
0 = user input to be in unpacked format;
1 = user input to be in SMS packed format.
bits 5 to 7: = RFU.
bit 8:
0 = no help information available;
1 = help information available.
Command Qualifier 应该设置为 1 或者 3
当终端为 响应 GET INKEY或者GET INPUT命令,发出一个成功的Terminal Response时
它应该提供用户输入的字符串 给UICC
对于GET INPUT, 如果用户输入为空
那么应该设置 Length=01, Value=数据编码(data coding scheme)
真正的null字符串,应该是Length=00, 没有value
数据编码方案 SMS Data coding scheme, 定义在 ETSI TS 123 038 中
推荐这三种编码方案
’00’: GSM default alphabet 7 bits packed;
’04’: GSM default alphabet 8 bits;
’08’: UCS2
每个实例(intance)放在EEPROM或者RAM里。这依赖于对象(object)的内容
1. 如果对象的内容在卡中从一个session到另一个session都保持不变,它的内容和引用都会保存在EEPROM中,只要它从属的applet 没有被删除。
2. 如果内容会改变,发生下面的事件时会被清零:
CLEAR_ON_RESET
CLEAR_ON_DESELECT
临时内容会存放在RAM中,但它们的引用会放在EEPROM中
内容也保存在EEPROM中的:
所有基本数据类型(byte,short)的全局变量;
实例变量/Instance variables(全局变量/global variables)
在 对象安装时 创建。
局部变量/Local variables:只能在函数内部 或者 代码块 内部访问
可以使用 静态变量时 ,不要用实例变量
但静态变量是类变量, 可能会引起一些安全问题(可能被其他applet访问)
使用new创建出来对象
可以使用 局部的基本类型(byte, short)
但不要使用 局部的 byte[] 和 object
低版本的Java Card没有垃圾回收
常量 加上 static final 关键字
用基本数据类型,而不要对其进行二次封装,因为会带来更多内存消耗
在applet的构造方法里,就把所有的对象都建立好,这样在安装时就能确定自己需要多少资源。
用switch-case代替 if-else
使用复合算术语句而不是单独的赋值
本文档旨在为Java Card开发人员提供经过验证的技术指南,重点放在applet的设计和优化上。我们希望这些指南将有助于面对Java Card开发的特殊性,即:
•卡资源极为有限。 EEPROM大小通常为32-64Kb,用于保存卡片发行后小应用程序(Applet)和应用程序数据。 RAM大小通常约为4KB,并且必须容纳应用程序栈,瞬态数据和各种运行时缓冲区。你负担不起任何浪费
•CPU功率非常有限。另外,Java Card applet比本机代码慢得多,因此您不想执行不必要的操作
•即使您的小程序在平台上运行良好,您仍然希望节省尽可能多的资源,以便它也可以在更多受限的平台上运行(较少的堆栈,较小的事务缓冲区等)。还记得吗?编写一次,随处运行;-)
Applet设计
Java Card不是Java。花式的面向对象设计会导致小程序变胖和变慢,这是因为:
•CAP文件中的类/接口开销
•运行时的虚拟方法开销
下面介绍的设计准则是基于我们自己的经验。和往常一样,当然还有改进的余地,但是遵循它们肯定可以防止大量常见(以及一些不是那么常见)的错误。
高级设计
通常在设计时会犯下悲剧性的错误。如果发生这种情况,那简直是只有完整的重新设计才会拯救你……为避免这种情况,请非常仔细地阅读本节并确保您遵循这些准则。从合理的面向对象设计开始。这将帮助您了解applet的工作原理,尤其是当applet具有复杂功能(PKCS,EMV等)时。将问题分解为较小的对象仍然是找出问题的最佳方法。不过,您必须避免以下陷阱:
•类扩散:一切都不是对象。因此请控制 类/抽象类/接口 的数量
•实用工具 类/接口:将这些方法移至实际实例化的类,而不是定义仅包含静态方法的类。对于仅包含常量的接口,也是如此:将常量移至实际类
•较深的类层次结构:类树越深,继承方法的调用就越昂贵。继承还意味着您将嵌套地调用构造函数,这使栈(stack)处于危险之中。简而言之,继承的必要性必须明确:如果没有必要性,就不要继承。
•设计模式:模式在规范中看起来很棒,但是其实现意味着很多抽象类,接口和深层树
•系统化的get/set方法:同样,这在UML图表中看起来不错,但是对于Java Card应用程序来说,它的负担却很大。增加成员可见性将使您可以删除它们,从而减小代码大小并提高执行速度
•程序包扩散:实际上,除非一个程序包将由另一个applet使用,否则实际上无需将应用程序分解为程序包。看起来更好,但是跨包方法调用有一定的成本:如果不是必要,则避免使用它
•C样式错误处理:请勿在您的方法中使用C样式错误处理,即检查返回代码以查看是否发生了错误。这会产生效率低下的代码(更不用说难看的代码了),因为即使没有发生错误,您也会继续运行与错误相关的代码。您的设计必须使用Java异常:错误仅在发生后才需要处理!
设计完成后,您的应用程序应包括一个或两个软件包,一到十个类和不超过三个层级(level )的类。任何较大的问题都需要进行彻底调查和论证。一旦实现并测试了applet,您就可以确信已正确理解并实现了功能要求。在这个阶段,您可能会开始打破面向对象(OO)设计并进行优化以减小代码大小和/或提高性能。
数据存储
您的设计必须考虑以下约束:
•快速写入RAM
•写入EEPROM的速度非常慢
•分配对象甚至更糟
•垃圾收集不是Java Card 2.1.1的一部分
内存分配
关键的规则是, 在安装时创建所有实例数据,即在javacard.framework.Applet.install()调用的applet构造函数中创建:使用新的临时数组分配,所有显式初始化的对象构造。这样,在applet的使用期间就不会面临内存不足的风险:在安装时所有内容都已保留。此外,内存分配不会降低applet命令的速度。
内存写入
对象存储在EEPROM中。这意味着每次执行
myObject.myByte =(byte)5
时,您都在写入EEPROM。必须尽可能避免这种情况。显然,您需要存储持久性数据,但是在许多情况下,您只需要存储临时数据(中间结果,发送回终端的数据等)。这是APDU缓冲区派上用场的地方!毕竟,它是一个“普通”字节数组,因此请尽可能多地使用它。
安全
如果将小程序提交给安全评估(通用标准等),则可以采取一些特定步骤:
•将所有安全功能(密钥/ PIN处理等)隔离在单独的类中。这样,只需要记录和评估这些类
•尽可能使用Java Card API(PIN,密钥等)。如果平台提供了您需要的安全服务,则applet实施这些服务是无用且不安全的
固定大小的PIN
使用固定大小的PIN,即,将PIN长度存储在PIN本身中,并填充其余字节。这有助于防止定时攻击,还简化了PIN处理。
反DFA RSA签名
在将结果发送到终端之前,请验证所有RSA签名。这将减慢签名操作的速度,但可以防止DFA攻击。
其他需要注意的事项
注意平台错误:它们可能会对您的设计产生影响。
如果小程序必须可互操作,请不要使用任何专有的API。
小程序优化
本节列出了许多实际的(即经过测试和验证的)优化。在应用它们之前,请记住优化的黄金法则:
•专注于最常运行的代码:在这里您将有所作为
•您通常必须在大小和速度之间进行选择
•每次优化通过后运行基准测试
•每次优化通过后运行验证测试
减少代码大小
避免复杂的面向对象设计
如果您阅读了上一节,这应该很明显。简而言之:
•使类/接口的数量最少。组合“相似”类并在实例化时使用构造函数参数对其进行区分
•尽量减少方法数量。尤其要摆脱所有get/set方法:提高数据成员的可见性(例如,从私有到可见的包),以减少get/set方法的数量。由于您保存了方法调用,因此这也将提高性能
权衡:封装被削弱。如果您控制程序包中的所有类,那么这不成问题。
删除无效代码
查找未使用的变量和代码。听起来很明显,但您会感到惊讶…
另外,请尝试使初始化/个性化设置尽可能短。它只需要运行一次,因此非常重。也许个性化系统可以执行更复杂的操作?
分解重复代码
查找所有冗余代码并将其分解。如果这意味着分解类并将其替换为私有/静态方法,请执行此操作。
权衡:如果经常调用该代码,则可能会减慢速度。
限制参数的个数
对于虚拟方法,尝试使用不超过3个参数,对于静态方法,尝试使用不超过4个参数。这样,编译器将使用数字或字节码快捷方式,这有助于减小代码大小:aload_x(1字节)而不是aload x(2字节),依此类推。
权衡:如果这意味着从对象实例保存/读取数据,则速度会明显下降。
减少EEPROM消耗
回收所有对象。
Java Card标准不需要垃圾收集。因此,除非您坚持要浪费内存,否则必须跟踪旧对象并重用它们。切记:Java Card虚拟机“永远”运行,因此,如果某个对象变得不可访问,则其内存将”永远”消失(或者至少在删除小程序之前)。即使您的平台提供了专有的垃圾回收,您也最好重用对象。分配新对象很慢,垃圾回收也很慢。你明白了…
仔细分配数组
操作系统以32字节的块(称为clusters)分配内存。一个cluster无法在两个对象之间共享,因此任何对象都将至少吞噬一个群集,无论它多么小。此外,虚拟机将标头附加到所有对象:
•6个字节用于“普通”对象
•8个字节用于基本类型数组
•对象数组12个字节
这意味着您不必创建多个相同类型的小数组,而应将它们组合成一个数组,并使用固定偏移量访问后者。
折衷方案:代码复杂度略有增加。
减少内存消耗
重用局部变量
与其在需要时分配新的局部变量,不如尝试重用先前声明的局部变量。
折衷:滥用此技术会产生无法读取的代码。
仔细分配临时数组 (重复了,上面提到过)
与其创建多个相同类型的小型临时数组,不如将它们组合成一个数组并使用固定偏移量对其进行访问。
折衷方案:代码复杂度略有增加。
限制参数的个数 ( 重复了)
对于虚拟方法,尝试使用不超过3个参数,对于静态方法,尝试使用不超过4个参数。这样,编译器将使用短字节码指令,这有助于减小代码大小:aload_x(1字节)而不是aload x(2字节),依此类推。
权衡:如果这意味着从对象实例保存/读取数据,则速度会明显下降。
避免深层嵌套的方法调用
危险的嵌套调用通常发生在深层类树(基类方法调用)和递归上。后者产生了花哨且紧凑的代码,但它也往往会很快粉碎stack……三遍检查最终条件,并确保测试实际上触发了最坏的情况。
权衡:可能增加代码大小。
当心开关/案例结构中的局部变量
不必在每种情况下分配新的局部变量,而仅在切换之前声明一个。
提高执行速度
开启编译器优化
如果您的编译器提供了优化标志,请使用它们(对于Javac,为-O)。
权衡:可能的代码大小增加。
避免复杂的面向对象设计
有关详细信息,请参见Applet设计和Applet优化。
权衡:封装被削弱。如果您控制程序包中的所有类,那么这不成问题。
支持静态,私有和最终方法
所有公共/受保护的Java方法都是隐式虚拟方法(与C++不同,在C++中,必须将它们声明为虚拟)。这意味着始终进行动态绑定,即,虚拟机在调用公共/受保护的方法之前必须始终确定对象的实际类型。这种查找成本很高,尤其是如果要调用的方法是从基类继承的。这是不欢迎使用深层树的主要原因。
要解决此问题:
1.支持静态方法,因为它们不受动态绑定的约束,这使得它们调用起来更快
2.如果方法不是静态的,请尝试将其设为私有。私有方法不能在派生类中被覆盖,因此虚拟机更容易找到它们
3.如果某个方法不能私有,请尽快将其定型。这将有助于虚拟机
使用本机API
只要平台提供本机代码来执行操作,就使用它!本机方法比您能想到的任何聪明的Java代码要快得多。
尤其是:
•使用
Util.arrayFillNonAtomic()
Util.arrayCopy()
Util.arrayCopyNonAtomic()
初始化或修改数组
•Util.arrayFillNonAtomic()对于在加密操作期间填充缓冲区特别有用
•使用Util.getShort(),Util.setShort()处理byte [] / short转换
避免不必要的初始化
Java Card虚拟机保证将新分配的数据成员和局部变量设置为0 / false / null。
不要将异常用于流程控制
使用异常进行流控制从来都不是一个好主意,Java Card也不例外。异常处理非常慢,除异常处理外,不应将其用于其他任何用途。换句话说,如果您正在考虑使用throw模拟goto,只需加倍努力,寻找更好的解决方案。
使用RAM缓冲区
写入EEPROM的速度大约比写入RAM的速度慢1,000倍,因此,执行的次数越少越好。您可以使用APDU缓冲区或瞬态数组来存储会话数据和临时结果。这对于中间解密解密操作特别有效,并且也更安全。
清理循环
访问实例成员比访问局部变量要昂贵。如果必须执行重复访问,则在第一次访问时将实例成员存储在本地变量中,然后仅使用该本地变量。数组访问(array [i],array.length)和方法调用也是如此。这将大大提高性能。
使用逻辑表达式的求值顺序
逻辑表达式(测试等)从左到右评估。如果条件失败,则不会评估其余测试。如果其中一种情况几乎总是错误,请首先进行检查:您将避免执行不必要的代码。
尽可能经常使用返回值
许多API的返回值都是有意义的:使用它可以节省不必要的操作。
谨慎使用事务(transactions)
由于许多本机API已经进行事务写操作,因此请确保必须在applet中明确使用事务。特别是,如果您不需要交易,请不要使用Util.arrayCopy(),请改用Util.arrayCopyNonAtomic()。
同时检查APDU的类(CLA)和指令(INS)
当使用OP Secure Messaging时,这特别有效,因为所有安全命令将使用两个不同的类(CLA)值。
同时检查P1和P2
尽可能同时检查P1和P2。您的代码将更快,更紧凑。
PKCS5Padding和PKCS7Padding唯一不同的地方在于块的大小
在PKCS5Padding中, 明确定义Block的大小是8个字节
而在PKCS7Padding定义中,块的大小是不确定的,可以在1-255字节之间
PKCS5, PKCS7 有如下相同的特点:
1)填充的字节都是一个相同的字节
2)该字节的值,就是要填充的字节的个数
如果要填充8个字节,那么填充的字节的值就是0×8;
要填充7个字节,那么填入的值就是0×7;
…
如果只填充1个字节,那么填入的值就是0×1;
正是这种即使恰好是8个字节也需要再填充字节的规定,可以让解密的数据很确定无误的移除多余的字节。
因为解密恢复的明文的最后一个字节,就能告诉你 存在多少个填充字节
举例:假定块长度为 8,数据长度为 9,
数据: FF FF FF FF FF FF FF FF FF
PKCS7 填充后: FF FF FF FF FF FF FF FF FF 07 07 07 07 07 07 07
一种python 实现
pad_len = 8 - (len(data) % self.block_size) if _pythonMajorVersion < 3: data += pad_len * chr(pad_len) else: data += bytes([pad_len] * pad_len) if _pythonMajorVersion < 3: pad_len = ord(data[-1]) else: pad_len = data[-1] data = data[:-pad_len]
另外一种实现
BS = 8 pad = lambda s: s + (BS - len(s) % BS) * bytes([BS - len(s) % BS]) unpad = lambda s : s[0:-(s[-1])] ############# pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS) unpad = lambda s : s[0:-ord(s[-1])]
PKCS5 pad/unpad的 PHP实现
function pkcs5_pad ($text, $blocksize) { $pad = $blocksize - (strlen($text) % $blocksize); return $text . str_repeat(chr($pad), $pad); } function pkcs5_unpad($text) { $pad = ord($text{strlen($text)-1}); if ($pad > strlen($text)) return false; if (strspn($text, chr($pad), strlen($text) - $pad) != $pad) return false; return substr($text, 0, -1 * $pad); }
还有一点需要说明的是:
块大小是 加密方案(加密算法)的一种固有属性,对于AES来说,块大小是128位(16字节)
填充是为了 让加密的输入, 匹配 输入块(一般是块加密算法中矩阵)的长度, 而不是 Key的长度。 输入块是 64位, Key可以是128位,256位。
严格来讲, PKCS5Padding 是不能用于 AES的,因为
PKCS5Padding的块大小,是8字节
AES的块大小,是16字节。
如果有人说 AES算法,使用 PKCS5Padding填充时, 实践上,他使用的是 PKCS7Padding
SSL3Padding跟PKCS5Padding只是稍有不同:PKCS5 Padding填充的值是 要填充的字节数, 而SSL 3 Padding 是要填充的字节数 减去1
所以,很容易得到 ssl3padding的php实现
function ssl3_pad ($text, $blocksize) { $pad = $blocksize - (strlen($text) % $blocksize); return $text . str_repeat(chr($pad-1), $pad); } function ssl3_unpad($text) { $text_len = strlen($text); $pad = ord($text{$text_len-1}); $pad_len = $pad + 1; if ($pad_len > $text_len) { return false; } if (strspn($text, chr($pad), $text_len - $pad_len) != $pad_len) { return false; } return substr($text, 0, -1 * $pad_len); }
有OpenSSL生成RSA私钥 (其实应该叫 keypair,里面也有公钥的modulus)
openssl genrsa -out private.pem 512
由私钥 生成 公钥
openssl rsa -in private.pem -pubout -out public.pem
默认格式是pem,所以不必要加 -outform PEM 选项>
从公钥中查看 modulus
openssl rsa -pubin -in public.pem -modulus -noout
列出modulus和Exponent
openssl rsa -pubin -in public.pem -text -noout
exponent一般为 65537 (0x10001)<
查看密钥对信息
openssl rsa -in private.pem -text -noout
privateExponent是由 prime1 prime2 exponent1 exponent2 生成,都应该保密
USB 转UART 连接 simtrace的debug接口(管脚1为 地, 4为发送, 5 为接收)
在PC上配置串口 (921600 8N1)
波特率 921600
8位数据位
无奇偶校验
停止位 1
重启板子(按 RESET 按钮, 或者 重新拔插 usb线缆)
在串口会看到输出
DFU模式(Device Firmware Upgrade)
firemware编译出 几种应用:
dfu: USB DFU 启动加载器,用来 升级其他应用的
ccid: USB CCID读卡器
cardem: 卡模拟,提供本地cos,以及远程sim卡功能
trace: 监听sim卡和设备间的通信
triple_play: 支持 ccid, cardem, trace三种功能,通过usb来配置。
应用可以放在设备的不同位置
flash
bootloader区域内(第1个16k区域保留为bootloader所用,dfu放这里)
ram (通过 jtag/swd 直接下载到ram)
越狱后iPhone(iPad或iPod)
获取debugserver
添加权限
分离fat binary lipo -thin armv7s ~/debugserver -output ~/debugserver_7s lipo -thin arm64 ~/debugserver -output ~/debugserver_64 (iPhone 6之后是 64位的) 强制添加权限 codesign -s - --entitlements entitlements.xml -f debugserver_7s 查看权限 codesign -d --entitlements :- debugserver_7s codesign -d --entitlements - debugserver_7s
权限文件
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>com.apple.backboardd.debugapplications</key> <true/> <key>com.apple.backboardd.launchapplications</key> <true/> <key>com.apple.springboard.debugapplications</key> <true/> <key>run-unsigned-code</key> <true/> <key>get-task-allow</key> <true/> <key>task_for_pid-allow</key> <true/> </dict> </plist>
0000000
开启端口转发
./tcprelay.py -i 192.168.0.119 -b 8192 -t 22:2222 8341:8341
ssh -v root@192.168.0.119 -p 2222
附加到被调试进程
debugserver *:8341 --attach Preferences
开启lldb
(lldb) platform select remote-ios
(lldb) process connect connect://192.168.0.119:1234
(lldb) po [[UIApp keyWindow] recursiveDescription]
ipsw iphone XR 有完整iPhone XR软件恢复包的下载地址
选择下载了 http://updates-http.cdn-apple.com/2018FallFCS/fullrestores/041-19415/F69DC39C-DEBF-11E8-BA95-89533F25C8D2/iPhone11,8_12.1_16B94_Restore.ipsw
iOS 12.1版本
unzip iPhone11,8_12.1_16B94_Restore.ipsw dmg2img 048-32857-105.dmg system.img mount -t hfsplus system.img /mnt (mount -o loop -t hfsplus system.img /mnt)
如果没有dmg2img,需要先安装
apt install dmg2img
用 P7ZIP 也可提取
/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64e
https://github.com/macmade/dyld_cache_extract
参考资料:
https://iphonedevwiki.net/index.php/Dyld_shared_cache
https://github.com/malus-security/iExtractor
https://github.com/malus-security/iExtractor/tree/master/tools/dyld
对于 iOS 10之后的img, 用的是APFS