When to use / when not to use
- Use: you have a Java Card applet's CAP file (Converted Applet Package, .cap) — from an EEPROM dump, firmware image, data package, or card reader extraction
- Use: analysis of smart card applications such as SIM / USIM / bank cards — package AID and applet AID, installation parameters, install() initialization, recovery of process(APDU) dispatch logic
- Use: disassembly of CAP bytecode (Java subset + card extension instructions), method body recovery, CLA/INS instruction table reconstruction
- Don't use: APDU protocol interaction and packet capture, ISO14443 link, MIFARE/DESFire card family weaknesses (go to [[re-iot-proto]])
- Don't use: generic .class/.jar Java bytecode reverse engineering (go to [[re-java]]); original .class files embedded in CAP can be cross-checked with javap, but that does not replace this skill
- Don't use: the CAP has not yet been obtained — first do physical card reader interaction ([[re-hardware-io]] / [[re-iot-proto]] APDU) or firmware extraction ([[re-firmware]] / [[re-fw-extract]]), and come back once you have the file
- Don't use: you only need to read/write card data or capture APDU traffic ([[re-iot-proto]])
Tool preparation
Verify all tools before use. Static parsing can be done without a sandbox; for interaction with real cards and card readers, first confirm the authorization boundary (card ownership and analysis purpose); dynamic execution defaults to a sandbox ([[re-analyze/platform-tips]] highest principle).
CAP file parsing (open-source parser or self-written script)
- capfile(Java library, martinpaljak, CAP parsing/verification, ant-javacard ecosystem):
git clone https://github.com/martinpaljak/ant-javacard(capfile has been merged into it); reference it as a library when building. Verification: parsing a sample CAP raises no exceptions - caprunner(Python, CAP parsing + bytecode executor, benallard):
git clone https://github.com/benallard/caprunner+git clone https://github.com/benallard/pythoncard(put the pythoncard/pythoncardx directory on the same path level as caprunner); verification:python3 readcap.py <file.cap>can list component information - pysim's javacard.py(osmocom, production-grade SIM toolchain component):
git clone https://github.com/osmocom/pysim;pySim/javacard.pyCapFile class — verifies the Header magic 0xDECAFFED, reads the package AID/applet AID, assembles the installation order load file; verification:python3 -c "import sys; sys.path.insert(0,'pysim'); from pySim.javacard import CapFile; print(CapFile('x.cap').get_loadfile_aid())" - GlobalPlatformPro(gp, Java, interacts with cards/installs apps/lists app AIDs): download the JAR from GitHub releases (Java 11+):
java -jar gp.jar --version;java -jar gp.jar --list(list apps and AIDs on the card) - Key points for a self-written parser: CAP is a ZIP, components are stored as files, parse them file by file as "tag + size + info", do not rely on a single offset table (for the structure see operation steps 2-5)
javap — original .class cross-check (bundled with the JDK)
- Linux:
apt install default-jdk/dnf install java-17-openjdk/pacman -S jdk-openjdk;macOS:brew install openjdk; Windows: official JDK installer - Verification:
javap -version - Purpose: CAP JARs often contain compile-time equivalent artifacts
APPLET-INF/classes/**/*.class(packaged by the SDK converter,unzip -lvisible),unzip -p x.cap APPLET-INF/classes/com/example/Foo.class > Foo.class && javap -c -p Foo.classgets you a Java view to cross-check against the CAP bytecode (note the differences in opcode numbering and instruction names, see pitfall 3)
Emulated execution (no-card verification, optional)
- JCardSim(Java emulator, loads compiled classes rather than CAP bytecode): download the JAR from GitHub releases; after configuring the vsmartcard virtual card reader it is exposed as a PC/SC reader for pyscard to send APDUs and verify behavior
- Linux:
git clone https://github.com/frankmorgner/vsmartcard, after compiling virtualsmartcardsystemctl restart pcscd; Windows: BixVReader class virtual reader
- Linux:
- pyscard(Python PC/SC interaction):
pip install pyscard(Linux depends onapt install libpcsclite-dev pcsclite pcscd/dnf install pcsc-lite-devel pcsc-lite/pacman -S pcsclite ccid;macOS:brew install pcsc-lite); verification:python3 -c "from smartcard.System import readers; print(readers())" - For direct execution of CAP bytecode, use caprunner's runcap.py (no hardware required)
Decompilers and card readers (generalized)
- Ghidra / IDA have no native CAP loader and processor module——CAP bytecode requires scripted parsing: import per this skill's component structure using Ghidra Python / IDA Python, or first export a disassembly with a parser and then compare manually
- Card readers (generalized selection): two types, contact ISO7816 (PC/SC CCID driverless class) and contactless ISO14443 (NFC); when selecting, pay attention to: PC/SC driver completeness (CCID driverless vs vendor proprietary drivers), SIM card socket form factor and adapters (full-size / micro-SIM, etc.), T=0/T=1 protocol support, power supply stability
- Authorization boundary: card reading, extraction, and card writing all require confirming authorization for the card's ownership and the analysis purpose; even read-only contact with a third-party card is considered a scenario requiring authorization ([[re-analyze/platform-tips]] highest principle)
Operating steps
Execute in order, archive each step's artifacts (parsing scripts, disassembly text, AID/INS lists) together with the sample sha256; dynamic execution defaults to a sandbox.
-
CAP source and file identification:
- Sources: EEPROM dump ([[re-hardware-io]] flash reading), firmware image extraction ([[re-firmware]] / [[re-fw-extract]]), data packages/test samples, authorized card reading and extraction
- Identification:
file x.cap(ZIP archive) →unzip -l x.cap——the presence ofMETA-INF/MANIFEST.MF+<包路径>/javacard/*.capcomponent files means it is a CAP; containing APPLET-INF/classes indicates it has original class files (step 4 can compare with javap); if it does not contain javacard/ component files, it may be a wrong extension, encrypted payload, or another format (see pitfall 4) sha256sum x.caparchive; note the format version in the Header
-
Component enumeration and Header verification:
-
Component list (Java Card specification, tags 1-12; core required + optional):
tag Component Content and role 1 Header Magic number, format version, flags, package AID, package name 2 Directory Size table for each component (missing optional components recorded as 0), static field sizes, import/applet counts 3 Applet (optional) Number of applets, each applet AID + install() method offset 4 Import List of external package AIDs (for locating external references in the constant pool) 5 ConstantPool token → class/field/method reference (6 types of entries, 4 bytes each) 6 Class Class/interface definitions: flags, superclass reference, virtual method table, implemented interfaces 7 Method Method bodies: exception handler table + method header (max_stack/nargs/max_locals) + CAP bytecode 8 StaticField Static field image area and array initialization data 9 ReferenceLocation List of positions of constant pool token operands in the bytecode (used for patching at installation time) 10 Export (optional) Externally visible class/method/field symbols (used for cross-package linking) 11 Descriptor Signature pool: method names, descriptors, method_offset and bytecode_count 12 Debug (optional) Debug information string table (including method names), used for offline verification/restoration -
The "nine components" enumeration circulating online (Header/Directory/Import/ConstantPool/Class/Method/Descriptor/Export/Applet) is a simplified statement; it omits the StaticField and ReferenceLocation required for bytecode linking—parsing is based on this table
-
Header byte layout:
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; if the magic number is not 0xDECAFFED, it is not a valid CAP (or has been encrypted/truncated, see pitfall 4)
-
-
Directory-driven component consistency verification:
- Directory component: a u2 array of component sizes in tag order (Header/Directory/Applet/Import/ConstantPool/Class/Method/StaticField/ReferenceLocation/Export/Descriptor/Debug) + static fields image_size/array_init + import_count + applet_count
- Check item by item against the component files in the JAR: existence, tag order, and consistency of the size field; when optional components (Applet/Export/Descriptor/Debug) are missing, the directory records 0
- The installation order load file can be assembled (Header→Directory→Import→Applet→Class→Method→StaticField→Export→ConstantPool→RefLocation→Descriptor), to be compared with the installation sequence in the firmware/reader (linked with [[re-iot-proto]] actual testing)
-
Method component method body restoration:
- Method component layout:
07 | size(u2) | handler_count(u1) | 异常处理器表(每条 8 字节:start_offset u2、active_length 位域 u2、handler_offset u2、catch_type_index u2)| 方法体序列(方法头 + 字节码) - Method header: 2 bytes (the high 4 bits of the first byte are flags—0x8=ACC_EXTENDED, 0x4=ACC_ABSTRACT, the low 4 bits are max_stack; the high 4 bits of the second byte are nargs, the low 4 bits are max_locals) or an extended 4 bytes; the method's offset within the component and the bytecode length are given by the Descriptor component (method_offset + bytecode_count)
- The offset of the applet's install() entry point is in the Applet component (install_method_offset)
- Disassemble according to the CAP instruction set (see the opcode table in pitfall 3): operands are mostly 2-byte constant pool tokens, and methods end with a return-family instruction/athrow
- When a Debug component is present, use its string table to directly restore method names; otherwise, method names are inferred from the Descriptor signature pool + call-site semantics (framework methods compared against the SDK export file)
- Method component layout:
-
Import / constant pool / Export cross-component reference resolution:
- ConstantPool entries (each 4 bytes: tag u1 + 3-byte reference): 1=classref, 2=instance fieldref, 3=virtual methodref, 4=super methodref, 5=static fieldref, 6=static methodref; the high bit 0x80 of the first byte of the reference distinguishes internal (intra-class offset/token) from external (package_token + class_token + token)
- Call instructions correspond to entry types: invokevirtual → tag 3 (virtual method), invokespecial calling a superclass method → tag 4 (super methodref), invokespecial/invokestatic calling constructors and private methods → tag 6 (static method)
- Import component: the list of external package AIDs; the order is the package_token numbering of external references in the constant pool
- The positions of token operands in the bytecode are annotated by the ReferenceLocation component (incremental offset table); the parsing script uses this to locate reference points and replace them with constant pool entries
- External symbol name resolution: for exports of this package, see the Export component; for framework packages (javacard.framework), compare against the SDK's .exp export files—javacard.framework is a package almost inevitably referenced by SIM/bank card applets, and its constants (OFFSET_CLA=0, OFFSET_INS=1, OFFSET_P1=2, OFFSET_P2=3, OFFSET_LC=4; SW1/SW2, 0x9000=success) are the anchors for restoring dispatch logic
-
Applet entry point and process(APDU) dispatch restoration:
- Lifecycle: install(APDU) (located via the Applet component's install_method_offset) → register() → after the application is selected, process(APDU) is called on every APDU; locating process: find the process descriptor in the Descriptor method table, or enter from the install call chain
- Dispatch recovery: typical form—invokevirtual APDU.getBuffer() (external classref virtual method call) → baload + sconst_/bipush/sspush to fetch CLA/INS → icmp/if_ comparison chain or switch-family instructions (itableswitch/slookupswitch, etc.) branch table → one handler branch per INS → ISOException.throwIt(SW1SW2) exception path
- Produce an INS → handler logic mapping table: entry point of each branch, P1/P2/Lc/data parsing, return SW; cross-check with APDU capture/live testing in [[re-iot-proto]] to verify the dispatch table in both directions
- Installation parameters: the install method reads the install APDU data (initialization parameters/cardholder data) and writes it to static fields or the file system, affecting subsequent behavioral branches—follow the parameter write points starting from install, don't only look at process (see pitfall 6)
Cross-domain combination
- [[re-iot-proto]]: APDU protocol interaction/capture/card family identification—the CLA/INS dispatch table recovered by this skill and its live APDU behavior verify each other
- [[re-firmware]] / [[re-fw-extract]]: CAP extraction from firmware images/OTA packages
- [[re-hardware-io]]: EEPROM/flash dump acquisition, JTAG debugging, physical card reader access
- [[re-java]]: general-purpose JVM bytecode reverse engineering foundation (javap comparison for .class embedded in CAP)
- [[re-crypto-id]] / [[re-crypto-keys]] / [[re-crypto-decrypt]]: cryptographic algorithm identification, key extraction, and data decryption within applets (common in smart card applications)
- [[re-variant]]: comparison of multiple CAP versions of the same applet to locate differences (upgrade/patch analysis)
- [[re-patching]]: repackaging and reinstalling after modifying CAP bytecode (requires corresponding installation/signature authorization)
- [[re-sandbox]]: mandatory prerequisite for all dynamic execution/card reader interaction ([[re-analyze/platform-tips]] highest principle)
- [[re-triage]]: preliminary survey prerequisite (file type/entropy/hash/architecture identification)
Common pitfalls and traps
- Using the wrong component structure (treating the "nine components" as the specification): Symptom—parsing by enumerating the circulated nine components, StaticField/ReferenceLocation are missing, bytecode references cannot be resolved, static fields are misaligned; Cause—the simplified enumeration omits the two components directly related to bytecode linking; Countermeasure—use the specification's 12-component table as the standard (see operation step 2), and double-check boundaries using the Directory size table + each component file's own tag/size
- Confusing JAR structure with the concatenated format: Symptom—slicing components consecutively from the file header according to a single offset table, all misaligned; Cause—CAP is a ZIP (components are stored in separate files, and may also include META-INF, APPLET-INF/classes, etc.); only the old format (before 2.2) or non-standard conversion output is the component-concatenation style; Countermeasure—first
unzip -lconfirm the structure; component boundaries are based on each component file's own tag+size - Disassembling CAP bytecode as JVM bytecode: Symptoms—the opcode table does not match, instruction lengths are misaligned, and the disassembly is completely garbled; Cause—the CAP instruction set uses independent numbering: return=0x7A (JVM 0xB1), new=0x8F (0xBB), invokevirtual/invokespecial/invokestatic/invokeinterface=0x8B-0x8E (JVM 0xB6-0xB9, and the operands are 2-byte constant pool tokens rather than symbolic references), aload_0=0x18; there are also typed variants that JVM does not have (getfield/putfield's _a/_b/_s/_i and _this/w, short-specific instructions such as sadd/ssub/smul, icmp/if_scmp*, iipush/sspush constant loading); no ldc family (constants go through sconst*/bipush/sspush/iipush + constant pool token), no multianewarray (only one-dimensional arrays), no monitorenter/monitorexit (single-threaded), no invokedynamic, no long/double arithmetic instructions (Classic 2.2.2; since 3.0.5 the language subset supports long); "no invokevirtual" is misinformation—all four call instruction types are present, and framework class instance methods (such as APDU.getBuffer) use invokevirtual; Countermeasure—use CAP-specific disassembly (caprunner instruction table/self-written scripts according to the table above), and when comparing with javap against the original .class, note that instructions with the same name have different numbers
- Import external reference broken link: Symptoms—the call site token cannot resolve the target method name, and the logic breaks at the framework call; Cause—the applet's dependencies are in external packages (framework packages/other CAPs), and this CAP only contains reference tokens; Countermeasure—the Import component lists external package AIDs, and external symbols are resolved via the Export component or SDK .exp files; the method names and constants of javacard.framework are matched against the SDK export files
- Card dump is ciphertext or fragments: Symptoms—the "CAP" magic number is wrong or components cannot be parsed, and the content has high entropy; Cause—the dump is encrypted by the card OS (GlobalPlatform domain keys/secure messaging encapsulation), or truncated/incomplete due to cross-region extraction; Countermeasure—first use entropy and magic numbers to determine plaintext/encrypted payloads; for encryption, follow the [[re-crypto-id]] / [[re-crypto-keys]] approach; use the Directory size table to check component integrity, and do the same when extracting from firmware
- Only restoring process while ignoring install and AID: Symptoms—the dispatch table is restored but the behavior does not match, or multiple applications in the same package are confused with each other; Cause—install()'s installation parameters (initialization data/holder data) are written to static fields or the file system, affecting subsequent branches; the Applet component can contain multiple applet AIDs (multiple instances in the same package); Countermeasure—trace parameter write points starting from install, and distinguish instances by AID; AID format: 5-byte RID + 0-11 byte PIX (total length 5-16, ISO/IEC 7816-5; those starting with 0xA0 are internationally registered RIDs, such as the common SIM/USIM application RID A000000087, with the PIX being application-defined)
- Card reading/writing authorization out of bounds: Symptoms—reading and writing a real card for analysis without confirming ownership and purpose; Countermeasure—third-party card contact requires authorization by default; prioritize laboratory samples and documentation ([[re-analyze/platform-tips]] highest principle)