Exemplo n.º 1
0
def find_devices(ctx, buses):
    results = []
    for bus in buses:
        log.note('Probing bus {:d}'.format(bus))
        try:

            cmd = 'i2c dev {:d}'.format(bus)
            # Raise an exception on error via check=True
            resp = ctx.send_command(cmd, check=True)

            # This may fail for buses (or pinmux settings) that are configured
            # appropriately. Thus, we drop check=True and just look results
            resp = ctx.send_command('i2c probe')

            match = re.match(r'Valid chip addresses: ([0-9a-fA-F\t ]+)', resp)
            if not match:
                # A failing bus will spew failures for a while. Keep trying
                # to interrupt it (Ctrl-C) until we know we're back at a prompt.
                log.warning('No devices or bus failing. Waiting for prompt.')
                ctx.interrupt(timeout=120)
                continue

            for addr in match.group(1).split():
                addr = int(addr, 16)
                log.info('Found device: Bus={:d}, Address=0x{:02x}'.format(
                    bus, addr))
                results.append((bus, addr))

        except OperationFailed as error:
            log.error('Command failed: ' + cmd + os.linesep + str(error))

    return results
Exemplo n.º 2
0
def is_vulnerable(ctx):
    """
    Attempt to check if target is vulnerable to i2c-based unlock bypass,
    based upon the Sonos-provided (i.e. not the U-Boot) version number.
    """
    ver_regex = re.compile(
        r'U-Boot \d{4}\.\d{2}-Royale(-Strict)?-Rev(?P<rev>\d{1,}\.\d{1,})\s')
    for info in ctx.version():
        log.debug('Checking version string: ' + info)
        m = ver_regex.match(info)
        if m is not None:
            ver = float(m.group('rev'))
            if ver == 0.2:
                log.info('Vulnerable version detected: ' + info)
                return True

            if ver <= 0.3:
                msg = 'Version may be vulnerable, but our memory patches are specific to v0.2'
                log.error(msg)
            else:
                log.error('Patched or unknown version detected: ' + info)
            return False

    log.error('Did not detect "U-Boot Royale" version string.')
    return False
Exemplo n.º 3
0
def setup_stratagem(ctx, data, pattern_name, writer):
    global _STRATAGEM_DATA_LOADED

    stratagem_input_data_addr = address + STRATAGEM_DATA_OFF

    # Only need to load the input data once
    if not _STRATAGEM_DATA_LOADED:
        log.info('Loading Stratagem input data.')
        ctx.write_memory(stratagem_input_data_addr, STRATAGEM_DATA)
        _STRATAGEM_DATA_LOADED = True
    had_resource = False
    filename = '{:s}.0x{:08x}.{:d}.stratagem'.format(pattern_name, address,
                                                     len(data))
    try:
        stratagem = load_resource(filename, Stratagem.from_json_file,
                                  'memory_test', writer.name)
        had_resource = True
    except FileNotFoundError:
        hunter = writer.stratagem_hunter(STRATAGEM_DATA,
                                         stratagem_input_data_addr,
                                         revlut_maxlen=1024)
        stratagem = hunter.build_stratagem(data)

    if not had_resource:
        save_resource(filename, stratagem.to_json_file, 'memory_test',
                      writer.name)

    return stratagem
Exemplo n.º 4
0
def read_loop(console):
    state = 'AWAIT_PROMPT'

    log.info('Waiting for menu. Please power on the target.')
    while True:
        line = console.readline()
        if not line:
            continue

        line = line.strip()
        print(line)

        if state == 'AWAIT_PROMPT':
            if 'Input i key to enter' in line:
                console.write('i')
                state = 'SELECT_CLI'
        elif state == 'SELECT_CLI':
            if 'Please input test item' in line:
                console.write('Z')
                console.write('\x03')  # Ctrl-C
                state = 'AWAIT_UBOOT'
        elif state == 'AWAIT_UBOOT':
            if 'MT7628 #' in line:
                state = 'DONE'
        elif state == 'DONE':
            log.info('U-Boot prompt detected!')
            return
