作者归档:softsim

STK GET INPUT

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

Java卡的内存管理

每个实例(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卡:编程准则和最佳实践

本文档旨在为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。您的代码将更快,更紧凑。

块加密的几种padding方案

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);
}

RSA

有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 生成,都应该保密

EF_SMS短消息文件

在UST(USIM Service Table)中 10号服务可用,那么EF_SMS文件应该存在。 文件标识 6F3C 结构: 线性定长 记录长度: 176字节 每条记录的第1个字节 状态 This data item commences with the TS-Service-Centre-Address as specified in TS 24.011 The bytes
immediately following the TS-Service-Centre-Address contain an appropriate short message TPDU as specified in
TS 23.040 [6], with identical coding and ordering of parameter

simtrace2使用记录

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 debugserver lldb IDA调试

越狱后iPhone(iPad或iPod)

获取debugserver

  1. xcode新建一个singleview的简单项目,在iPhone设备上跑一遍。这样在设备的/Developer/usr/bin 下就会有debugserver
  2. 第二种方法,在 /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport/ 里找到对应版本的DeveloperDiskImage.dmg, 从里面提取

添加权限

分离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的动态连接库

  • 下载 ipsw
  • 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版本

  • 从dmg中提取文件
  •  
    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