blob: 4c8696d43e74fda90193942ebb7ac8879c321734 [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 *
9from pySim.filesystem import RuntimeState
10
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
14from pySim.ts_102_221 import CardProfileUICCSIM
15from 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):
73 # we assume a generic SIM + UICC + USIM + ISIM card
74 profile = CardProfileUICCSIM()
75 profile.add_application(CardApplicationUSIM())
76 profile.add_application(CardApplicationISIM())
77 scc = SimCardCommands(transport=DummySimLink())
Harald Weltef8d2e2b2023-07-09 17:58:38 +020078 card = UiccCardBase(scc)
Harald Welte21caf322022-07-16 14:06:46 +020079 self.rs = RuntimeState(card, profile)
80 # APDU Decoder
81 self.ad = ApduDecoder(ApduCommands)
82 # parameters
83 self.suppress_status = kwargs.get('suppress_status', True)
84 self.suppress_select = kwargs.get('suppress_select', True)
85 self.source = kwargs.get('source', None)
86
87 def format_capdu(self, inst: ApduCommand):
88 """Output a single decoded + processed ApduCommand."""
89 print("%02u %-16s %-35s %-8s %s %s" % (inst.lchan_nr, inst._name, inst.path_str, inst.col_id, inst.col_sw, inst.processed))
90 print("===============================")
91
92 def main(self):
93 """Main loop of tracer: Iterates over all Apdu received from source."""
94 while True:
95 # obtain the next APDU from the source (blocking read)
96 apdu = self.source.read()
97 #print(apdu)
98
99 if isinstance(apdu, CardReset):
100 self.rs.reset()
101 continue
102
103 # ask ApduDecoder to look-up (INS,CLA) + instantiate an ApduCommand derived
104 # class like 'UiccSelect'
105 inst = self.ad.input(apdu)
106 # process the APDU (may modify the RuntimeState)
107 inst.process(self.rs)
108
109 # Avoid cluttering the log with too much verbosity
110 if self.suppress_select and isinstance(inst, UiccSelect):
111 continue
112 if self.suppress_status and isinstance(inst, UiccStatus):
113 continue
114 #print(inst)
115 self.format_capdu(inst)
116
Harald Welte7e555692023-06-09 11:14:44 +0200117option_parser = argparse.ArgumentParser(description='Osmocom pySim high-level SIM card trace decoder',
Harald Welte21caf322022-07-16 14:06:46 +0200118 formatter_class=argparse.ArgumentDefaultsHelpFormatter)
119
120global_group = option_parser.add_argument_group('General Options')
Harald Welte72c5b2d2022-07-24 10:21:41 +0200121global_group.add_argument('--no-suppress-select', action='store_false', dest='suppress_select',
Harald Welte7e555692023-06-09 11:14:44 +0200122 help="""
123 Don't suppress displaying SELECT APDUs. We normally suppress them as they just clutter up
124 the output without giving any useful information. Any subsequent READ/UPDATE/... operations
125 on the selected file will log the file name most recently SELECTed.""")
Harald Welte72c5b2d2022-07-24 10:21:41 +0200126global_group.add_argument('--no-suppress-status', action='store_false', dest='suppress_status',
Harald Welte7e555692023-06-09 11:14:44 +0200127 help="""
128 Don't suppress displaying STATUS APDUs. We normally suppress them as they don't provide any
129 information that was not already received in resposne to the most recent SEELCT.""")
Harald Welte21caf322022-07-16 14:06:46 +0200130
131subparsers = option_parser.add_subparsers(help='APDU Source', dest='source', required=True)
132
Harald Welte7e555692023-06-09 11:14:44 +0200133parser_gsmtap = subparsers.add_parser('gsmtap-udp', help="""
134 Read APDUs from live capture by receiving GSMTAP-SIM packets on specified UDP port.
135 Use this for live capture from SIMtrace2 or osmo-qcdiag.""")
Harald Welte21caf322022-07-16 14:06:46 +0200136parser_gsmtap.add_argument('-i', '--bind-ip', default='127.0.0.1',
137 help='Local IP address to which to bind the UDP port')
138parser_gsmtap.add_argument('-p', '--bind-port', default=4729,
139 help='Local UDP port')
140
Harald Weltec95f6e22022-12-02 22:50:35 +0100141parser_gsmtap_pyshark_pcap = subparsers.add_parser('gsmtap-pyshark-pcap', help="""
Harald Welte7e555692023-06-09 11:14:44 +0200142 Read APDUs from PCAP file containing GSMTAP (SIM APDU) communication; processed via pyshark.
143 Use this if you have recorded a PCAP file containing GSMTAP (SIM APDU) e.g. via tcpdump or
144 wireshark/tshark.""")
Harald Weltec95f6e22022-12-02 22:50:35 +0100145parser_gsmtap_pyshark_pcap.add_argument('-f', '--pcap-file', required=True,
146 help='Name of the PCAP[ng] file to be read')
147
Harald Welte21caf322022-07-16 14:06:46 +0200148parser_rspro_pyshark_pcap = subparsers.add_parser('rspro-pyshark-pcap', help="""
Harald Welte7e555692023-06-09 11:14:44 +0200149 Read APDUs from PCAP file containing RSPRO (osmo-remsim) communication; processed via pyshark.
Harald Welte21caf322022-07-16 14:06:46 +0200150 REQUIRES OSMOCOM PATCHED WIRESHARK!""")
151parser_rspro_pyshark_pcap.add_argument('-f', '--pcap-file', required=True,
152 help='Name of the PCAP[ng] file to be read')
153
154parser_rspro_pyshark_live = subparsers.add_parser('rspro-pyshark-live', help="""
Harald Welte7e555692023-06-09 11:14:44 +0200155 Read APDUs from live capture of RSPRO (osmo-remsim) communication; processed via pyshark.
Harald Welte21caf322022-07-16 14:06:46 +0200156 REQUIRES OSMOCOM PATCHED WIRESHARK!""")
157parser_rspro_pyshark_live.add_argument('-i', '--interface', required=True,
158 help='Name of the network interface to capture on')
159
160if __name__ == '__main__':
161
162 opts = option_parser.parse_args()
Harald Welte21caf322022-07-16 14:06:46 +0200163
164 logger.info('Opening source %s...' % opts.source)
165 if opts.source == 'gsmtap-udp':
166 s = GsmtapApduSource(opts.bind_ip, opts.bind_port)
167 elif opts.source == 'rspro-pyshark-pcap':
168 s = PysharkRsproPcap(opts.pcap_file)
169 elif opts.source == 'rspro-pyshark-live':
170 s = PysharkRsproLive(opts.interface)
Harald Weltec95f6e22022-12-02 22:50:35 +0100171 elif opts.source == 'gsmtap-pyshark-pcap':
172 s = PysharkGsmtapPcap(opts.pcap_file)
Harald Welte21caf322022-07-16 14:06:46 +0200173
Harald Welte72c5b2d2022-07-24 10:21:41 +0200174 tracer = Tracer(source=s, suppress_status=opts.suppress_status, suppress_select=opts.suppress_select)
Harald Welte21caf322022-07-16 14:06:46 +0200175 logger.info('Entering main loop...')
176 tracer.main()
177