Exemplo n.º 5
0
def read_nand_to_file(ctx, filename: str, name: str, load_addr: int,
                      nand_addr: int, size: int):

    # Copy NAND contents to ${loadaddr}
    cmd = 'nand read 0x{loadaddr:x} 0x{nand_addr:x} 0x{size:x}'
    cmd = cmd.format(loadaddr=load_addr, nand_addr=nand_addr, size=size)

    log.info('Copying ' + name + ' to RAM buffer')
    resp = ctx.send_command(cmd, check=True)
    log.note('Device response: ' + resp.strip())

    log.info('Reading RAM buffer to file: ' + filename)
    ctx.read_memory_to_file(load_addr, size, filename)
Exemplo n.º 6
0
def perform_unlock_bypass(ctx):
    if is_unlocked(ctx):
        log.info("Device is already using 'unlocked' command table.")
        return True

    if not is_vulnerable(ctx):
        log.error('Device does not appear to be vulnerable.')
        return False

    ctx.patch_memory(_patch_list, impl='i2c')
    success = is_unlocked(ctx)
    if success:
        log.info("Success! Device is now using 'unlocked' command table.")
    else:
        log.error("Failed to switch to 'unlocked' command table.")

    return success
Exemplo n.º 7
0
def test_read_mem__uboot(state: dict, script: str):
    """
    Read 512KiB of post-relocated U-Boot code/data to a u-boot.bin file in the
    resource directory in preparation for test_find_cmd() can be exercised.


    Populates state['config]', state['uboot_bin_file']
    """
    _READ_SIZE = 512 * 1024

    state['uboot_bin_file'] = os.path.join(state['test_dir'], 'uboot_512K.bin')
    uboot_addr = state['config']['gd']['bd']['relocaddr']['value']

    log.info('Reading 512K of U-Boot code/data to a binary file')

    args = [
        script,
        '-a', hex(uboot_addr), '-l', str(_READ_SIZE),
        '-f', state['uboot_bin_file'],
    ]
    run(args, check=True)
Exemplo n.º 8
0
def execute_all_tests(ctx, address):
    report = {}

    try:
        log.info('Testing memory read implementations')
        for reader in ctx.memory_readers:
            default_writer = ctx.default_memory_writer()
            test_name = '{:s} / {:s}'.format(reader.name, default_writer.name)
            results = run_tests(ctx, reader, default_writer, address)
            report[test_name] = results

        log.info('Testing memory write implementations')
        for writer in ctx.memory_writers:
            default_reader = ctx.default_memory_reader()
            test_name = '{:s} / {:s}'.format(writer.name, default_reader.name)
            results = run_tests(ctx, default_reader, writer, address)
            report[test_name] = results

    except KeyboardInterrupt:
        log.warning('Memory test suite interrupted. Returning partial results.')

    return report
Exemplo n.º 9
0
def run_tests(ctx, reader, writer, address):
    report = []
    log.info('  Currently testing: {} / {}'.format(reader.name, writer.name))

    for (data, pattern_name) in test_cases():
        try:
            if isinstance(writer, StratagemMemoryWriter):
                stratagem = setup_stratagem(ctx, data, pattern_name, writer)
                msg = '    Using {:d}-entry stratagem for {:d}-byte payload with {:s}'
                msg = msg.format(len(stratagem), len(data), pattern_name.replace('_', ' '))
            else:
                msg = '    Using {:d}-byte payload with {:s}'
                msg = msg.format(len(data), pattern_name.replace('_', ' '))
                stratagem = None

            log.note(msg)
            result = run_test(ctx, reader, writer, address, data, stratagem)
        except (StratagemCreationFailed, OperationAlignmentError):
            result = 'Skipped (Alignment)'
        entry = {'size': len(data), 'pattern': pattern_name, 'status': result}
        report.append(entry)

    return report
