SEOS emulation support

This commit is contained in:
Aaron Tulino (Aaronjamt)
2026-07-28 11:18:18 -07:00
parent 3d1ffe9b47
commit 1f99ddd4db
11 changed files with 1205 additions and 3 deletions
+1
View File
@@ -31,6 +31,7 @@ SRC_FILES += \
$(PROJ_DIR)/rfid/nfctag/hf/nfc_14a_4.c \
$(PROJ_DIR)/rfid/nfctag/hf/nfc_mf1.c \
$(PROJ_DIR)/rfid/nfctag/hf/nfc_mf0_ntag.c \
$(PROJ_DIR)/rfid/nfctag/hf/nfc_seos.c \
$(PROJ_DIR)/rfid/nfctag/lf/lf_tag_em.c \
$(PROJ_DIR)/rfid/nfctag/lf/utils/fskdemod.c \
$(PROJ_DIR)/rfid/nfctag/lf/utils/circular_buffer.c \
+90
View File
@@ -1163,6 +1163,88 @@ static data_frame_tx_t *cmd_processor_idteck_get_emu_id(uint16_t cmd, uint16_t s
return data_frame_make(cmd, STATUS_SUCCESS, LF_IDTECK_TAG_ID_SIZE, buffer->buffer);
}
static data_frame_tx_t *cmd_processor_seos_read_emu_data(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) {
tag_data_buffer_t *buffer = get_buffer_by_tag_type(TAG_TYPE_SEOS);
nfc_tag_seos_information_t *info = (nfc_tag_seos_information_t *)buffer->buffer;
uint8_t output[1+info->diversifier_len + 1+info->oid_len + 1+info->data_tag_len + 1+info->data_len + 2];
uint16_t offset = 0;
output[offset++] = info->data_len;
memcpy(output+offset, info->data, info->data_len);
offset += info->data_len;
output[offset++] = info->oid_len;
memcpy(output+offset, info->oid, info->oid_len);
offset += info->oid_len;
output[offset++] = info->data_tag_len;
memcpy(output+offset, info->data_tag, info->data_tag_len);
offset += info->data_tag_len;
output[offset++] = info->diversifier_len;
memcpy(output+offset, info->diversifier, info->diversifier_len);
offset += info->diversifier_len;
output[offset++] = info->hash_alg;
output[offset++] = info->encr_alg;
return data_frame_make(cmd, STATUS_SUCCESS, sizeof(output), output);
}
static data_frame_tx_t *cmd_processor_seos_write_emu_data(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) {
if (length < 6) {
return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL);
}
tag_data_buffer_t *buffer = get_buffer_by_tag_type(TAG_TYPE_SEOS);
nfc_tag_seos_information_t *info = (nfc_tag_seos_information_t *)buffer->buffer;
uint16_t offset = 0;
uint8_t len = data[offset++];
if (len > NFC_TAG_SEOS_DATA_MAX) return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL);
info->data_len = len;
memcpy(info->data, data+offset, len);
offset += len;
len = data[offset++];
if (len > NFC_TAG_SEOS_OID_MAX) return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL);
info->oid_len = len;
memcpy(info->oid, data+offset, len);
offset += len;
len = data[offset++];
if (len > NFC_TAG_SEOS_DATA_TAG_MAX) return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL);
info->data_tag_len = len;
memcpy(info->data_tag, data+offset, len);
offset += len;
len = data[offset++];
if (len > NFC_TAG_SEOS_DIVERSIFIER_MAX) return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL);
info->diversifier_len = len;
memcpy(info->diversifier, data+offset, len);
offset += len;
info->hash_alg = data[offset++];
info->encr_alg = data[offset++];
return data_frame_make(cmd, STATUS_SUCCESS, 0, NULL);
}
static data_frame_tx_t *cmd_processor_seos_write_emu_keys(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) {
if (length != 16 * 3) {
return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL);
}
tag_data_buffer_t *buffer = get_buffer_by_tag_type(TAG_TYPE_SEOS);
nfc_tag_seos_information_t *info = (nfc_tag_seos_information_t *)buffer->buffer;
memcpy(info->authkey, data+ 0, 16);
memcpy(info->privenc, data+16, 16);
memcpy(info->privmac, data+32, 16);
return data_frame_make(cmd, STATUS_SUCCESS, 0, NULL);
}
#if defined(PROJECT_CHAMELEON_ULTRA)
// T55xx clone is only available on Chameleon Ultra; the Lite firmware
// has no LF reader hardware and does not compile the write_*_to_t55xx
@@ -1284,6 +1366,9 @@ static nfc_tag_14a_coll_res_reference_t *get_coll_res_data(bool write) {
case TAG_TYPE_HF14A_4:
info = nfc_tag_14a_4_get_coll_res();
break;
case TAG_TYPE_SEOS:
info = nfc_tag_seos_get_coll_res();
break;
default:
// no collision resolution data for slot
info = NULL;
@@ -3121,6 +3206,11 @@ static cmd_data_map_t m_data_cmd_map[] = {
{ DATA_CMD_JABLOTRON_GET_EMU_ID, NULL, cmd_processor_jablotron_get_emu_id, NULL },
{ DATA_CMD_IDTECK_SET_EMU_ID, NULL, cmd_processor_idteck_set_emu_id, NULL },
{ DATA_CMD_IDTECK_GET_EMU_ID, NULL, cmd_processor_idteck_get_emu_id, NULL },
{ DATA_CMD_SEOS_READ_EMU_DATA, NULL, cmd_processor_seos_read_emu_data, NULL },
{ DATA_CMD_SEOS_WRITE_EMU_DATA, NULL, cmd_processor_seos_write_emu_data, NULL },
{ DATA_CMD_SEOS_WRITE_EMU_KEYS, NULL, cmd_processor_seos_write_emu_keys, NULL },
/* ISO14443-4 T=CL emulation */
#if defined(PROJECT_CHAMELEON_ULTRA)
/* ISO14443-4 T=CL emulation */
+3
View File
@@ -165,6 +165,9 @@
#define DATA_CMD_MF1_GET_FIELD_OFF_DO_RESET (4039)
#define DATA_CMD_MF1_GET_PRNG_TYPE (4040) // 0=static 1=weak(LFSR) 2=hard(rand)
#define DATA_CMD_MF1_SET_PRNG_TYPE (4041)
#define DATA_CMD_SEOS_READ_EMU_DATA (4042)
#define DATA_CMD_SEOS_WRITE_EMU_DATA (4043)
#define DATA_CMD_SEOS_WRITE_EMU_KEYS (4044)
//
// ******************************************************************
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,48 @@
#ifndef NFC_SEOS_H
#define NFC_SEOS_H
#include "nfc_14a.h"
#include "tag_emulation.h"
#define NFC_TAG_SEOS_DATA_MAX 255
#define NFC_TAG_SEOS_OID_MAX 32
#define NFC_TAG_SEOS_DATA_TAG_MAX 2
#define NFC_TAG_SEOS_DIVERSIFIER_MAX 16
/**
* Per-slot persistent data layout stored in FDS flash.
*/
typedef struct __attribute__((packed)) {
nfc_tag_14a_coll_res_entity_t res_coll;
uint8_t data[NFC_TAG_SEOS_DATA_MAX];
uint8_t data_len;
uint8_t oid[NFC_TAG_SEOS_OID_MAX];
uint8_t oid_len;
uint8_t data_tag[NFC_TAG_SEOS_DATA_TAG_MAX];
uint8_t data_tag_len;
uint8_t diversifier[NFC_TAG_SEOS_DIVERSIFIER_MAX];
uint8_t diversifier_len;
uint8_t hash_alg;
uint8_t encr_alg;
// Keys
uint8_t authkey[16];
uint8_t privenc[16];
uint8_t privmac[16];
}
nfc_tag_seos_information_t;
/* Anti-collision resource — used by get_coll_res_data in app_cmd.c */
nfc_tag_14a_coll_res_reference_t *nfc_tag_seos_get_coll_res(void);
/* tag_base_map callbacks */
int nfc_tag_seos_data_loadcb(tag_specific_type_t type, tag_data_buffer_t *buffer);
int nfc_tag_seos_data_savecb(tag_specific_type_t type, tag_data_buffer_t *buffer);
bool nfc_tag_seos_data_factory(uint8_t slot, tag_specific_type_t tag_type);
#endif
@@ -94,6 +94,7 @@ typedef enum {
// HF14A-4 series 3000
TAG_TYPE_HF14A_4 = 3000,
TAG_TYPE_SEOS,
} tag_specific_type_t;
@@ -124,7 +125,7 @@ typedef enum {
TAG_TYPE_MIFARE_4096, TAG_TYPE_NTAG_213, TAG_TYPE_NTAG_215, \
TAG_TYPE_NTAG_216, TAG_TYPE_MF0ICU1, TAG_TYPE_MF0ICU2, \
TAG_TYPE_MF0UL11, TAG_TYPE_MF0UL21, TAG_TYPE_NTAG_210, \
TAG_TYPE_NTAG_212, TAG_TYPE_HF14A_4
TAG_TYPE_NTAG_212, TAG_TYPE_HF14A_4, TAG_TYPE_SEOS
typedef struct {
tag_specific_type_t tag_hf;
@@ -8,6 +8,7 @@
#include "nfc_mf0_ntag.h"
#include "nfc_mf1.h"
#include "nfc_14a_4.h"
#include "nfc_seos.h"
#include "rgb_marquee.h"
#include "tag_persistence.h"
@@ -116,6 +117,7 @@ static tag_base_handler_map_t tag_base_map[] = {
{TAG_SENSE_HF, TAG_TYPE_MF0UL21, nfc_tag_mf0_ntag_data_loadcb, nfc_tag_mf0_ntag_data_savecb, nfc_tag_mf0_ntag_data_factory, &m_tag_data_hf},
// ISO14443-4 T=CL emulation
{TAG_SENSE_HF, TAG_TYPE_HF14A_4, nfc_tag_14a_4_data_loadcb, nfc_tag_14a_4_data_savecb, nfc_tag_14a_4_data_factory, &m_tag_data_hf},
{TAG_SENSE_HF, TAG_TYPE_SEOS, nfc_tag_seos_data_loadcb, nfc_tag_seos_data_savecb, nfc_tag_seos_data_factory, &m_tag_data_hf},
};
static void tag_emulation_load_config(void);
+1
View File
@@ -8,6 +8,7 @@
#include "nfc_14a.h"
#include "nfc_mf0_ntag.h"
#include "nfc_mf1.h"
#include "nfc_seos.h"
#include "nrf_gpio.h"
#include "tag_emulation.h"
+158 -1
View File
@@ -64,7 +64,7 @@ type_id_SAK_dict = {
0x18: "MIFARE Classic 4K | Plus S 4K | Plus X 4K",
0x19: "MIFARE Classic 2K",
0x20: "MIFARE Plus EV1/EV2 | DESFire EV1/EV2/EV3 | DESFire Light | NTAG 4xx | "
"MIFARE Plus S 2/4K | MIFARE Plus X 2/4K | MIFARE Plus SE 1K",
"MIFARE Plus S 2/4K | MIFARE Plus X 2/4K | MIFARE Plus SE 1K | SEOS",
0x28: "SmartMX with MIFARE Classic 1K",
0x38: "SmartMX with MIFARE Classic 4K",
}
@@ -883,6 +883,7 @@ hf_14a = hf.subgroup("14a", "ISO14443-a commands")
hf_mf = hf.subgroup("mf", "MIFARE Classic commands")
hf_mfu = hf.subgroup("mfu", "MIFARE Ultralight / NTAG commands")
hf_des = hf.subgroup("des", "MIFARE DESFire commands")
hf_seos = hf.subgroup("seos", "SEOS commands")
lf = root.subgroup("lf", "Low Frequency commands")
lf_em = lf.subgroup("em", "EM commands")
@@ -10556,3 +10557,159 @@ class HfDesChk(ReaderRequiredUnit):
print(f"\n {CG}{algo:8s} AID {aid} key#{kno} {key_hex}{C0}")
else:
print(f"\n {CR}No keys found{C0}")
@hf_seos.command("eview")
class HFSeosEView(SlotIndexArgsAndGoUnit, DeviceRequiredUnit):
def args_parser(self) -> ArgumentParserNoExit:
parser = ArgumentParserNoExit()
parser.description = "View data from emulator memory"
self.add_slot_args(parser)
return parser
def on_exec(self, args: argparse.Namespace):
selected_slot = self.cmd.get_active_slot()
slot_info = self.cmd.get_slot_info()
tag_type = TagSpecificType(slot_info[selected_slot]["hf"])
if tag_type != TagSpecificType.SEOS:
raise Exception(
"Card in current slot is not SEOS"
)
data = self.cmd.seos_read_emu_data()
print("[=] Data:", data["data"].hex().upper())
print("[=] OID:", data["oid"].hex().upper())
print("[=] Tag:", data["tag"].hex().upper())
print("[=] Diversifier:", data["diversifier"].hex().upper())
@hf_seos.command("eload")
class HFSeosELoad(SlotIndexArgsAndGoUnit, HF14AAntiCollArgsUnit, DeviceRequiredUnit):
def args_parser(self) -> ArgumentParserNoExit:
parser = ArgumentParserNoExit()
parser.description = "Load data into emulator memory"
self.add_slot_args(parser)
self.add_hf14a_anticoll_args(parser)
parser.add_argument("-d", "--data", type=str, default=None, metavar="<hex>",
help="Data to present to reader (2-255 bytes). Must be valid BER-TLV.")
parser.add_argument("-o", "--oid", type=str, default=None, metavar="<hex>",
help=f"Target OID (1-32 bytes).")
parser.add_argument("-t", "--tag", type=str, default=None, metavar="<hex>",
help=f"Tag of presented data (1-2 bytes).")
parser.add_argument("--diversifier", type=str, default=None, metavar="<hex>",
help=f"Simulated card diversifier (1-16 bytes).")
return parser
def on_exec(self, args: argparse.Namespace):
selected_slot = self.cmd.get_active_slot()
slot_info = self.cmd.get_slot_info()
tag_type = TagSpecificType(slot_info[selected_slot]["hf"])
if tag_type != TagSpecificType.SEOS:
raise Exception(
"Card in current slot is not SEOS"
)
# Handle ISO14443-A anticollision changes
anti_coll_data = self.cmd.hf14a_get_anti_coll_data()
if anti_coll_data is None or len(anti_coll_data) == 0:
print(
f"{color_string((CR, f'Slot does not contain any HF 14A config'))}"
)
return
uid = anti_coll_data["uid"]
atqa = anti_coll_data["atqa"]
sak = anti_coll_data["sak"]
ats = anti_coll_data["ats"]
change_requested, change_done, uid, atqa, sak, ats = self.update_hf14a_anticoll(
args, uid, atqa, sak, ats
)
if (
args.data is None and
args.oid is None and
args.tag is None and
args.diversifier is None and
change_requested is False
):
print(color_string((CR, "Error: No changes were requested.")))
seos_data = self.cmd.seos_read_emu_data()
# Parse args
data = bytes.fromhex(args.data) if args.data else seos_data["data"]
oid = bytes.fromhex(args.oid) if args.oid else seos_data["oid"]
tag = bytes.fromhex(args.tag) if args.tag else seos_data["tag"]
diversifier = bytes.fromhex(args.diversifier) if args.diversifier else seos_data["diversifier"]
# These are not currently configurable
hash_alg = seos_data["hash_alg"]
encr_alg = seos_data["encr_alg"]
if len(data) < 2 or len(data) > 255:
print(color_string((CR, "Error: invalid data length. Accepts 2-255 bytes.")))
return
if len(oid) < 1 or len(oid) > 32:
print(color_string((CR, "Error: invalid OID length. Accepts 1-32 bytes.")))
return
if len(tag) < 1 or len(tag) > 2:
print(color_string((CR, "Error: invalid tag length. Accepts 1-2 bytes.")))
return
if len(diversifier) < 1 or len(diversifier) > 16:
print(color_string((CR, "Error: invalid diversifier length. Accepts 1-16 bytes.")))
return
self.cmd.seos_write_emu_data(
data=data,
oid=oid,
tag=tag,
diversifier=diversifier,
hash_alg=hash_alg,
encr_alg=encr_alg
)
@hf_seos.command("keys")
class HFSeosKeys(SlotIndexArgsAndGoUnit, DeviceRequiredUnit):
def args_parser(self) -> ArgumentParserNoExit:
parser = ArgumentParserNoExit()
parser.description = "Load data into emulator memory"
self.add_slot_args(parser)
parser.add_argument("-a", "--auth", type=str, metavar="<hex>", required=True,
help="Auth key (16 bytes)")
parser.add_argument("-e", "--privenc", type=str, metavar="<hex>", required=True,
help="PrivEnc key (16 bytes)")
parser.add_argument("-m", "--privmac", type=str, metavar="<hex>", required=True,
help="PrivMac key (16 bytes)")
return parser
def on_exec(self, args: argparse.Namespace):
selected_slot = self.cmd.get_active_slot()
slot_info = self.cmd.get_slot_info()
tag_type = TagSpecificType(slot_info[selected_slot]["hf"])
if tag_type != TagSpecificType.SEOS:
raise Exception(
"Card in current slot is not SEOS"
)
# Parse args
auth = bytes.fromhex(args.auth)
privenc = bytes.fromhex(args.privenc)
privmac = bytes.fromhex(args.privmac)
if len(auth) != 16:
print(color_string((CR, "Error: invalid auth key length. Accepts 16 bytes.")))
return
if len(privenc) != 16:
print(color_string((CR, "Error: invalid PrivEnc key length. Accepts 16 bytes.")))
return
if len(privmac) != 16:
print(color_string((CR, "Error: invalid PrivMac key length. Accepts 16 bytes.")))
return
self.cmd.seos_write_emu_keys(
auth=auth,
privenc=privenc,
privmac=privmac
)
+43
View File
@@ -1823,6 +1823,49 @@ class ChameleonCMD:
data = struct.pack('!B', enabled)
return self.device.send_cmd_sync(Command.MF1_SET_FIELD_OFF_DO_RESET, data)
@expect_response(Status.SUCCESS)
def seos_read_emu_data(self):
resp = self.device.send_cmd_sync(Command.SEOS_READ_EMU_DATA, None)
resp.parsed = {}
def extract_next():
length = resp.data[0]
value = resp.data[1:length+1]
resp.data = resp.data[length+1:]
return value
data, oid, tag, diversifier = extract_next(), extract_next(), extract_next(), extract_next()
hash_alg, encr_alg = struct.unpack('!BB', resp.data)
resp.parsed = {
"data": data, "oid": oid, "tag": tag, "diversifier": diversifier,
"hash_alg": hash_alg, "encr_alg": encr_alg
}
return resp
@expect_response(Status.SUCCESS)
def seos_write_emu_data(self, data: bytes, oid: bytes, tag: bytes, diversifier: bytes, hash_alg: int, encr_alg: int):
data = bytes([len(data)]) + data
oid = bytes([len(oid)]) + oid
tag = bytes([len(tag)]) + tag
diversifier = bytes([len(diversifier)]) + diversifier
payload = (
data + oid + tag + diversifier +
struct.pack('!BB', hash_alg, encr_alg)
)
if len(payload) > 4096:
raise ValueError("Too much provided data")
return self.device.send_cmd_sync(Command.SEOS_WRITE_EMU_DATA, payload)
@expect_response(Status.SUCCESS)
def seos_write_emu_keys(self, auth: bytes, privenc: bytes, privmac: bytes):
payload = auth + privenc + privmac
return self.device.send_cmd_sync(Command.SEOS_WRITE_EMU_KEYS, payload)
def test_fn():
# connect to chameleon
+7
View File
@@ -150,6 +150,10 @@ class Command(enum.IntEnum):
MF1_GET_PRNG_TYPE = 4040
MF1_SET_PRNG_TYPE = 4041
SEOS_READ_EMU_DATA = 4042
SEOS_WRITE_EMU_DATA = 4043
SEOS_WRITE_EMU_KEYS = 4044
# ISO14443-4 T=CL emulation
HF14A_4_APDU_RECV = 6000
HF14A_4_APDU_SEND = 6001
@@ -360,6 +364,7 @@ class TagSpecificType(enum.IntEnum):
# ISO14443-4 T=CL emulation
HF14A_4 = 3000
SEOS = 3001
@staticmethod
def list(exclude_meta=True):
@@ -438,6 +443,8 @@ class TagSpecificType(enum.IntEnum):
return "NTAG 210"
elif self == TagSpecificType.NTAG_212:
return "NTAG 212"
elif self == TagSpecificType.SEOS:
return "SEOS"
elif self < TagSpecificType.OLD_TAG_TYPES_END:
return "Old tag type, must be migrated! Upgrade fw!"
return "Invalid"