Merge pull request #3326 from klks/master

Add native fmcos support to proxmark3
This commit is contained in:
Iceman
2026-05-24 13:23:27 +02:00
committed by GitHub
7 changed files with 4726 additions and 0 deletions
+1
View File
@@ -397,6 +397,7 @@ set (TARGET_SOURCES
${PM3_ROOT}/client/src/cmdhfepa.c
${PM3_ROOT}/client/src/cmdhffelica.c
${PM3_ROOT}/client/src/cmdhffido.c
${PM3_ROOT}/client/src/cmdhffmcos.c
${PM3_ROOT}/client/src/cmdhffudan.c
${PM3_ROOT}/client/src/cmdhfgallagher.c
${PM3_ROOT}/client/src/cmdhfgst.c
+1
View File
@@ -738,6 +738,7 @@ SRCS = mifare/aiddesfire.c \
cmdhfemrtd.c \
cmdhffelica.c \
cmdhffido.c \
cmdhffmcos.c \
cmdhffudan.c \
cmdhfgallagher.c \
cmdhfgst.c \
+2
View File
@@ -33,6 +33,7 @@
#include "cmdhfemrtd.h" // eMRTD
#include "cmdhffelica.h" // ISO18092 / FeliCa
#include "cmdhffido.h" // FIDO authenticators
#include "cmdhffmcos.h" // FMCOS CPU cards
#include "cmdhfsecc.h" // iClass SE Config Card
#include "cmdhffudan.h" // Fudan cards
#include "cmdhfgallagher.h" // Gallagher DESFire cards
@@ -588,6 +589,7 @@ static command_t CommandTable[] = {
{"emrtd", CmdHFeMRTD, AlwaysAvailable, "{ Machine Readable Travel Document... }"},
{"felica", CmdHFFelica, AlwaysAvailable, "{ ISO18092 / FeliCa RFIDs... }"},
{"fido", CmdHFFido, AlwaysAvailable, "{ FIDO and FIDO2 authenticators... }"},
{"fmcos", CmdHFFmcos, AlwaysAvailable, "{ FMCOS CPU cards... }"},
{"fudan", CmdHFFudan, AlwaysAvailable, "{ Fudan RFIDs... }"},
{"gallagher", CmdHFGallagher, AlwaysAvailable, "{ Gallagher DESFire RFIDs... }"},
{"gst", CmdHFGST, AlwaysAvailable, "{ Google Smart Tap passes... }"},
File diff suppressed because it is too large Load Diff
+26
View File
@@ -0,0 +1,26 @@
//-----------------------------------------------------------------------------
// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// See LICENSE.txt for the text of the license.
//-----------------------------------------------------------------------------
// Commands for FMCOS CPU smart cards (Fudan Microelectronics)
//-----------------------------------------------------------------------------
#ifndef CMDHFFMCOS_H__
#define CMDHFFMCOS_H__
#include "common.h"
int CmdHFFmcos(const char *Cmd);
#endif // CMDHFFMCOS_H__
+882
View File
@@ -0,0 +1,882 @@
# FMCOS CPU Smart Card Commands
<a id="Top"></a>
FMCOS (Fudan Microelectronics CPU OS) is an ISO14443-A CPU card operating system used in
Chinese transit and e-wallet cards (PBOC compliant). Common hardware: FM1208-09, FM1216.
All commands in this family are reachable via `hf fmcos <subcommand>`.
---
# Table of Contents
- [Card Information](#card-information)
- [info](#info)
- [select](#select)
- [File Management](#file-management)
- [erase](#erase)
- [create dir](#create-dir)
- [create file](#create-file)
- [create keyfile](#create-keyfile)
- [Data Access](#data-access)
- [read binary](#read-binary)
- [read record](#read-record)
- [write binary](#write-binary)
- [write record](#write-record)
- [append](#append)
- [key (write key)](#key-write-key)
- [Authentication](#authentication)
- [auth external](#auth-external)
- [auth internal](#auth-internal)
- [PIN Management](#pin-management)
- [pin verify](#pin-verify)
- [pin change](#pin-change)
- [pin reset](#pin-reset)
- [pin unblock](#pin-unblock)
- [Financial Operations](#financial-operations)
- [balance](#balance)
- [credit](#credit)
- [purchase](#purchase)
- [overdraft](#overdraft)
- [history](#history)
- [Card Lifecycle](#card-lifecycle)
- [block](#block)
- [unblock](#unblock)
- [File Access Reference](#file-access-reference)
- [Key Types Reference](#key-types-reference)
- [File Protection Modes](#file-protection-modes)
- [Access Rights Byte](#access-rights-byte)
- [Complete Wallet Session Walkthrough](#complete-wallet-session-walkthrough)
---
## Card Information
### info
Detect a FMCOS card and dump its file-system layout: MF, DFs, and EFs with their file
identifiers, types, sizes, and access attributes.
```
hf fmcos info
```
### select
SELECT a file or application directory by 2-byte file ID (hex) or by DF name (ASCII string).
After selection subsequent commands operate within that context.
```
hf fmcos select --id 3f00
hf fmcos select --id 3f01
hf fmcos select --name 77616C6C657454657374
```
| Flag | Description |
|------|-------------|
| `--id <hex>` | 2-byte file ID (e.g. `3f00` for MF, `3f01` for an ADF) |
| `--name <hex>` | DF name bytes as hex (up to 16 bytes, e.g. `77616C6C657454657374` = `walletTest`) |
| `-k` / `--keep` | Keep the RF field on after the command |
---
## File Management
### erase
ERASE DF -- delete all EFs and sub-DFs from the currently selected DF, but keep the DF
itself and its keyfile. Requires the MF or relevant DF to be selected first.
```
hf fmcos select --id 3f00
hf fmcos erase
```
### create dir
CREATE DF (directory / application directory).
```
hf fmcos create dir --id 3f01 --space 1500 --cperm f0 --eperm f0 --appid 95 --name 77616C6C657454657374
```
| Flag | Description |
|------|-------------|
| `--id <hex>` | 2-byte file ID for the new DF |
| `--space <hex>` | Total byte space to reserve (hex, e.g. `1500` = 5376 bytes) |
| `--cperm <hex>` | Create-permission byte (e.g. `f0` = always allowed) |
| `--eperm <hex>` | Erase-permission byte |
| `--appid <hex>` | 1-byte application SID / AID tag |
| `--name <hex>` | Optional DF name bytes as hex (up to 16 bytes, enables select-by-name) |
### create file
CREATE EF (elementary file) in the currently selected DF.
```
# Unprotected binary file
hf fmcos create file --id 0002 --type bin --size 50 --rperm f0 --wperm f0 --access ff
# Variable-length record file with MAC-only line protection
hf fmcos create file --id 0006 --type var --size 50 --rperm f0 --wperm f0 --access 7f --prot mac
# Loop (cyclic) file with MAC+encryption
hf fmcos create file --id 000a --type loop --size 210 --rperm f0 --wperm f0 --access 7f --prot enc
# Wallet/passbook balance file (EDEP) linked to loop file 0x0018
hf fmcos create file --id 0002 --type wallet --size 0208 --rperm f0 --wperm 00 --access 18
```
| Flag | Description |
|------|-------------|
| `--id <hex>` | 2-byte file ID |
| `--type <type>` | `bin` (0x28), `fix` (0x2A), `var` (0x2C), `loop` (0x2E), `wallet` (0x2F) |
| `--size <hex>` | File size in bytes (hex, e.g. `0208` = 520) |
| `--rperm <hex>` | Read/usage-rights byte; for wallet type this is the single usage rights byte |
| `--wperm <hex>` | Write permission byte; ignored for wallet type (always sent as 0x00) |
| `--access <hex>` | Access-rights byte; for wallet type this is the low byte of the linked loop EF's file ID |
| `--prot <mode>` | Line-protection mode: `none` (default), `mac`, `enc` (MAC+encrypt) |
**File type encodings:**
| Name | Code | Description |
|------|------|-------------|
| `bin` | 0x28 | Binary transparent file |
| `fix` | 0x2A | Fixed-length record file |
| `var` | 0x2C | Variable-length record file |
| `loop` | 0x2E | Cyclic (loop) file -- used for transaction logs |
| `wallet` | 0x2F | E-purse wallet / passbook balance file |
### create keyfile
CREATE KEYFILE in the currently selected DF. A DF must have a keyfile before any keys
can be written to it.
```
hf fmcos create keyfile --id 0000 --space 200 --dfsid 95 --perm f0
```
| Flag | Description |
|------|-------------|
| `--id <hex>` | 2-byte file ID for the keyfile (commonly `0000`) |
| `--space <hex>` | Space to reserve for key storage (bytes, hex, e.g. `200` = 512) |
| `--dfsid <hex>` | Parent DF SID (must match the DF's `--appid` value) |
| `--perm <hex>` | Key access permission byte |
---
## Data Access
### read binary
READ BINARY from the currently selected transparent (bin) EF.
```
# Plain read
hf fmcos read binary --p1 00 --p2 00 --len 10
# With MAC line-protection (verifies response MAC)
hf fmcos read binary --p1 00 --p2 00 --len 10 --prot mac --key 36363636363636363636363636363636
# With MAC+encryption (decrypts response)
hf fmcos read binary --p1 00 --p2 00 --len 10 --prot enc --key 36363636363636363636363636363636
```
| Flag | Description |
|------|-------------|
| `--p1 <hex>` | P1 byte (high byte of file offset) |
| `--p2 <hex>` | P2 byte (low byte of file offset) |
| `--len <hex>` | Number of bytes to read (Le) |
| `--prot <mode>` | `none`, `mac`, or `enc` |
| `--key <hex>` | Line-protection key (8 or 16 bytes, required when `--prot` is mac/enc) |
### read record
READ RECORD from the currently selected record or cyclic EF.
```
# Read record 1 from var file 0x06 (plain)
hf fmcos read record --rec 01 --fid 06 --len 10
# Read with MAC verification
hf fmcos read record --rec 01 --fid 07 --len 10 --prot mac --key 36363636363636363636363636363636
# Read with decryption
hf fmcos read record --rec 01 --fid 08 --len 10 --prot enc --key 36363636363636363636363636363636
```
| Flag | Description |
|------|-------------|
| `--rec <hex>` | Record number (P1); `00` = current record |
| `--fid <hex>` | File ID in P2 (upper 5 bits); `00` = current file |
| `--len <hex>` | Number of bytes to read |
| `--prot <mode>` | `none`, `mac`, or `enc` |
| `--key <hex>` | Line-protection key when prot is mac/enc |
### write binary
UPDATE BINARY -- write data to the currently selected transparent EF.
```
# Plain write
hf fmcos write binary --p1 00 --p2 00 --data 11121314151617181910
# Write with MAC
hf fmcos write binary --p1 00 --p2 00 --data 21222324252627282920 \
--prot mac --key 36363636363636363636363636363636
# Write with MAC+encryption (data is encrypted before sending)
hf fmcos write binary --p1 00 --p2 00 --data 31323334353637383930 \
--prot enc --key 36363636363636363636363636363636
```
| Flag | Description |
|------|-------------|
| `--p1 <hex>` | P1 byte (high offset byte) |
| `--p2 <hex>` | P2 byte (low offset byte) |
| `--data <hex>` | Data bytes to write |
| `--prot <mode>` | `none`, `mac`, or `enc` |
| `--key <hex>` | Line-protection key |
### write record
UPDATE RECORD -- write a record into the currently selected EF.
```
# Plain record write (P1=record number, P2=file-id<<3|04)
hf fmcos write record --rec 01 --fid 06 --data 5152535455565758595a
# With MAC
hf fmcos write record --rec 01 --fid 07 --data 6162636465666768696a \
--prot mac --key 36363636363636363636363636363636
# With MAC+encryption
hf fmcos write record --rec 01 --fid 08 --data 7172737475767778797a \
--prot enc --key 36363636363636363636363636363636
```
| Flag | Description |
|------|-------------|
| `--rec <hex>` | Record number (P1) |
| `--fid <hex>` | File ID for P2 encoding |
| `--data <hex>` | Record data bytes |
| `--prot <mode>` | `none`, `mac`, or `enc` |
| `--key <hex>` | Line-protection key |
### append
APPEND RECORD -- append a new record to a cyclic (loop) EF.
```
# Plain append
hf fmcos append --fid 0a --data 9192939495969798999a
# With MAC
hf fmcos append --fid 0b --data a1a2a3a4a5a6a7a8a9a0 \
--prot mac --key 36363636363636363636363636363636
# With MAC+encryption
hf fmcos append --fid 0c --data b1b2b3b4b5b6b7b8b9b0 \
--prot enc --key 36363636363636363636363636363636
```
| Flag | Description |
|------|-------------|
| `--fid <hex>` | File ID of the loop EF |
| `--data <hex>` | Record data bytes |
| `--prot <mode>` | `none`, `mac`, or `enc` |
| `--key <hex>` | Line-protection key |
### key (write key)
WRITE KEY -- write a key entry into the currently selected keyfile. Use `--op 01` to add
a new key, `--op 02` to update an existing one.
**Group A keys** (DES/3DES data keys with version + algorithm fields):
```
# Add InternalKey (0x34) at slot 0, always-free usage
hf fmcos key --op 01 --id 00 --type internal \
--usage f0 --change 02 --version 00 --algo 01 \
--key 2b8a438742c851566f02d881b09d58c0
# Add CreditKey (0x3F) at slot 1
hf fmcos key --op 01 --id 01 --type credit \
--usage f0 --change 02 --version 00 --algo 01 \
--key a9e6e145f5df09500a58eef8575d49db
# Add PurchaseKey (0x3E) at slot 1
hf fmcos key --op 01 --id 01 --type purchase \
--usage f0 --change 02 --version 00 --algo 01 \
--key eb18ce6986c820970e876219052ce0cf
```
**Group B keys** (PIN and external-auth keys with error counter):
```
# Add PIN key (0x3A) at slot 0 -- pin value is 2-6 raw bytes
hf fmcos key --op 01 --id 00 --type pin \
--usage f0 --followup 01 --errcount 33 \
--key 123456
# Add ExternalAuth key (0x39) at slot 0
hf fmcos key --op 01 --id 00 --type extauth \
--usage f0 --change 02 --followup 44 --errcount 33 \
--key f49dc1ba1b4deb5264718bc559106c0d
```
**Group C keys** (line-protection, unlock-PIN, change-PIN):
```
# Add line-protection key (0x36) at slot 0
hf fmcos key --op 01 --id 00 --type lineprotect \
--usage f0 --change 02 --errcount ff \
--key 8a021972bfec9d152ca9eb82d7d12c09
# Add unlock-PIN key (0x37)
hf fmcos key --op 01 --id 00 --type unlockpin \
--usage f0 --change 02 --errcount 33 \
--key d8f60fa2d791f3a658d27c0545824300
# Add change-PIN key (0x38)
hf fmcos key --op 01 --id 00 --type changepin \
--usage f0 --change 02 --errcount 33 \
--key fb487a6d1b7cbf1bf84c666b8338376e
```
**Key types:**
| Name | Code | Group | Description |
|------|------|-------|-------------|
| `desenc` | 0x30 | A | DES encrypt key |
| `desdec` | 0x31 | A | DES decrypt key |
| `desmac` | 0x32 | A | DES MAC key |
| `internal` | 0x34 | A | Internal-auth / TAC key |
| `overdraft` | 0x3C | A | Overdraft-limit key |
| `debit` | 0x3D | A | Debit (online transfer) key |
| `purchase` | 0x3E | A | Purchase / debit key |
| `credit` | 0x3F | A | Credit key |
| `extauth` | 0x39 | B | External-authentication key |
| `pin` | 0x3A | B | PIN code key |
| `lineprotect` | 0x36 | C | Line-protection key |
| `unlockpin` | 0x37 | C | Unlock-PIN key |
| `changepin` | 0x38 | C | Change-PIN key |
---
## Authentication
### auth external
EXTERNAL AUTHENTICATE -- authenticate the reader to the card. The card issues a challenge,
the reader encrypts it with the external-auth key, and sends the response back.
```
hf fmcos auth external --id 00 --key f49dc1ba1b4deb5264718bc559106c0d
```
| Flag | Description |
|------|-------------|
| `--id <hex>` | Key slot ID |
| `--key <hex>` | External-auth key (8 or 16 bytes) |
### auth internal
INTERNAL AUTHENTICATE -- authenticate the card to the reader. The reader sends an 8-byte
challenge (`--data`); the card responds with a DES-encrypted value that the reader verifies
offline.
```
hf fmcos auth internal --p1 00 --p2 00 --data 0102030405060708
```
| Flag | Description |
|------|-------------|
| `--p1 <hex>` | P1 byte (typically `00`) |
| `--p2 <hex>` | P2 byte (typically `00`) |
| `--data <hex>` | 8-byte challenge sent to the card |
---
## PIN Management
### pin verify
VERIFY PIN -- present the PIN code to the card to unlock PIN-gated operations.
PIN is 2-6 raw bytes.
```
hf fmcos pin verify --id 00 --pin 123456
```
| Flag | Description |
|------|-------------|
| `--id <hex>` | PIN key slot ID |
| `--pin <hex>` | PIN bytes (2-6 bytes) |
### pin change
CHANGE PIN -- change the PIN using the current (old) PIN for authorization.
```
hf fmcos pin change --id 00 --old 123456 --new 13371337
```
| Flag | Description |
|------|-------------|
| `--id <hex>` | PIN key slot ID |
| `--old <hex>` | Current PIN (2-6 bytes) |
| `--new <hex>` | New PIN (2-6 bytes) |
### pin reset
RESET PIN -- set a new PIN using the change-PIN key MAC for authorization (no old PIN needed).
The command computes a MAC over the new PIN using the change-PIN key and sends it to the card.
```
hf fmcos pin reset --id 00 --pin 13371337 \
--key fb487a6d1b7cbf1bf84c666b8338376e
```
| Flag | Description |
|------|-------------|
| `--id <hex>` | PIN key slot ID |
| `--pin <hex>` | New PIN (2-6 bytes) |
| `--key <hex>` | Change-PIN key (16 bytes); MAC = DES-MAC(new_pin, XOR_halves(key)) |
### pin unblock
UNBLOCK PIN -- clear the PIN blocked state and set a new PIN.
The new PIN is encrypted with the unlock-PIN key and a GET CHALLENGE IV.
```
hf fmcos pin unblock --id 00 --pin 123456 \
--key d8f60fa2d791f3a658d27c054582430e
```
| Flag | Description |
|------|-------------|
| `--id <hex>` | PIN key slot ID |
| `--pin <hex>` | New PIN (2-6 bytes) |
| `--key <hex>` | Unlock-PIN key (16 bytes) |
---
## Financial Operations
FMCOS implements a two-phase PBOC e-wallet protocol. Phase 1 initializes the transaction
and returns card-computed data (old balance, serial number, random seed, MAC1). Phase 2
commits the transaction with a terminal-computed MAC2 and receives a Transaction
Authentication Code (TAC) from the card which the terminal verifies.
**Keys involved:**
| Key | Role |
|-----|------|
| Credit key (16 B) | Derive the session process key for credit operations |
| Purchase key (16 B) | Derive the session process key for purchase/debit |
| Overdraft key (16 B) | Derive the session process key for overdraft-limit updates |
| Internal key / DTK (16 B) | Verify TAC: all financial commands use `tac_key = XOR(ikey[0:8], ikey[8:16])` → DES-CBC-MAC |
**Terminal ID:** 6 bytes identifying the terminal. Use any fixed value for testing (e.g. `666666666666`).
### balance
GET BALANCE -- read the current balance from the wallet or passbook balance file.
```
hf fmcos balance --type wallet
hf fmcos balance --type passbook
```
| Flag | Description |
|------|-------------|
| `--type <type>` | `wallet` (0x02) or `passbook` (0x01) |
| `-k` / `--keep` | Keep field on |
**APDU:** `80 5C 00 <type> 04`
**Response:** 4-byte big-endian balance.
Example output:
```
[=] Balance (wallet): 1000 (0x000003E8)
```
### credit
ADD CREDIT -- two-phase credit transaction.
**Phase 1** (`INS 50`, P1=00): send key ID, amount, terminal ID. Card returns old balance,
transaction serial, key version, algo ID, random seed, and MAC1. The implementation:
- Derives the process key: `encrypt(random[4] | serial[2], credit_key)` -> first 8 bytes
- Verifies MAC1: `DES-CBC-MAC(old_bal[4] | amount[4] | type[1] | terminal[6], process_key)`
**Phase 2** (`INS 52`, P1=00): send date, time, MAC2. Card returns TAC. The implementation:
- Computes MAC2: `DES-CBC-MAC(amount[4] | type[1] | terminal[6] | date[4] | time[3], process_key)`
- Verifies TAC: `DES-CBC-MAC(new_bal[4] | serial[2] | MAC2_buf[18], tac_key)`
```
hf fmcos credit --type wallet --id 01 --amount 1000 \
--terminal 666666666666 \
--key a9e6e145f5df09500a58eef8575d49db \
--ikey 2b8a438742c851566f02d881b09d58c0
hf fmcos credit --type passbook --id 01 --amount 2000 \
--terminal 666666666666 \
--key a9e6e145f5df09500a58eef8575d49db \
--ikey 2b8a438742c851566f02d881b09d58c0
```
| Flag | Description |
|------|-------------|
| `--type <type>` | `wallet` or `passbook` |
| `--id <n>` | Credit key slot ID (1 byte decimal) |
| `--amount <n>` | Credit amount (integer, units match card configuration) |
| `--terminal <hex>` | Terminal ID (6 bytes) |
| `--key <hex>` | Credit key (16 bytes) |
| `--ikey <hex>` | Internal key (16 bytes) for TAC verification |
| `-k` / `--keep` | Keep field on after command |
> **Note**: FMCOS resets the card's security status after each completed financial transaction.
> Re-verify PIN before each credit or purchase operation (SW:6985 indicates the security status was cleared).
Example output:
```
[=] MAC1 OK old balance 0
[+] TAC OK new balance 1000
[+] SW: 9000 - Success
```
### purchase
PURCHASE -- two-phase debit transaction from wallet or passbook.
**Phase 1** (`INS 50`, P1=01): send key ID, amount, terminal. Card returns old balance,
offline serial, overdraft limit, key version, algo, random seed (15 bytes total).
- Derives process key: `encrypt(random[4] | offline_serial[2] | tx_serial[2], purchase_key)[:8]`
- Computes MAC1: `DES-CBC-MAC(amount[4] | tx_type[1] | terminal[6] | date[4] | time[3], process_key)`
**Phase 2** (`INS 54`, P1=01, P2=00): send tx serial, date, time, MAC1. Card returns TAC[4]+MAC2[4].
- Verifies TAC: `DES-CBC-MAC(amount[4] | tx_type[1] | terminal[6] | serial[4] | date[4] | time[3], tac_key)`
Transaction type byte: 0x05 for passbook purchase, 0x06 for wallet purchase.
```
# Wallet purchase of 50 units
hf fmcos purchase --type wallet --id 01 --amount 50 \
--terminal 666666666666 \
--key eb18ce6986c820970e876219052ce0cf \
--ikey 2b8a438742c851566f02d881b09d58c0 \
--serial 01020304
# Passbook purchase (no explicit serial -- defaults to 00000001)
hf fmcos purchase --type passbook --id 01 --amount 50 \
--terminal 666666666666 \
--key eb18ce6986c820970e876219052ce0cf \
--ikey 2b8a438742c851566f02d881b09d58c0
```
| Flag | Description |
|------|-------------|
| `--type <type>` | `wallet` or `passbook` |
| `--id <n>` | Purchase key slot ID |
| `--amount <n>` | Debit amount |
| `--terminal <hex>` | Terminal ID (6 bytes) |
| `--key <hex>` | Purchase key (16 bytes) |
| `--ikey <hex>` | Internal key (16 bytes) for TAC verification |
| `--serial <hex>` | 4-byte transaction serial (optional, default `00000001`) |
| `-k` / `--keep` | Keep field on |
Example output:
```
[=] Old balance: 1000
[+] TAC OK new balance 950
[+] SW: 9000 - Success
```
### overdraft
UPDATE OVERDRAFT LIMIT -- two-phase overdraft-limit update on the passbook.
**Phase 1** (`INS 50`, P1=04, P2=01): send key ID and terminal. Card returns old balance,
online serial, old limit, key version, algo, random seed, and MAC1 (19 bytes total).
- Derives process key: `encrypt(random[4] | serial[2], overdraft_key)[:8]`
- Verifies MAC1: `DES-CBC-MAC(old_bal[4] | old_limit[3] | 0x07[1] | terminal[6], process_key)`
**Phase 2** (`INS 58`, P1=00, P2=00): send new limit (3 bytes), date, time, MAC2.
- Computes MAC2: `DES-CBC-MAC(new_limit[3] | 0x07[1] | terminal[6] | date[4] | time[3], process_key)`
- Card returns TAC[4]. When `--ikey` is provided the TAC is verified:
`DES-CBC-MAC(XOR(ikey[0:8], ikey[8:16]), tac_bal[4] | online_serial[2] | new_limit[3] | 0x07[1] | terminal[6] | date[4] | time[3])`
where `tac_bal = old_balance + new_limit - old_od_limit`. The card stores
`actual_funds + overdraft_limit` as its balance field, so when the limit changes the new
stored balance shifts by the limit delta.
```
hf fmcos overdraft --id 01 --limit 1000 \
--terminal 666666666666 \
--key 94f63c4fae5e4977d749928ad12bc128 \
--ikey 659a500f0f1fce35b6884bdff966576a
```
| Flag | Description |
|------|-------------|
| `--id <hex>` | Overdraft key slot ID |
| `--limit <n>` | New overdraft limit (24-bit integer, max 16777215) |
| `--terminal <hex>` | Terminal ID (6 bytes) |
| `--key <hex>` | Overdraft key (16 bytes) |
| `--ikey <hex>` | Internal key DTK (16 bytes) for TAC verification (optional) |
| `-k` / `--keep` | Keep field on |
Example output:
```
[=] Old balance: 1000 old overdraft limit: 0
[=] MAC1 OK
[+] Overdraft limit updated to 1000
[+] TAC OK aabbccdd
[+] SW: 9000 - Success
```
### history
READ TRANSACTION HISTORY -- decode all records in the loop (cyclic) EF used as a transaction
log. The card appends a 23-byte record to the loop file after every financial operation.
```
# Wallet transaction log (loop file SFI 0x18 in the example setup)
hf fmcos history --fid 18
# Passbook transaction log, read up to 20 records
hf fmcos history --fid 19 --count 20
```
| Flag | Description |
|------|-------------|
| `--fid <hex>` | Loop file SFI byte (1 byte, e.g. `18` for wallet loop, `19` for passbook loop) |
| `--count <n>` | Max records to read (default 10; `0` = read all, up to 255) |
| `-k` / `--keep` | Keep field on after command |
| `-a` / `--apdu` | Show raw APDU traffic |
**Record layout (23 bytes):**
| Offset | Length | Field | Notes |
|--------|--------|-------|-------|
| 0 | 2 | Serial | Transaction serial number (big-endian) |
| 2 | 3 | OD limit | Overdraft limit at time of transaction |
| 5 | 4 | Amount | Transaction amount (big-endian) |
| 9 | 1 | Type | Transaction type byte |
| 10 | 6 | Terminal | Terminal ID |
| 16 | 4 | Date | BCD date `YYYYMMDD` |
| 20 | 3 | Time | BCD time `HHMMSS` |
**Transaction type codes:**
| Code | Description |
|------|-------------|
| `0x04` | Passbook cash withdrawal |
| `0x05` | Passbook purchase |
| `0x06` | Wallet purchase |
| `0x07` | Overdraft limit update |
| `0x09` | Compound purchase |
Example output:
```
# | Date | Time | Type | Amount | OD Limit | Serial | Terminal
---+------------+----------+--------------+------------+----------+--------+-------------------
1 | 2026-05-24 | 14:30:22 | WL purchase | 50 | 0 | 000002 | 66 66 66 66 66 66
2 | 2026-05-24 | 14:28:05 | WL purchase | 1000 | 0 | 000001 | 66 66 66 66 66 66
[+] 2 records
```
> **Note**: Record 1 is always the most recently written entry. Reading stops automatically
> when the card returns a non-9000 SW (record number exceeds log capacity).
---
## Card Lifecycle
### block
BLOCK the entire card (CARD BLOCK, `INS 16`) or the currently selected application
(APP BLOCK, `INS 1E`). Uses the line-protection key to compute a packet MAC over the
command header via GET CHALLENGE.
```
# Block the card permanently
hf fmcos block --card --key 8a021972bfec9d152ca9eb82d7d12c09
# Block application temporarily (default)
hf fmcos block --app --key 8a021972bfec9d152ca9eb82d7d12c09
# Block application permanently
hf fmcos block --app --perm --key 8a021972bfec9d152ca9eb82d7d12c09
```
| Flag | Description |
|------|-------------|
| `--card` | Block the whole card |
| `--app` | Block the current application |
| `--perm` | Permanent block (default is temporary for `--app`) |
| `--key <hex>` | Line-protection key (8 or 16 bytes) |
### unblock
UNBLOCK the currently selected application (APP UNBLOCK, `INS 18`).
Same MAC pattern as block.
```
hf fmcos unblock --key 8a021972bfec9d152ca9eb82d7d12c09
```
| Flag | Description |
|------|-------------|
| `--key <hex>` | Line-protection key (8 or 16 bytes) |
---
## File Access Reference
### MF (Master File)
- Automatically selected on card reset.
- Can be selected at any DF level using FID `3F00` or the MF name.
- Default name assigned at creation: `1PAY.SYS.DDF01`.
### DF (Directory File)
- Selected by file identifier (FID) or directory name (DF name).
### Binary EF (type `0x28`)
- Read with READ BINARY when the read condition is satisfied.
- Updated with UPDATE BINARY when the write condition is satisfied.
### Fixed-Length Record EF (type `0x2A`)
- Read a specific record with READ RECORD when the read condition is satisfied.
- Update a specific record with UPDATE RECORD when the write condition is satisfied.
- Append a record at the end with APPEND RECORD when the append condition is satisfied.
### Cyclic (Loop) EF (type `0x2E`)
- Read a specific record with READ RECORD when the read condition is satisfied.
- Prepend a new record at the front with APPEND RECORD when the append condition is satisfied.
- When the file is full, the oldest record is automatically overwritten.
- The most recently written record always has record number 1; the prior record is number 2; and so on.
### Wallet/Purse EF (EDEP/EP, type `0x2F`)
- GET BALANCE reads the current balance.
- Under key control: CREDIT FOR LOAD (圈存), DEBIT FOR PURCHASE / CASH WITHDRAW (消费/取现),
DEBIT FOR UNLOAD (圈提), and UPDATE OVERDRAFT LIMIT (修改透支限额).
### Variable-Length Record EF (type `0x2C`)
- Read a specific record with READ RECORD when the read condition is satisfied.
- Update an existing record with UPDATE RECORD; append a new record with APPEND RECORD.
- **TLV format:** each record is `Tag (1 byte) | Length (1 byte) | Value (Length bytes)`.
Tag `0x00` is used by FMCOS for the standard record wrapper.
- UPDATE RECORD requires the new record's total TLV length to equal the original; otherwise the
command fails (SW `6A83`).
### KEY File (type `0x3F`)
- Only one KEY file is allowed per DF/MF; it **must be created before any other file** in that directory.
- Key data can **never be read out** from the card.
- While a DF/MF has no KEY file (and no other files), any file can be created and accessed without
access-rights restrictions. Once you leave and re-enter that directory, access rights are enforced.
- Each key is stored as a variable-length record: `key_data + 8 header bytes`.
- Triple-DES (16-byte) key record: **24 bytes** total.
- Single-DES (8-byte) key record: **16 bytes** total.
- WRITE KEY adds a new key (when the "add key" permission is satisfied) or updates key data
(when that specific key's "change" permission is satisfied).
- Key data can only be used when the key's "use" permission is satisfied.
### Key Independence
Each key is bound to exactly one function (encrypt, decrypt, MAC, etc.) and cannot be used
for any other function — including keys that generate, derive, or transport other card keys.
### PIN Key
- VERIFY checks the PIN; PIN CHANGE / UNBLOCK changes and optionally unlocks it.
- On a successful VERIFY, the security-status register is updated to the post-condition value
stored in the PIN key record.
- An error counter decrements on every failed VERIFY; when it reaches 0 the PIN key is locked.
### Unlock-PIN Key
- UNBLOCK verifies the unlock password and simultaneously unlocks a PIN key that was blocked
by repeated wrong attempts, while also setting a new PIN.
- Once the unlock-PIN key's own error counter reaches 0, it is permanently locked with no recovery.
### External Authentication Key
- EXTERNAL AUTHENTICATE can be executed when the key's use condition is satisfied.
- WRITE KEY updates the key when the change condition is satisfied.
- Once locked by exhausting its error counter, it **cannot be unlocked**.
---
## Key Types Reference
FMCOS keys are stored in a keyfile EF. Each key entry begins with a type byte that
encodes both the functional role (high nibble = 0x3x) and the line-protection mode
OR-ed in by `--prot` when writing the key itself.
| Type name | Byte | Role |
|-----------|------|------|
| `desenc` | 0x30 | 3DES ECB encryption |
| `desdec` | 0x31 | 3DES ECB decryption |
| `desmac` | 0x32 | DES MAC generation |
| `internal` | 0x34 | Internal-authenticate / TAC key |
| `lineprotect` | 0x36 | Line-protection key (MAC-only or MAC+enc mode) |
| `unlockpin` | 0x37 | Authorize PIN unblock |
| `changepin` | 0x38 | Authorize PIN reset |
| `extauth` | 0x39 | External-authenticate key |
| `pin` | 0x3A | PIN code key |
| `overdraft` | 0x3C | Overdraft-limit session key |
| `debit` | 0x3D | Online-transfer (debit) session key |
| `purchase` | 0x3E | Purchase / offline-debit session key |
| `credit` | 0x3F | Credit session key |
---
## File Protection Modes
When creating a file or writing with protection, the `--prot` flag selects the mode:
| Mode | Value | Description |
|------|-------|-------------|
| `none` | 0x00 | No line protection |
| `mac` | 0x80 | MAC-only; command includes 4-byte packet MAC, response includes MAC |
| `enc` | 0xC0 | MAC + encryption; data encrypted, 4-byte MAC appended |
MAC is computed by `fmcos_packet_mac`: DES(8-byte key) or 3DES-Retail-MAC(16-byte key)
over `CLA|INS|P1|P2|Lc[|data]` with a GET CHALLENGE response as the CBC IV.
---
## Access Rights Byte
The access-rights byte passed to `create file` controls whether line protection is needed
and which key slot guards read / write access.
```
Bit 7 (MSB): 1 = protection NOT required, 0 = protection required
Bit 6-5: reserved
Bits 4-3: read key index (11=key0, 10=key1, 01=key2, 00=key3)
Bits 2-1 (LSB): write key index (11=key0, 10=key1, 01=key2, 00=key3)
```
Common values:
| Value | Meaning |
|-------|---------|
| `ff` | No protection required, key0 for both read/write |
| `7f` | Protection required, key0 for both read/write |
| `f0` | No protection required (permission byte for directories/keys) |
+697
View File
@@ -0,0 +1,697 @@
# FMCOS Wallet Walkthrough
When commands are chained in a session the RF field must stay on between them.
Add `-k` (keep field on) to every command in a chain except the last one.
## Keys used throughout
| Variable | Hex |
|---|---|
| `external_auth_key` | `f49dc1ba1b4deb52647186bc59106c0d` |
| `internal_key` | `2b8a438742c851566f02d881b09d58c0` |
| `line_protection_key` | `8a021972bfec9d152ca9eb82d7d12c09` |
| `unlock_pin_key` | `d8f60fa2d791f3a658d27c05458243ed` |
| `change_pin_key` | `fb487a6d1b7cbf1bf84c666b8338376e` |
| `purchase_key` | `eb18ce6986c820970e876219052ce0cf` |
| `credit_key` | `a9e6e145f5df09500a58eef8575d49db` |
| `debit_key` | `97fb4eda4b5237035946ee62d325d909` |
| `overdraw_limit_key` | `94f63c4fae5e4977d749928ad12bc128` |
| `int_enc` | `c4608b786af1992343e91a076670ae7c` |
| `int_dec` | `b8d4190c76856901fc686f36ab9b1ce0` |
| `int_mac` | `46a3ea8b254ee2749cc681050fd0dbcc` |
| PIN (`\x12\x34\x56`) | `123456` (3 bytes, raw BCD) |
| New PIN (`\x13\x37\x13\x37`) | `13371337` (4 bytes, raw BCD) |
| Terminal ID | `666666666666` (6 bytes) |
---
## 1. reset — select MF and erase DF
Select the Master File then erase the application directory from a previous run:
```
hf fmcos select --id 3f00 -k
hf fmcos erase
```
`hf fmcos erase` sends INS=0x0E to delete the currently-selected DF and all
its children. Run it only when the card already has a DF selected (the MF
itself cannot be erased this way).
---
## 2. setup — create directory, keyfile, keys, loop files, balance files
### 2a. Create the application directory (ADF)
> **Note**: `--space` and `--size` arguments are parsed as **hexadecimal**.
> `--space 1500` = 0x1500 = 5376 bytes, `--size 0208` = 0x0208 = 520 bytes.
`77616C6C657454657374` is the hex encoding of ASCII `walletTest`.
```
hf fmcos select --id 3f00 -k
hf fmcos create dir --id 3F01 --space 1500 --cperm F0 --eperm F0 --appid 95 --name 77616C6C657454657374 -k
```
### 2b. Select the new ADF by name
```
hf fmcos select --name 77616C6C657454657374 -k
```
All subsequent setup commands assume this DF remains selected (field stays on).
### 2c. Create the keyfile
```
hf fmcos create keyfile --id 0000 --space 200 --dfsid 95 --perm F0 -k
```
### 2d. Write keys
- `--op 01` = P1, authorization operation code (0x01 = add/update)
- `--id 00` = P2, key slot to write (0x00 = auto-assign next slot)
- `--usage F0` = usage rights byte
All key writes continue in the same session so every command carries `-k`.
**Key 0 — DES Encrypt (int_enc)**
```
hf fmcos key --op 01 --id 00 --usage F0 --type desenc --change F4 --version 05 --algo 98 --key c4608b786af1992343e91a076670ae7c -k
```
**Key 1 — DES Decrypt (int_dec)**
```
hf fmcos key --op 01 --id 00 --usage F0 --type desdec --change F4 --version 05 --algo 98 --key b8d4190c76856901fc686f36ab9b1ce0 -k
```
**Key 2 — DES MAC (int_mac)**
```
hf fmcos key --op 01 --id 00 --usage F0 --type desmac --change F4 --version 05 --algo 98 --key 46a3ea8b254ee2749cc681050fd0dbcc -k
```
**Key 3 — Internal Key (internal_key)**
```
hf fmcos key --op 01 --id 00 --usage F0 --type internal --change 02 --version 00 --algo 01 --key 2b8a438742c851566f02d881b09d58c0 -k
```
**Key 4 — File Line Protection Key (line_protection_key)**
Group C type — uses `--change` and `--errcount`.
```
hf fmcos key --op 01 --id 00 --usage F0 --type lineprotect --change 02 --errcount 33 --key 8a021972bfec9d152ca9eb82d7d12c09 -k
```
**Key 5 — Unlock PIN Key (unlock_pin_key)**
```
hf fmcos key --op 01 --id 00 --usage F0 --type unlockpin --change 02 --errcount 33 --key d8f60fa2d791f3a658d27c05458243ed -k
```
**Key 6 — Change PIN Key (change_pin_key)**
```
hf fmcos key --op 01 --id 00 --usage F0 --type changepin --change 02 --errcount 33 --key fb487a6d1b7cbf1bf84c666b8338376e -k
```
**Key 7 — External Authentication Key (external_auth_key)**
Group B type with `--change` — uses `--change`, `--followup`, and `--errcount`.
```
hf fmcos key --op 01 --id 00 --usage F0 --type extauth --change 02 --followup 44 --errcount 33 --key f49dc1ba1b4deb52647186bc59106c0d -k
```
**Key 8 — Purchase Key (purchase_key)**
```
hf fmcos key --op 01 --id 00 --usage F0 --type purchase --change 02 --version 00 --algo 01 --key eb18ce6986c820970e876219052ce0cf -k
```
**Key 9 — Credit Key (credit_key)**
```
hf fmcos key --op 01 --id 00 --usage F0 --type credit --change 02 --version 00 --algo 01 --key a9e6e145f5df09500a58eef8575d49db -k
```
**Key 10 — Debit Key (debit_key)**
```
hf fmcos key --op 01 --id 00 --usage F0 --type debit --change 02 --version 00 --algo 01 --key 97fb4eda4b5237035946ee62d325d909 -k
```
**Key 11 — Overdraw Limit Key (overdraw_limit_key)**
```
hf fmcos key --op 01 --id 00 --usage F0 --type overdraft --change 02 --version 00 --algo 01 --key 94f63c4fae5e4977d749928ad12bc128 -k
```
**Key 12 — PIN Key (pin_code)**
The PIN `\x12\x34\x56` is 3 raw BCD bytes.
Group B type — uses `--followup` and `--errcount`.
```
hf fmcos key --op 01 --id 00 --usage F0 --type pin --followup 01 --errcount 33 --key 123456 -k
```
### 2e. Create loop files for transaction logging
Loop file 0x0018 (to be linked to the wallet balance file):
```
hf fmcos create file --id 0018 --type loop --size 0517 --rperm F0 --wperm EF --access FF -k
```
Loop file 0x0019 (to be linked to the passbook balance file):
```
hf fmcos create file --id 0019 --type loop --size 0517 --rperm F0 --wperm EF --access FF -k
```
### 2f. Create wallet and passbook balance files
Wallet balance file (EF 0x0002, linked to loop file 0x0018):
```
hf fmcos create file --id 0002 --type wallet --size 0208 --rperm F0 --wperm 00 --access 18 -k
```
Passbook balance file (EF 0x0001, linked to loop file 0x0019):
```
hf fmcos create file --id 0001 --type wallet --size 0208 --rperm F0 --wperm 00 --access 19
```
The last command drops the field to end the setup session.
---
## 3. verify_pin — verify the PIN
The PIN `\x12\x34\x56` is 3 raw BCD bytes.
```
hf fmcos select --name 77616C6C657454657374 -k
hf fmcos pin verify --id 00 --pin 123456
```
---
## 4. get_balance — read wallet and passbook balances
```
hf fmcos select --name 77616C6C657454657374 -k
hf fmcos pin verify --id 00 --pin 123456 -k
hf fmcos balance --type wallet -k
hf fmcos balance --type passbook
```
The command prints the 4-byte big-endian balance in decimal and hex.
---
## 5. add_money — credit (load funds)
Credit key index is 9 (written ninth in step 2d, 0-based index = 9 = 0x09).
Credit 1000 units to the wallet, then 2000 to the passbook in one session:
```
hf fmcos select --name 77616C6C657454657374 -k
hf fmcos pin verify --id 00 --pin 123456 -k
hf fmcos credit --type wallet --id 09 --amount 1000 --terminal 666666666666 --key a9e6e145f5df09500a58eef8575d49db --ikey 2b8a438742c851566f02d881b09d58c0 -k
hf fmcos pin verify --id 00 --pin 123456 -k
hf fmcos credit --type passbook --id 09 --amount 2000 --terminal 666666666666 --key a9e6e145f5df09500a58eef8575d49db --ikey 2b8a438742c851566f02d881b09d58c0
```
> **Note**: FMCOS resets the card's internal security status after each completed
> financial transaction (Phase 1 + Phase 2). PIN verification must be repeated
> before each credit or purchase operation, even within the same RF session.
`--key` is the credit_key (16-byte 3DES key used to derive the process key).
`--ikey` is the internal_key used for TAC verification.
---
## 6. spend_wallet / spend_passbook — purchase (deduct funds)
Purchase key index is 8 (0-based index = 8 = 0x08).
Purchase (deduct) 50 units from the wallet:
```
hf fmcos select --name 77616C6C657454657374 -k
hf fmcos pin verify --id 00 --pin 123456 -k
hf fmcos purchase --type wallet --id 08 --amount 50 --terminal 666666666666 --key eb18ce6986c820970e876219052ce0cf --ikey 2b8a438742c851566f02d881b09d58c0
```
Purchase 50 units from the passbook:
```
hf fmcos select --name 77616C6C657454657374 -k
hf fmcos pin verify --id 00 --pin 123456 -k
hf fmcos purchase --type passbook --id 08 --amount 50 --terminal 666666666666 --key eb18ce6986c820970e876219052ce0cf --ikey 2b8a438742c851566f02d881b09d58c0
```
`--serial` (optional) sets the 4-byte transaction serial number; defaults to
`00000001` when omitted.
---
## 7. withdraw_money — cash withdrawal (NOT SUPPORTED)
Cash withdrawal uses INS=0x50 P1=0x02. There is currently no `hf fmcos`
command for this operation.
---
## 8. pin_block — deliberately block the PIN
```
hf fmcos select --name 77616C6C657454657374 -k
hf fmcos pin verify --id 00 --pin 11223344 -k
hf fmcos pin verify --id 00 --pin 11223344 -k
hf fmcos pin verify --id 00 --pin 11223344 -k
hf fmcos pin verify --id 00 --pin 11223344
```
After the error counter reaches zero the card blocks the PIN and returns
SW=`6983`.
---
## 9. pin_unblock — restore a blocked PIN
Unlock PIN key index is 5 (0-based index = 5 = 0x05).
```
hf fmcos select --name 77616C6C657454657374 -k
hf fmcos pin unblock --id 00 --pin 123456 --key d8f60fa2d791f3a658d27c05458243ed
```
---
## 10. online_debit — online transfer (NOT SUPPORTED)
Online debit uses INS=0x50 P1=0x05 (initialize) and INS=0x54 P1=0x03
(commit). There is currently no `hf fmcos` command for this operation.
---
## 11. update_overdraft — set the overdraft limit
Overdraft key index is 11 (0-based index = 11 = 0x0B).
```
hf fmcos select --name 77616C6C657454657374 -k
hf fmcos pin verify --id 00 --pin 123456 -k
hf fmcos overdraft --id 0B --limit 1000 --terminal 666666666666 --key 94f63c4fae5e4977d749928ad12bc128 --ikey 2b8a438742c851566f02d881b09d58c0
```
`--limit` is a 24-bit unsigned integer (maximum 16777215).
`--ikey` is the internal key (DTK, 16 bytes). When provided the card's 4-byte TAC response is
verified using DES-CBC-MAC with `tac_key = XOR(ikey[0:8], ikey[8:16])` over:
`balance[4] | online_serial[2] | new_limit[3] | 0x07[1] | terminal[6] | date[4] | time[3]`
---
## 12. get_history — read transaction history
Read the most recent 10 records from the wallet loop file (SFI 0x18):
```
hf fmcos select --name 77616C6C657454657374 -k
hf fmcos history --fid 18
```
Read up to 20 records from the passbook loop file (SFI 0x19):
```
hf fmcos select --name 77616C6C657454657374 -k
hf fmcos history --fid 19 --count 20
```
Example output (after a credit of 1000 and a purchase of 50):
```
# | Date | Time | Type | Amount | OD Limit | Serial | Terminal
---+------------+----------+--------------+------------+----------+--------+-------------------
1 | 2026-05-24 | 14:30:22 | WL purchase | 50 | 0 | 000002 | 66 66 66 66 66 66
2 | 2026-05-24 | 14:28:05 | WL purchase | 1000 | 0 | 000001 | 66 66 66 66 66 66
[+] 2 records
```
The SFI bytes (`18`, `19`) match the loop file IDs created in step 2e. Loop file record 1 is
always the most recently written entry.
---
## 13. pin_change — change PIN using old PIN authorization
```
hf fmcos select --name 77616C6C657454657374 -k
hf fmcos pin verify --id 00 --pin 123456 -k
hf fmcos pin change --id 00 --old 123456 --new 13371337 -k
hf fmcos pin verify --id 00 --pin 13371337 -k
hf fmcos pin change --id 00 --old 13371337 --new 123456 -k
hf fmcos pin verify --id 00 --pin 123456
```
---
## 14. pin_reset — set new PIN using change-PIN key (no old PIN needed)
Change PIN key index is 6 (0-based index = 6 = 0x06).
```
hf fmcos select --name 77616C6C657454657374 -k
hf fmcos pin verify --id 00 --pin 123456 -k
hf fmcos pin reset --id 00 --pin 13371337 --key fb487a6d1b7cbf1bf84c666b8338376e -k
hf fmcos pin verify --id 00 --pin 13371337 -k
hf fmcos pin change --id 00 --old 13371337 --new 123456 -k
hf fmcos pin verify --id 00 --pin 123456
```
---
## Complete session (sequential)
The following block shows a full session in order: reset, setup, load money,
spend, and check balance. Each sub-section starts a new session (field
activates) and ends when the last command drops the field.
> **Note**: `--space` and `--size` are hex — `--space 1500` = 5376 bytes, `--space 400` = 1024 bytes, `--size 0517` = 1303 bytes, `--size 0208` = 520 bytes.
```
# ── 1. Reset ────────────────────────────────────────────────────────────────
hf fmcos select --id 3f00 -k
hf fmcos erase
# ── 2. Create ADF ────────────────────────────────────────────────────────────
hf fmcos select --id 3f00 -k
hf fmcos create dir --id 3F01 --space 1500 --cperm F0 --eperm F0 --appid 95 --name 77616C6C657454657374 -k
# ── 3. Select ADF ────────────────────────────────────────────────────────────
hf fmcos select --name 77616C6C657454657374 -k
# ── 4. Create keyfile ────────────────────────────────────────────────────────
hf fmcos create keyfile --id 0000 --space 400 --dfsid 95 --perm F0 -k
# ── 5. Write 13 keys (indexes 0-12) ─────────────────────────────────────────
hf fmcos key --op 01 --id 00 --usage F0 --type desenc --change F4 --version 05 --algo 98 --key c4608b786af1992343e91a076670ae7c -k
hf fmcos key --op 01 --id 00 --usage F0 --type desdec --change F4 --version 05 --algo 98 --key b8d4190c76856901fc686f36ab9b1ce0 -k
hf fmcos key --op 01 --id 00 --usage F0 --type desmac --change F4 --version 05 --algo 98 --key 46a3ea8b254ee2749cc681050fd0dbcc -k
hf fmcos key --op 01 --id 00 --usage F0 --type internal --change 02 --version 00 --algo 01 --key 2b8a438742c851566f02d881b09d58c0 -k
hf fmcos key --op 01 --id 00 --usage F0 --type lineprotect --change 02 --errcount 33 --key 8a021972bfec9d152ca9eb82d7d12c09 -k
hf fmcos key --op 01 --id 00 --usage F0 --type unlockpin --change 02 --errcount 33 --key d8f60fa2d791f3a658d27c05458243ed -k
hf fmcos key --op 01 --id 00 --usage F0 --type changepin --change 02 --errcount 33 --key fb487a6d1b7cbf1bf84c666b8338376e -k
hf fmcos key --op 01 --id 00 --usage F0 --type extauth --change 02 --followup 44 --errcount 33 --key f49dc1ba1b4deb52647186bc59106c0d -k
hf fmcos key --op 01 --id 00 --usage F0 --type purchase --change 02 --version 00 --algo 01 --key eb18ce6986c820970e876219052ce0cf -k
hf fmcos key --op 01 --id 00 --usage F0 --type credit --change 02 --version 00 --algo 01 --key a9e6e145f5df09500a58eef8575d49db -k
hf fmcos key --op 01 --id 00 --usage F0 --type debit --change 02 --version 00 --algo 01 --key 97fb4eda4b5237035946ee62d325d909 -k
hf fmcos key --op 01 --id 00 --usage F0 --type overdraft --change 02 --version 00 --algo 01 --key 94f63c4fae5e4977d749928ad12bc128 -k
hf fmcos key --op 01 --id 00 --usage F0 --type pin --followup 01 --errcount 33 --key 123456 -k
# ── 6. Create loop files ─────────────────────────────────────────────────────
hf fmcos create file --id 0018 --type loop --size 0517 --rperm F0 --wperm EF --access FF -k
hf fmcos create file --id 0019 --type loop --size 0517 --rperm F0 --wperm EF --access FF -k
# ── 7. Create balance files ──────────────────────────────────────────────────
hf fmcos create file --id 0002 --type wallet --size 0208 --rperm F0 --wperm 00 --access 18 -k
hf fmcos create file --id 0001 --type wallet --size 0208 --rperm F0 --wperm 00 --access 19
# ── 8. Verify PIN ────────────────────────────────────────────────────────────
hf fmcos select --name 77616C6C657454657374 -k
hf fmcos pin verify --id 00 --pin 123456
# ── 9. Credit wallet +1000, passbook +2000 ───────────────────────────────────
hf fmcos select --name 77616C6C657454657374 -k
hf fmcos pin verify --id 00 --pin 123456 -k
hf fmcos credit --type wallet --id 09 --amount 1000 --terminal 666666666666 --key a9e6e145f5df09500a58eef8575d49db --ikey 2b8a438742c851566f02d881b09d58c0 -k
hf fmcos pin verify --id 00 --pin 123456 -k
hf fmcos credit --type passbook --id 09 --amount 2000 --terminal 666666666666 --key a9e6e145f5df09500a58eef8575d49db --ikey 2b8a438742c851566f02d881b09d58c0
# ── 10. Check balances ───────────────────────────────────────────────────────
hf fmcos select --name 77616C6C657454657374 -k
hf fmcos pin verify --id 00 --pin 123456 -k
hf fmcos balance --type wallet -k
hf fmcos balance --type passbook
# ── 11. Purchase (spend) from wallet then passbook ───────────────────────────
hf fmcos select --name 77616C6C657454657374 -k
hf fmcos pin verify --id 00 --pin 123456 -k
hf fmcos purchase --type wallet --id 08 --amount 50 --terminal 666666666666 --key eb18ce6986c820970e876219052ce0cf --ikey 2b8a438742c851566f02d881b09d58c0 -k
hf fmcos pin verify --id 00 --pin 123456 -k
hf fmcos purchase --type passbook --id 08 --amount 50 --terminal 666666666666 --key eb18ce6986c820970e876219052ce0cf --ikey 2b8a438742c851566f02d881b09d58c0
# ── 12. Check balances again ─────────────────────────────────────────────────
hf fmcos select --name 77616C6C657454657374 -k
hf fmcos pin verify --id 00 --pin 123456 -k
hf fmcos balance --type wallet -k
hf fmcos balance --type passbook
# ── 13. Update overdraft limit to 1000 ──────────────────────────────────────
hf fmcos select --name 77616C6C657454657374 -k
hf fmcos pin verify --id 00 --pin 123456 -k
hf fmcos overdraft --id 0B --limit 1000 --terminal 666666666666 --key 94f63c4fae5e4977d749928ad12bc128 --ikey 2b8a438742c851566f02d881b09d58c0
# ── 14. Read transaction history ─────────────────────────────────────────────
hf fmcos select --name 77616C6C657454657374 -k
hf fmcos history --fid 18 -k
hf fmcos history --fid 19
```
---
## File operations walkthrough
That script demos binary/variable/loop file creation with line protection (none, MAC, MAC+enc),
plus application block/unblock. Two DFs are created on the same card:
- **blockTest** (DF 3FFF, appid 0x94) — block/unblock target
- **fileTest** (DF 3F01, appid 0x95) — file read/write target
### Keys used
| Variable | Hex |
|---|---|
| `internal_key` | `659a500f0f1fce35b6884bdff966576a` |
| `line_protection_key` | `980093b4d77ff65f7476bf9019a80892` |
| `external_auth_key` | `7c3f149ed331b11211d2fb62e2df9637` |
| `line_protection_key_1` | `49439874a1f623fc5e14818365d34699` |
| `external_auth_key_1` | `da152a9a56def40a1386ca258788fea6` |
| `internal_key_1` | `bb4a314981b20ce696d6c1e1cda5820c` |
| `enc_external_auth_key` | `44ea0184094995a845b612522a8ab463` |
DF names as hex: `626c6f636b54657374` = `blockTest`, `66696c6554657374` = `fileTest`
---
### reset
```
hf fmcos select --id 3f00 -k
hf fmcos erase
```
---
### setup — create both DFs
#### blockTest DF (3FFF)
```
hf fmcos select --id 3f00 -k
hf fmcos create dir --id 3FFF --space 500 --cperm F0 --eperm F0 --appid 94 --name 626c6f636b54657374 -k
hf fmcos select --name 626c6f636b54657374 -k
hf fmcos create keyfile --id 0001 --space 200 --dfsid 94 --perm F0 -k
```
Keys written into blockTest (keyfile 0001):
```
# Key 0 — lineprotect (line_protection_key_1)
hf fmcos key --op 01 --id 00 --usage F0 --type lineprotect --change 02 --errcount 33 --key 49439874a1f623fc5e14818365d34699 -k
# Key 1 — extauth (external_auth_key_1)
hf fmcos key --op 01 --id 00 --usage F0 --type extauth --change F0 --followup AA --errcount FF --key da152a9a56def40a1386ca258788fea6 -k
# Key 2 — internal (internal_key_1)
hf fmcos key --op 01 --id 00 --usage F0 --type internal --change F0 --version 00 --algo 01 --key bb4a314981b20ce696d6c1e1cda5820c -k
```
Seed binary file 0x0002 in blockTest:
```
hf fmcos create file --id 0002 --type bin --size 50 --rperm F0 --wperm F0 --access FF -k
hf fmcos select --id 0002 -k
hf fmcos write binary --p1 00 --p2 00 --data 62696e66696c655f626c6f636b5f74657374
```
(`62696e66696c655f626c6f636b5f74657374` = ASCII `binfile_block_test`)
#### fileTest DF (3F01)
```
hf fmcos select --id 3f00 -k
hf fmcos create dir --id 3F01 --space 1500 --cperm F0 --eperm F0 --appid 95 --name 66696c6554657374 -k
hf fmcos select --name 66696c6554657374 -k
hf fmcos create keyfile --id 0001 --space 200 --dfsid 95 --perm F0 -k
```
Keys written into fileTest (keyfile 0001). Keys 2 and 3 are written with MAC+enc line protection
(`--prot enc --authkey <external_auth_key>`):
```
# Key 0 — extauth (external_auth_key), unprotected write
hf fmcos key --op 01 --id 00 --usage F0 --type extauth --change F0 --followup AA --errcount FF --key 7c3f149ed331b11211d2fb62e2df9637 -k
# Key 1 — internal (internal_key), unprotected write
hf fmcos key --op 01 --id 00 --usage F0 --type internal --change F0 --version 00 --algo 01 --key 659a500f0f1fce35b6884bdff966576a -k
# Key 2 — lineprotect (line_protection_key), written with enc protection
hf fmcos key --op 01 --id 00 --usage F0 --type lineprotect --change F0 --errcount FF --key 980093b4d77ff65f7476bf9019a80892 --authkey 7c3f149ed331b11211d2fb62e2df9637 --prot enc -k
# Key 3 (slot 02) — extauth (enc_external_auth_key), written with enc protection
hf fmcos key --op 01 --id 02 --usage F0 --type extauth --change F0 --followup AA --errcount FF --key 44ea0184094995a845b612522a8ab463 --authkey 7c3f149ed331b11211d2fb62e2df9637 --prot enc -k
```
Files created in fileTest. Three trios: unprotected (`--access FF`), MAC (`--access 7F --prot mac`),
MAC+enc (`--access 7F --prot enc`). `--access 7F` = protection required, use key 0.
```
# Binary files
hf fmcos create file --id 0002 --type bin --size 50 --rperm F0 --wperm F0 --access FF -k
hf fmcos create file --id 0003 --type bin --size 50 --rperm F0 --wperm F0 --access 7F --prot mac -k
hf fmcos create file --id 0004 --type bin --size 50 --rperm F0 --wperm F0 --access 7F --prot enc -k
# Variable-length record files
hf fmcos create file --id 0006 --type var --size 50 --rperm F0 --wperm F0 --access FF -k
hf fmcos create file --id 0007 --type var --size 50 --rperm F0 --wperm F0 --access 7F --prot mac -k
hf fmcos create file --id 0008 --type var --size 50 --rperm F0 --wperm F0 --access 7F --prot enc -k
# Loop (cyclic) files --size 210 = 0x210 = 528 bytes
# space = record_count*(record_len+1)+8; e.g. 5 records × (0x50+1) + 8 = 0x19D
hf fmcos create file --id 000A --type loop --size 210 --rperm F0 --wperm F0 --access FF -k
hf fmcos create file --id 000B --type loop --size 210 --rperm F0 --wperm F0 --access 7F --prot mac -k
hf fmcos create file --id 000C --type loop --size 210 --rperm F0 --wperm F0 --access 7F --prot enc
```
---
### write_binary
```
hf fmcos select --id 3f01 -k
hf fmcos select --id 0002 -k
hf fmcos write binary --p1 00 --p2 00 --data 111213141516171819101a1b1c1d1e1f -k
hf fmcos select --id 0003 -k
hf fmcos write binary --p1 00 --p2 00 --data 212223242526272829202a2b2c2d2e2f --prot mac --key 980093b4d77ff65f7476bf9019a80892 -k
hf fmcos select --id 0004 -k
hf fmcos write binary --p1 00 --p2 00 --data 313233343536373839303a3b3c3d3e3f --prot enc --key 980093b4d77ff65f7476bf9019a80892
```
---
### write_loop (APPEND RECORD)
```
hf fmcos select --id 3f01 -k
hf fmcos select --id 000a -k
hf fmcos append --fid 0a --data 919293949596979899909a9b9c9d9e9f -k
hf fmcos select --id 000b -k
hf fmcos append --fid 0b --data a1a2a3a4a5a6a7a8a9a0aaabacadaeaf --prot mac --key 980093b4d77ff65f7476bf9019a80892 -k
hf fmcos select --id 000c -k
hf fmcos append --fid 0c --data b1b2b3b4b5b6b7b8b9b0babbbcbdbebf --prot enc --key 980093b4d77ff65f7476bf9019a80892
```
---
### write_record (UPDATE RECORD)
P2 encodes the SFI: `(file_id & 0x1F) << 3 | 4`. The CLI `--fid` takes the raw file ID byte
(130) and encodes P2 automatically.
Variable-length files require `--tlv` so the data is wrapped as `00[len][data]`.
```
hf fmcos select --id 3f01 -k
hf fmcos select --id 0006 -k
hf fmcos write record --rec 1 --fid 06 --data 515253545556575859505a5b5c5d5e5f --tlv -k
hf fmcos select --id 0007 -k
hf fmcos write record --rec 1 --fid 07 --data 616263646566676869606a6b6c6d6e6f --tlv --prot mac --key 980093b4d77ff65f7476bf9019a80892 -k
hf fmcos select --id 0008 -k
hf fmcos write record --rec 1 --fid 08 --data 717273747576777879707a7b7c7d7e7f --tlv --prot enc --key 980093b4d77ff65f7476bf9019a80892
```
---
### read_binary
For MAC-protected reads the card appends a 4-byte MAC before the SW; the CLI strips it automatically.
```
hf fmcos select --id 3f01 -k
hf fmcos select --id 0002 -k
hf fmcos read binary --p1 00 --p2 00 --len 16 -k
hf fmcos select --id 0003 -k
hf fmcos read binary --p1 00 --p2 00 --len 16 --prot mac --key 980093b4d77ff65f7476bf9019a80892 -k
hf fmcos select --id 0004 -k
hf fmcos read binary --p1 00 --p2 00 --len 16 --prot enc --key 980093b4d77ff65f7476bf9019a80892
```
---
### read_record
Variable-length files need `--tlv`; the CLI requests 2 extra bytes and strips the `00[len]` prefix before printing.
```
hf fmcos select --id 3f01 -k
hf fmcos read record --rec 01 --fid 06 --len 16 --tlv -k
hf fmcos read record --rec 01 --fid 07 --len 16 --tlv --prot mac --key 980093b4d77ff65f7476bf9019a80892 -k
hf fmcos read record --rec 01 --fid 08 --len 16 --tlv --prot enc --key 980093b4d77ff65f7476bf9019a80892
```
---
### read_loop
Loop files use the same READ RECORD command; `--rec 01` reads the most recently appended record.
```
hf fmcos select --id 3f01 -k
hf fmcos read record --rec 01 --fid 0a --len 16 -k
hf fmcos read record --rec 01 --fid 0b --len 16 --prot mac --key 980093b4d77ff65f7476bf9019a80892 -k
hf fmcos read record --rec 01 --fid 0c --len 16 --prot enc --key 980093b4d77ff65f7476bf9019a80892
```
---
### card_block / app_block
Permanent application block
```
hf fmcos select --name 626c6f636b54657374 -k
hf fmcos block --app --perm --key 49439874a1f623fc5e14818365d34699
```
Temporary application block:
```
hf fmcos select --name 626c6f636b54657374 -k
hf fmcos block --app --key 49439874a1f623fc5e14818365d34699
```
---
### app_unblock
After a block the app cannot be selected normally (SELECT returns an error SW), but the DF is still
addressed. Send unblock while the field is still on from the failed select, then re-select:
```
hf fmcos select --name 626c6f636b54657374 -k
hf fmcos unblock --key 49439874a1f623fc5e14818365d34699 -k
hf fmcos select --name 626c6f636b54657374 -k
hf fmcos select --id 0002 -k
hf fmcos read binary --p1 00 --p2 00 --len 16
```