Exemplo n.º 10
0
def locate_scripts() -> dict:
    """
    Return full paths to each depthcharge script.

    Dict keys are the script names, with the 'depthcharge-' prefix removed.
    Values are the full paths to the script.

    """
    script_dir = realpath(os.path.join(_THIS_DIR, '../../scripts'))
    log.info('Searching for scripts in: ' + script_dir)

    ret = {}
    for root, _, files in os.walk(script_dir):
        for filename in files:
            script = os.path.join(root, filename)
            key = basename(script).replace('depthcharge-', '')
            ret[key] = script

    for key in _EXPECTED:
        if key not in ret:
            raise FileNotFoundError('Failed to locate depthcharge-' + key)

    return ret
Exemplo n.º 11
0
if __name__ == '__main__':

    if len(sys.argv) < 2 or '-h' in sys.argv or '--help' in sys.argv:
        print('MT7628 (MediaTek/Ralink) boot menu example')
        print('Usage: <serial port> [baud rate]')
        print('Baud rate defaults to 57600.')
        print()
        sys.exit(1 if len(sys.argv) < 1 else 0)

    uart_device = sys.argv[1]
    try:
        baud_rate = sys.argv[2]
        print(baud_rate)
    except IndexError:
        baud_rate = '57600'

    console = Console(uart_device, baudrate=baud_rate)
    log.note('Opened ' + uart_device + ': ' + baud_rate)

    try:
        read_loop(console)
    except KeyboardInterrupt:
        print('\r', end='')  # Overwrite "^C" on terminal
        log.warning('Interrupted! Exiting.')
        sys.exit(1)

    msg = ('You may now run the following command to inspect the platform.\n'
           '  depthcharge-inspect -i {:s}:{:s} -c rp-wd008.cfg [-m term]')

    log.info(msg.format(uart_device, baud_rate))
Exemplo n.º 12
0
            match = re.match(r'Valid chip addresses: ([0-9a-fA-F\t ]+)', resp)
            if not match:
                # A failing bus will spew failures for a while. Keep trying
                # to interrupt it (Ctrl-C) until we know we're back at a prompt.
                log.warning('No devices or bus failing. Waiting for prompt.')
                ctx.interrupt(timeout=120)
                continue

            for addr in match.group(1).split():
                addr = int(addr, 16)
                log.info('Found device: Bus={:d}, Address=0x{:02x}'.format(
                    bus, addr))
                results.append((bus, addr))

        except OperationFailed as error:
            log.error('Command failed: ' + cmd + os.linesep + str(error))

    return results


if __name__ == '__main__':
    # Attach to the device and get a Depthcharge context
    ctx = setup()

    log.info('Identifying available I2C buses.')
    buses = get_buses(ctx)

    log.info('Probing I2C buses for devices. This may take some time.')
    # We'll just log results as we go, rather than use the return value
    find_devices(ctx, buses)
Exemplo n.º 13
0
        sim_memory[dst_off:dst_off + 4] = state.to_bytes(4, sys.byteorder)

        # Remaining iterations are performed in-place on the intermediate
        # result located in the destination memory location
        for i in range(1, iterations):
            state = crc32(sim_memory[dst_off:dst_off + 4])
            sim_memory[dst_off:dst_off + 4] = state.to_bytes(4, sys.byteorder)


if __name__ == '__main__':
    hunter = ReverseCRC32Hunter(THE_RAVEN, 0x0000_0000, revlut_maxlen=1024)
    stratagem = hunter.build_stratagem(TARGET_PAYLOAD, max_iterations=16384)

    filename = 'raven-stratagem.json'
    stratagem.to_json_file(filename)
    log.info('Saved stratagem to ' + filename)

    # Here's a good spot to set a breakpoint and inspect `stratagem`
    # breakpoint()

    # Simulate a memory space to write into
    sim_memory = bytearray(len(TARGET_PAYLOAD))

    execute_stratagem(stratagem, sim_memory)

    if sim_memory == TARGET_PAYLOAD:
        log.info('Result:\n' + sim_memory.decode('ascii'))
    else:
        log.error('Produced result does not match desired target payload!')
        # breakpoint()
