fix: replay issues
Some checks failed
Build Python Package / build (push) Failing after 22s

This commit is contained in:
2025-02-13 17:02:25 +01:00
parent 06b708828f
commit 4a12d1add4
9 changed files with 98 additions and 83 deletions

4
.vscode/launch.json vendored
View File

@@ -11,6 +11,7 @@
"program": "${workspaceFolder}/resimulate.py",
"console": "integratedTerminal",
"args": [
"--verbose",
"record",
"-o",
"${input:promptFilename}",
@@ -26,11 +27,10 @@
"program": "${workspaceFolder}/resimulate.py",
"console": "integratedTerminal",
"args": [
"--verbose",
"replay",
"-i",
"${input:pickFile}",
"--src-isd-r",
"${input:pickISDR}",
"--target-isd-r",
"${input:pickISDR}"
],

View File

@@ -26,4 +26,4 @@ ruff = "^0.9.4"
pyright = "^1.1.393"
[tool.poetry.dependencies]
pysim = {develop = true}
pysim = { develop = true }

View File

@@ -2,6 +2,7 @@
# PYTHON_ARGCOMPLETE_OK
import argparse
import importlib.metadata
import logging
import argcomplete
@@ -16,6 +17,20 @@ parser = argparse.ArgumentParser(
description="ReSIMulate is a terminal application built for eSIM and SIM-specific APDU analysis. It captures APDU commands, saves them, and replays them to facilitate differential testing, ensuring accurate validation and debugging of SIM interactions.",
formatter_class=RichHelpFormatter,
)
parser.add_argument(
"--version",
action="version",
version=f"%(prog)s {importlib.metadata.version('resimulate')}",
)
parser.add_argument(
"-v",
"--verbose",
action="store_true",
help="Enable verbose output during replay.",
required=False,
)
global_arg_parser = argparse.ArgumentParser(add_help=False)
subparsers = parser.add_subparsers(
title="Commands",
@@ -23,14 +38,11 @@ subparsers = parser.add_subparsers(
required=True,
help="Available commands: record, replay",
)
parser.add_argument(
"-v", "--verbose", action="store_true", help="Enable verbose output during replay."
)
parser.add_argument("--version", action="version", version="%(prog)s 0.1")
# Record command
record_parser = subparsers.add_parser(
"record",
parents=[global_arg_parser],
help="Record APDU commands from a specified source. Uses the SIMtrace2 GSMTAP to capture APDUs via UDP.",
)
record_parser.add_argument(
@@ -60,13 +72,15 @@ record_parser.add_argument(
"-t",
"--timeout",
type=int,
default=10,
default=15,
help="Timeout in seconds for recording (default: 10).",
)
# Replay command
replay_parser = subparsers.add_parser(
"replay", help="Replay saved APDU commands to a target device."
"replay",
parents=[global_arg_parser],
help="Replay saved APDU commands to a target device.",
)
replay_parser.add_argument(
"-i",

View File

@@ -1,15 +1,14 @@
from pySim.transport import LinkBase
from pySim.commands import SimCardCommands
from pySim.filesystem import CardModel, CardApplication
from pySim.cards import SimCardBase, UiccCardBase
from pySim.runtime import RuntimeState
from pySim.commands import SimCardCommands
from pySim.euicc import CardApplicationISDR, EuiccInfo2
from pySim.exceptions import SwMatchError
from pySim.filesystem import CardApplication, CardModel
from pySim.profile import CardProfile
from pySim.runtime import RuntimeState
from pySim.transport import LinkBase
from pySim.ts_102_221 import CardProfileUICC
from pySim.utils import all_subclasses
from pySim.exceptions import SwMatchError
from pySim.euicc import CardApplicationISDR, EuiccInfo2
from resimulate.models.euicc_info_2 import EuiccInfo2Data
from resimulate.util.enums import ISDR_AID
from resimulate.util.logger import log
@@ -19,6 +18,7 @@ class Card:
card: SimCardBase | None
profile: CardProfile | None
generic_card: bool = False
euicc_info_2: dict | None = None
def __init__(self, sim_link: LinkBase):
self.sim_link = sim_link
@@ -75,10 +75,9 @@ class Card:
euicc_info_2 = CardApplicationISDR.store_data_tlv(
self.sim_card_commands, EuiccInfo2(), EuiccInfo2
)
self.euicc_info_2 = EuiccInfo2Data.from_list(
euicc_info_2.to_dict()["euicc_info2"]
)
self.euicc_info_2 = euicc_info_2.to_dict()["euicc_info2"]
except SwMatchError:
log.warning("Card has ISD-R but not a SGP.22/SGP.32 eUICC.")
# has ISD-R but not a SGP.22/SGP.32 eUICC - maybe SGP.02?
pass
finally:

View File

@@ -26,7 +26,7 @@ class Recorder:
self.tracer_thread = Thread(
target=self.tracer.main, args=(self.package_queue,), daemon=True
)
self.recording = Recording()
self.recording = Recording(src_isd_r=isd_r_aid)
signal.signal(signal.SIGINT, self.__signal_handler)
def __signal_handler(self, sig, frame):
@@ -45,7 +45,11 @@ class Recorder:
# Panel(capture_progress, title="APDU Packets captured", expand=False),
overall_progress,
Align.left(
Text.assemble("Press ", ("Ctrl+C", "bold red"), " to stop capturing."),
Text.assemble(
"Press ",
("Ctrl+C", "bold green"),
" to stop capturing and save recorded commands.",
),
vertical="bottom",
),
)

View File

@@ -2,6 +2,7 @@ from osmocom.utils import b2h, h2b, h2i, i2h
from pySim.apdu import Apdu
from pySim.transport.pcsc import PcscSimLink
from pySim.ts_102_221 import CardProfileUICC
from pySim.utils import ResTuple
from rich.align import Align
from rich.console import Group
from rich.live import Live
@@ -19,20 +20,28 @@ class Replayer:
self.device = device
self.target_isd_r_aid = ISDR_AID.get_aid(target_isd_r)
def __get_remaining_bytes(self, link: PcscSimLink, bytes_to_receive: int, cla: int):
def __get_remaining_bytes(
self, link: PcscSimLink, bytes_to_receive: int, cla: int
) -> ResTuple:
log.debug("Retrieving remaining bytes: %d", bytes_to_receive)
apdu = Apdu(i2h([cla]) + "C00000" + i2h([bytes_to_receive]))
return self.__send_apdu(link, apdu)
def __resend_with_modified_le(self, link: PcscSimLink, apdu: Apdu, le: int):
def __resend_with_modified_le(
self, link: PcscSimLink, apdu: Apdu, le: int
) -> ResTuple:
log.debug("Resending APDU with modified Le: %d", le)
modified_apdu = Apdu(b2h(apdu.cmd)[:-2] + i2h([le]))
return self.__send_apdu(link, modified_apdu)
def __send_apdu(self, link: PcscSimLink, apdu: Apdu):
def __send_apdu(self, link: PcscSimLink, apdu: Apdu) -> ResTuple:
if self.recording.src_isd_r_aid and self.target_isd_r_aid:
if b2h(apdu.cmd_data) == self.recording.src_isd_r_aid.value:
apdu.cmd_data = h2b(self.target_isd_r_aid.value)
cmd_data = b2h(apdu.cmd_data)
if self.recording.src_isd_r_aid.value in cmd_data:
cmd_data = cmd_data.replace(
self.recording.src_isd_r_aid.value, self.target_isd_r_aid.value
)
apdu.cmd_data = h2b(cmd_data)
log.debug(
"Sending APDU(%s) where CLA(%s), INS(%s), P1(%s), P2(%s), Lc(%s), DATA(%s), P3/Le(%s)",
@@ -89,20 +98,21 @@ class Replayer:
)
return
successful_replays = 0
try:
with pcsc_link as link:
log.debug("Replaying APDUs...")
for idx, apdu in enumerate(self.recording.apdus, start=1):
progress.update(
progress_id,
total=len(self.recording.apdus),
completed=idx,
description=f"Replaying APDU {idx} / {len(self.recording.apdus)}",
)
data, resp = self.__send_apdu(link, apdu)
if resp == b2h(apdu.sw):
progress.update(
progress_id,
total=len(self.recording.apdus),
completed=idx,
description=f"Replaying APDU {idx} / {len(self.recording.apdus)}",
)
successful_replays += 1
continue
log.debug(
@@ -145,7 +155,15 @@ class Replayer:
)
else:
log.debug("Replay finished.")
progress.update(
progress_id,
description=":white_check_mark: [bold green]Replay finished.",
)
if not progress.finished:
progress.update(
progress_id,
description=f":police_car_light: [bold yellow]Failed to replay all APDUs ({successful_replays}/{len(self.recording.apdus)}).",
)
else:
progress.update(
progress_id,
description=":white_check_mark: [bold green]Replay finished.",
)

View File

@@ -1,39 +0,0 @@
from dataclasses import dataclass
@dataclass
class EuiccInfo2Data:
profile_version: str
svn: str
euicc_firmware_version: str
ext_card_resource: str
uicc_capability: str
ts102241_version: str
global_platform_version: str
rsp_capability: str
euicc_ci_pki_list_for_verification: list[dict[str, str]]
euicc_ci_pki_list_for_signing: list[dict[str, str]]
pp_version: str
sas_accreditation_number: str
@staticmethod
def from_list(obj: list[dict[str, str]]) -> "EuiccInfo2Data":
return EuiccInfo2Data(
profile_version=obj[0]["profile_version"],
svn=obj[1]["svn"],
euicc_firmware_version=obj[2]["euicc_firmware_ver"],
ext_card_resource=obj[3]["ext_card_resource"],
uicc_capability=obj[4]["uicc_capability"],
ts102241_version=obj[5]["ts102241_version"],
global_platform_version=obj[6]["global_platform_version"],
rsp_capability=obj[7]["rsp_capability"],
euicc_ci_pki_list_for_verification=obj[8][
"euicc_ci_pki_list_for_verification"
],
euicc_ci_pki_list_for_signing=obj[9]["euicc_ci_pki_list_for_signing"],
pp_version=obj[11]["pp_version"],
sas_accreditation_number=obj[12]["ss_acreditation_number"],
)
def __repr_rich__(self) -> str:
return f"EuiccInfo2(profile_version={self.profile_version!r}, svn={self.svn!r}, euicc_firmware_version={self.euicc_firmware_version!r}, ext_card_resource={self.ext_card_resource!r}, uicc_capability={self.uicc_capability!r}, ts102241_version={self.ts102241_version!r}, global_platform_version={self.global_platform_version!r}, rsp_capability={self.rsp_capability!r}, euicc_ci_pki_list_for_verification={self.euicc_ci_pki_list_for_verification!r}, euicc_ci_pki_list_for_signing={self.euicc_ci_pki_list_for_signing!r}, pp_version={self.pp_version!r}, sas_accreditation_number={self.sas_accreditation_number!r})"

View File

@@ -1,3 +1,4 @@
import importlib.metadata
import pickle
from os.path import exists, isfile
@@ -10,9 +11,10 @@ from resimulate.util.logger import log
class Recording:
apdus: list[Apdu]
src_isd_r_aid: ISDR_AID
version = importlib.metadata.version("resimulate")
def __init__(self, src_isd_r: str = "default"):
self.src_isd_r_aid = ISDR_AID.get_aid(src_isd_r)
def __init__(self, src_isd_r: ISDR_AID):
self.src_isd_r_aid = src_isd_r
self.apdus = []
@staticmethod
@@ -27,6 +29,19 @@ class Recording:
raise TypeError(f"File {file_path} does not contain a Recording object.")
log.debug("Loaded %d APDUs from %s", len(recording.apdus), file_path)
if recording.src_isd_r_aid != ISDR_AID._DEFAULT:
log.debug(
"Recording used ISD-R AID: %s (%s)",
recording.src_isd_r_aid.value,
recording.src_isd_r_aid.name,
)
if recording.version != importlib.metadata.version("resimulate"):
log.warning(
"File %s was created with a different version of ReSIMulate. "
"Please ensure compatibility.",
file_path,
)
return recording

View File

@@ -1,12 +1,16 @@
import enum
from typing import Union
class ISDR_AID(str, enum.Enum):
_DEFAULT = "A0000005591010FFFFFFFF8900000100"
_5BER = "A0000005591010FFFFFFFF8900050500"
_DEFAULT = "a0000005591010ffffffff8900000100"
_5BER = "a0000005591010ffffffff8900050500"
@staticmethod
def get_aid(aid_description: str) -> Union["ISDR_AID", None]:
def get_aid(aid_description: str) -> "ISDR_AID":
mapping = {"default": ISDR_AID._DEFAULT, "5ber": ISDR_AID._5BER}
return mapping.get(aid_description)
isdr_aid = mapping.get(aid_description)
if not isdr_aid:
raise ValueError(f"ISD-R AID {aid_description} is not supported!")
return isdr_aid