# Benjamin DELPY `gentilkiwi` # https://blog.gentilkiwi.com/ # benjamin@gentilkiwi.com # # Basic script to try to interpret Intertic data on ST25TB / SRT512 in french transports # For Proxmark3 with love <3 # # 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. # # See LICENSE.txt for the text of the license. import sys, os from datetime import datetime, timedelta from bitarray import bitarray from bitarray.util import ba2int class BitMe: def __init__(self): self.data = bitarray() self.idx = 0 def addBits(self, bits): self.data += bits def addBytes(self, bytes): self.data.frombytes(bytes) def nom_bits(self, cb): ret = self.data[self.idx:self.idx + cb] self.idx += cb return ret def nom(self, cb): return ba2int(self.nom_bits(cb)) def nom_bits_left(self): return self.data[self.idx:None] def isEmpty(self): return (len(self.data) == 0) ISO_Countries = { 0x250: 'France', } FRA_OrganizationalAuthority_Contract_Provider = { 0x000: { 5: 'Lille (Ilévia / Keolis)', 7: 'Lens-Béthune (Tadao / Transdev)', }, 0x006: { 1: 'Amiens (Ametis / Keolis)', }, 0x008: { 15: 'Angoulême (STGA)', }, 0x021: { 1: 'Bordeaux (TBM / Keolis)', }, 0x057: { 1: 'Lyon (TCL / Keolis)', }, 0x072: { 1: 'Tours (filbleu / Keolis)', }, 0x078: { 4: 'Reims (Citura / Transdev)', }, 0x502: { 83: 'Annecy (Sibra)', }, 0x091: { 1: 'Strasbourg (CTS)', }, 0x907: { 1: 'Dijon (Divia / Keolis)', }, 0x908: { 1: 'Rennes (STAR / Keolis)', }, 0x911: { 5: 'Besançon (Ginko / Keolis)', }, 0x912: { 3: 'Le Havre (Lia / Transdev)', 35: 'Cherbourg-en-Cotentin (Cap Cotentin / Transdev)', }, 0x913: { 3: 'Nîmes (Tango / Transdev)', }, 0x917: { 4: 'Angers (Irigo / RATP)', 7: 'Saint-Nazaire (Stran)', }, } def main(): print('Basic script to try to interpret Intertic data on ST25TB / SRT512 in french transports') print('--------------------------------------------------------------------------------------\n') if(len(sys.argv) != 2): print('\tUsage : {0} \n\tExample: {0} hf-14b-D00233787DFBB4D5-dump.bin\n'.format(sys.argv[0])) return 1 binaryDumpFileName = sys.argv[1] data = BitMe() print('Using \'{}\' as binary dump file...'.format(binaryDumpFileName)) file = open(binaryDumpFileName, mode='rb') file.seek(0, os.SEEK_END) size = file.tell() file.seek(0, os.SEEK_SET) if (size != 68): print('\'{}\' file size is not 68 bytes'.format(binaryDumpFileName)) return 2 while True: chunk = file.read(4) 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() Distribution_Cer = BitMe() 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)); 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)) Distribution_Cer.addBits(data.nom_bits(32)) case 0x06: Distribution_Data.addBits(data.nom_bits(4 * 32)) C1.addBits(data.nom_bits(32)) C2.addBits(data.nom_bits(32)) Distribution_Data.addBits(data.nom_bits(3 * 32)) Distribution_Data.addBits(Distribution_Data_End) Usage_Data_End = data.nom_bits(30) Usage_Sta_B.addBits(data.nom_bits(2)) Usage_Data.addBits(data.nom_bits(3 * 32)) Usage_Data.addBits(Usage_Data_End) Usage_Data.addBits(data.nom_bits(14)) Usage_Sta_E.addBits(data.nom_bits(2)) Usage_Cer.addBits(data.nom_bits(16)) Distribution_Cer.addBits(data.nom_bits(32)) case 0x07: Distribution_Data.addBits(data.nom_bits(4 * 32)) C1.addBits(data.nom_bits(32)) C2.addBits(data.nom_bits(32)) Distribution_Data.addBits(data.nom_bits(4 * 32)) Distribution_Data.addBits(Distribution_Data_End) Usage_Data_End = data.nom_bits(30) Usage_Sta_B.addBits(data.nom_bits(2)) Usage_Data.addBits(data.nom_bits(3 * 32)) Usage_Data.addBits(Usage_Data_End) Usage_Data.addBits(data.nom_bits(14)) Usage_Sta_E.addBits(data.nom_bits(2)) Usage_Cer.addBits(data.nom_bits(16)) Distribution_Cer.addBits(data.nom_bits(32)) case 0x0a: Distribution_Data.addBits(data.nom_bits(4 * 32)) C1.addBits(data.nom_bits(32)) C2.addBits(data.nom_bits(32)) Distribution_Data.addBits(data.nom_bits(8 * 32)) Distribution_Data.addBits(Distribution_Data_End) Distribution_Cer.addBits(data.nom_bits(32)) # No USAGE for 0x0a case 0x0b: # Not in the draft :( Distribution_Data.addBits(data.nom_bits(4 * 32)) C1.addBits(data.nom_bits(32)) C2.addBits(data.nom_bits(32)) Distribution_Data.addBits(data.nom_bits(8 * 32)) Distribution_Data.addBits(Distribution_Data_End) Distribution_Cer.addBits(data.nom_bits(32)) case _: print('PID not (yet?) supported') return 3 ''' 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() print('DISTRIBUTION') print(' CountryCode : {:03x} - {}'.format(CountryCode, ISO_Countries.get(CountryCode, '?'))); print(' OrganizationalAuthority : {:03x}'.format(OrganizationalAuthority)); print(' ContractApplicationVersionNumber:', ContractApplicationVersionNumber); print(' ContractProvider :', ContractProvider); if (CountryCode == 0x250): oa = FRA_OrganizationalAuthority_Contract_Provider.get(OrganizationalAuthority) if (oa is not None): s = oa.get(ContractProvider) if (s is not None): print(' ~ Authority & Provider ~ :', s) 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))) return 0 if __name__ == '__main__': sys.exit(main())