blob: d457bf43e29ed00a0c55da67fbdeb8b0a4c32985 [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)
86 self.source = kwargs.get('source', None)
87
88 def format_capdu(self, inst: ApduCommand):
89 """Output a single decoded + processed ApduCommand."""
90 print("%02u %-16s %-35s %-8s %s %s" % (inst.lchan_nr, inst._name, inst.path_str, inst.col_id, inst.col_sw, inst.processed))
91 print("===============================")
92
93 def main(self):
94 """Main loop of tracer: Iterates over all Apdu received from source."""
95 while True:
96 # obtain the next APDU from the source (blocking read)
97 apdu = self.source.read()
98 #print(apdu)
99
100 if isinstance(apdu, CardReset):
101 self.rs.reset()
102 continue
103
104 # ask ApduDecoder to look-up (INS,CLA) + instantiate an ApduCommand derived
105 # class like 'UiccSelect'
106 inst = self.ad.input(apdu)
107 # process the APDU (may modify the RuntimeState)
108 inst.process(self.rs)
109
110 # Avoid cluttering the log with too much verbosity
111 if self.suppress_select and isinstance(inst, UiccSelect):
112 continue
113 if self.suppress_status and isinstance(inst, UiccStatus):
114 continue
115 #print(inst)
116 self.format_capdu(inst)
117
Harald Welte7e555692023-06-09 11:14:44 +0200118option_parser = argparse.ArgumentParser(description='Osmocom pySim high-level SIM card trace decoder',
Harald Welte21caf322022-07-16 14:06:46 +0200119 formatter_class=argparse.ArgumentDefaultsHelpFormatter)
120
121global_group = option_parser.add_argument_group('General Options')
Harald Welte72c5b2d2022-07-24 10:21:41 +0200122global_group.add_argument('--no-suppress-select', action='store_false', dest='suppress_select',
Harald Welte7e555692023-06-09 11:14:44 +0200123 help="""
124 Don't suppress displaying SELECT APDUs. We normally suppress them as they just clutter up
125 the output without giving any useful information. Any subsequent READ/UPDATE/... operations
126 on the selected file will log the file name most recently SELECTed.""")
Harald Welte72c5b2d2022-07-24 10:21:41 +0200127global_group.add_argument('--no-suppress-status', action='store_false', dest='suppress_status',
Harald Welte7e555692023-06-09 11:14:44 +0200128 help="""
129 Don't suppress displaying STATUS APDUs. We normally suppress them as they don't provide any
130 information that was not already received in resposne to the most recent SEELCT.""")
Harald Welte21caf322022-07-16 14:06:46 +0200131
132subparsers = option_parser.add_subparsers(help='APDU Source', dest='source', required=True)
133
Harald Welte7e555692023-06-09 11:14:44 +0200134parser_gsmtap = subparsers.add_parser('gsmtap-udp', help="""
135 Read APDUs from live capture by receiving GSMTAP-SIM packets on specified UDP port.
136 Use this for live capture from SIMtrace2 or osmo-qcdiag.""")
Harald Welte21caf322022-07-16 14:06:46 +0200137parser_gsmtap.add_argument('-i', '--bind-ip', default='127.0.0.1',
138 help='Local IP address to which to bind the UDP port')
139parser_gsmtap.add_argument('-p', '--bind-port', default=4729,
140 help='Local UDP port')
141
Harald Weltec95f6e22022-12-02 22:50:35 +0100142parser_gsmtap_pyshark_pcap = subparsers.add_parser('gsmtap-pyshark-pcap', help="""
Harald Welte7e555692023-06-09 11:14:44 +0200143 Read APDUs from PCAP file containing GSMTAP (SIM APDU) communication; processed via pyshark.
144 Use this if you have recorded a PCAP file containing GSMTAP (SIM APDU) e.g. via tcpdump or
145 wireshark/tshark.""")
Harald Weltec95f6e22022-12-02 22:50:35 +0100146parser_gsmtap_pyshark_pcap.add_argument('-f', '--pcap-file', required=True,
147 help='Name of the PCAP[ng] file to be read')
148
Harald Welte21caf322022-07-16 14:06:46 +0200149parser_rspro_pyshark_pcap = subparsers.add_parser('rspro-pyshark-pcap', help="""
Harald Welte7e555692023-06-09 11:14:44 +0200150 Read APDUs from PCAP file containing RSPRO (osmo-remsim) communication; processed via pyshark.
Harald Welte21caf322022-07-16 14:06:46 +0200151 REQUIRES OSMOCOM PATCHED WIRESHARK!""")
152parser_rspro_pyshark_pcap.add_argument('-f', '--pcap-file', required=True,
153 help='Name of the PCAP[ng] file to be read')
154
155parser_rspro_pyshark_live = subparsers.add_parser('rspro-pyshark-live', help="""
Harald Welte7e555692023-06-09 11:14:44 +0200156 Read APDUs from live capture of RSPRO (osmo-remsim) communication; processed via pyshark.
Harald Welte21caf322022-07-16 14:06:46 +0200157 REQUIRES OSMOCOM PATCHED WIRESHARK!""")
158parser_rspro_pyshark_live.add_argument('-i', '--interface', required=True,
159 help='Name of the network interface to capture on')
160
161if __name__ == '__main__':
162
163 opts = option_parser.parse_args()
Harald Welte21caf322022-07-16 14:06:46 +0200164
165 logger.info('Opening source %s...' % opts.source)
166 if opts.source == 'gsmtap-udp':
167 s = GsmtapApduSource(opts.bind_ip, opts.bind_port)
168 elif opts.source == 'rspro-pyshark-pcap':
169 s = PysharkRsproPcap(opts.pcap_file)
170 elif opts.source == 'rspro-pyshark-live':
171 s = PysharkRsproLive(opts.interface)
Harald Weltec95f6e22022-12-02 22:50:35 +0100172 elif opts.source == 'gsmtap-pyshark-pcap':
173 s = PysharkGsmtapPcap(opts.pcap_file)
Harald Welte21caf322022-07-16 14:06:46 +0200174
Harald Welte72c5b2d2022-07-24 10:21:41 +0200175 tracer = Tracer(source=s, suppress_status=opts.suppress_status, suppress_select=opts.suppress_select)
Harald Welte21caf322022-07-16 14:06:46 +0200176 logger.info('Entering main loop...')
177 tracer.main()
178