blob: 8313c27c29fbdd5719b07c8450184cdd529598fe [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."""
103 while True:
104 # obtain the next APDU from the source (blocking read)
105 apdu = self.source.read()
Harald Welte21caf322022-07-16 14:06:46 +0200106
107 if isinstance(apdu, CardReset):
108 self.rs.reset()
Philipp Maier162ba3a2023-07-27 11:46:26 +0200109 self.format_reset(apdu)
Harald Welte21caf322022-07-16 14:06:46 +0200110 continue
111
112 # ask ApduDecoder to look-up (INS,CLA) + instantiate an ApduCommand derived
113 # class like 'UiccSelect'
114 inst = self.ad.input(apdu)
115 # process the APDU (may modify the RuntimeState)
116 inst.process(self.rs)
117
118 # Avoid cluttering the log with too much verbosity
119 if self.suppress_select and isinstance(inst, UiccSelect):
120 continue
121 if self.suppress_status and isinstance(inst, UiccStatus):
122 continue
Philipp Maier784b9472023-07-27 12:48:55 +0200123
Philipp Maier407c9552023-07-27 11:11:11 +0200124 self.format_capdu(apdu, inst)
Harald Welte21caf322022-07-16 14:06:46 +0200125
Harald Welte7e555692023-06-09 11:14:44 +0200126option_parser = argparse.ArgumentParser(description='Osmocom pySim high-level SIM card trace decoder',
Harald Welte21caf322022-07-16 14:06:46 +0200127 formatter_class=argparse.ArgumentDefaultsHelpFormatter)
128
129global_group = option_parser.add_argument_group('General Options')
Harald Welte72c5b2d2022-07-24 10:21:41 +0200130global_group.add_argument('--no-suppress-select', action='store_false', dest='suppress_select',
Harald Welte7e555692023-06-09 11:14:44 +0200131 help="""
132 Don't suppress displaying SELECT APDUs. We normally suppress them as they just clutter up
133 the output without giving any useful information. Any subsequent READ/UPDATE/... operations
134 on the selected file will log the file name most recently SELECTed.""")
Harald Welte72c5b2d2022-07-24 10:21:41 +0200135global_group.add_argument('--no-suppress-status', action='store_false', dest='suppress_status',
Harald Welte7e555692023-06-09 11:14:44 +0200136 help="""
137 Don't suppress displaying STATUS APDUs. We normally suppress them as they don't provide any
138 information that was not already received in resposne to the most recent SEELCT.""")
Philipp Maier407c9552023-07-27 11:11:11 +0200139global_group.add_argument('--show-raw-apdu', action='store_true', dest='show_raw_apdu',
140 help="""Show the raw APDU in addition to its parsed form.""")
141
Harald Welte21caf322022-07-16 14:06:46 +0200142
143subparsers = option_parser.add_subparsers(help='APDU Source', dest='source', required=True)
144
Harald Welte7e555692023-06-09 11:14:44 +0200145parser_gsmtap = subparsers.add_parser('gsmtap-udp', help="""
146 Read APDUs from live capture by receiving GSMTAP-SIM packets on specified UDP port.
147 Use this for live capture from SIMtrace2 or osmo-qcdiag.""")
Harald Welte21caf322022-07-16 14:06:46 +0200148parser_gsmtap.add_argument('-i', '--bind-ip', default='127.0.0.1',
149 help='Local IP address to which to bind the UDP port')
150parser_gsmtap.add_argument('-p', '--bind-port', default=4729,
151 help='Local UDP port')
152
Harald Weltec95f6e22022-12-02 22:50:35 +0100153parser_gsmtap_pyshark_pcap = subparsers.add_parser('gsmtap-pyshark-pcap', help="""
Harald Welte7e555692023-06-09 11:14:44 +0200154 Read APDUs from PCAP file containing GSMTAP (SIM APDU) communication; processed via pyshark.
155 Use this if you have recorded a PCAP file containing GSMTAP (SIM APDU) e.g. via tcpdump or
156 wireshark/tshark.""")
Harald Weltec95f6e22022-12-02 22:50:35 +0100157parser_gsmtap_pyshark_pcap.add_argument('-f', '--pcap-file', required=True,
158 help='Name of the PCAP[ng] file to be read')
159
Harald Welte21caf322022-07-16 14:06:46 +0200160parser_rspro_pyshark_pcap = subparsers.add_parser('rspro-pyshark-pcap', help="""
Harald Welte7e555692023-06-09 11:14:44 +0200161 Read APDUs from PCAP file containing RSPRO (osmo-remsim) communication; processed via pyshark.
Harald Welte21caf322022-07-16 14:06:46 +0200162 REQUIRES OSMOCOM PATCHED WIRESHARK!""")
163parser_rspro_pyshark_pcap.add_argument('-f', '--pcap-file', required=True,
164 help='Name of the PCAP[ng] file to be read')
165
166parser_rspro_pyshark_live = subparsers.add_parser('rspro-pyshark-live', help="""
Harald Welte7e555692023-06-09 11:14:44 +0200167 Read APDUs from live capture of RSPRO (osmo-remsim) communication; processed via pyshark.
Harald Welte21caf322022-07-16 14:06:46 +0200168 REQUIRES OSMOCOM PATCHED WIRESHARK!""")
169parser_rspro_pyshark_live.add_argument('-i', '--interface', required=True,
170 help='Name of the network interface to capture on')
171
172if __name__ == '__main__':
173
174 opts = option_parser.parse_args()
Harald Welte21caf322022-07-16 14:06:46 +0200175
176 logger.info('Opening source %s...' % opts.source)
177 if opts.source == 'gsmtap-udp':
178 s = GsmtapApduSource(opts.bind_ip, opts.bind_port)
179 elif opts.source == 'rspro-pyshark-pcap':
180 s = PysharkRsproPcap(opts.pcap_file)
181 elif opts.source == 'rspro-pyshark-live':
182 s = PysharkRsproLive(opts.interface)
Harald Weltec95f6e22022-12-02 22:50:35 +0100183 elif opts.source == 'gsmtap-pyshark-pcap':
184 s = PysharkGsmtapPcap(opts.pcap_file)
Harald Welte21caf322022-07-16 14:06:46 +0200185
Philipp Maier407c9552023-07-27 11:11:11 +0200186 tracer = Tracer(source=s, suppress_status=opts.suppress_status, suppress_select=opts.suppress_select,
187 show_raw_apdu=opts.show_raw_apdu)
Harald Welte21caf322022-07-16 14:06:46 +0200188 logger.info('Entering main loop...')
189 tracer.main()
190