Exemplo n.º 14
0
def create_depthcharge_ctx(args, **kwargs):
    """
    Create and return an initialized :py:class:`~depthcharge.Depthcharge` handle based upon
    command-line arguments.  Examples of this function's usage can be found in the Depthcharge
    scripts.

    The *args* parameter should contain the results of :py:class:`ArgumentParser.parse_args()`.

    The following must be included in the **args** Namespace, even if set to their
    unspecified (e.g. default, ``None``) values.

    * *monitor* - From :py:meth:`ArgumentParser.add_monitor_argument()`
    * *iface* - From :py:meth:`ArgumentParser.add_interface_argument()`
    * *prompt* - From :py:meth:`ArgumentParser.add_prompt_argument()`
    * *extra* - From :py:meth:`ArgumentParser.add_extra_argument()`
    * *skip_deploy* - From :py:meth:`ArgumentParser.add_skip_deploy_argument()`

    The following items are optional:

    * *config* - From :py:meth:`ArgumentParser.add_config_argument()`
    * *companion* - From :py:meth:`ArgumentParser.add_companion_argument()`


    """
    monitor = Monitor.create(args.monitor)

    # Added as a quick way to sneak in a timeout=... value to increase down
    # intra-command time for demos.
    #
    # TODO: Mull this over a bit more, clean it up if needed, and document this.
    console_kwargs = kwargs.get('console_kwargs', {})

    console = Console(args.iface,
                      prompt=args.prompt,
                      monitor=monitor,
                      **console_kwargs)

    if hasattr(args, 'companion') and args.companion:
        device, companion_kwargs = args.companion
        companion = Companion(device, **companion_kwargs)
    else:
        companion = None

    # Join any "extra" arguments destined for depthcharge.Operation **kwargs
    # to out existing kwargs.
    if args.extra:
        kwargs = {**kwargs, **args.extra}

    if hasattr(args, 'no_reboot'):
        kwargs['allow_reboot'] = not args.no_reboot

    if hasattr(args, 'config') and args.config:
        try:
            log.info('Loading existing config: ' + args.config)
            return Depthcharge.load(args.config,
                                    console,
                                    companion=companion,
                                    skip_deploy=args.skip_deploy,
                                    **kwargs)
        except FileNotFoundError:
            # We'll create it when we call save()
            pass

    return Depthcharge(console,
                       companion=companion,
                       skip_deploy=args.skip_deploy,
                       **kwargs)
Exemplo n.º 15
0
def create_depthcharge_ctx(args, **kwargs):
    """
    Create and return an initialized :py:class:`~depthcharge.Depthcharge` handle based upon
    command-line arguments.  Examples of this function's usage can be found in the Depthcharge
    scripts.

    The *args* parameter should contain the results of :py:class:`ArgumentParser.parse_args()`.

    The following must be included in the **args** Namespace, even if set to their
    unspecified (e.g. default, ``None``) values.

    * *monitor* - From :py:meth:`ArgumentParser.add_monitor_argument()`
    * *iface* - From :py:meth:`ArgumentParser.add_interface_argument()`
    * *prompt* - From :py:meth:`ArgumentParser.add_prompt_argument()`
    * *extra* - From :py:meth:`ArgumentParser.add_extra_argument()`

    The following items are optional:

    * *config* - From :py:meth:`ArgumentParser.add_config_argument()`
    * *companion* - From :py:meth:`ArgumentParser.add_companion_argument()`
    * *allow_deploy* - From :py:meth:`ArgumentParser.add_allow_deploy_argument()`
    * *skip_deploy* - From :py:meth:`ArgumentParser.add_skip_deploy_argument()`
    * *allow_reboot* - From :py:meth:`ArgumentParser.add_allow_reboot_argument()`

    """
    monitor = Monitor.create(args.monitor)

    # Added as a quick way to sneak in a timeout=... value to increase down
    # intra-command time for demos.
    #
    # TODO: Mull this over a bit more, clean it up if needed, and document this.
    console_kwargs = kwargs.get('console_kwargs', {})

    # Attempt to retrive baudrate from config. This is intended to make life
    # less annoying when working with a device that uses the non-default
    # baud rate and forgetting to provide -i <iface>, thereby relying on the
    # /dev/ttyUSB0 default.
    if args.iface == _DEFAULT_IFACE and hasattr(args,
                                                'config') and args.config:
        try:
            with open(args.config) as infile:
                config = json.loads(infile.read())
                args.iface = _DEFAULT_IFACE_DEVICE + ':' + str(
                    config['baudrate'])
        except (KeyError, FileNotFoundError):
            # No worries! Keep calm and carry on hacking.
            pass

    console = Console(args.iface,
                      prompt=args.prompt,
                      monitor=monitor,
                      **console_kwargs)

    if hasattr(args, 'companion') and args.companion:
        device, companion_kwargs = args.companion
        companion = Companion(device, **companion_kwargs)
    else:
        companion = None

    # Join any "extra" arguments destined for depthcharge.Operation **kwargs
    # to out existing kwargs.
    if args.extra:
        kwargs = {**kwargs, **args.extra}

    # Arguments to pass to Depthcharge if non-None or True (for bools)
    keys = ('arch', 'allow_deploy', 'skip_deploy', 'allow_reboot')

    for key in keys:
        if hasattr(args, key) and getattr(args, key):
            kwargs[key] = getattr(args, key)

    if hasattr(args, 'config') and args.config:
        try:
            log.info('Loading existing config: ' + args.config)
            return Depthcharge.load(args.config,
                                    console,
                                    companion=companion,
                                    **kwargs)
        except FileNotFoundError:
            # We'll create it when we call save()
            pass

    return Depthcharge(console, companion=companion, **kwargs)
