blob: 325fb8ce9426796e2e4e5a6022bb914734ee5f34 [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
17from pySim.transport import LinkBase
18
19from pySim.apdu_source.gsmtap import GsmtapApduSource
20from pySim.apdu_source.pyshark_rspro import PysharkRsproPcap, PysharkRsproLive
Harald Weltec95f6e22022-12-02 22:50:35 +010021from pySim.apdu_source.pyshark_gsmtap import PysharkGsmtapPcap
Harald Welte21caf322022-07-16 14:06:46 +020022
23from pySim.apdu.ts_102_221 import UiccSelect, UiccStatus
24
25log_format='%(log_color)s%(levelname)-8s%(reset)s %(name)s: %(message)s'
26colorlog.basicConfig(level=logging.INFO, format = log_format)
27logger = colorlog.getLogger()
28
29# merge all of the command sets into one global set. This will override instructions,
30# the one from the 'last' set in the addition below will prevail.
31from pySim.apdu.ts_102_221 import ApduCommands as UiccApduCommands
32from pySim.apdu.ts_31_102 import ApduCommands as UsimApduCommands
33from pySim.apdu.global_platform import ApduCommands as GpApduCommands
34ApduCommands = UiccApduCommands + UsimApduCommands #+ GpApduCommands
35
36
37class DummySimLink(LinkBase):
38 """A dummy implementation of the LinkBase abstract base class. Currently required
Harald Weltef8d2e2b2023-07-09 17:58:38 +020039 as the UiccCardBase doesn't work without SimCardCommands, which in turn require
Harald Welte21caf322022-07-16 14:06:46 +020040 a LinkBase implementation talking to a card.
41
42 In the tracer, we don't actually talk to any card, so we simply drop everything
43 and claim it is successful.
44
Harald Weltef8d2e2b2023-07-09 17:58:38 +020045 The UiccCardBase / SimCardCommands should be refactored to make this obsolete later."""
Harald Welte21caf322022-07-16 14:06:46 +020046 def __init__(self, debug: bool = False, **kwargs):
47 super().__init__(**kwargs)
48 self._debug = debug
49 self._atr = h2i('3B9F96801F878031E073FE211B674A4C753034054BA9')
50
51 def _send_apdu_raw(self, pdu):
52 #print("DummySimLink-apdu: %s" % pdu)
53 return [], '9000'
54
55 def connect(self):
56 pass
57
58 def disconnect(self):
59 pass
60
61 def reset_card(self):
62 return 1
63
64 def get_atr(self):
65 return self._atr
66
67 def wait_for_card(self):
68 pass
69
70
71class Tracer:
72 def __init__(self, **kwargs):
Harald Welte323a3502023-07-11 17:26:39 +020073 # we assume a generic UICC profile; as all APDUs return 9000 in DummySimLink above,
74 # all CardProfileAddon (including SIM) will probe successful.
75 profile = CardProfileUICC()
Harald Welte21caf322022-07-16 14:06:46 +020076 profile.add_application(CardApplicationUSIM())
77 profile.add_application(CardApplicationISIM())
78 scc = SimCardCommands(transport=DummySimLink())
Harald Weltef8d2e2b2023-07-09 17:58:38 +020079 card = UiccCardBase(scc)
Harald Welte21caf322022-07-16 14:06:46 +020080 self.rs = RuntimeState(card, profile)
81 # APDU Decoder
82 self.ad = ApduDecoder(ApduCommands)
83 # parameters
84 self.suppress_status = kwargs.get('suppress_status', True)
85 self.suppress_select = kwargs.get('suppress_select', True)
Philipp Maier407c9552023-07-27 11:11:11 +020086 self.show_raw_apdu = kwargs.get('show_raw_apdu', False)
Harald Welte21caf322022-07-16 14:06:46 +020087 self.source = kwargs.get('source', None)
88
Philipp Maier407c9552023-07-27 11:11:11 +020089 def format_capdu(self, apdu: Apdu, inst: ApduCommand):
Harald Welte21caf322022-07-16 14:06:46 +020090 """Output a single decoded + processed ApduCommand."""
Philipp Maier407c9552023-07-27 11:11:11 +020091 if self.show_raw_apdu:
92 print(apdu)
Harald Welte21caf322022-07-16 14:06:46 +020093 print("%02u %-16s %-35s %-8s %s %s" % (inst.lchan_nr, inst._name, inst.path_str, inst.col_id, inst.col_sw, inst.processed))
94 print("===============================")
95
Philipp Maier162ba3a2023-07-27 11:46:26 +020096 def format_reset(self, apdu: CardReset):
97 """Output a single decoded CardReset."""
98 print(apdu)
99 print("===============================")
100
Harald Welte21caf322022-07-16 14:06:46 +0200101 def main(self):
102 """Main loop of tracer: Iterates over all Apdu received from source."""
Philipp Maier8dc2ca22023-07-27 13:00:41 +0200103 apdu_counter = 0
Harald Welte21caf322022-07-16 14:06:46 +0200104 while True:
105 # obtain the next APDU from the source (blocking read)
Philipp Maier8dc2ca22023-07-27 13:00:41 +0200106 try:
107 apdu = self.source.read()
108 apdu_counter = apdu_counter + 1
109 except StopIteration:
110 print("%i APDUs parsed, stop iteration." % apdu_counter)
111 return 0
Harald Welte21caf322022-07-16 14:06:46 +0200112
113 if isinstance(apdu, CardReset):
114 self.rs.reset()
Philipp Maier162ba3a2023-07-27 11:46:26 +0200115 self.format_reset(apdu)
Harald Welte21caf322022-07-16 14:06:46 +0200116 continue
117
118 # ask ApduDecoder to look-up (INS,CLA) + instantiate an ApduCommand derived
119 # class like 'UiccSelect'
120 inst = self.ad.input(apdu)
121 # process the APDU (may modify the RuntimeState)
122 inst.process(self.rs)
123
124 # Avoid cluttering the log with too much verbosity
125 if self.suppress_select and isinstance(inst, UiccSelect):
126 continue
127 if self.suppress_status and isinstance(inst, UiccStatus):
128 continue
Philipp Maier784b9472023-07-27 12:48:55 +0200129
Philipp Maier407c9552023-07-27 11:11:11 +0200130 self.format_capdu(apdu, inst)
Harald Welte21caf322022-07-16 14:06:46 +0200131
Harald Welte7e555692023-06-09 11:14:44 +0200132option_parser = argparse.ArgumentParser(description='Osmocom pySim high-level SIM card trace decoder',
Harald Welte21caf322022-07-16 14:06:46 +0200133 formatter_class=argparse.ArgumentDefaultsHelpFormatter)
134
135global_group = option_parser.add_argument_group('General Options')
Harald Welte72c5b2d2022-07-24 10:21:41 +0200136global_group.add_argument('--no-suppress-select', action='store_false', dest='suppress_select',
Harald Welte7e555692023-06-09 11:14:44 +0200137 help="""
138 Don't suppress displaying SELECT APDUs. We normally suppress them as they just clutter up
139 the output without giving any useful information. Any subsequent READ/UPDATE/... operations
140 on the selected file will log the file name most recently SELECTed.""")
Harald Welte72c5b2d2022-07-24 10:21:41 +0200141global_group.add_argument('--no-suppress-status', action='store_false', dest='suppress_status',
Harald Welte7e555692023-06-09 11:14:44 +0200142 help="""
143 Don't suppress displaying STATUS APDUs. We normally suppress them as they don't provide any
144 information that was not already received in resposne to the most recent SEELCT.""")
Philipp Maier407c9552023-07-27 11:11:11 +0200145global_group.add_argument('--show-raw-apdu', action='store_true', dest='show_raw_apdu',
146 help="""Show the raw APDU in addition to its parsed form.""")
147
Harald Welte21caf322022-07-16 14:06:46 +0200148
149subparsers = option_parser.add_subparsers(help='APDU Source', dest='source', required=True)
150
Harald Welte7e555692023-06-09 11:14:44 +0200151parser_gsmtap = subparsers.add_parser('gsmtap-udp', help="""
152 Read APDUs from live capture by receiving GSMTAP-SIM packets on specified UDP port.
153 Use this for live capture from SIMtrace2 or osmo-qcdiag.""")
Harald Welte21caf322022-07-16 14:06:46 +0200154parser_gsmtap.add_argument('-i', '--bind-ip', default='127.0.0.1',
155 help='Local IP address to which to bind the UDP port')
156parser_gsmtap.add_argument('-p', '--bind-port', default=4729,
157 help='Local UDP port')
158
Harald Weltec95f6e22022-12-02 22:50:35 +0100159parser_gsmtap_pyshark_pcap = subparsers.add_parser('gsmtap-pyshark-pcap', help="""
Harald Welte7e555692023-06-09 11:14:44 +0200160 Read APDUs from PCAP file containing GSMTAP (SIM APDU) communication; processed via pyshark.
161 Use this if you have recorded a PCAP file containing GSMTAP (SIM APDU) e.g. via tcpdump or
162 wireshark/tshark.""")
Harald Weltec95f6e22022-12-02 22:50:35 +0100163parser_gsmtap_pyshark_pcap.add_argument('-f', '--pcap-file', required=True,
164 help='Name of the PCAP[ng] file to be read')
165
Harald Welte21caf322022-07-16 14:06:46 +0200166parser_rspro_pyshark_pcap = subparsers.add_parser('rspro-pyshark-pcap', help="""
Harald Welte7e555692023-06-09 11:14:44 +0200167 Read APDUs from PCAP file containing RSPRO (osmo-remsim) communication; processed via pyshark.
Harald Welte21caf322022-07-16 14:06:46 +0200168 REQUIRES OSMOCOM PATCHED WIRESHARK!""")
169parser_rspro_pyshark_pcap.add_argument('-f', '--pcap-file', required=True,
170 help='Name of the PCAP[ng] file to be read')
171
172parser_rspro_pyshark_live = subparsers.add_parser('rspro-pyshark-live', help="""
Harald Welte7e555692023-06-09 11:14:44 +0200173 Read APDUs from live capture of RSPRO (osmo-remsim) communication; processed via pyshark.
Harald Welte21caf322022-07-16 14:06:46 +0200174 REQUIRES OSMOCOM PATCHED WIRESHARK!""")
175parser_rspro_pyshark_live.add_argument('-i', '--interface', required=True,
176 help='Name of the network interface to capture on')
177
178if __name__ == '__main__':
179
180 opts = option_parser.parse_args()
Harald Welte21caf322022-07-16 14:06:46 +0200181
182 logger.info('Opening source %s...' % opts.source)
183 if opts.source == 'gsmtap-udp':
184 s = GsmtapApduSource(opts.bind_ip, opts.bind_port)
185 elif opts.source == 'rspro-pyshark-pcap':
186 s = PysharkRsproPcap(opts.pcap_file)
187 elif opts.source == 'rspro-pyshark-live':
188 s = PysharkRsproLive(opts.interface)
Harald Weltec95f6e22022-12-02 22:50:35 +0100189 elif opts.source == 'gsmtap-pyshark-pcap':
190 s = PysharkGsmtapPcap(opts.pcap_file)
Harald Welte21caf322022-07-16 14:06:46 +0200191
Philipp Maier407c9552023-07-27 11:11:11 +0200192 tracer = Tracer(source=s, suppress_status=opts.suppress_status, suppress_select=opts.suppress_select,
193 show_raw_apdu=opts.show_raw_apdu)
Harald Welte21caf322022-07-16 14:06:46 +0200194 logger.info('Entering main loop...')
195 tracer.main()
196