This commit is contained in:
4
.vscode/launch.json
vendored
4
.vscode/launch.json
vendored
@@ -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}"
|
||||
],
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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",
|
||||
),
|
||||
)
|
||||
|
||||
@@ -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):
|
||||
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)}",
|
||||
)
|
||||
|
||||
data, resp = self.__send_apdu(link, apdu)
|
||||
|
||||
if resp == b2h(apdu.sw):
|
||||
successful_replays += 1
|
||||
continue
|
||||
|
||||
log.debug(
|
||||
@@ -145,6 +155,14 @@ class Replayer:
|
||||
)
|
||||
else:
|
||||
log.debug("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.",
|
||||
|
||||
@@ -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})"
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user