mirror of
https://github.com/RfidResearchGroup/proxmark3.git
synced 2025-08-14 02:27:26 -07:00
Create intertic.py
Basic script to try to interpret Intertic data on ST25TB / SRT512 in french transports Signed-off-by: Benjamin DELPY <benjamin@gentilkiwi.com>
This commit is contained in:
parent
309e88ca65
commit
ae0a15fd48
1 changed files with 238 additions and 0 deletions
238
client/pyscripts/intertic.py
Normal file
238
client/pyscripts/intertic.py
Normal file
|
@ -0,0 +1,238 @@
|
|||
# Benjamin DELPY `gentilkiwi`
|
||||
# https://blog.gentilkiwi.com /
|
||||
# benjamin@gentilkiwi.com
|
||||
# Licence : https://creativecommons.org/licenses/by/4.0/
|
||||
#
|
||||
# Basic script to try to interpret Intertic data on ST25TB / SRT512 in french transports
|
||||
# For Proxmark3 with love <3
|
||||
#
|
||||
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 = {
|
||||
0x000: 'Lille (Ilévia / Keolis) - Angoulême (Tadao / Transdev)',
|
||||
0x006: 'Amiens (Ametis / Keolis)',
|
||||
0x008: 'Angoulême (STGA)',
|
||||
0x021: 'Bordeaux (TBM / Keolis)',
|
||||
0x072: 'Tours (filbleu / Keolis)',
|
||||
0x078: 'Reims (Citura / Transdev)',
|
||||
0x907: 'Dijon (Divia / Keolis)',
|
||||
0x908: 'Rennes (STAR / Keolis)',
|
||||
0x912: 'Cherbourg-en-Cotentin (Cap Cotentin / Transdev)',
|
||||
0x913: 'Nîmes (Tango / Transdev)',
|
||||
0x917: 'Saint-Nazaire (Stran) / Angers (Irigo / RATP)',
|
||||
}
|
||||
|
||||
|
||||
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} <dumpfile.bin>\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 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, FRA_OrganizationalAuthority.get(OrganizationalAuthority, '?') if(CountryCode == 0x250) else 'not FR'));
|
||||
print(' ContractApplicationVersionNumber:', ContractApplicationVersionNumber);
|
||||
print(' ContractProvider :', ContractProvider);
|
||||
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())
|
Loading…
Add table
Add a link
Reference in a new issue