blob: 91f50a3411728dc7bb46efe845a6f8c0d70416cc [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
Philipp Maier6bfa8a82023-10-09 13:32:49 +020051 def __str__(self):
52 return "dummy"
53
Harald Welte21caf322022-07-16 14:06:46 +020054 def _send_apdu_raw(self, pdu):
55 #print("DummySimLink-apdu: %s" % pdu)
56 return [], '9000'
57
58 def connect(self):
59 pass
60
61 def disconnect(self):
62 pass
63
64 def reset_card(self):
65 return 1
66
67 def get_atr(self):
68 return self._atr
69
70 def wait_for_card(self):
71 pass
72
73
74class Tracer:
75 def __init__(self, **kwargs):
Harald Welte323a3502023-07-11 17:26:39 +020076 # we assume a generic UICC profile; as all APDUs return 9000 in DummySimLink above,
77 # all CardProfileAddon (including SIM) will probe successful.
78 profile = CardProfileUICC()
Harald Welte21caf322022-07-16 14:06:46 +020079 profile.add_application(CardApplicationUSIM())
80 profile.add_application(CardApplicationISIM())
81 scc = SimCardCommands(transport=DummySimLink())
Harald Weltef8d2e2b2023-07-09 17:58:38 +020082 card = UiccCardBase(scc)
Harald Welte21caf322022-07-16 14:06:46 +020083 self.rs = RuntimeState(card, profile)
84 # APDU Decoder
85 self.ad = ApduDecoder(ApduCommands)
86 # parameters
87 self.suppress_status = kwargs.get('suppress_status', True)
88 self.suppress_select = kwargs.get('suppress_select', True)
Philipp Maier407c9552023-07-27 11:11:11 +020089 self.show_raw_apdu = kwargs.get('show_raw_apdu', False)
Harald Welte21caf322022-07-16 14:06:46 +020090 self.source = kwargs.get('source', None)
91
Philipp Maier407c9552023-07-27 11:11:11 +020092 def format_capdu(self, apdu: Apdu, inst: ApduCommand):
Harald Welte21caf322022-07-16 14:06:46 +020093 """Output a single decoded + processed ApduCommand."""
Philipp Maier407c9552023-07-27 11:11:11 +020094 if self.show_raw_apdu:
95 print(apdu)
Harald Welte21caf322022-07-16 14:06:46 +020096 print("%02u %-16s %-35s %-8s %s %s" % (inst.lchan_nr, inst._name, inst.path_str, inst.col_id, inst.col_sw, inst.processed))
97 print("===============================")
98
Philipp Maier162ba3a2023-07-27 11:46:26 +020099 def format_reset(self, apdu: CardReset):
100 """Output a single decoded CardReset."""
101 print(apdu)
102 print("===============================")
103
Harald Welte21caf322022-07-16 14:06:46 +0200104 def main(self):
105 """Main loop of tracer: Iterates over all Apdu received from source."""
Philipp Maier8dc2ca22023-07-27 13:00:41 +0200106 apdu_counter = 0
Harald Welte21caf322022-07-16 14:06:46 +0200107 while True:
108 # obtain the next APDU from the source (blocking read)
Philipp Maier8dc2ca22023-07-27 13:00:41 +0200109 try:
110 apdu = self.source.read()
111 apdu_counter = apdu_counter + 1
112 except StopIteration:
113 print("%i APDUs parsed, stop iteration." % apdu_counter)
114 return 0
Harald Welte21caf322022-07-16 14:06:46 +0200115
116 if isinstance(apdu, CardReset):
117 self.rs.reset()
Philipp Maier162ba3a2023-07-27 11:46:26 +0200118 self.format_reset(apdu)
Harald Welte21caf322022-07-16 14:06:46 +0200119 continue
120
121 # ask ApduDecoder to look-up (INS,CLA) + instantiate an ApduCommand derived
122 # class like 'UiccSelect'
123 inst = self.ad.input(apdu)
124 # process the APDU (may modify the RuntimeState)
125 inst.process(self.rs)
126
127 # Avoid cluttering the log with too much verbosity
128 if self.suppress_select and isinstance(inst, UiccSelect):
129 continue
130 if self.suppress_status and isinstance(inst, UiccStatus):
131 continue
Philipp Maier784b9472023-07-27 12:48:55 +0200132
Philipp Maier407c9552023-07-27 11:11:11 +0200133 self.format_capdu(apdu, inst)
Harald Welte21caf322022-07-16 14:06:46 +0200134
Harald Welte7e555692023-06-09 11:14:44 +0200135option_parser = argparse.ArgumentParser(description='Osmocom pySim high-level SIM card trace decoder',
Harald Welte21caf322022-07-16 14:06:46 +0200136 formatter_class=argparse.ArgumentDefaultsHelpFormatter)
137
138global_group = option_parser.add_argument_group('General Options')
Harald Welte72c5b2d2022-07-24 10:21:41 +0200139global_group.add_argument('--no-suppress-select', action='store_false', dest='suppress_select',
Harald Welte7e555692023-06-09 11:14:44 +0200140 help="""
141 Don't suppress displaying SELECT APDUs. We normally suppress them as they just clutter up
142 the output without giving any useful information. Any subsequent READ/UPDATE/... operations
143 on the selected file will log the file name most recently SELECTed.""")
Harald Welte72c5b2d2022-07-24 10:21:41 +0200144global_group.add_argument('--no-suppress-status', action='store_false', dest='suppress_status',
Harald Welte7e555692023-06-09 11:14:44 +0200145 help="""
146 Don't suppress displaying STATUS APDUs. We normally suppress them as they don't provide any
147 information that was not already received in resposne to the most recent SEELCT.""")
Philipp Maier407c9552023-07-27 11:11:11 +0200148global_group.add_argument('--show-raw-apdu', action='store_true', dest='show_raw_apdu',
149 help="""Show the raw APDU in addition to its parsed form.""")
150
Harald Welte21caf322022-07-16 14:06:46 +0200151
152subparsers = option_parser.add_subparsers(help='APDU Source', dest='source', required=True)
153
Harald Welte7e555692023-06-09 11:14:44 +0200154parser_gsmtap = subparsers.add_parser('gsmtap-udp', help="""
155 Read APDUs from live capture by receiving GSMTAP-SIM packets on specified UDP port.
156 Use this for live capture from SIMtrace2 or osmo-qcdiag.""")
Harald Welte21caf322022-07-16 14:06:46 +0200157parser_gsmtap.add_argument('-i', '--bind-ip', default='127.0.0.1',
158 help='Local IP address to which to bind the UDP port')
159parser_gsmtap.add_argument('-p', '--bind-port', default=4729,
160 help='Local UDP port')
161
Harald Weltec95f6e22022-12-02 22:50:35 +0100162parser_gsmtap_pyshark_pcap = subparsers.add_parser('gsmtap-pyshark-pcap', help="""
Harald Welte7e555692023-06-09 11:14:44 +0200163 Read APDUs from PCAP file containing GSMTAP (SIM APDU) communication; processed via pyshark.
164 Use this if you have recorded a PCAP file containing GSMTAP (SIM APDU) e.g. via tcpdump or
165 wireshark/tshark.""")
Harald Weltec95f6e22022-12-02 22:50:35 +0100166parser_gsmtap_pyshark_pcap.add_argument('-f', '--pcap-file', required=True,
167 help='Name of the PCAP[ng] file to be read')
168
Harald Welte21caf322022-07-16 14:06:46 +0200169parser_rspro_pyshark_pcap = subparsers.add_parser('rspro-pyshark-pcap', help="""
Harald Welte7e555692023-06-09 11:14:44 +0200170 Read APDUs from PCAP file containing RSPRO (osmo-remsim) communication; processed via pyshark.
Harald Welte21caf322022-07-16 14:06:46 +0200171 REQUIRES OSMOCOM PATCHED WIRESHARK!""")
172parser_rspro_pyshark_pcap.add_argument('-f', '--pcap-file', required=True,
173 help='Name of the PCAP[ng] file to be read')
174
175parser_rspro_pyshark_live = subparsers.add_parser('rspro-pyshark-live', help="""
Harald Welte7e555692023-06-09 11:14:44 +0200176 Read APDUs from live capture of RSPRO (osmo-remsim) communication; processed via pyshark.
Harald Welte21caf322022-07-16 14:06:46 +0200177 REQUIRES OSMOCOM PATCHED WIRESHARK!""")
178parser_rspro_pyshark_live.add_argument('-i', '--interface', required=True,
179 help='Name of the network interface to capture on')
180
181if __name__ == '__main__':
182
183 opts = option_parser.parse_args()
Harald Welte21caf322022-07-16 14:06:46 +0200184
Harald Weltecfa62cb2024-01-07 10:18:43 +0100185 logger.info('Opening source %s...', opts.source)
Harald Welte21caf322022-07-16 14:06:46 +0200186 if opts.source == 'gsmtap-udp':
187 s = GsmtapApduSource(opts.bind_ip, opts.bind_port)
188 elif opts.source == 'rspro-pyshark-pcap':
189 s = PysharkRsproPcap(opts.pcap_file)
190 elif opts.source == 'rspro-pyshark-live':
191 s = PysharkRsproLive(opts.interface)
Harald Weltec95f6e22022-12-02 22:50:35 +0100192 elif opts.source == 'gsmtap-pyshark-pcap':
193 s = PysharkGsmtapPcap(opts.pcap_file)
Harald Welte21caf322022-07-16 14:06:46 +0200194
Philipp Maier407c9552023-07-27 11:11:11 +0200195 tracer = Tracer(source=s, suppress_status=opts.suppress_status, suppress_select=opts.suppress_select,
196 show_raw_apdu=opts.show_raw_apdu)
Harald Welte21caf322022-07-16 14:06:46 +0200197 logger.info('Entering main loop...')
198 tracer.main()
199