mirror of
https://sharelatex.tu-darmstadt.de/git/681e0e7a3a9c7c9c6b8bb298
synced 2025-12-07 05:08:01 +00:00
Update on Overleaf.
This commit is contained in:
@@ -31,7 +31,7 @@ All exceptions inherit from a common \texttt{EuiccException} base class, ensurin
|
||||
|
||||
\subsection*{Exception Hierarchy}
|
||||
|
||||
The exception hierarchy is structured such that all specific error exceptions (e.g., \texttt{IccidNotFound}) inherit from a relevant function exception (e.g., \texttt{ProfileInteractionException}), which itself inherits from \texttt{EuiccException}. This enables both precise and broad exception matching depending on the caller’s needs. For example:
|
||||
The exception hierarchy is structured such that all specific error exceptions (e.g. \texttt{IccidNotFound}) inherit from a relevant function exception (e.g. \texttt{ProfileInteractionException}), which itself inherits from \texttt{EuiccException}. This enables both precise and broad exception matching, depending on the caller’s needs. For example:
|
||||
|
||||
\begin{center}
|
||||
\texttt{EuiccException} $\rightarrow$ \texttt{ProfileInteractionException} $\rightarrow$ \texttt{IccidOrAidNotFound}
|
||||
|
||||
@@ -217,7 +217,7 @@ Known \glspl{adf} for \gls{isdr} observed during analysis:
|
||||
|
||||
The decoded response data is further processed using \texttt{pydantic} data classes. These enable structured parsing of values including Base64-encoded strings, bitfields, version types, and more. Custom encoders/decoders are used to simplify readability and downstream data processing. For bit fields, a mixin is used to allow checking for specific feature flags via simple accessors.
|
||||
|
||||
The \texttt{estk\_fwupd} application implements a proprietary firmware update interface, which was reverse-engineered (see \cref{sec:findings}). It supports reading the current firmware version, unlocking the \gls{euicc} for updates, and installing new binaries.\footnote{This unlocking is distinct from \gls{gp}-defined unlocking, which allows the execution of generic \gls{gp} commands. See \gls{gp} Card Specification.}
|
||||
The \texttt{estk\_fwupd} application implements a proprietary firmware update interface, which was reverse-engineered (see \cref{sec:findings}). It supports reading the current firmware version, unlocking\footnote{This unlocking is distinct from \gls{gp}-defined unlocking, which allows the execution of generic \gls{gp} commands. See \gls{gp} Card Specification.} the \gls{euicc} for updates, and installing new binaries.
|
||||
|
||||
\paragraph{Exception Handling}
|
||||
The \gls{sgp22} standard defines a variety of response codes and error conditions. The \gls{lpa} library maps these response codes to custom exception classes for precise error handling. This is essential for both debugging and for the differential testing framework to reason about diverging behavior across implementations. A code listing of the exception handling mappings is provided in \cref{sec:exception-handling}.
|
||||
@@ -247,12 +247,14 @@ The \gls{smdpp} client is primarily used by the \gls{isdr} application to execut
|
||||
% in comparison to the tracing and compare approach: the data is generated newly for each execution with valid data i.e no exact replay
|
||||
% since the generated data is valid we need to mutate it to trigger some errors and see differences -> mutation engine: handles mutation of apdus
|
||||
% each function that the lpa executes is recorded before and after its mutation including the response codes
|
||||
% the recordings are structured in a tree where each node repesents a function executed on the euicc with one mutation i.e each level represents one function and each node on that level a mutation
|
||||
% the recordings are structured in a tree where each node repesents a function executed on the euicc with one mutation i.e each level represents one function and each node on that level a mutation (ref to figure that shows mutation tree example)
|
||||
% fuzzing can be called "coverage guided" since we try every function with multiple mutations until we handled all operations implemented in the scenario -> not contiuing indefinitly and we some sense of progress
|
||||
% other advantages with the tree based approach: we save each step -> can continue after failure without loosing previouse work; can potentially be easy to parallelize i.e each compute node handles one subtree of the root node etc; easier to visualize and see differences when printed as a tree
|
||||
|
||||
|
||||
% mutation engine
|
||||
% deterministic and random mutation engine
|
||||
% both implement the same kind of mutations: bitflip, random byte, zero block, shuffle block, truncate
|
||||
% both implement the same kind of mutation types: bitflip, random byte, zero block, shuffle block, truncate
|
||||
% deteministic always modifies the same data to have conistency between function execution for different euiccs
|
||||
% random engine applies the mutation to randome pieces of data
|
||||
% bitflip: flips a number of bits -> number is determined based on mutation_rate and length of data $max(1, len(data) * mutation_rate)$
|
||||
@@ -262,7 +264,7 @@ The \gls{smdpp} client is primarily used by the \gls{isdr} application to execut
|
||||
% truncate: removes tail of data
|
||||
|
||||
% scenarios
|
||||
% represents a row of functions that are executed on each euicc (insert listing for example scenario)
|
||||
% represents a sequence of functions that are executed on each euicc (insert listing for example scenario)
|
||||
% is executed by scenario runner
|
||||
% scenario runner controls scenario execution, handles errors and records data to the operation recorder
|
||||
% operation recorder handles tree structure and determines which mutation is applied next
|
||||
@@ -273,21 +275,20 @@ The \gls{smdpp} client is primarily used by the \gls{isdr} application to execut
|
||||
% mutation engine: mutate a given apdu
|
||||
% operation recorder: records mutation and responses, and takes care of the tree structure
|
||||
% scenario runner executes all scenarios on a given euicc
|
||||
% for each scenario the scenario runner initates a pcsc link with the card and resets the card (processes all notifications, euicc memory reset with all options set)
|
||||
% for each scenario the scenario runner initates a pcsc link with the card and resets the card (processes all notifications, euicc memory reset with all options set) -> clean and same "base" state for all scenarios
|
||||
% runs all operations defined in the scenario -> operations invoke euicc commands
|
||||
% euicc commands are called with the send_apdu_with_mutation function -> handles apdu transmission and mutation aswell as recording of data
|
||||
|
||||
% apdu mutation workflow
|
||||
% ref to figure that shows scenario runner flow
|
||||
% 1. mutation selection
|
||||
% operation recorder handles recording and the tree structure
|
||||
% operation recorder returns next mutation type to choose
|
||||
% reason: we want to try every mutation for a function but when all are tried check if any of the child nodes have not tried mutations, if child node has not tried mutation: perform mutation so we get to the child node
|
||||
|
||||
% to determine the next mutation: Traverses or expands a mutation tree to decide which mutation to try next.
|
||||
% reason: we want to try every mutation for a function but when all are tried check if any of the child nodes have not tried mutations, if child node has not tried mutation: perform mutation so we get to the child node, if child node has tried all mutations: use None mutation node to continue down the tree
|
||||
% to determine the next mutation: Traverses or expands a mutation tree to decide which mutation to try next. -> ref to figure that shows flow graph
|
||||
% Each function call (e.g., get_euicc_info_1) becomes a node in the mutation tree.
|
||||
% If untried mutations exist for the current node: Create and move to a new child node with the selected mutation type.
|
||||
% If all mutations are tried: Traverse children to find another node to explore.
|
||||
|
||||
% 2. apdu mutation
|
||||
% selected mutation is applied to the original apdu with the mutation engine
|
||||
% 3. apdu transmission
|
||||
@@ -295,6 +296,9 @@ The \gls{smdpp} client is primarily used by the \gls{isdr} application to execut
|
||||
% if successful: response is recorded and current node marked as success
|
||||
% if error occured: error is recorded and current node is marked as failed -> no child nodes will be explored
|
||||
% 4. recording mutation
|
||||
% response is saved to mutation node
|
||||
|
||||
% Doing this for all operations in the scenario: results in a mutation tree (ref to figure that shows mutation tree example)
|
||||
|
||||
% error handling and retry logic
|
||||
% errors and exceptions during the scenario execution are handled
|
||||
@@ -303,17 +307,51 @@ The \gls{smdpp} client is primarily used by the \gls{isdr} application to execut
|
||||
% check if the card still has any untried mutations or if its fully explored -> continue with scenario or switch to new one
|
||||
|
||||
% saving recording
|
||||
% recordings are saved for response comparison between the cards and also for euicc that might be released in the future
|
||||
% recordings are saved for comparison between the cards and also for euicc that might be released in the future
|
||||
% use python pickle to store the whole mutation tree as a ".resim" file
|
||||
% afterwards: clear the recorder, reset the link, reset card -> continue with next scenario
|
||||
|
||||
% differential testing
|
||||
% - to find differences in responses -> compare two or more recording files with each other
|
||||
% - operation recorder compares mutation tree nodes and prints tree with differences
|
||||
% - traverses the tree with a depth first search and compares nodes -> tree should have the same structe i.e same depth and each node has same numbers of childs
|
||||
% differences in the tree strcuture are also handled i.e failed mutations and therefor no child nodes
|
||||
% - nodes are considered different if the response code is different or it has a different failure reason i.e EuiccException or AssertionError (Problems occurd outside of euicc)
|
||||
|
||||
\begin{figure}
|
||||
\centering
|
||||
\input{Graphics/determine_next_mutation_flow.tikz}
|
||||
\caption{Flow on how to determine the next mutation that should be used.}
|
||||
\label{fig:next_mutation_flow}
|
||||
\end{figure}
|
||||
To uncover behavioral differences between \gls{euicc} implementations, we implemented a fuzzing framework that mutates valid \glspl{apdu} generated via our custom \gls{lpa} implementation. Unlike the tracing-and-compare approach described earlier, the fuzzing strategy dynamically constructs valid request data and intentionally mutates it prior to transmission, allowing for meaningful analysis of error-handling behavior across cards.
|
||||
|
||||
\subsubsection*{Fuzzing Scenarios and Execution}
|
||||
|
||||
Fuzzing is conducted through predefined \emph{scenarios}—sequences of function calls that operate on the \gls{euicc}. Each function in a scenario interacts with the \gls{euicc} through the \gls{lpa} and is subject to mutation. The scenario runner initiates a fresh \gls{pcsc} link, resets the card into a clean state (processing all notifications and performing a full memory reset), and executes each function with multiple mutations.
|
||||
|
||||
This process is guided by an \textbf{operation recorder} that tracks each function call, applied mutations, and resulting responses in a structured \emph{mutation tree}. Each tree node represents a specific function call executed with one type of mutation. A tree level corresponds to a function in the scenario; sibling nodes represent different mutations of that function.
|
||||
|
||||
\subsubsection*{Mutation Engine}
|
||||
|
||||
The mutation engine supports both \textit{deterministic} and \textit{random} mutation modes, and implements the following strategies:
|
||||
|
||||
\begin{itemize}
|
||||
\item \textbf{Bit flip:} Flips a fixed number of bits based on mutation rate and payload length.
|
||||
\item \textbf{Random byte:} Swaps random byte positions.
|
||||
\item \textbf{Zero block:} Replaces a sequence of bytes with zeros.
|
||||
\item \textbf{Shuffle block:} Chunks data into 16-bit segments and sorts them by their checksum.
|
||||
\item \textbf{Truncate:} Removes the tail of the \gls{apdu}.
|
||||
\end{itemize}
|
||||
|
||||
Deterministic mode ensures reproducibility by always mutating the same offset, while the random mode selects targets dynamically. This allows us to explore both fixed and variable fuzzing behavior.
|
||||
|
||||
\subsubsection*{Fuzzing Workflow}
|
||||
|
||||
The \gls{apdu} fuzzing workflow is illustrated in \cref{fig:scenario_flow} and proceeds in four main steps:
|
||||
|
||||
\begin{enumerate}
|
||||
\item \textbf{Mutation selection:} The operation recorder decides the next mutation to apply based on a depth-first traversal of the mutation tree. If all mutations for the current function are exhausted, the runner searches for unexplored child nodes.
|
||||
\item \textbf{\gls{apdu} mutation:} The selected mutation is applied to the original APDU using the mutation engine.
|
||||
\item \textbf{\gls{apdu} transmission:} The mutated \gls{apdu} is sent to the card. Success or failure is recorded in the current node.
|
||||
\item \textbf{Recording:} The response (or exception) is saved to the mutation tree node for further analysis.
|
||||
\end{enumerate}
|
||||
|
||||
This process repeats for all functions defined in the scenario, resulting in a complete mutation tree (see \cref{fig:tree_structure}) that captures all inputs, outputs, and error states.
|
||||
|
||||
\begin{figure}
|
||||
\centering
|
||||
@@ -322,27 +360,77 @@ The \gls{smdpp} client is primarily used by the \gls{isdr} application to execut
|
||||
\label{fig:tree_structure}
|
||||
\end{figure}
|
||||
|
||||
\subsubsection*{Determine Next Mutation Logic}
|
||||
% shown in figure4 (flow graph on how to determine next mutation)
|
||||
% goals we want to try all mutations for each node
|
||||
% handled by operation recorder and next mutation is requeststed by pcsc link
|
||||
% information that we have is the current node
|
||||
% 1. check if the current node still has some untried mutations i.e if the node has one child for each possible mutations
|
||||
% if so we create a new node with the not yet tried mutation type and make it the new current mode and return the mutation type that we just applied to this node
|
||||
% if not: we continue to traverse each child of the current node and check if they have any untried mutations left
|
||||
% if so return the mutation type of the child that still has not tried mutation types -> brings us on the subtree where a child has not tried mutations -> next time this function is called we return the new not tried mutation type
|
||||
% if child does not have any not tried mutations: we return the NoneNode of that child i.e the mutation type of the child that was successfully executed and did not make any mutations. idea: continue down the good path to find untried mutations
|
||||
|
||||
The decision process for selecting the next mutation to apply is a key component of the fuzzing framework and is handled entirely by the \texttt{OperationRecorder}. Its responsibility is to ensure that all mutations are eventually applied to each function within a scenario while maintaining a consistent and deterministic traversal order across runs.
|
||||
|
||||
\begin{figure}
|
||||
\centering
|
||||
\input{Graphics/determine_next_mutation_flow.tikz}
|
||||
\caption{Flow on how to determine the next mutation that should be used.}
|
||||
\label{fig:next_mutation_flow}
|
||||
\end{figure}
|
||||
|
||||
The algorithm, illustrated in \cref{fig:next_mutation_flow}, operates based on the current node in the mutation tree. Each node represents a function invocation, and its children represent the same invocation with different mutations. The logic proceeds as follows:
|
||||
|
||||
\begin{enumerate}
|
||||
\item \textbf{Check for untried mutations at the current node:}
|
||||
The recorder checks whether the current node has already created child nodes for every defined mutation type (e.g., bitflip, zero-block, truncate, etc.). If there are untried mutation types, it selects one of them, creates a new child node with that mutation, sets it as the new current node, and returns the selected mutation type.
|
||||
|
||||
\item \textbf{Recursive traversal of child nodes:}
|
||||
If all mutation types have already been tried at the current node (i.e., all child mutations are present), the recorder traverses the subtree rooted at each child node. For each child, it checks if there are any untried mutations deeper in the tree.
|
||||
|
||||
\item \textbf{Descent via valid (None) paths:}
|
||||
If no untried mutations are found among the children, the recorder follows the \texttt{NoneNode} child—representing the unmutated, successful execution of the function. This path is presumed to lead to deeper parts of the tree where further mutations might be unexplored. In essence, this descent along the ``clean'' path enables the system to reach other branches that may still contain untested mutations.
|
||||
|
||||
\item \textbf{Backtrack or complete:}
|
||||
If the entire subtree from the current node has been fully explored (i.e., all mutations at all levels are exhausted), the recorder signals completion by returning a sentinel (e.g., \texttt{None}) to the scenario runner.
|
||||
\end{enumerate}
|
||||
|
||||
This strategy is both exhaustive and progress-aware. It ensures that:
|
||||
\begin{itemize}
|
||||
\item All mutation types are attempted for every scenario function.
|
||||
\item Tree traversal avoids redundant work and naturally prioritizes unexplored paths.
|
||||
\item The fuzzing process remains deterministic and resumable due to the structured tree format.
|
||||
\end{itemize}
|
||||
|
||||
\subsubsection*{Error Handling and Retry Logic}
|
||||
|
||||
Errors during execution are logged and associated with the current mutation node. If a function fails (e.g., due to protocol state loss or card reset), the runner resets the \gls{pcsc} link and the card, then resumes execution. This ensures that failures do not corrupt the mutation tree and allows exploration to continue.
|
||||
|
||||
\subsubsection*{Scenario Persistence and Reuse}
|
||||
|
||||
To preserve fuzzing results, the entire mutation tree is serialized and stored using Python's \texttt{pickle} module in a \texttt{.resim} file. This enables post-analysis, comparison across card models, and reproducibility for future \gls{euicc} versions.
|
||||
|
||||
\subsubsection*{Differential Testing}
|
||||
|
||||
After multiple cards are fuzzed with the same scenario, their corresponding mutation trees are compared to identify behavioral discrepancies. This is done via depth-first traversal of the trees:
|
||||
|
||||
\begin{itemize}
|
||||
\item Trees must have equivalent structure (same function call order and mutation types).
|
||||
\item Nodes are flagged as different if their response status word differs or if the failure reason (e.g. \texttt{EuiccException}, \texttt{AssertionError}) is inconsistent.
|
||||
\item Discrepancies due to failed mutations (missing child nodes) are also handled gracefully.
|
||||
\end{itemize}
|
||||
|
||||
This differential testing method highlights edge-case inconsistencies across \gls{euicc} vendors and enables systematic validation of the \gls{rsp} protocol compliance.
|
||||
|
||||
|
||||
\begin{figure}
|
||||
\centering
|
||||
\input{Graphics/record_scenario_flow.tikz}
|
||||
\caption{Flow for recording a scenario}
|
||||
\caption{Flow for recording a scenario.}
|
||||
\label{fig:scenario_flow}
|
||||
\end{figure}
|
||||
|
||||
\begin{figure}
|
||||
\centering
|
||||
\input{Graphics/scenario_runner.tikz}
|
||||
\caption{Main Flow for the scenario runner}
|
||||
\label{fig:scenario_runner}
|
||||
\end{figure}
|
||||
|
||||
\begin{figure}
|
||||
\centering
|
||||
\input{Graphics/scenario_recorder.tikz}
|
||||
\caption{Recorder logic: mutation selection and recording}
|
||||
\label{fig:scenario_runner}
|
||||
\end{figure}
|
||||
|
||||
|
||||
\subsection{Data Fuzzing}
|
||||
\label{subsec:data_fuzzing}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
\node[decision, below=of A] (B) {Current Node has\\ not tried mutations?};
|
||||
\node[block, right=of B] (C) {Create new Node as\\ child and make it current};
|
||||
\node[startstop, above=of C] (n1) {Return a not\\ tried mutation type};
|
||||
\node[block, below=of B] (D) {Iterate over children};
|
||||
\node[block, below=of B] (D) {Traverse children};
|
||||
\node[decision, below=of D] (n2) {Child function\\ execution failed?};
|
||||
\node[decision, right=of n2] (n3) {Child has not\\ tried mutations?};
|
||||
\node[startstop, above=of n3] (n4) {Make child current and\\ return its mutation type};
|
||||
|
||||
@@ -12,17 +12,17 @@
|
||||
\node[box, below=of scenarioLoop] (isdrcall) {Card ISD-R command};
|
||||
|
||||
% Mutation engine path
|
||||
\node[decision, below=of isdrcall] (mutateQ) {Mutation Engine?};
|
||||
\node[decision, below=of isdrcall] (mutateQ) {Mutation\\ Engine?};
|
||||
|
||||
\node[box, right=1.2cm of mutateQ] (originalAPDU) {Send original APDU};
|
||||
\node[box, left=1.2cm of mutateQ] (mutateAPDU) {Mutate APDU};
|
||||
\node[box, left=1.2cm of mutateQ] (mutateAPDU) {Get next mutation\\ and Mutate APDU};
|
||||
|
||||
\node[box, below=of mutateQ] (sendAPDU) {Transmit APDU to card};
|
||||
\node[box, below=of sendAPDU] (record) {Record mutation result\\ in OperationRecorder};
|
||||
|
||||
% Error / reset path
|
||||
\node[decision, below=of record] (errorQ) {Exception during scenario?};
|
||||
\node[box, left=of errorQ] (logFail) {Log failure\\ in current mutation node};
|
||||
\node[decision, below=of record] (errorQ) {Exception\\ during scenario?};
|
||||
\node[box, left=of errorQ] (logFail) {Log failure\\ in current\\ mutation node};
|
||||
\node[box, below=of errorQ] (checkTree) {All mutations tried?};
|
||||
|
||||
\node[box, right=of checkTree] (repeatScenario) {Repeat Scenario};
|
||||
@@ -46,7 +46,7 @@
|
||||
\draw[arrow] (errorQ) -- node[above] {Yes} (logFail);
|
||||
\draw[arrow] (logFail) |- (checkTree);
|
||||
|
||||
\draw[arrow] (errorQ) -- node[above] {No} (checkTree);
|
||||
\draw[arrow] (errorQ) -- node[right] {No} (checkTree);
|
||||
|
||||
\draw[arrow] (checkTree) -- node[above] {No} (repeatScenario);
|
||||
\draw[arrow] (checkTree) -- node[right] {Yes} (saveFile);
|
||||
|
||||
Reference in New Issue
Block a user