Ejemplo n.º 1
0
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
Ejemplo n.º 2
0
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
Ejemplo n.º 3
0
    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))
Ejemplo n.º 4
0
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
Ejemplo n.º 5
0
    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()
Ejemplo n.º 6
0
def main():  # noqa: C901
    args = udsmap_parse_args()

    # TODO: move this to it's own class in cancatlib.utils, the config/args global
    #       data thing will be better handled that way
    # cancatlib.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 = cancatlib.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 cancatlib.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)
Ejemplo n.º 7
0
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
Ejemplo n.º 8
0
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