def _evaluate_negative_response_code( self, state, # type: EcuState response, # type: Packet **kwargs # type: Optional[Dict[str, Any]] # noqa: E501 ): # type: (...) -> bool exit_if_service_not_supported = \ kwargs.pop("exit_if_service_not_supported", False) exit_scan_on_first_negative_response = \ kwargs.pop("exit_scan_on_first_negative_response", False) if exit_scan_on_first_negative_response and response.service == 0x7f: return True if exit_if_service_not_supported and response.service == 0x7f: response_code = self._get_negative_response_code(response) if response_code in [0x11, 0x7f]: names = { 0x11: "serviceNotSupported", 0x7f: "serviceNotSupportedInActiveSession" } msg = "[-] Exit execute because negative response " \ "%s received!" % names[response_code] log_interactive.debug(msg) # execute of current state is completed, # since a serviceNotSupported negative response was received self._state_completed[state] = True # stop current execute and exit return True return False
def pre_execute(self, socket, # type: _SocketUnion state, # type: EcuState global_configuration # type: AutomotiveTestCaseExecutorConfiguration # noqa: E501 ): # type: (...) -> None test_case_cls = self.current_test_case.__class__ try: self.__current_kwargs = global_configuration[ test_case_cls.__name__] except KeyError: self.__current_kwargs = dict() global_configuration[test_case_cls.__name__] = \ self.__current_kwargs if callable(self.current_connector) and self.__stage_index > 0: if self.previous_test_case: con = self.current_connector # type: _TestCaseConnectorCallable # noqa: E501 con_kwargs = con(self.previous_test_case, self.current_test_case) if self.__current_kwargs is not None and con_kwargs is not None: # noqa: E501 self.__current_kwargs.update(con_kwargs) log_interactive.debug("[i] Stage AutomotiveTestCase %s kwargs: %s", self.current_test_case.__class__.__name__, self.__current_kwargs) self.current_test_case.pre_execute(socket, state, global_configuration)
def get_new_edge(self, socket, config): # type: (_SocketUnion, AutomotiveTestCaseExecutorConfiguration) -> Optional[_Edge] # noqa: E501 last_resp = self._results[-1].resp last_state = self._results[-1].state if last_resp is None or last_resp.service == 0x7f: return None try: if last_resp.service != 0x67 or \ last_resp.securityAccessType % 2 != 1: return None seed = last_resp sec_lvl = seed.securityAccessType if self.get_security_access(socket, sec_lvl, seed): log_interactive.debug("Security Access found.") # create edge new_state = copy.copy(last_state) new_state.security_level = seed.securityAccessType + 1 # type: ignore # noqa: E501 if last_state == new_state: return None edge = (last_state, new_state) self._transition_function_args[edge] = \ {"level": sec_lvl, "desc": "SA=%d" % sec_lvl} return edge except AttributeError: pass return None
def get_new_edge(self, socket, config): # type: (_SocketUnion, AutomotiveTestCaseExecutorConfiguration) -> Optional[_Edge] # noqa: E501 last_resp = self._results[-1].resp last_state = self._results[-1].state if last_resp is None or last_resp.service == 0x7f: return None try: if last_resp.service != 0x67 or \ last_resp.subfunction % 2 != 1: return None seed = last_resp sec_lvl = seed.subfunction kf = config[self.__class__.__name__].get("keyfunction", None) if self.get_security_access(socket, level=sec_lvl, seed_pkt=seed, keyfunction=kf): log_interactive.debug("Security Access found.") # create edge new_state = copy.copy(last_state) new_state.security_level = seed.subfunction + 1 # type: ignore # noqa: E501 if last_state == new_state: return None edge = (last_state, new_state) self._transition_function_args[edge] = (sec_lvl, kf) return edge except AttributeError: pass return None
def scan(self, state, requests, timeout=1, **kwargs): if state not in self.request_iterators: self.request_iterators[state] = iter(requests) if self.retry_pkt: it = [self.retry_pkt] else: it = self.request_iterators[state] log_interactive.debug("Using iterator %s in state %s", it, state) for req in it: try: res = self.sock.sr1(req, timeout=timeout, verbose=False) except ValueError as e: warning("Exception in scan %s", e) break self.results.append(Enumerator.ScanResult(state, req, res)) if self.evaluate_response(res, **kwargs): return self.update_stats() self.state_completed[state] = True
def cleanup(_, configuration): # type: (_SocketUnion, AutomotiveTestCaseExecutorConfiguration) -> bool try: configuration["tps"].stop() configuration["tps"] = None except (AttributeError, KeyError) as e: log_interactive.debug("Cleanup TP-Sender Error: %s", e) return True
def enter_diagnostic_session(socket): # type: (_SocketUnion) -> bool ans = socket.sr1( GMLAN() / GMLAN_IDO(subfunction=2), timeout=5, verbose=False) if ans is not None and ans.service == 0x7f: log_interactive.debug( "[-] InitiateDiagnosticOperation received negative response!\n" "%s", repr(ans)) return ans is not None and ans.service != 0x7f
def _evaluate_ecu_state_modifications(self, state, # type: EcuState request, # type: Packet response, # type: Packet ): # type: (...) -> bool if EcuState.is_modifier_pkt(response): if state != EcuState.get_modified_ecu_state( response, request, state): log_interactive.debug( "[-] Exit execute. Ecu state was modified!") return True return False
def __get_retry_iterator(self, state): # type: (EcuState) -> Iterable[Packet] retry_entry = self._retry_pkt[state] if retry_entry is None: return [] elif isinstance(retry_entry, Packet): log_interactive.debug("[i] Provide retry packet") return [retry_entry] else: log_interactive.debug("[i] Provide retry iterator") # assume self.retry_pkt is a generator or list return retry_entry
def enter_state(socket, # type: _SocketUnion configuration, # type: AutomotiveTestCaseExecutorConfiguration # noqa: E501 request # type: Packet ): # type: (...) -> bool timeout = configuration[UDS_DSCEnumerator.__name__].get("timeout", 3) ans = socket.sr1(request, timeout=timeout, verbose=False) if ans is not None: if configuration.verbose: log_interactive.debug( "Try to enter session req: %s, resp: %s" % (repr(request), repr(ans))) return cast(int, ans.service) != 0x7f else: return False
def _evaluate_response(self, state, # type: EcuState request, # type: Packet response, # type: Optional[Packet] **kwargs # type: Optional[Dict[str, Any]] ): # type: (...) -> bool if super(GMLAN_SAEnumerator, self)._evaluate_response( state, request, response, **kwargs): return True if response is not None and \ response.service == 0x67 and response.subfunction % 2 == 1: log_interactive.debug("[i] Seed received. Leave scan to try a key") return True return False
def reconnect(self): # type: () -> None if self.reconnect_handler: try: self.socket.close() except Exception as e: log_interactive.debug("[i] Exception '%s' during socket.close", e) log_interactive.info("[i] Target reconnect") socket = self.reconnect_handler() if not isinstance(socket, SingleConversationSocket): self.socket = SingleConversationSocket(socket) else: self.socket = socket
def _evaluate_retry(self, state, # type: EcuState request, # type: Packet response, # type: Packet **kwargs # type: Optional[Dict[str, Any]] ): # type: (...) -> bool retry_if_busy_returncode = \ kwargs.pop("retry_if_busy_returncode", True) if retry_if_busy_returncode and response.service == 0x7f \ and self._get_negative_response_code(response) == 0x21: log_interactive.debug( "[i] Retry %s because retry_if_busy_returncode received", repr(request)) return self._populate_retry(state, request) return False
def execute(self, socket, state, **kwargs): # type: (_SocketUnion, EcuState, Any) -> None timeout = kwargs.pop('timeout', 1) execution_time = kwargs.pop("execution_time", 1200) it = self.__get_request_iterator(state, **kwargs) # log_interactive.debug("[i] Using iterator %s in state %s", it, state) start_time = time.time() log_interactive.debug("[i] Start execution of enumerator: %s", time.ctime(start_time)) for req in it: try: res = socket.sr1(req, timeout=timeout, verbose=False) except (OSError, ValueError, Scapy_Exception) as e: if self._retry_pkt[state] is None: log_interactive.debug( "[-] Exception '%s' in execute. Prepare for retry", e) self._retry_pkt[state] = req else: log_interactive.critical( "[-] Exception during retry. This is bad") raise e if socket.closed: log_interactive.critical("[-] Socket closed during scan.") return self._store_result(state, req, res) if self._evaluate_response(state, req, res, **kwargs): log_interactive.debug("[i] Stop test_case execution because " "of response evaluation") return if (start_time + execution_time) < time.time(): log_interactive.debug( "[i] Finished execution time of enumerator: %s", time.ctime()) return log_interactive.info("[i] Finished iterator execution") self._state_completed[state] = True log_interactive.debug("[i] States completed %s", repr(self._state_completed))
def _get_initial_requests(self, **kwargs): # type: (Any) -> Iterable[Packet] scan_range = kwargs.pop("scan_range", range(0x10000)) rdbi_enumerator = kwargs.pop("rdbi_enumerator", None) if rdbi_enumerator is None: log_interactive.debug("[i] Use entire scan range") return (UDS() / UDS_WDBI(dataIdentifier=x) for x in scan_range) elif isinstance(rdbi_enumerator, UDS_RDBIEnumerator): log_interactive.debug("[i] Selective scan based on RDBI results") return (UDS() / UDS_WDBI(dataIdentifier=t.resp.dataIdentifier) / Raw(load=bytes(t.resp)[3:]) for t in rdbi_enumerator.results_with_positive_response if len(bytes(t.resp)) >= 3) else: raise Scapy_Exception("rdbi_enumerator has to be an instance " "of UDS_RDBIEnumerator")
def __init__(self, test_cases, **kwargs): # type: (Union[List[Union[AutomotiveTestCaseABC, Type[AutomotiveTestCaseABC]]], List[Type[AutomotiveTestCaseABC]]], Any) -> None # noqa: E501 self.verbose = kwargs.get("verbose", False) self.debug = kwargs.get("debug", False) self.unittest = kwargs.pop("unittest", False) self.state_graph = Graph() self.test_cases = list() # type: List[AutomotiveTestCaseABC] self.stages = list() # type: List[StagedAutomotiveTestCase] self.staged_test_cases = list() # type: List[AutomotiveTestCaseABC] self.test_case_clss = set() # type: Set[Type[AutomotiveTestCaseABC]] self.global_kwargs = kwargs for tc in test_cases: self.add_test_case(tc) log_interactive.debug("The following configuration was created") log_interactive.debug(self.__dict__)
def __init__(self, socket, reset_handler=None, enumerators=None, **kwargs): # The TesterPresentSender can interfere with a enumerator, since a # target may only allow one request at a time. # The SingleConversationSocket prevents interleaving requests. if not isinstance(socket, SingleConversationSocket): self.socket = SingleConversationSocket(socket) else: self.socket = socket self.tps = None # TesterPresentSender self.target_state = ECU_State() self.reset_handler = reset_handler self.verbose = kwargs.get("verbose", False) if enumerators: # enumerators can be a mix of classes or instances self.enumerators = [ e(self.socket) for e in enumerators if not isinstance(e, Enumerator) ] + [e for e in enumerators if isinstance(e, Enumerator) ] # noqa: E501 else: self.enumerators = [ e(self.socket) for e in self.default_enumerator_clss ] # noqa: E501 self.enumerator_classes = [e.__class__ for e in self.enumerators] self.state_graph = Graph() self.state_graph.add_edge(ECU_State(), ECU_State()) self.configuration = \ {"dynamic_timeout": kwargs.pop("dynamic_timeout", False), "enumerator_classes": self.enumerator_classes, "verbose": self.verbose, "state_graph": self.state_graph, "delay_state_change": kwargs.pop("delay_state_change", 0.5)} for e in self.enumerators: self.configuration[e.__class__] = kwargs.pop( e.__class__.__name__ + "_kwargs", dict()) for conf_key in self.enumerators: conf_val = self.configuration[conf_key.__class__] for kwargs_key, kwargs_val in kwargs.items(): if kwargs_key not in conf_val.keys(): conf_val[kwargs_key] = kwargs_val self.configuration[conf_key.__class__] = conf_val log_interactive.debug("The following configuration was created") log_interactive.debug(self.configuration)
def _evaluate_response( self, state, # type: EcuState request, # type: Packet response, # type: Optional[Packet] **kwargs # type: Optional[Dict[str, Any]] ): # type: (...) -> bool """ Evaluates the response and determines if the current scan execution should be stopped. :param state: Current state of the ECU under test :param request: Sent request :param response: Received response :param kwargs: Arguments to modify the behavior of this function. Supported arguments: - retry_if_none_received: True/False - exit_if_no_answer_received: True/False - exit_if_service_not_supported: True/False - exit_scan_on_first_negative_response: True/False - retry_if_busy_returncode: True/False :return: True, if current execution needs to be interrupted. False, if enumerator should proceed with the execution. """ if response is None: if cast(bool, kwargs.pop("retry_if_none_received", False)): log_interactive.debug("[i] Retry %s because None received", repr(request)) return self._populate_retry(state, request) return cast(bool, kwargs.pop("exit_if_no_answer_received", False)) if self._evaluate_negative_response_code(state, response, **kwargs): # leave current execution, because of a negative response code return True if self._evaluate_retry(state, request, response, **kwargs): # leave current execution, because a retry was set return True # cleanup retry packet self._retry_pkt[state] = None return self._evaluate_ecu_state_modifications(state, request, response)
def _evaluate_retry( self, state, # type: EcuState request, # type: Packet response, # type: Packet **kwargs # type: Optional[Dict[str, Any]] ): # type: (...) -> bool if super(GMLAN_SAEnumerator, self)._evaluate_retry(state, request, response, **kwargs): return True if response.service == 0x7f and \ self._get_negative_response_code(response) in [0x22, 0x37]: log_interactive.debug( "[i] Retry %s because requiredTimeDelayNotExpired or " "requestSequenceError received", repr(request)) return super(GMLAN_SAEnumerator, self)._populate_retry(state, request) return False
def scan(self): scan_complete = False while not scan_complete: scan_complete = True log_interactive.info("[i] Scan paths %s", self.get_state_paths()) for p in self.get_state_paths(): log_interactive.info("[i] Scan path %s", p) final_state = p[-1] for e in self.enumerators: if e.state_completed[final_state]: log_interactive.debug("[+] State %s for %s completed", repr(final_state), e) continue if not self.enter_state_path(p): log_interactive.error("[-] Error entering path %s", p) continue log_interactive.info("[i] EXECUTE SCAN %s for path %s", e.__class__.__name__, p) self.execute_enumerator(e) scan_complete = False self.reset_target()
def __init__(self, test_cases, **kwargs): # type: (Union[List[Union[AutomotiveTestCaseABC, Type[AutomotiveTestCaseABC]]], List[Type[AutomotiveTestCaseABC]]], Any) -> None # noqa: E501 self.verbose = kwargs.get("verbose", False) self.debug = kwargs.get("debug", False) self.delay_state_change = kwargs.get("delay_state_change", 0.5) self.state_graph = Graph() # test_case can be a mix of classes or instances self.test_cases = \ [e() for e in test_cases if not isinstance(e, AutomotiveTestCaseABC)] # type: List[AutomotiveTestCaseABC] # noqa: E501 self.test_cases += \ [e for e in test_cases if isinstance(e, AutomotiveTestCaseABC)] self.stages = [ e for e in self.test_cases if isinstance(e, StagedAutomotiveTestCase) ] self.staged_test_cases = \ [i for sublist in [e.test_cases for e in self.stages] for i in sublist] self.test_case_clss = set([ case.__class__ for case in set(self.staged_test_cases + self.test_cases) ]) for cls in self.test_case_clss: kwargs_name = cls.__name__ + "_kwargs" self.__setattr__(cls.__name__, kwargs.pop(kwargs_name, dict())) for cls in self.test_case_clss: val = self.__getattribute__(cls.__name__) for kwargs_key, kwargs_val in kwargs.items(): if kwargs_key not in val.keys(): val[kwargs_key] = kwargs_val self.__setattr__(cls.__name__, val) log_interactive.debug("The following configuration was created") log_interactive.debug(self.__dict__)
def scan(self, timeout=None): # type: (Optional[int]) -> None """ Executes all testcases for a given time. :param timeout: Time for execution. :return: None """ kill_time = time.time() + (timeout or 0xffffffff) while kill_time > time.time(): test_case_executed = False log_interactive.debug("[i] Scan paths %s", self.state_paths) for p, test_case in product(self.state_paths, self.configuration.test_cases): log_interactive.info("[i] Scan path %s", p) terminate = kill_time < time.time() if terminate: log_interactive.debug( "[-] Execution time exceeded. Terminating scan!") break final_state = p[-1] if test_case.has_completed(final_state): log_interactive.debug("[+] State %s for %s completed", repr(final_state), test_case) continue try: if not self.enter_state_path(p): log_interactive.error("[-] Error entering path %s", p) continue log_interactive.info("[i] Execute %s for path %s", str(test_case), p) self.execute_test_case(test_case) test_case_executed = True except (OSError, ValueError, Scapy_Exception) as e: log_interactive.critical("[-] Exception: %s", e) if self.configuration.debug: raise e if isinstance(e, OSError): log_interactive.critical( "[-] OSError occurred, closing socket") self.socket.close() if cast(SuperSocket, self.socket).closed and \ self.reconnect_handler is None: log_interactive.critical( "Socket went down. Need to leave scan") raise e finally: self.cleanup_state() if not test_case_executed: log_interactive.info( "[i] Execute failure or scan completed. Exit scan!") break self.cleanup_state() self.reset_target()
def _populate_retry( self, state, # type: EcuState request, # type: Packet ): # type: (...) -> bool """ Populates internal storage with request for a retry. :param state: Current state :param request: Request which needs a retry :return: True, if storage was populated. If False is returned, the retry storage is still populated. This indicates that the current execution was already a retry execution. """ if self._retry_pkt[state] is None: # This was no retry since the retry_pkt is None self._retry_pkt[state] = request log_interactive.debug("[-] Exit execute. Retry packet next time!") return True else: # This was a unsuccessful retry, continue execute log_interactive.debug("[-] Unsuccessful retry!") return False
def evaluate_security_access_response(res, seed, key): # type: (Optional[Packet], Packet, Optional[Packet]) -> bool if res is None or res.service == 0x7f: log_interactive.debug(repr(seed)) log_interactive.debug(repr(key)) log_interactive.debug(repr(res)) log_interactive.info("Security access error!") return False else: log_interactive.info("Security access granted!") return True
def execute_test_case(self, test_case): # type: (AutomotiveTestCaseABC) -> None """ This function ensures the correct execution of a testcase, including the pre_execute, execute and post_execute. Finally the testcase is asked if a new edge or a new testcase was generated. :param test_case: A test case to be executed :return: None """ test_case.pre_execute(self.socket, self.target_state, self.configuration) try: test_case_kwargs = self.configuration[test_case.__class__.__name__] except KeyError: test_case_kwargs = dict() log_interactive.debug("[i] Execute test_case %s with args %s", test_case.__class__.__name__, test_case_kwargs) test_case.execute(self.socket, self.target_state, **test_case_kwargs) test_case.post_execute(self.socket, self.target_state, self.configuration) if isinstance(test_case, StateGenerator): edge = test_case.get_new_edge(self.socket, self.configuration) if edge: log_interactive.debug("Edge found %s", edge) tf = test_case.get_transition_function(self.socket, edge) self.state_graph.add_edge(edge, tf) if isinstance(test_case, TestCaseGenerator): new_test_case = test_case.get_generated_test_case() if new_test_case: log_interactive.debug("Testcase generated %s", new_test_case) self.configuration.add_test_case(new_test_case)
def debug(self, lvl, msg): if self.debug_level >= lvl: log_interactive.debug(msg)
def debug(self, lvl, msg): if self.debug_level >= lvl: log_interactive.debug(msg)
def execute(self, socket, state, **kwargs): # type: (_SocketUnion, EcuState, Any) -> None self.check_kwargs(kwargs) timeout = kwargs.pop('timeout', 1) execution_time = kwargs.pop("execution_time", 1200) state_block_list = kwargs.get('state_block_list', list()) if state_block_list and state in state_block_list: self._state_completed[state] = True log_interactive.debug("[i] State %s in block list!", repr(state)) return state_allow_list = kwargs.get('state_allow_list', list()) if state_allow_list and state not in state_allow_list: self._state_completed[state] = True log_interactive.debug("[i] State %s not in allow list!", repr(state)) return it = self.__get_request_iterator(state, **kwargs) # log_interactive.debug("[i] Using iterator %s in state %s", it, state) start_time = time.time() log_interactive.debug("[i] Start execution of enumerator: %s", time.ctime(start_time)) for req in it: try: res = socket.sr1(req, timeout=timeout, verbose=False) except (OSError, ValueError, Scapy_Exception) as e: if not self._populate_retry(state, req): log_interactive.critical( "[-] Exception during retry. This is bad") raise e if socket.closed: if not self._populate_retry(state, req): log_interactive.critical( "[-] Socket closed during retry. This is bad") log_interactive.critical("[-] Socket closed during scan.") raise Scapy_Exception("Socket closed during scan") self._store_result(state, req, res) if self._evaluate_response(state, req, res, **kwargs): log_interactive.debug("[i] Stop test_case execution because " "of response evaluation") return if (start_time + execution_time) < time.time(): log_interactive.debug( "[i] Finished execution time of enumerator: %s", time.ctime()) return log_interactive.info("[i] Finished iterator execution") self._state_completed[state] = True log_interactive.debug("[i] States completed %s", repr(self._state_completed))
def execute(self, socket, state, **kwargs): # type: (_SocketUnion, EcuState, Any) -> None self.check_kwargs(kwargs) timeout = kwargs.pop('timeout', 1) execution_time = kwargs.pop("execution_time", 1200) state_block_list = kwargs.get('state_block_list', list()) if state_block_list and state in state_block_list: self._state_completed[state] = True log_interactive.debug("[i] State %s in block list!", repr(state)) return state_allow_list = kwargs.get('state_allow_list', list()) if state_allow_list and state not in state_allow_list: self._state_completed[state] = True log_interactive.debug("[i] State %s not in allow list!", repr(state)) return it = self.__get_request_iterator(state, **kwargs) # log_interactive.debug("[i] Using iterator %s in state %s", it, state) start_time = time.time() log_interactive.debug("[i] Start execution of enumerator: %s", time.ctime(start_time)) for req in it: res = self.sr1_with_retry_on_error(req, socket, state, timeout) self._store_result(state, req, res) if self._evaluate_response(state, req, res, **kwargs): log_interactive.debug("[i] Stop test_case execution because " "of response evaluation") return if (start_time + execution_time) < time.time(): log_interactive.debug( "[i] Finished execution time of enumerator: %s", time.ctime()) return log_interactive.info("[i] Finished iterator execution") self._state_completed[state] = True log_interactive.debug("[i] States completed %s", repr(self._state_completed))