re-javacard について
re-javacardは、CAPファイル内のJava Cardアプレットを調べるセキュリティ分析者向けのリバースエンジニアリングスキルです。コンポーネント解析、バイトコードの復元、AIDやインストールパラメーター、APDU分岐の分析を扱います。
主な機能
- 01CAPコンポーネントの解析と検証
- 02CAPメソッドとバイトコードの復元
- 03パッケージAIDとアプレットAIDの調査
- 04CLA/INS分岐表の再構築
- 05javap出力との照合
活用シーン
- 01SIM、USIM、銀行カードのアプレット分析
- 02CAPファイルからAPDU処理を復元
- 03インストールパラメーターの動作への影響確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ dslsdzc/ rev-skills --skill re-javacardインストール後、Claude Code で/re-javacardを入力して読み込まれたか確認します。
詳細
Java Card/SIM カードアプレットのリバースエンジニアリング(CAP コンポーネント解析とバイトコード復元)
使用する場合/使用しない場合
- 使用する:Java Card アプレットの CAP ファイル(Converted Applet Package、.cap)を入手した場合——EEPROM ダンプ、ファームウェアイメージ、資料パッケージ、またはカード読み取りで取得したもの
- 使用する:SIM/USIM/銀行カードなどのスマートカードアプリケーションを分析する場合——パッケージ AID とアプレット AID、インストールパラメーター、install() の初期化、process(APDU) のディスパッチロジックを復元する場合
- 使用する:CAP バイトコード(Java サブセット+カード拡張命令)の逆アセンブル、メソッド本体の復元、CLA/INS 命令表の再構築を行う場合
- 使用しない:APDU プロトコルのやり取りやパケットキャプチャ、ISO14443 リンク、MIFARE/DESFire カードファミリーの脆弱性([[re-iot-proto]] を使用)
- 使用しない:一般的な .class/.jar Java バイトコードのリバースエンジニアリング([[re-java]] を使用);CAP に埋め込まれた元の .class は javap と照合できますが、このスキルの代替にはなりません
- 使用しない:CAP をまだ入手していない場合——まずカードリーダーとの物理的なやり取り([[re-hardware-io]]/[[re-iot-proto]] の APDU)またはファームウェアの抽出([[re-firmware]]/[[re-fw-extract]])を行い、ファイルを入手してから戻ってください
- 使用しない:カードデータの読み書きまたは APDU トラフィックのキャプチャだけが必要な場合([[re-iot-proto]])
ツールの準備
すべてのツールは使用前に動作を確認してください。静的解析にはサンドボックスは不要です。実際のカードやカードリーダーとのやり取りを伴う場合は、まず許可範囲(カードの所有者と分析目的)を確認してください。動的実行はデフォルトでサンドボックスを使用します([[re-analyze/platform-tips]] の最優先原則)。
CAP ファイルの解析(オープンソースパーサーまたは自作スクリプト)
- capfile(Java ライブラリ、martinpaljak、CAP の解析/検証、ant-javacard エコシステム):
git clone https://github.com/martinpaljak/ant-javacard(capfile はすでにこの中に統合されています);ライブラリとして参照してビルドします。検証:サンプル CAP を問題なく解析できること - caprunner(Python、CAP パーサー+バイトコード実行器、benallard):
git clone https://github.com/benallard/caprunner+git clone https://github.com/benallard/pythoncard(pythoncard/pythoncardx ディレクトリを caprunner と同じ階層に配置します);検証:python3 readcap.py <file.cap>でコンポーネント情報を一覧表示できること - pysim の javacard.py(osmocom、実運用レベルの SIM ツールチェーンコンポーネント):
git clone https://github.com/osmocom/pysim;pySim/javacard.pyの CapFile クラス——Header のマジック値 0xDECAFFED を検証し、パッケージ AID/アプレット AID を読み取り、load file のインストール順を組み立てます;検証:python3 -c "import sys; sys.path.insert(0,'pysim'); from pySim.javacard import CapFile; print(CapFile('x.cap').get_loadfile_aid())" - GlobalPlatformPro(gp、Java、カードとのやり取り/アプリのインストール/アプリ AID の一覧表示):GitHub releases から JAR をダウンロードします(Java 11+):
java -jar gp.jar --version;java -jar gp.jar --list(カード上のアプリと AID を一覧表示) - 自作パーサーの要点:CAP は ZIP 形式で、コンポーネントはファイルとして格納されています。単一のオフセット表に依存せず、ファイルごとに「tag + size + info」の形式で解析すれば十分です(構造は操作手順 2-5 を参照)
javap —— 元の .class との照合(JDK に付属)
- Linux:
apt install default-jdk/dnf install java-17-openjdk/pacman -S jdk-openjdk;macOS:brew install openjdk;Windows: 公式 JDK インストーラー - 検証:
javap -version - 用途:CAP JAR にはコンパイル時に同等の成果物である
APPLET-INF/classes/**/*.classが含まれていることがよくあります(SDK コンバーターがパッケージ化したもの。unzip -lで確認できます)。unzip -p x.cap APPLET-INF/classes/com/example/Foo.class > Foo.class && javap -c -p Foo.classで Java のビューを取得し、CAP バイトコードと相互に照合します(オペコード番号と命令名の違いに注意してください。落とし穴 3 を参照)
シミュレーション実行(カードなしで検証、任意)
- JCardSim(Java シミュレーター。CAP バイトコードではなく、コンパイル済みクラスを読み込みます):GitHub releases から JAR をダウンロードします。vsmartcard 仮想カードリーダーと組み合わせると PC/SC カードリーダーとして公開され、pyscard から APDU を送信して動作を検証できます
- Linux:
git clone https://github.com/frankmorgner/vsmartcardを実行し、virtualsmartcard のコンパイル後にsystemctl restart pcscdを実行します;Windows: BixVReader 系の仮想カードリーダー
- Linux:
- pyscard(Python PC/SC インタラクション):
pip install pyscard(Linux の依存関係:apt install libpcsclite-dev pcsclite pcscd/dnf install pcsc-lite-devel pcsc-lite/pacman -S pcsclite ccid;macOS:brew install pcsc-lite);検証:python3 -c "from smartcard.System import readers; print(readers())" - CAP バイトコードを直接実行する場合は caprunner の runcap.py を使用します(ハードウェアは不要)
逆コンパイラーとカードリーダー(一般情報)
- Ghidra/IDA にはネイティブの CAP ローダーとプロセッサーモジュールがありません——CAP バイトコードはスクリプトで解析する必要があります:Ghidra Python/IDA Python でこのスキルのコンポーネント構造に従ってインポートするか、先にパーサーで逆アセンブル結果をエクスポートしてから手作業で照合してください
- カードリーダー(一般的な選び方):接触型 ISO7816(PC/SC CCID のドライバー不要タイプ)と非接触型 ISO14443(NFC)の 2 種類があります。選ぶ際は、PC/SC ドライバーの充実度(CCID ドライバー不要タイプか、メーカー独自ドライバーか)、SIM カードスロットの形式とアダプター(フルサイズ/micro-SIM など)、T=0/T=1 プロトコルのサポート、電源の安定性を確認してください
- 許可範囲:カードの読み取り、抽出、書き込みには、いずれもカードの所有者と分析目的に関する許可の確認が必要です。第三者のカードに対する読み取り専用のアクセスも、許可が必要なケースとして扱います([[re-analyze/platform-tips]] の最優先原則)
操作手順
順番に実行し、各ステップの成果物(解析スクリプト、逆アセンブルテキスト、AID/INS リスト)とサンプルの sha256 を保存します。動的実行はデフォルトでサンドボックスを使用します。
-
CAP の入手元とファイルの識別:
- 入手元:EEPROM ダンプ([[re-hardware-io]] のフラッシュ読み取り)、ファームウェアイメージからの抽出([[re-firmware]]/[[re-fw-extract]])、資料パッケージ/テストサンプル、許可を得たカード読み取りによる抽出
- 識別:
file x.cap(ZIP アーカイブ)→unzip -l x.cap——META-INF/MANIFEST.MF+<パッケージパス>/javacard/*.capのコンポーネントファイルがあれば CAP です;APPLET-INF/classes が含まれていれば元のクラスファイルも含まれており(ステップ 4 で javap と照合できます);javacard/ コンポーネントファイルが含まれていなければ、拡張子の誤り、暗号化されたペイロード、または別の形式である可能性があります(落とし穴 4 を参照) sha256sum x.capで記録します;Header のフォーマットバージョンを書き留めます
-
コンポーネントの列挙と Header の検証:
-
コンポーネント一覧(Java Card 仕様、tag 1-12;必須のコアコンポーネント+任意コンポーネント):
tag 组件 内容与作用 1 Header 魔数、格式版本、flags、包 AID、包名 2 Directory 各组件大小表(缺失可选组件记 0)、静态字段尺寸、import/applet 计数 3 Applet(可选) applet 数、各 applet AID + install() 方法偏移 4 Import 外部包 AID 列表(供常量池外部引用定位) 5 ConstantPool token → 类/字段/方法引用(6 类条目,每条 4 字节) 6 Class 类/接口定义:标志位、父类引用、虚方法表、实现接口 7 Method 方法体:异常处理器表 + 方法头(max_stack/nargs/max_locals)+ CAP 字节码 8 StaticField 静态字段镜像区与数组初始化数据 9 ReferenceLocation 字节码中常量池 token 操作数的位置列表(安装时打补丁用) 10 Export(可选) 对外可见的类/方法/字段符号(跨包链接用) 11 Descriptor 签名池:方法名、描述符、method_offset 与 bytecode_count 12 Debug(可选) 调试信息字符串表(含方法名),离线验证/还原用 -
网上流传的「九组件」列举(Header/Directory/Import/ConstantPool/Class/Method/Descriptor/Export/Applet)是简化说法,遗漏了字节码链接必需的 StaticField 与 ReferenceLocation——解析以本表为准
-
Header 字节布局:
01 | size(u2) | magic=0xDECAFFED | fmt_minor | fmt_major | flags | pkg_minor | pkg_major | aid_len | 包AID | [包名];flags: 0x01=ACC_INT、0x02=ACC_EXPORT、0x04=ACC_APPLET;魔数不是 0xDECAFFED 即非合法 CAP(或已被加密/截断,见坑 4)
-
-
Directory 驱动组件一致性校验:
- Directory 组件:按 tag 顺序的组件大小 u2 数组(Header/Directory/Applet/Import/ConstantPool/Class/Method/StaticField/ReferenceLocation/Export/Descriptor/Debug)+ 静态字段 image_size/array_init + import_count + applet_count
- 与 JAR 内组件文件逐项核对:存在性、tag 顺序、size 字段一致;可选组件(Applet/Export/Descriptor/Debug)缺失时目录记 0
- 可拼出安装顺序 load file(Header→Directory→Import→Applet→Class→Method→StaticField→Export→ConstantPool→RefLocation→Descriptor),与固件/读卡器中的安装序列对照(联动 [[re-iot-proto]] 实测)
-
Method 组件方法体还原:
- Method 组件布局:
07 | size(u2) | handler_count(u1) | 异常处理器表(每条 8 字节:start_offset u2、active_length 位域 u2、handler_offset u2、catch_type_index u2)| 方法体序列(方法头 + 字节码) - 方法头:2 字节(首字节高 4 位 flags——0x8=ACC_EXTENDED、0x4=ACC_ABSTRACT,低 4 位 max_stack;次字节高 4 位 nargs、低 4 位 max_locals)或扩展 4 字节;方法在组件内的偏移与字节码长度由 Descriptor 组件给出(method_offset + bytecode_count)
- applet 的 install() 入口偏移在 Applet 组件(install_method_offset)
- 反汇编按 CAP 指令集(见坑 3 的操作码表):操作数多为 2 字节常量池 token,方法以 return 系/athrow 结尾
- 有 Debug 组件时用其字符串表直接还原方法名;否则方法名靠 Descriptor 签名池 + 调用点语义推断(框架方法对照 SDK export 文件)
- Method 组件布局:
-
Import / 常量池 / Export 跨组件引用解析:
- ConstantPool 条目(每条 4 字节:tag u1 + 引用 3 字节):1=classref、2=instance fieldref、3=virtual methodref、4=super methodref、5=static fieldref、6=static methodref;引用首字节高位 0x80 区分内部(类内偏移/token)与外部(package_token + class_token + token)
- 调用指令与条目类型对应:invokevirtual → tag 3(虚方法),invokespecial 调超类方法 → tag 4(super methodref),invokespecial/invokestatic 调构造器与私有方法 → tag 6(静态方法)
- Import 组件:外部包 AID 列表,顺序即常量池外部引用的 package_token 编号
- 字节码中 token 操作数的位置由 ReferenceLocation 组件标注(增量偏移表);解析脚本据此定位引用点并替换为常量池条目
- 外部符号名解析:本包导出看 Export 组件;框架包(javacard.framework)按 SDK 的 .exp 导出文件对照——javacard.framework 是 SIM/银行卡 applet 几乎必引的包,其常量(OFFSET_CLA=0、OFFSET_INS=1、OFFSET_P1=2、OFFSET_P2=3、OFFSET_LC=4;SW1/SW2,0x9000=成功)是还原分派逻辑的锚点
-
Applet 入口与 process(APDU) 分派还原:
- 生命周期:install(APDU)(Applet 组件 install_method_offset 定位)→ register() → 应用选中后每次 APDU 调 process(APDU);定位 process:Descriptor 方法表找 process 描述符,或从 install 调用链进入
- 分派还原:典型形态——invokevirtual APDU.getBuffer()(外部 classref 虚方法调用)→ baload + sconst_/bipush/sspush 取 CLA/INS → icmp/if_ 比较链或 switch 系指令(itableswitch/slookupswitch 等)分支表 → 每 INS 一个处理分支 → ISOException.throwIt(SW1SW2) 异常路径
- 产出 INS → 处理逻辑映射表:各分支入口、P1/P2/Lc/数据解析、返回 SW;与 [[re-iot-proto]] 的 APDU 抓包/实测对照,双向验证分派表
- 安装参数:install 方法读取安装 APDU 数据(初始化参数/持卡人数据),写入静态字段或文件系统,影响后续行为分支——从 install 开始跟参数写入点,别只看 process(见坑 6)
跨域联合
- [[re-iot-proto]]:APDU 协议交互/抓包/卡族判型——本技能还原的 CLA/INS 分派表与其实测 APDU 行为互相验证
- [[re-firmware]] / [[re-fw-extract]]:CAP 从固件镜像/OTA 包提取
- [[re-hardware-io]]:EEPROM/flash dump 获取、JTAG 调试、读卡器物理访问
- [[re-java]]:通用 JVM 字节码逆向底座(CAP 内嵌 .class 的 javap 对照)
- [[re-crypto-id]] / [[re-crypto-keys]] / [[re-crypto-decrypt]]:applet 内加密算法识别、密钥提取与数据解密(智能卡应用常见)
- [[re-variant]]:同 applet 多版本 CAP 对比定位差异(升级/补丁分析)
- [[re-patching]]:改动 CAP 字节码后重打包重装(需对应安装/签名授权)
- [[re-sandbox]]:一切动态执行/读卡交互强制前置([[re-analyze/platform-tips]] 最高原则)
- [[re-triage]]:初勘前置(文件类型/熵/哈希/架构识别)
常见坑与陷阱
- 组件结构用错(把「九组件」当规范):现象——按流传的九组件列举解析,缺 StaticField/ReferenceLocation,字节码引用解不出、静态字段错位;原因——简化列举遗漏了与字节码链接直接相关的两个组件;对策——以规范 12 组件表为准(见操作步骤 2),用 Directory 大小表 + 各组件文件自身 tag/size 双重校验边界
- JAR 结构与拼接格式混淆:现象——按单一偏移表从文件头连续切组件,全部错位;原因——CAP 是 ZIP(组件分文件存储,还可能带 META-INF、APPLET-INF/classes 等文件);旧格式(2.2 之前)或非标准转换产物才是组件拼接式;对策——先
unzip -l确认结构,组件边界以各组件文件自身的 tag+size 为准 - CAP 字节码当 JVM 字节码反汇编:现象——操作码表对不上、指令长度错位,反汇编全乱;原因——CAP 指令集是独立编号:return=0x7A(JVM 0xB1)、new=0x8F(0xBB)、invokevirtual/invokespecial/invokestatic/invokeinterface=0x8B-0x8E(JVM 0xB6-0xB9,且操作数为 2 字节常量池 token 而非符号引用)、aload_0=0x18;另有 JVM 没有的类型化变体(getfield/putfield 的 _a/_b/_s/_i 及 _this/w、sadd/ssub/smul 等 short 专用指令、icmp/if_scmp*、iipush/sspush 常量装载);无 ldc 系(常量走 sconst*/bipush/sspush/iipush + 常量池 token)、无 multianewarray(仅一维数组)、无 monitorenter/monitorexit(单线程)、无 invokedynamic、无 long/double 运算指令(Classic 2.2.2;3.0.5 起语言子集支持 long);「无 invokevirtual」是误传——四类调用指令齐备,框架类实例方法(如 APDU.getBuffer)就走 invokevirtual;对策——用 CAP 专用反汇编(caprunner 指令表/自写脚本按上表),与 javap 对照原 .class 时注意同名指令编号不同
- Import 外部引用断链:现象——调用点 token 解不出目标方法名,逻辑断在框架调用处;原因——applet 的依赖在外部包(框架包/其他 CAP),本 CAP 只含引用 token;对策——Import 组件列外部包 AID,外部符号经 Export 组件或 SDK .exp 文件解析;javacard.framework 的方法名与常量按 SDK 导出文件对照
- 卡片 dump 是密文或片段:现象——「CAP」魔数不对或组件解不开,内容熵高;原因——dump 被卡 OS 加密(GlobalPlatform 域密钥/安全消息封装),或截断/跨区提取不完整;对策——先用熵与魔数判断明文/加密负载,加密走 [[re-crypto-id]] / [[re-crypto-keys]] 思路;用 Directory 大小表核对组件完整性,从固件提取时同样核对
- 只还原 process 忽略 install 与 AID:现象——分派表还原了但行为对不上,或同包多应用互相混淆;原因——install() 的安装参数(初始化数据/持卡人数据)写入静态字段或文件系统,影响后续分支;Applet 组件可含多个 applet AID(同包多实例);对策——从 install 开始跟踪参数写入点,按 AID 区分实例;AID 格式:5 字节 RID + 0-11 字节 PIX(总长 5-16,ISO/IEC 7816-5;0xA0 开头为国际注册 RID,如 SIM/USIM 应用通用 RID A000000087,PIX 为应用自定义)
- 读卡/写卡授权越界:现象——对真实卡片读写分析未确认归属与目的;对策——第三方卡接触默认需授权,优先实验室样本与文档资料([[re-analyze/platform-tips]] 最高原则)