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: key = '' else: key = key_func(i) log.detail('Trying auth level {}: key \'{}\''.format(i, key)) u.c.placeCanBookmark('SecurityAccess({}, {})'.format(i, repr(key))) resp = try_auth(u, i, key) if resp is not None: log.debug('auth {}: {}'.format(i, resp)) if 'resp' in resp: log.msg('SECURITY {}: {}'.format(i, resp['resp'].encode('hex'))) else: log.msg('SECURITY {}: {}'.format(i, err_str(resp['err']))) auth_levels[i] = resp if delay: time.sleep(delay) return auth_levels
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_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'].encode('hex'))) else: log.msg('DID {}: {}'.format(did_str(i), err_str(resp['err']))) dids[i] = resp if delay: time.sleep(delay) return dids
def main(): args = udsmap_parse_args() # TODO: move this to it's own class in cancat.utils, the config/args global # data thing will be better handled that way #cancat.utils.canmap(args) if args.verbose == 1: loglevel = log.DEBUG elif args.verbose and args.verbose >= 2: loglevel = log.DETAIL else: loglevel = log.WARNING if args.log_file is not None: logfile = time.strftime(args.log_file) else: logfile = None log.start(level=loglevel, filename=logfile) log.debug(args) # TODO: Check for potentially damaging options # TODO: Warn about how long the combined scan options will probably take: # - ECU Scan: if timeout is 3s: # worst case with both std & ext = (247 + 255) * 3 = # 3012 sec = 25.1 minutes # - ECU Scan: if timeout is 0.1s: # worst case with both std & ext = (247 + 255) * 0.1 = # 50 sec = ~ 1 minute # - DID Scan: #log.warning('ECU scan {} may take up to 25 minutes to complete'.format(args.E)) global c ifaceclass = cancat.CanInterface scancls = None if args.uds_class: pkg, cls = args.uds_class.rsplit('.', 1) scanlib = importlib.import_module(pkg) scancls = getattr(scanlib, cls) # If the custom UDS class package has a "CanInterface" class, use that # instead of the real cancat.CanInterface class if hasattr(scanlib, 'CanInterface'): ifaceclass = getattr(scanlib, 'CanInterface') c = ifaceclass(port=args.port) global _config if args.input_file is not None: _config = import_results(args, c, scancls) else: _config = { 'config': { 'baud': args.baud, 'timeout': args.timeout, 'no_recursive_session_scanning': args.no_recursive_session_scanning, }, 'notes': {}, 'ECUs': {}, } c.setCanBaud(_get_baud_value(_config['config']['baud'])) if args.output_file: global _output_filename _output_filename = time.strftime(args.output_file) if args.can_session_file: global _can_session_filename _can_session_filename = time.strftime(args.can_session_file) start_time = now() _config['notes'][start_time] = [] _config['start_time'] = start_time _config['notes'][start_time] = 'command: {}'.format(' '.join(sys.argv)) if args.startup_wait: # Listen for messages to ensure that the bus is working right count1 = c.getCanMsgCount() time.sleep(args.startup_wait) count2 = c.getCanMsgCount() if count2 <= count1: log_and_save( _config, 'ERROR: No CAN traffic detected on {} @ {}'.format( args.port, args.baud)) save_and_exit(2) # signal will catch CTRL-C in script contexts signal.signal(signal.SIGINT, sigint_handler) # catching KeyboardInterrupt will catch CTRL-C in interactive contexts try: scan(_config, args, c, scancls) log_and_save(_config, 'scans completed @ {}'.format(now())) save_and_exit(0) except KeyboardInterrupt: log_and_save(_config, 'scan aborted @ {}'.format(now())) save_and_exit(1) except Exception as e: saved_trace = traceback.format_exc() log_and_save(_config, 'Exception @ {}: {}'.format(now(), e)) save() print(saved_trace, file=sys.stderr) sys.exit(3)
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 try_session_scan(u, session_range, prereq_sessions, found_sessions, delay=None, 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.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'].encode('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 as e: # 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, 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: if ext and i == uds.ARBID_CONSTS['29bit']['tester']: # Skip i == 0xF1 because in that case the sender and receiver IDs # are the same log.detail('Skipping 0xF1 in ext ECU scan: invalid ECU address') continue elif ext == False and i > uds.ARBID_CONSTS['11bit']['max_req_id']: # For non-extended scans the valid range goes from 0x00 to 0xFF, but # stop the scan at 0xf7 because at that time the response is the # largest possible valid value log.detail('Stopping std ECU scan at 0xF7: last valid ECU address') break 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(addr)) rx_arbid, msg = possible_match log.warn('{}: {}'.format(hex(rx_arbid), msg.encode('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 for addr in possible_ecus: 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, sess) as msg: if msg is not None: log.debug('{} session {}: {}'.format(addr, sess, repr(msg))) log.msg('found {}'.format(addr)) ecus.append(addr) except uds.NegativeResponseException as e: log.debug('{} session {}: {}'.format(addr, sess, 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
def ecu_session_scan(c, arb_id_range, ext=0, session=1, udscls=None, timeout=3.0, 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) 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, sess) as msg: if msg is not None: log.debug('{} session {}: {}'.format( addr, sess, repr(msg))) log.msg('found {}'.format(addr)) ecus.append(addr) except uds.NegativeResponseException as e: log.debug('{} session {}: {}'.format(addr, sess, 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