def auth_scan(u, auth_range, key_func=None, delay=None): log.debug('Starting auth scan for range: {}'.format(auth_range)) u.c.placeCanBookmark('auth_scan({}, key_func={}, delay={})'.format( auth_range, key_func, delay)) auth_levels = {} for i in auth_range: if key_func: secret = '' else: secret = key_func(i) log.detail('Trying auth level {}: secret \'{}\''.format(i, secret)) u.c.placeCanBookmark('SecurityAccess({}, {})'.format(i, repr(secret))) resp = try_auth(u, i, secret) if resp is not None: log.debug('auth {}: {}'.format(i, resp)) if 'resp' in resp: log.msg('SECURITY {}: {}'.format(i, resp['resp'].hex())) else: log.msg('SECURITY {}: {}'.format(i, err_str(resp['err']))) auth_levels[i] = resp if delay: time.sleep(delay) return auth_levels
def auth_scan(self, auth_range, rescan=False): arb, resp, ext = self._addr u = self._scancls(self.c, arb, resp, extflag=ext, verbose=False, timeout=self._timeout) u.StartTesterPresent(request_response=False) for sess in self._sessions: if sess != 1 and \ ('auth' not in self._sessions[sess] or len(self._sessions[sess]['auth']) == 0 or rescan): log.msg('{} session {} starting auth scan'.format( self._addr, sess)) with utils.new_session(u, sess, self._sessions[sess]['prereqs'], True): # Pass the get_key() function in the UDS scan class through results = utils.auth_scan(u, auth_range, lambda x: u.get_key(sess, x), delay=self._delay) if 'auth' in self._sessions[sess]: self._sessions[sess]['auth'].update(results) else: self._sessions[sess]['auth'] = results
def did_read_scan(u, did_range, delay=None): log.debug('Starting DID read scan for range: {}'.format(did_range)) u.c.placeCanBookmark('did_read_scan({}, delay={})'.format( did_range, delay)) dids = {} for i in did_range: log.detail('Trying DID read {}'.format(hex(i))) u.c.placeCanBookmark('ReadDID({})'.format(hex(i))) resp = try_read_did(u, i) if resp is not None: log.debug('DID {}: {}'.format(hex(i), resp)) if 'resp' in resp: printable_did = ''.join([ x if x in string.printable else '' for x in resp['resp'][3:] ]) log.msg('DID {}: {} ({})'.format(did_str(i), resp['resp'], printable_did)) else: log.msg('DID {}: ERR {}'.format(did_str(i), err_str(resp['err']))) dids[i] = resp if delay: time.sleep(delay) return dids
def session_scan(self, session_range, rescan=False, rescan_did_range=None, recursive_scan=True): arb, resp, ext = self._addr # Unfortunately session scanning (and the later DID scanning) is more # reliable with the standard 3 second timeout u = self._scancls(self.c, arb, resp, extflag=ext, verbose=False, timeout=3.0) log.msg('{} starting session scan'.format(self._addr)) # Only scan for new sessions if the session list consists only of session 1 if len(self._sessions) == 1: new_sessions = utils.session_scan(u, session_range, delay=self._delay, recursive_scan=recursive_scan) self._sessions.update(new_sessions) # For each session that was found, go through the list of DIDs and # identify which DIDs can be read in this session for sess in self._sessions: if sess != 1 and 'resp' in self._sessions[sess] and \ (rescan or 'dids' not in self._sessions[sess] or len(self._sessions[sess]['dids']) == 0): try: with utils.new_session(u, sess, self._sessions[sess]['prereqs'], True): log.debug('{} session {} ({}) re-reading DIDs'.format( self._addr, sess, self._sessions[sess]['prereqs'])) self._sessions[sess]['dids'] = {} # If rescan is set do a full DID scan instead of the short # scan of only existing DIDs if rescan: results = utils.did_read_scan(u, rescan_did_range, delay=self._delay) self._sessions[sess]['dids'].update(results) else: valid_did_range = [ d for d in self._sessions[1]['dids'] ] results = utils.did_read_scan(u, valid_did_range, delay=self._delay) self._sessions[sess]['dids'].update(results) except NegativeResponseException as e: log.error( 'Failed to enter session {} ({}) to re-scan DIDs, try again later: {}' .format(sess, self._sessions[sess]['prereqs'], e))
def did_read_scan(self, did_range, rescan=False): # Only do a scan if we don't already have data, unless rescan is set if not self._sessions[1]['dids'] or rescan: arb, resp, ext = self._addr log.msg('{} starting DID scan'.format(self._addr)) # The DID scan is more reliable using the standard UDS timeout # because of the length of time that block transfers can take u = self._scancls(self.c, arb, resp, extflag=ext, verbose=False, timeout=3.0) self._sessions[1]['dids'].update( utils.did_read_scan(u, did_range, delay=self._delay))
def did_write_scan(self, did_range, rescan=False): # Only do a scan if we don't already have data, unless rescan is set if not self._sessions[1]['write_dids'] or rescan: # Attempt to write an empty array, which probably won't succeed? # but if it does we're pretty screwed. arb, resp, ext = self._addr log.msg('{} starting DID write scan'.format(self._addr)) u = self._scancls(self.c, arb, resp, extflag=ext, verbose=False, timeout=self._timeout) self._write_dids.update( utils.did_write_scan(u, did_range, b'', delay=self._delay))
def did_write_scan(u, did_range, write_data, delay=None): log.debug('Starting DID write scan for range: {}'.format(did_range)) u.c.placeCanBookmark('did_write_scan({}, write_data={}, delay={})'.format( did_range, write_data, delay)) dids = {} for i in did_range: log.detail('Trying DID write {}'.format(hex(i))) u.c.placeCanBookmark('WriteDID({})'.format(hex(i))) resp = try_write_did(u, i, write_data) if resp is not None: log.detail('DID {}: {}'.format(hex(i), resp)) if 'resp' in resp: log.msg('DID {}: {}'.format(did_str(i), resp['resp'].hex())) else: log.msg('DID {}: {}'.format(did_str(i), err_str(resp['err']))) dids[i] = resp if delay: time.sleep(delay) return dids
def key_length_scan(self, len_range, rescan=False): log.msg('{} starting key/seed length scan {}'.format( self._addr, len_range)) self.c.placeCanBookmark('canmap key_length_scan({}, delay={})'.format( len_range, self._delay)) arb, resp, ext = self._addr u = self._scancls(self.c, arb, resp, extflag=ext, verbose=False, timeout=self._timeout) u.StartTesterPresent(request_response=False) # I can't think of a good way to turn this into a more generic utility # function for sess in self._sessions: # Skip session 1 if sess == 1: continue with utils.new_session(u, sess, True): for lvl in self._sessions[sess]['auth']: if not self._found_key_len(sess, lvl) or rescan: # TODO: For now, we delete the old scan data, not sure how # best to track to new vs. old key key scans otherwise self._sessions[sess]['auth'][lvl] = {'seeds': []} log.msg( '{} session {} auth {} starting key length scan'. format(self._addr, sess, lvl)) for keylen in len_range: key = '\x00' * keylen log.detail( 'Trying session {}, auth {}, key \'{}\''. format(sess, lvl, key)) self.c.placeCanBookmark( 'SecurityAccess({}, {})'.format( sess, repr(key))) resp = self._try_key(u, lvl, key) self._sessions[sess]['auth'][lvl].update(resp) self._sessions[sess]['auth'][lvl]['seeds'].append( dict(u.seed)) if 'resp' in resp: # Get the key attempted from the recorded seed data # in case the scanning class modified it log.msg( 'Session {}, auth {} key found! secret {} (seed {})' .format(sess, lvl, repr(u.seed['secret']), repr(u.seed['seed']))) break elif resp['err'] == 0x35: log.debug( 'Session {}, auth {} length found! {} (seed {})' .format(sess, lvl, len(u.seed['secret']), repr(u.seed['seed']))) break if self._delay: time.sleep(self._delay) # Ensure tester present is not being sent anymore u.StopTesterPresent()
def log_and_save(results, note): log.msg(note) results['notes'][results['start_time']] += '\n' + note
def try_session_scan( u, session_range, prereq_sessions, found_sessions, delay=None, # noqa: C901 recursive_scan=True, try_ecu_reset=True, try_sess_ctrl_reset=True): log.debug('Starting session scan for range: {}'.format(session_range)) u.c.placeCanBookmark('session_scan({}, delay={})'.format( session_range, delay)) sessions = {} for i in session_range: # If this session matches any one of the prereqs, or one of the # sessions already found, skip it if i in prereq_sessions or i in found_sessions: continue log.detail('Trying session {}'.format(i)) u.c.placeCanBookmark('DiagnosticSessionControl({})'.format(i)) # Enter any required preq sessions try: for prereq in prereq_sessions: enter_session(u, prereq) except uds.UDSTimeout as e: log.detail( 'SESSION ({}) TIMEOUT: Can\'t enter prereqs, stopping session scan' .format(prereq_sessions)) return sessions except uds.NegativeResponseException as e: # 0x7f:'ServiceNotSupportedInActiveSession' if e.neg_code == 0x7f: log.detail( 'SESSION ({}): Can\'t enter prereqs, stopping session scan' .format(prereq_sessions)) return sessions else: raise e resp = try_session(u, i) if resp is not None: resp['prereqs'] = list(prereq_sessions) log.debug('session {}: {}'.format(i, resp)) if 'resp' in resp: log.msg('SESSION {}: {} ({})'.format(i, resp['resp'].hex(), prereq_sessions)) else: log.msg('SESSION {}: {} ({})'.format(i, err_str(resp['err']), prereq_sessions)) sessions[i] = resp # Only bother with this if a successful response was received if resp and 'resp' in resp: if try_ecu_reset: try: u.EcuReset() # Small delay to allow for the reset to complete time.sleep(0.2) except uds.NegativeResponseException as e: # 0x11:'ServiceNotSupported' # 0x22:'ConditionsNotCorrect' if e.neg_code in [0x11, 0x22]: try_ecu_reset = False elif try_sess_ctrl_reset: try: # Try just changing back to session 1 new_session(u, 1) except uds.NegativeResponseException: # The default method to try returning to session 1 is EcuReset, if # EcuReset doesn't work (or isn't enabled), then try using the # DiagnosticSessionControl message to return to session 1, if that # doesn't work then we can't attempt recursive session scanning try_sess_ctrl_reset = False # Extra delay between attempts if configured if delay: time.sleep(delay) # For each session found re-scan for new sessions that can be entered from # those, but only if we have a valid reset method: if recursive_scan and (try_ecu_reset or try_sess_ctrl_reset): subsessions = {} for sess in sessions: # Only attempt this with sessions that we got a successful response # for if 'msg' in sessions[sess]: log.debug('Scanning for sessions from session {} ({})'.format( sess, prereq_sessions)) prereqs = prereq_sessions + [sess] subsessions.update( try_session_scan(u, session_range, prereqs, sessions.keys(), delay=delay, recursive_scan=recursive_scan, try_ecu_reset=try_ecu_reset, try_sess_ctrl_reset=try_sess_ctrl_reset)) sessions.update(subsessions) return sessions
def ecu_session_scan( c, arb_id_range, ext=0, session=1, udscls=None, timeout=3.0, # noqa: C901 delay=None, verbose_flag=False): scan_type = '' if ext: scan_type = ' ext' if udscls is None: udscls = UDS log.debug('Starting{} Session ECU scan for range: {}'.format( scan_type, arb_id_range)) c.placeCanBookmark( 'ecu_session_scan({}, ext={}, session={}, timeout={}, delay={})'. format(arb_id_range, ext, session, timeout, delay)) ecus = [] possible_ecus = [] for i in arb_id_range: tester_id = uds.ARBID_CONSTS[ext]['tester'] if i == tester_id: # Skip i == 0xF1 because in that case the sender and receiver IDs # are the same log.detail( 'Skipping {} in ext ECU scan: invalid ECU address'.format( hex(tester_id))) continue arb_id, resp_id = gen_arbids(i, ext) addr = ECUAddress(arb_id, resp_id, ext) u = udscls(c, addr.tx_arbid, addr.rx_arbid, extflag=addr.extflag, verbose=verbose_flag, timeout=timeout) log.detail('Trying {}'.format(addr)) try: start_index = u.c.getCanMsgCount() with new_session(u, session) as msg: if msg is not None: log.debug('{} session {}: {}'.format( addr, session, repr(msg))) log.msg('found {}'.format(addr)) ecus.append(addr) else: tx_msg, responses = find_possible_resp( u, start_index, arb_id, uds.SVC_DIAGNOSTICS_SESSION_CONTROL, session, timeout) if responses: log.warn( 'Possible non-standard responses for {} found:'. format(hex(addr.tx_arbid))) for rx_arbid, msg in responses: log.warn('{}: {}'.format(hex(rx_arbid), msg.hex())) possible_ecus.append(ECUAddress(arb_id, rx_arbid, ext)) except uds.NegativeResponseException as e: log.debug('{} session {}: {}'.format(addr, session, e)) log.msg('found {}'.format(addr)) # If a negative response happened, that means an ECU is present # to respond at this address. ecus.append(addr) if delay: time.sleep(delay) # Double check any non-standard responses that were found if possible_ecus: log.detail('Retrying possible non-standard ECU addresses') for addr in possible_ecus: # if the TX ID is the OBD2 request ID, skip it if addr.tx_arbid == uds.ARBID_CONSTS[addr.extflag]['obd2_broadcast']: log.detail('Skipping OBD2 broadcast address ECU {}'.format(addr)) continue u = udscls(c, addr.tx_arbid, addr.rx_arbid, extflag=addr.extflag, verbose=verbose_flag, timeout=timeout) log.detail('Trying {}'.format(addr)) try: with new_session(u, session) as msg: if msg is not None: log.debug('{} session {}: {}'.format( addr, session, repr(msg))) log.msg('found {}'.format(addr)) ecus.append(addr) except uds.NegativeResponseException as e: log.debug('{} session {}: {}'.format(addr, session, e)) log.msg('found {}'.format(addr)) # If a negative response happened, that means an ECU is present # to respond at this address. ecus.append(addr) if delay: time.sleep(delay) return ecus