Exemplo n.º 16
0
        pickle.dump(state, outfile)


if __name__ == '__main__':
    args = handle_cmdline()

    try:
        scripts = locate_scripts()
    except FileNotFoundError as error:
        log.error(str(error))
        sys.exit(1)

    tests = load_tests(args)
    state = load_state(args)

    t_start = time.time()

    for test in tests:
        script = test_name_to_script(test.__name__)
        log.info('Running ' + test.__name__)
        test_help(state, script)
        test(state, script)
        save_state(state)

    t_end = time.time()
    t_elapsed = timedelta(seconds=(t_end - t_start))
    print(os.linesep + '=' * 76)
    print('Tests complete successfully.')
    print('Elapsed: ' + str(t_elapsed))
    print()
Exemplo n.º 17
0

if __name__ == '__main__':
    args = handle_cmdline()

    try:
        scripts = locate_scripts()
    except FileNotFoundError as error:
        log.error(str(error))
        sys.exit(1)

    tests = load_tests(args)
    state = load_state(args)

    t_start = time.time()

    for test in tests:
        script = test_name_to_script(test.__name__)
        log.note('Running ' + test.__name__)
        test_help(state, script)
        test(state, script)
        save_state(state)

    t_end = time.time()
    t_elapsed = timedelta(seconds=(t_end - t_start))

    msg = ('=' * 76 + os.linesep + '  Tests complete successfully.' +
           os.linesep + '  Elapsed: ' + str(t_elapsed) + os.linesep)

    log.info(msg)
Exemplo n.º 18
0
    # You can remove the console_kwargs parameter if you just want things
    # to run as fast as possible
    #
    console_kwargs = {'timeout': 0.085}

    ctx = create_depthcharge_ctx(args, console_kwargs=console_kwargs)

    success = perform_unlock_bypass(ctx)
    if success:
        if args.inspect:
            cfg_name = 'symfonsik_rev0.2_unlock_bypassed.cfg'

            msg = ('Addtional device inspection will begin in 5 seconds.\n'
                   '     This will induce a crash and then re-bypass the unlock.\n'
                   '     Results will be saved to: ' + cfg_name)
            log.info(msg)

            # Give the user a moment to digest this
            countdown = ctx.create_progress_indicator('countdown', 5, desc='Delaying')
            for _ in range(0, 5):
                time.sleep(1)
                countdown.update(1)
            ctx.close_progress_indicator(countdown)

            # Close the serial console we were using
            ctx.console.close()

            # Now re-inspect the device in its unlocked state by creating a new context
            # This will require crashing the device to obtain register r9 from ,
            # So we need to re-perform the unlock bypass after this reset.
            args.config = None  # Zap this so we perform inspection