From c95f6e2124fcd97a4b5b6fe5a1ca81b4a92c9480 Mon Sep 17 00:00:00 2001 From: Harald Welte Date: Fri, 2 Dec 2022 22:50:35 +0100 Subject: [PATCH] pySim-trace: Add support for reading GSMTAP from pcap files So far we supported * GSMTAP live traces via a UDP socket * RSPRO traces from pcap files (or live) We were lacking support for reading GSMTAP stored in pcap, which is what this patch implements. Change-Id: I46d42774b39a2735500ff5804206ddcfa545568c --- pySim-trace.py | 8 +++ pySim/apdu_source/pyshark_gsmtap.py | 90 +++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+) create mode 100644 pySim/apdu_source/pyshark_gsmtap.py diff --git a/pySim-trace.py b/pySim-trace.py index 33cb75a3..79069a69 100755 --- a/pySim-trace.py +++ b/pySim-trace.py @@ -18,6 +18,7 @@ from pySim.transport import LinkBase from pySim.apdu_source.gsmtap import GsmtapApduSource from pySim.apdu_source.pyshark_rspro import PysharkRsproPcap, PysharkRsproLive +from pySim.apdu_source.pyshark_gsmtap import PysharkGsmtapPcap from pySim.apdu.ts_102_221 import UiccSelect, UiccStatus @@ -130,6 +131,11 @@ parser_gsmtap.add_argument('-i', '--bind-ip', default='127.0.0.1', parser_gsmtap.add_argument('-p', '--bind-port', default=4729, help='Local UDP port') +parser_gsmtap_pyshark_pcap = subparsers.add_parser('gsmtap-pyshark-pcap', help=""" + PCAP file containing GSMTAP (SIM APDU) communication; processed via pyshark.""") +parser_gsmtap_pyshark_pcap.add_argument('-f', '--pcap-file', required=True, + help='Name of the PCAP[ng] file to be read') + parser_rspro_pyshark_pcap = subparsers.add_parser('rspro-pyshark-pcap', help=""" PCAP file containing RSPRO (osmo-remsim) communication; processed via pyshark. REQUIRES OSMOCOM PATCHED WIRESHARK!""") @@ -153,6 +159,8 @@ if __name__ == '__main__': s = PysharkRsproPcap(opts.pcap_file) elif opts.source == 'rspro-pyshark-live': s = PysharkRsproLive(opts.interface) + elif opts.source == 'gsmtap-pyshark-pcap': + s = PysharkGsmtapPcap(opts.pcap_file) tracer = Tracer(source=s, suppress_status=opts.suppress_status, suppress_select=opts.suppress_select) logger.info('Entering main loop...') diff --git a/pySim/apdu_source/pyshark_gsmtap.py b/pySim/apdu_source/pyshark_gsmtap.py new file mode 100644 index 00000000..8ea9ae7c --- /dev/null +++ b/pySim/apdu_source/pyshark_gsmtap.py @@ -0,0 +1,90 @@ +# coding=utf-8 + +# (C) 2022 by Harald Welte +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + + +import sys +import logging +from pprint import pprint as pp +from typing import Tuple +import pyshark + +from pySim.utils import h2b, b2h +from pySim.apdu import Tpdu +from pySim.gsmtap import GsmtapMessage +from . import ApduSource, PacketType, CardReset + +from pySim.apdu.ts_102_221 import ApduCommands as UiccApduCommands +from pySim.apdu.ts_31_102 import ApduCommands as UsimApduCommands +from pySim.apdu.global_platform import ApduCommands as GpApduCommands +ApduCommands = UiccApduCommands + UsimApduCommands + GpApduCommands + +logger = logging.getLogger(__name__) + +class _PysharkGsmtap(ApduSource): + """APDU Source [provider] base class for reading GSMTAP SIM APDU via tshark.""" + + def __init__(self, pyshark_inst): + self.pyshark = pyshark_inst + self.bank_id = None + self.bank_slot = None + self.cmd_tpdu = None + super().__init__() + + def read_packet(self) -> PacketType: + p = self.pyshark.next() + return self._parse_packet(p) + + def _set_or_verify_bank_slot(self, bsl: Tuple[int, int]): + """Keep track of the bank:slot to make sure we don't mix traces of multiple cards""" + if not self.bank_id: + self.bank_id = bsl[0] + self.bank_slot = bsl[1] + else: + if self.bank_id != bsl[0] or self.bank_slot != bsl[1]: + raise ValueError('Received data for unexpected B(%u:%u)' % (bsl[0], bsl[1])) + + def _parse_packet(self, p) -> PacketType: + udp_layer = p['udp'] + udp_payload_hex = udp_layer.get_field('payload').replace(':','') + gsmtap = GsmtapMessage(h2b(udp_payload_hex)) + gsmtap_msg = gsmtap.decode() + if gsmtap_msg['type'] != 'sim': + raise ValueError('Unsupported GSMTAP type %s' % gsmtap_msg['type']) + sub_type = gsmtap_msg['sub_type'] + if sub_type == 'apdu': + return ApduCommands.parse_cmd_bytes(gsmtap_msg['body']) + elif sub_type == 'atr': + # card has been reset + return CardReset() + elif sub_type in ['pps_req', 'pps_rsp']: + # simply ignore for now + pass + else: + raise ValueError('Unsupported GSMTAP-SIM sub-type %s' % sub_type) + +class PysharkGsmtapPcap(_PysharkGsmtap): + """APDU Source [provider] class for reading GSMTAP from a PCAP + file via pyshark, which in turn uses tshark (part of wireshark). + """ + def __init__(self, pcap_filename): + """ + Args: + pcap_filename: File name of the pcap file to be opened + """ + pyshark_inst = pyshark.FileCapture(pcap_filename, display_filter='gsm_sim', use_json=True, keep_packets=False) + super().__init__(pyshark_inst) +