blob: 6e5fc934d65079bb7b3a4f8f8223a5e8e5332359 [file] [log] [blame]
Harald Welte21caf322022-07-16 14:06:46 +02001#!/usr/bin/env python3
2
3import sys
4import logging, colorlog
5import argparse
6from pprint import pprint as pp
7
8from pySim.apdu import *
Harald Welte531894d2023-07-11 19:11:11 +02009from pySim.runtime import RuntimeState
Harald Welte21caf322022-07-16 14:06:46 +020010
Harald Weltef8d2e2b2023-07-09 17:58:38 +020011from pySim.cards import UiccCardBase
Harald Welte21caf322022-07-16 14:06:46 +020012from pySim.commands import SimCardCommands
13from pySim.profile import CardProfile
Harald Welte323a3502023-07-11 17:26:39 +020014from pySim.ts_102_221 import CardProfileUICC
Harald Welte21caf322022-07-16 14:06:46 +020015from pySim.ts_31_102 import CardApplicationUSIM
16from pySim.ts_31_103 import CardApplicationISIM
Harald Welte73a5c742024-04-07 12:13:52 +020017from pySim.euicc import CardApplicationISDR, CardApplicationECASD
Harald Welte21caf322022-07-16 14:06:46 +020018from pySim.transport import LinkBase
19
20from pySim.apdu_source.gsmtap import GsmtapApduSource
21from pySim.apdu_source.pyshark_rspro import PysharkRsproPcap, PysharkRsproLive
Harald Weltec95f6e22022-12-02 22:50:35 +010022from pySim.apdu_source.pyshark_gsmtap import PysharkGsmtapPcap
Harald Welte16749072024-06-08 15:49:58 +020023from pySim.apdu_source.tca_loader_log import TcaLoaderLogApduSource
Harald Welte21caf322022-07-16 14:06:46 +020024
25from pySim.apdu.ts_102_221 import UiccSelect, UiccStatus
26
27log_format='%(log_color)s%(levelname)-8s%(reset)s %(name)s: %(message)s'
28colorlog.basicConfig(level=logging.INFO, format = log_format)
29logger = colorlog.getLogger()
30
31# merge all of the command sets into one global set. This will override instructions,
32# the one from the 'last' set in the addition below will prevail.
Harald Weltef15f9c32022-07-24 10:10:37 +020033from pySim.apdu.ts_51_011 import ApduCommands as SimApduCommands
Harald Welte21caf322022-07-16 14:06:46 +020034from pySim.apdu.ts_102_221 import ApduCommands as UiccApduCommands
35from pySim.apdu.ts_31_102 import ApduCommands as UsimApduCommands
36from pySim.apdu.global_platform import ApduCommands as GpApduCommands
Harald Weltef15f9c32022-07-24 10:10:37 +020037ApduCommands = SimApduCommands + UiccApduCommands + UsimApduCommands #+ GpApduCommands
Harald Welte21caf322022-07-16 14:06:46 +020038
39
40class DummySimLink(LinkBase):
41 """A dummy implementation of the LinkBase abstract base class. Currently required
Harald Weltef8d2e2b2023-07-09 17:58:38 +020042 as the UiccCardBase doesn't work without SimCardCommands, which in turn require
Harald Welte21caf322022-07-16 14:06:46 +020043 a LinkBase implementation talking to a card.
44
45 In the tracer, we don't actually talk to any card, so we simply drop everything
46 and claim it is successful.
47
Harald Weltef8d2e2b2023-07-09 17:58:38 +020048 The UiccCardBase / SimCardCommands should be refactored to make this obsolete later."""
Harald Welte21caf322022-07-16 14:06:46 +020049 def __init__(self, debug: bool = False, **kwargs):
50 super().__init__(**kwargs)
51 self._debug = debug
52 self._atr = h2i('3B9F96801F878031E073FE211B674A4C753034054BA9')
53
Philipp Maier6bfa8a82023-10-09 13:32:49 +020054 def __str__(self):
55 return "dummy"
56
Harald Welte21caf322022-07-16 14:06:46 +020057 def _send_apdu_raw(self, pdu):
58 #print("DummySimLink-apdu: %s" % pdu)
59 return [], '9000'
60
61 def connect(self):
62 pass
63
64 def disconnect(self):
65 pass
66
67 def reset_card(self):
68 return 1
69
70 def get_atr(self):
71 return self._atr
72
73 def wait_for_card(self):
74 pass
75
76
77class Tracer:
78 def __init__(self, **kwargs):
Harald Welte323a3502023-07-11 17:26:39 +020079 # we assume a generic UICC profile; as all APDUs return 9000 in DummySimLink above,
80 # all CardProfileAddon (including SIM) will probe successful.
81 profile = CardProfileUICC()
Harald Welte21caf322022-07-16 14:06:46 +020082 profile.add_application(CardApplicationUSIM())
83 profile.add_application(CardApplicationISIM())
Harald Welte73a5c742024-04-07 12:13:52 +020084 profile.add_application(CardApplicationISDR())
85 profile.add_application(CardApplicationECASD())
Harald Welte21caf322022-07-16 14:06:46 +020086 scc = SimCardCommands(transport=DummySimLink())
Harald Weltef8d2e2b2023-07-09 17:58:38 +020087 card = UiccCardBase(scc)
Harald Welte21caf322022-07-16 14:06:46 +020088 self.rs = RuntimeState(card, profile)
89 # APDU Decoder
90 self.ad = ApduDecoder(ApduCommands)
91 # parameters
92 self.suppress_status = kwargs.get('suppress_status', True)
93 self.suppress_select = kwargs.get('suppress_select', True)
Philipp Maier407c9552023-07-27 11:11:11 +020094 self.show_raw_apdu = kwargs.get('show_raw_apdu', False)
Harald Welte21caf322022-07-16 14:06:46 +020095 self.source = kwargs.get('source', None)
96
Philipp Maier407c9552023-07-27 11:11:11 +020097 def format_capdu(self, apdu: Apdu, inst: ApduCommand):
Harald Welte21caf322022-07-16 14:06:46 +020098 """Output a single decoded + processed ApduCommand."""
Philipp Maier407c9552023-07-27 11:11:11 +020099 if self.show_raw_apdu:
100 print(apdu)
Harald Welte21caf322022-07-16 14:06:46 +0200101 print("%02u %-16s %-35s %-8s %s %s" % (inst.lchan_nr, inst._name, inst.path_str, inst.col_id, inst.col_sw, inst.processed))
102 print("===============================")
103
Philipp Maier162ba3a2023-07-27 11:46:26 +0200104 def format_reset(self, apdu: CardReset):
105 """Output a single decoded CardReset."""
106 print(apdu)
107 print("===============================")
108
Harald Welte21caf322022-07-16 14:06:46 +0200109 def main(self):
110 """Main loop of tracer: Iterates over all Apdu received from source."""
Philipp Maier8dc2ca22023-07-27 13:00:41 +0200111 apdu_counter = 0
Harald Welte21caf322022-07-16 14:06:46 +0200112 while True:
113 # obtain the next APDU from the source (blocking read)
Philipp Maier8dc2ca22023-07-27 13:00:41 +0200114 try:
115 apdu = self.source.read()
116 apdu_counter = apdu_counter + 1
117 except StopIteration:
118 print("%i APDUs parsed, stop iteration." % apdu_counter)
119 return 0
Harald Welte21caf322022-07-16 14:06:46 +0200120
121 if isinstance(apdu, CardReset):
122 self.rs.reset()
Philipp Maier162ba3a2023-07-27 11:46:26 +0200123 self.format_reset(apdu)
Harald Welte21caf322022-07-16 14:06:46 +0200124 continue
125
126 # ask ApduDecoder to look-up (INS,CLA) + instantiate an ApduCommand derived
127 # class like 'UiccSelect'
128 inst = self.ad.input(apdu)
129 # process the APDU (may modify the RuntimeState)
130 inst.process(self.rs)
131
132 # Avoid cluttering the log with too much verbosity
133 if self.suppress_select and isinstance(inst, UiccSelect):
134 continue
135 if self.suppress_status and isinstance(inst, UiccStatus):
136 continue
Philipp Maier784b9472023-07-27 12:48:55 +0200137
Philipp Maier407c9552023-07-27 11:11:11 +0200138 self.format_capdu(apdu, inst)
Harald Welte21caf322022-07-16 14:06:46 +0200139
Harald Welte7e555692023-06-09 11:14:44 +0200140option_parser = argparse.ArgumentParser(description='Osmocom pySim high-level SIM card trace decoder',
Harald Welte21caf322022-07-16 14:06:46 +0200141 formatter_class=argparse.ArgumentDefaultsHelpFormatter)
142
143global_group = option_parser.add_argument_group('General Options')
Harald Welte72c5b2d2022-07-24 10:21:41 +0200144global_group.add_argument('--no-suppress-select', action='store_false', dest='suppress_select',
Harald Welte7e555692023-06-09 11:14:44 +0200145 help="""
146 Don't suppress displaying SELECT APDUs. We normally suppress them as they just clutter up
147 the output without giving any useful information. Any subsequent READ/UPDATE/... operations
148 on the selected file will log the file name most recently SELECTed.""")
Harald Welte72c5b2d2022-07-24 10:21:41 +0200149global_group.add_argument('--no-suppress-status', action='store_false', dest='suppress_status',
Harald Welte7e555692023-06-09 11:14:44 +0200150 help="""
151 Don't suppress displaying STATUS APDUs. We normally suppress them as they don't provide any
152 information that was not already received in resposne to the most recent SEELCT.""")
Philipp Maier407c9552023-07-27 11:11:11 +0200153global_group.add_argument('--show-raw-apdu', action='store_true', dest='show_raw_apdu',
154 help="""Show the raw APDU in addition to its parsed form.""")
155
Harald Welte21caf322022-07-16 14:06:46 +0200156
157subparsers = option_parser.add_subparsers(help='APDU Source', dest='source', required=True)
158
Harald Welte7e555692023-06-09 11:14:44 +0200159parser_gsmtap = subparsers.add_parser('gsmtap-udp', help="""
160 Read APDUs from live capture by receiving GSMTAP-SIM packets on specified UDP port.
161 Use this for live capture from SIMtrace2 or osmo-qcdiag.""")
Harald Welte21caf322022-07-16 14:06:46 +0200162parser_gsmtap.add_argument('-i', '--bind-ip', default='127.0.0.1',
163 help='Local IP address to which to bind the UDP port')
164parser_gsmtap.add_argument('-p', '--bind-port', default=4729,
165 help='Local UDP port')
166
Harald Weltec95f6e22022-12-02 22:50:35 +0100167parser_gsmtap_pyshark_pcap = subparsers.add_parser('gsmtap-pyshark-pcap', help="""
Harald Welte7e555692023-06-09 11:14:44 +0200168 Read APDUs from PCAP file containing GSMTAP (SIM APDU) communication; processed via pyshark.
169 Use this if you have recorded a PCAP file containing GSMTAP (SIM APDU) e.g. via tcpdump or
170 wireshark/tshark.""")
Harald Weltec95f6e22022-12-02 22:50:35 +0100171parser_gsmtap_pyshark_pcap.add_argument('-f', '--pcap-file', required=True,
172 help='Name of the PCAP[ng] file to be read')
173
Harald Welte21caf322022-07-16 14:06:46 +0200174parser_rspro_pyshark_pcap = subparsers.add_parser('rspro-pyshark-pcap', help="""
Harald Welte7e555692023-06-09 11:14:44 +0200175 Read APDUs from PCAP file containing RSPRO (osmo-remsim) communication; processed via pyshark.
Harald Welte21caf322022-07-16 14:06:46 +0200176 REQUIRES OSMOCOM PATCHED WIRESHARK!""")
177parser_rspro_pyshark_pcap.add_argument('-f', '--pcap-file', required=True,
178 help='Name of the PCAP[ng] file to be read')
179
180parser_rspro_pyshark_live = subparsers.add_parser('rspro-pyshark-live', help="""
Harald Welte7e555692023-06-09 11:14:44 +0200181 Read APDUs from live capture of RSPRO (osmo-remsim) communication; processed via pyshark.
Harald Welte21caf322022-07-16 14:06:46 +0200182 REQUIRES OSMOCOM PATCHED WIRESHARK!""")
183parser_rspro_pyshark_live.add_argument('-i', '--interface', required=True,
184 help='Name of the network interface to capture on')
185
Harald Welte16749072024-06-08 15:49:58 +0200186parser_tcaloader_log = subparsers.add_parser('tca-loader-log', help="""
187 Read APDUs from a TCA Loader log file.""")
188parser_tcaloader_log.add_argument('-f', '--log-file', required=True,
189 help='Name of te log file to be read')
190
Harald Welte21caf322022-07-16 14:06:46 +0200191if __name__ == '__main__':
192
193 opts = option_parser.parse_args()
Harald Welte21caf322022-07-16 14:06:46 +0200194
Harald Weltecfa62cb2024-01-07 10:18:43 +0100195 logger.info('Opening source %s...', opts.source)
Harald Welte21caf322022-07-16 14:06:46 +0200196 if opts.source == 'gsmtap-udp':
197 s = GsmtapApduSource(opts.bind_ip, opts.bind_port)
198 elif opts.source == 'rspro-pyshark-pcap':
199 s = PysharkRsproPcap(opts.pcap_file)
200 elif opts.source == 'rspro-pyshark-live':
201 s = PysharkRsproLive(opts.interface)
Harald Weltec95f6e22022-12-02 22:50:35 +0100202 elif opts.source == 'gsmtap-pyshark-pcap':
203 s = PysharkGsmtapPcap(opts.pcap_file)
Harald Welte16749072024-06-08 15:49:58 +0200204 elif opts.source == 'tca-loader-log':
205 s = TcaLoaderLogApduSource(opts.log_file)
Harald Welte568d8cf2024-05-22 18:03:17 +0200206 else:
207 raise ValueError("unsupported source %s", opts.source)
Harald Welte21caf322022-07-16 14:06:46 +0200208
Philipp Maier407c9552023-07-27 11:11:11 +0200209 tracer = Tracer(source=s, suppress_status=opts.suppress_status, suppress_select=opts.suppress_select,
210 show_raw_apdu=opts.show_raw_apdu)
Harald Welte21caf322022-07-16 14:06:46 +0200211 logger.info('Entering main loop...')
212 tracer.main()
213