1 # Benjamin DELPY `gentilkiwi`
2 # https://blog.gentilkiwi.com/
3 # benjamin@gentilkiwi.com
5 # Basic script to try to interpret Intertic data on ST25TB / SRT512 in french transports
6 # For Proxmark3 with love <3
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
18 # See LICENSE.txt for the text of the license.
21 from datetime
import datetime
, timedelta
22 from bitarray
import bitarray
23 from bitarray
.util
import ba2int
24 from typing
import NamedTuple
28 self
.data
= bitarray(endian
= 'big')
34 def addBits(self
, bits
):
37 def addBytes(self
, bytes
):
38 self
.data
.frombytes(bytes
)
40 def nom_bits(self
, cb
):
41 ret
= self
.data
[self
.idx
:self
.idx
+ cb
]
46 return ba2int(self
.nom_bits(cb
))
48 def nom_bits_left(self
):
49 return self
.data
[self
.idx
:None]
52 return (len(self
.data
) == 0)
55 A generic Describe_Usage function with variable number of bits between stamps will be more optimal
56 At this time I want to keep more places/functions to try to parse other fields in 'unk1' and 'left'
59 TYPE_EventCode_Nature
= {
61 0x2: 'interurban bus',
68 TYPE_EventCode_Type
= {
69 0x1: 'entry validation',
70 0x2: 'exit validation',
71 0x4: 'ticket inspecting',
72 0x6: 'connection entry validation',
73 0x14: 'test validation',
74 0x15: 'connection exit validation',
75 0x16: 'canceled validation',
80 TYPE_EventGeoRoute_Direction
= {
87 def Describe_Usage_1(Usage
, ContractMediumEndDate
, Certificate
):
88 EventDateStamp
= Usage
.nom(10)
89 EventTimeStamp
= Usage
.nom(11)
90 unk
= Usage
.nom_bits(65)
91 EventValidityTimeFirstStamp
= Usage
.nom(11)
93 print(' EventDateStamp : {} ({})'.format(EventDateStamp
, (datetime(1997, 1, 1) + timedelta(days
= ContractMediumEndDate
- EventDateStamp
)).strftime('%Y-%m-%d')));
94 print(' EventTimeStamp : {} ({:02d}:{:02d})'. format(EventTimeStamp
, EventTimeStamp
// 60, EventTimeStamp
% 60))
95 print(' unk1... :', unk
);
96 print(' EventValidityTimeFirstStamp: {} ({:02d}:{:02d})'. format(EventValidityTimeFirstStamp
, EventValidityTimeFirstStamp
// 60, EventValidityTimeFirstStamp
% 60))
97 print(' left... :', Usage
.nom_bits_left());
98 print(' [CER] Usage : {:04x}'.format(Certificate
.nom(16)))
100 def Describe_Usage_1_1(Usage
, ContractMediumEndDate
, Certificate
):
101 EventDateStamp
= Usage
.nom(10)
102 EventTimeStamp
= Usage
.nom(11)
103 unk0
= Usage
.nom_bits(8)
104 EventCode_Nature
= Usage
.nom(5)
105 EventCode_Type
= Usage
.nom(5)
106 unk1
= Usage
.nom_bits(11)
107 EventGeoVehicleId
= Usage
.nom(16)
108 EventGeoRouteId
= Usage
.nom(14)
109 EventGeoRoute_Direction
= Usage
.nom(2)
110 EventCountPassengers_mb
= Usage
.nom(4)
111 EventValidityTimeFirstStamp
= Usage
.nom(11)
113 print(' DateStamp : {} ({})'.format(EventDateStamp
, (datetime(1997, 1, 1) + timedelta(days
= ContractMediumEndDate
- EventDateStamp
)).strftime('%Y-%m-%d')));
114 print(' TimeStamp : {} ({:02d}:{:02d})'. format(EventTimeStamp
, EventTimeStamp
// 60, EventTimeStamp
% 60))
115 print(' unk0... :', unk0
);
116 print(' Code/Nature : 0x{:x} ({})'.format(EventCode_Nature
, TYPE_EventCode_Nature
.get(EventCode_Nature
, '?')))
117 print(' Code/Type : 0x{:x} ({})'.format(EventCode_Type
, TYPE_EventCode_Type
.get(EventCode_Type
, '?')))
118 print(' unk1... :', unk1
);
119 print(' GeoVehicleId : {}'. format(EventGeoVehicleId
))
120 print(' GeoRouteId : {}'. format(EventGeoRouteId
))
121 print(' Direction : {} ({})'. format(EventGeoRoute_Direction
, TYPE_EventGeoRoute_Direction
.get(EventGeoRoute_Direction
, '?')))
122 print(' Passengers(?) : {}'. format(EventCountPassengers_mb
))
123 print(' ValidityTimeFirstStamp: {} ({:02d}:{:02d})'. format(EventValidityTimeFirstStamp
, EventValidityTimeFirstStamp
// 60, EventValidityTimeFirstStamp
% 60))
124 print(' left... :', Usage
.nom_bits_left());
125 print(' [CER] Usage : {:04x}'.format(Certificate
.nom(16)))
127 def Describe_Usage_1_2(Usage
, ContractMediumEndDate
, Certificate
):
128 EventDateStamp
= Usage
.nom(10)
129 EventTimeStamp
= Usage
.nom(11)
130 EventCount_mb
= Usage
.nom(6)
131 unk0
= Usage
.nom_bits(4)
132 EventCode_Nature_mb
= Usage
.nom(4)
133 EventCode_Type_mb
= Usage
.nom(4)
134 unk1
= Usage
.nom_bits(11)
135 EventGeoVehicleId
= Usage
.nom(16)
136 EventGeoRouteId
= Usage
.nom(14)
137 EventGeoRoute_Direction
= Usage
.nom(2)
138 EventCountPassengers_mb
= Usage
.nom(4)
139 EventValidityTimeFirstStamp
= Usage
.nom(11)
141 TYPE_EventCode_Nature_Reims
= { # usually it's the opposite, but ... ?
146 print(' DateStamp : {} ({})'.format(EventDateStamp
, (datetime(1997, 1, 1) + timedelta(days
= ContractMediumEndDate
- EventDateStamp
)).strftime('%Y-%m-%d')));
147 print(' TimeStamp : {} ({:02d}:{:02d})'. format(EventTimeStamp
, EventTimeStamp
// 60, EventTimeStamp
% 60))
148 print(' Count(?) : {}'. format(EventCount_mb
))
149 print(' unk0... :', unk0
);
150 print(' Code/Nature(?) : 0x{:x} ({})'.format(EventCode_Nature_mb
, TYPE_EventCode_Nature_Reims
.get(EventCode_Nature_mb
, '?')))
151 print(' Code/Type(?) : 0x{:x} ({})'.format(EventCode_Type_mb
, TYPE_EventCode_Type
.get(EventCode_Type_mb
, '?')))
152 print(' unk1... :', unk1
);
153 print(' GeoVehicleId : {}'. format(EventGeoVehicleId
))
154 print(' GeoRouteId : {}'. format(EventGeoRouteId
))
155 print(' Direction : {} ({})'. format(EventGeoRoute_Direction
, TYPE_EventGeoRoute_Direction
.get(EventGeoRoute_Direction
, '?')))
156 print(' Passengers(?) : {}'. format(EventCountPassengers_mb
))
157 print(' ValidityTimeFirstStamp: {} ({:02d}:{:02d})'. format(EventValidityTimeFirstStamp
, EventValidityTimeFirstStamp
// 60, EventValidityTimeFirstStamp
% 60))
158 print(' left... :', Usage
.nom_bits_left());
159 print(' [CER] Usage : {:04x}'.format(Certificate
.nom(16)))
162 def Describe_Usage_2(Usage
, ContractMediumEndDate
, Certificate
):
163 EventDateStamp
= Usage
.nom(10)
164 EventTimeStamp
= Usage
.nom(11)
165 unk0
= Usage
.nom_bits(8)
166 EventCode_Nature
= Usage
.nom(5)
167 EventCode_Type
= Usage
.nom(5)
168 unk1
= Usage
.nom_bits(11)
169 EventGeoRouteId
= Usage
.nom(14)
170 EventGeoRoute_Direction
= Usage
.nom(2)
171 EventCountPassengers_mb
= Usage
.nom(4)
172 EventValidityTimeFirstStamp
= Usage
.nom(11)
174 print(' DateStamp : {} ({})'.format(EventDateStamp
, (datetime(1997, 1, 1) + timedelta(days
= ContractMediumEndDate
- EventDateStamp
)).strftime('%Y-%m-%d')));
175 print(' TimeStamp : {} ({:02d}:{:02d})'. format(EventTimeStamp
, EventTimeStamp
// 60, EventTimeStamp
% 60))
176 print(' unk0... :', unk0
);
177 print(' Code/Nature : 0x{:x} ({})'.format(EventCode_Nature
, TYPE_EventCode_Nature
.get(EventCode_Nature
, '?')))
178 print(' Code/Type : 0x{:x} ({})'.format(EventCode_Type
, TYPE_EventCode_Type
.get(EventCode_Type
, '?')))
179 print(' unk1... :', unk1
);
180 print(' GeoRouteId : {}'. format(EventGeoRouteId
))
181 print(' Direction : {} ({})'. format(EventGeoRoute_Direction
, TYPE_EventGeoRoute_Direction
.get(EventGeoRoute_Direction
, '?')))
182 print(' Passengers(?) : {}'. format(EventCountPassengers_mb
))
183 print(' ValidityTimeFirstStamp: {} ({:02d}:{:02d})'. format(EventValidityTimeFirstStamp
, EventValidityTimeFirstStamp
// 60, EventValidityTimeFirstStamp
% 60))
184 print(' left... :', Usage
.nom_bits_left());
185 print(' [CER] Usage : {:04x}'.format(Certificate
.nom(16)))
187 def Describe_Usage_3(Usage
, ContractMediumEndDate
, Certificate
):
188 EventDateStamp
= Usage
.nom(10)
189 EventTimeStamp
= Usage
.nom(11)
190 unk
= Usage
.nom_bits(27)
191 EventValidityTimeFirstStamp
= Usage
.nom(11)
193 print(' EventDateStamp : {} ({})'.format(EventDateStamp
, (datetime(1997, 1, 1) + timedelta(days
= ContractMediumEndDate
- EventDateStamp
)).strftime('%Y-%m-%d')));
194 print(' EventTimeStamp : {} ({:02d}:{:02d})'. format(EventTimeStamp
, EventTimeStamp
// 60, EventTimeStamp
% 60))
195 print(' unk1... :', unk
);
196 print(' EventValidityTimeFirstStamp: {} ({:02d}:{:02d})'. format(EventValidityTimeFirstStamp
, EventValidityTimeFirstStamp
// 60, EventValidityTimeFirstStamp
% 60))
197 print(' left... :', Usage
.nom_bits_left());
198 print(' [CER] Usage : {:04x}'.format(Certificate
.nom(16)))
200 def Describe_Usage_4(Usage
, ContractMediumEndDate
, Certificate
):
201 EventDateStamp
= Usage
.nom(10)
202 EventTimeStamp
= Usage
.nom(11)
203 unk
= Usage
.nom_bits(63)
204 EventValidityTimeFirstStamp
= Usage
.nom(11)
206 print(' EventDateStamp : {} ({})'.format(EventDateStamp
, (datetime(1997, 1, 1) + timedelta(days
= ContractMediumEndDate
- EventDateStamp
)).strftime('%Y-%m-%d')));
207 print(' EventTimeStamp : {} ({:02d}:{:02d})'. format(EventTimeStamp
, EventTimeStamp
// 60, EventTimeStamp
% 60))
208 print(' unk1... :', unk
);
209 print(' EventValidityTimeFirstStamp: {} ({:02d}:{:02d})'. format(EventValidityTimeFirstStamp
, EventValidityTimeFirstStamp
// 60, EventValidityTimeFirstStamp
% 60))
210 print(' left... :', Usage
.nom_bits_left());
211 print(' [CER] Usage : {:04x}'.format(Certificate
.nom(16)))
213 def Describe_Usage_Generic(Usage
, ContractMediumEndDate
, Certificate
):
214 print(' !!! GENERIC DUMP - please provide full file dump to benjamin@gentilkiwi.com - especially if NOT empty !!!')
215 print(' left... :', Usage
.nom_bits_left());
216 print(' [CER] Usage : {:04x}'.format(Certificate
.nom(16)))
217 print(' !!! Trying Usage_1 (the most common) !!!')
220 Describe_Usage_1(Usage
, ContractMediumEndDate
, Certificate
)
222 class InterticHelper(NamedTuple
):
223 OrganizationalAuthority
: str
224 ContractProvider
: str
225 UsageDescribeFunction
: callable = None
231 FRA_OrganizationalAuthority_Contract_Provider
= {
233 5: InterticHelper('Lille', 'Ilévia / Keolis', Describe_Usage_1_1
),
234 7: InterticHelper('Lens-Béthune', 'Tadao / Transdev', Describe_Usage_1_1
),
237 1: InterticHelper('Amiens', 'Ametis / Keolis'),
240 15: InterticHelper('Angoulême', 'STGA', Describe_Usage_1_1
), # May have a problem with date ?
243 1: InterticHelper('Bordeaux', 'TBM / Keolis', Describe_Usage_1_1
),
246 1: InterticHelper('Lyon', 'TCL / Keolis', Describe_Usage_1
), # Strange usage ?, kept on generic 1
249 1: InterticHelper('Tours', 'filbleu / Keolis', Describe_Usage_1_1
),
252 4: InterticHelper('Reims', 'Citura / Transdev', Describe_Usage_1_2
),
255 1: InterticHelper('Strasbourg', 'CTS', Describe_Usage_4
), # More dump needed, not only tram !
258 83: InterticHelper('Annecy', 'Sibra', Describe_Usage_2
),
259 10: InterticHelper('Clermont-Ferrand', 'T2C'),
262 1: InterticHelper('Dijon', 'Divia / Keolis'),
265 1: InterticHelper('Rennes', 'STAR / Keolis', Describe_Usage_2
),
266 8: InterticHelper('Saint-Malo', 'MAT / RATP', Describe_Usage_1_1
),
269 5: InterticHelper('Besançon', 'Ginko / Keolis'),
272 3: InterticHelper('Le Havre', 'Lia / Transdev', Describe_Usage_1_1
),
273 35: InterticHelper('Cherbourg-en-Cotentin', 'Cap Cotentin / Transdev'),
276 3: InterticHelper('Nîmes', 'Tango / Transdev', Describe_Usage_3
),
279 1: InterticHelper('Metz', 'Le Met\' / TAMM'),
282 4: InterticHelper('Angers', 'Irigo / RATP', Describe_Usage_1_2
),
283 7: InterticHelper('Saint-Nazaire', 'Stran'),
290 print('Basic script to try to interpret Intertic data on ST25TB / SRT512 in french transports')
291 print('--------------------------------------------------------------------------------------\n')
293 if(len(sys
.argv
) != 2):
294 print('\tUsage : {0} <dumpfile.bin>\n\tExample: {0} hf-14b-D00233787DFBB4D5-dump.bin\n'.format(sys
.argv
[0]))
297 binaryDumpFileName
= sys
.argv
[1]
301 print('Using \'{}\' as binary dump file...'.format(binaryDumpFileName
))
302 file = open(binaryDumpFileName
, mode
='rb')
304 file.seek(0, os
.SEEK_END
)
306 file.seek(0, os
.SEEK_SET
)
309 print('\'{}\' file size is not 68 bytes'.format(binaryDumpFileName
))
316 data
.addBytes(chunk
[::-1])
320 Distribution_Data
= BitMe()
322 # Usage_DAT = BitMe()
323 # Usage_CER = BitMe()
324 Usage_A_DAT
= BitMe()
325 Usage_A_CER
= BitMe()
326 Usage_B_DAT
= BitMe()
327 Usage_B_CER
= BitMe()
328 Distribution_Cer
= BitMe()
335 Describe_Usage
= None
337 Block0Left
.addBits(data
.nom_bits(23))
344 Distribution_Data
.addBits(data
.nom_bits(2 * 32))
345 Distribution_Data
.addBits(Block0Left
.nom_bits_left())
346 Usage_A_DAT
.addBits(data
.nom_bits(2 * 32))
347 RELOADING1
= data
.nom(8)
348 COUNTER1
= data
.nom(24)
350 Usage_A_DAT
.addBits(data
.nom_bits(2 * 32))
351 Usage_A_DAT
.addBits(data
.nom_bits(16))
352 Usage_A_CER
.addBits(data
.nom_bits(16))
353 Usage_B_DAT
.addBits(data
.nom_bits(4 * 32))
354 Usage_B_DAT
.addBits(data
.nom_bits(16))
355 Usage_B_CER
.addBits(data
.nom_bits(16))
356 Distribution_Cer
.addBits(data
.nom_bits(32))
359 Distribution_Data
.addBits(data
.nom_bits(4 * 32))
360 Distribution_Data
.addBits(Block0Left
.nom_bits_left())
361 RELOADING1
= data
.nom(8)
362 COUNTER1
= data
.nom(24)
364 Usage_A_DAT
.addBits(data
.nom_bits(3 * 32))
365 Usage_A_DAT
.addBits(data
.nom_bits(16))
366 Usage_A_CER
.addBits(data
.nom_bits(16))
367 Usage_B_DAT
.addBits(data
.nom_bits(3 * 32))
368 Usage_B_DAT
.addBits(data
.nom_bits(16))
369 Usage_B_CER
.addBits(data
.nom_bits(16))
370 Distribution_Cer
.addBits(data
.nom_bits(32))
373 print('PID not (yet?) supported: 0x{:02x}'.format(PID
))
377 print('PID (product): 0x{:02x} (flipflop?: {})'.format(PID
, (PID
& 0x10) != 0));
378 print('KeyId : 0x{:1x}'.format(KeyId
))
384 Not very well documented but seems standard for this part
386 if not Distribution_Data
.isEmpty():
388 ContractNetworkId
= Distribution_Data
.nom_bits(24)
389 CountryCode
= ba2int(ContractNetworkId
[0:0+12])
390 OrganizationalAuthority
= ba2int(ContractNetworkId
[12:12+12])
392 ContractApplicationVersionNumber
= Distribution_Data
.nom(6)
393 ContractProvider
= Distribution_Data
.nom(8)
394 ContractTariff
= Distribution_Data
.nom(16)
395 ContractMediumEndDate
= Distribution_Data
.nom(14)
397 Distribution_left
= Distribution_Data
.nom_bits_left()
399 print('DISTRIBUTION')
400 print(' CountryCode : {:03x} - {}'.format(CountryCode
, ISO_Countries
.get(CountryCode
, '?')));
401 print(' OrganizationalAuthority : {:03x}'.format(OrganizationalAuthority
));
402 print(' ContractApplicationVersionNumber:', ContractApplicationVersionNumber
);
403 print(' ContractProvider :', ContractProvider
);
404 if (CountryCode
== 0x250):
405 oa
= FRA_OrganizationalAuthority_Contract_Provider
.get(OrganizationalAuthority
)
407 s
= oa
.get(ContractProvider
)
409 print(' ~ Authority & Provider ~ : {} ({})'.format(s
.OrganizationalAuthority
, s
.ContractProvider
))
410 Describe_Usage
= s
.UsageDescribeFunction
411 print(' ContractTariff :', ContractTariff
);
412 print(' ContractMediumEndDate : {} ({})'.format(ContractMediumEndDate
, (datetime(1997, 1, 1) + timedelta(days
= ContractMediumEndDate
)).strftime('%Y-%m-%d')));
413 print(' left... :', Distribution_left
);
414 print(' [CER] Distribution : {:08x}'.format(Distribution_Cer
.nom(32)))
417 if(Describe_Usage
is None):
418 Describe_Usage
= Describe_Usage_Generic
420 if COUNTER1
is not None:
421 print('[1] Counter: 0x{:06x} - Reloading available: 0x{:02x}'.format(COUNTER1
, RELOADING1
))
422 # if COUNTER2 is not None:
423 # print('[2] Counter: 0x{:06x} - Reloading available: 0x{:02x}'.format(COUNTER2, RELOADING2))
425 print('[S] SWAP : 0x{:08x} - last usage on USAGE_{}'.format(SWAP
, 'B' if SWAP
& 0b1 else 'A'))
431 No real documentation about Usage
432 Nearly all is left... - did not seen implementation with 2 counters or 1 Usage
435 if not Usage_A_DAT
.isEmpty():
438 Describe_Usage(Usage_A_DAT
, ContractMediumEndDate
, Usage_A_CER
)
440 if not Usage_B_DAT
.isEmpty():
443 Describe_Usage(Usage_B_DAT
, ContractMediumEndDate
, Usage_B_CER
)
449 if __name__
== '__main__':