diff --git a/objection/commands/device.py b/objection/commands/device.py index 5e9f9420..31c3a8c1 100644 --- a/objection/commands/device.py +++ b/objection/commands/device.py @@ -3,51 +3,6 @@ from ..state.connection import state_connection from ..state.device import device_state, Android, Ios -from ..utils.helpers import pretty_concat - - -def get_device_info() -> tuple: - """ - Get device information by first checking which runtimes - are available, and then extracting information about the - device based on the result. - """ - - api = state_connection.get_api() - - # set the frida version - frida = api.env_frida() - device_state.frida_version = frida['version'] - - environment = api.env_runtime() - - # ios device information - if environment == 'ios': - device_state.device_type = Ios - package_info = api.env_ios() - - # {'applicationName': 'za.sensepost.ipewpew', - # 'deviceName': 'iPhone 7 Plus', - # 'identifierForVendor': 'foo', - # 'model': 'iPhone', 'systemName': 'iOS', 'systemVersion': '12.1'} - device_state.os_version = package_info['systemVersion'] - - return pretty_concat(package_info['applicationName'], 30, left=True), \ - package_info['systemName'], package_info['model'], package_info['systemVersion'] - - # android device information - if environment == 'android': - device_state.device_type = Android - package_info = api.env_android() - - # {'application_name': 'com.sensepost.apewpew', - # 'board': 'universal5422', 'brand': 'samsung', 'device': 'foo', - # 'host': 'foo.bar', 'id': '1234', 'model': 'foo-bar', - # 'product': 'foo', 'user': 'root', 'version': '7.1.2'} - device_state.os_version = package_info['version'] - - return pretty_concat(package_info['application_name'], 30, left=True), \ - package_info['device'], package_info['brand'], package_info['version'] def get_environment(args: list = None) -> None: @@ -61,10 +16,10 @@ def get_environment(args: list = None) -> None: :return: """ - if device_state.device_type == Ios: + if device_state.platform == Ios: _get_ios_environment() - if device_state.device_type == Android: + if device_state.platform == Android: _get_android_environment() diff --git a/objection/commands/filemanager.py b/objection/commands/filemanager.py index 51b17b76..89f99560 100644 --- a/objection/commands/filemanager.py +++ b/objection/commands/filemanager.py @@ -67,10 +67,10 @@ def cd(args: list) -> None: does_exist = False # check for existence based on the runtime - if device_state.device_type == Ios: + if device_state.platform == Ios: does_exist = _path_exists_ios(path) - if device_state.device_type == Android: + if device_state.platform == Android: does_exist = _path_exists_android(path) # if we checked with the device that the path exists @@ -89,16 +89,16 @@ def cd(args: list) -> None: # see if its legit. else: - proposed_path = device_state.device_type.path_seperator.join([current_dir, path]) + proposed_path = device_state.platform.path_separator.join([current_dir, path]) # assume the proposed_path does not exist by default does_exist = False # check for existence based on the runtime - if device_state.device_type == Ios: + if device_state.platform == Ios: does_exist = _path_exists_ios(proposed_path) - if device_state.device_type == Android: + if device_state.platform == Android: does_exist = _path_exists_android(proposed_path) # if we checked with the device that the path exists @@ -122,10 +122,10 @@ def path_exists(path: str) -> bool: :return: """ - if device_state.device_type == Ios: + if device_state.platform == Ios: return _path_exists_ios(path) - if device_state.device_type == Android: + if device_state.platform == Android: return _path_exists_android(path) @@ -169,10 +169,10 @@ def pwd(args: list = None) -> str: if file_manager_state.cwd is not None: return file_manager_state.cwd - if device_state.device_type == Ios: + if device_state.platform == Ios: return _pwd_ios() - if device_state.device_type == Android: + if device_state.platform == Android: return _pwd_android() @@ -236,13 +236,13 @@ def ls(args: list) -> None: else: path = args[0] if not os.path.isabs(path): - path = device_state.device_type.path_seperator.join([pwd(), path]) + path = device_state.platform.path_separator.join([pwd(), path]) # based on the runtime, execute the correct ls method. - if device_state.device_type == Ios: + if device_state.platform == Ios: _ls_ios(path) - if device_state.device_type == Android: + if device_state.platform == Android: _ls_android(path) @@ -393,10 +393,10 @@ def download(args: list) -> None: source = args[0] destination = args[1] if len(args) > 1 else os.path.basename(source) - if device_state.device_type == Ios: + if device_state.platform == Ios: _download_ios(source, destination) - if device_state.device_type == Android: + if device_state.platform == Android: _download_android(source, destination) @@ -412,7 +412,7 @@ def _download_ios(path: str, destination: str) -> None: # if the path we got is not absolute, join it with the # current working directory if not os.path.isabs(path): - path = device_state.device_type.path_seperator.join([pwd(), path]) + path = device_state.platform.path_separator.join([pwd(), path]) api = state_connection.get_api() @@ -448,7 +448,7 @@ def _download_android(path: str, destination: str) -> None: # if the path we got is not absolute, join it with the # current working directory if not os.path.isabs(path): - path = device_state.device_type.path_seperator.join([pwd(), path]) + path = device_state.platform.path_separator.join([pwd(), path]) api = state_connection.get_api() @@ -488,13 +488,13 @@ def upload(args: list) -> None: return source = args[0] - destination = args[1] if len(args) > 1 else device_state.device_type.path_seperator.join( + destination = args[1] if len(args) > 1 else device_state.platform.path_separator.join( [pwd(), os.path.basename(source)]) - if device_state.device_type == Ios: + if device_state.platform == Ios: _upload_ios(source, destination) - if device_state.device_type == Android: + if device_state.platform == Android: _upload_android(source, destination) @@ -508,7 +508,7 @@ def _upload_ios(path: str, destination: str) -> None: """ if not os.path.isabs(destination): - destination = device_state.device_type.path_seperator.join([pwd(), destination]) + destination = device_state.platform.path_separator.join([pwd(), destination]) api = state_connection.get_api() click.secho('Uploading {0} to {1}'.format(path, destination), fg='green', dim=True) @@ -543,7 +543,7 @@ def _upload_android(path: str, destination: str) -> None: """ if not os.path.isabs(destination): - destination = device_state.device_type.path_seperator.join([pwd(), destination]) + destination = device_state.platform.path_separator.join([pwd(), destination]) api = state_connection.get_api() click.secho('Uploading {0} to {1}'.format(path, destination), fg='green', dim=True) @@ -583,16 +583,16 @@ def rm(args: list) -> None: target = args[0] if not os.path.isabs(target): - target = device_state.device_type.path_seperator.join([pwd(), target]) + target = device_state.platform.path_separator.join([pwd(), target]) if not click.confirm('Really delete {0} ?'.format(target)): click.secho('Not deleting {0}'.format(target), dim=True) return - if device_state.device_type == Ios: + if device_state.platform == Ios: _rm_ios(target) - if device_state.device_type == Android: + if device_state.platform == Android: _rm_android(target) @@ -662,10 +662,10 @@ def cat(args: list): source = args[0] _, destination = tempfile.mkstemp('.file') - if device_state.device_type == Ios: + if device_state.platform == Ios: _download_ios(source, destination) - if device_state.device_type == Android: + if device_state.platform == Android: _download_android(source, destination) click.secho('====', dim=True) @@ -768,10 +768,10 @@ def list_folders_in_current_fm_directory() -> dict: resp = {} # get the folders based on the runtime - if device_state.device_type == Ios: + if device_state.platform == Ios: response = _get_short_ios_listing() - elif device_state.device_type == Android: + elif device_state.platform == Android: response = _get_short_android_listing() # looks like we landed in an unknown runtime. @@ -799,10 +799,10 @@ def list_files_in_current_fm_directory() -> dict: resp = {} # check for existence based on the runtime - if device_state.device_type == Ios: + if device_state.platform == Ios: response = _get_short_ios_listing() - elif device_state.device_type == Android: + elif device_state.platform == Android: response = _get_short_android_listing() # looks like we landed in an unknown runtime. diff --git a/objection/commands/ios/plist.py b/objection/commands/ios/plist.py index 4fe0ab69..4eb513ab 100644 --- a/objection/commands/ios/plist.py +++ b/objection/commands/ios/plist.py @@ -24,7 +24,7 @@ def cat(args: list = None) -> None: if not os.path.isabs(plist): pwd = filemanager.pwd() - plist = device_state.device_type.path_seperator.join([pwd, plist]) + plist = device_state.platform.path_separator.join([pwd, plist]) api = state_connection.get_api() plist_data = api.ios_plist_read(plist) diff --git a/objection/commands/ui.py b/objection/commands/ui.py index 3e04b62e..90f7e745 100644 --- a/objection/commands/ui.py +++ b/objection/commands/ui.py @@ -18,10 +18,10 @@ def alert(args: list = None) -> None: else: message = args[0] - if device_state.device_type == Ios: + if device_state.platform == Ios: _alert_ios(message) - if device_state.device_type == Android: + if device_state.platform == Android: pass diff --git a/objection/console/cli.py b/objection/console/cli.py index 612d1a9b..f9738d1e 100644 --- a/objection/console/cli.py +++ b/objection/console/cli.py @@ -1,21 +1,38 @@ -import os import threading import time +from pathlib import Path import click -import frida +from objection.commands.plugin_manager import load_plugin +from objection.utils.agent import Agent, AgentConfig +from objection.utils.helpers import debug_print, warn_about_older_operating_systems from .repl import Repl from ..__init__ import __version__ -from ..commands.device import get_device_info from ..commands.mobile_packages import patch_ios_ipa, patch_android_apk, sign_android_apk -from ..commands.plugin_manager import load_plugin from ..state.api import api_state from ..state.app import app_state from ..state.connection import state_connection -from ..utils.agent import Agent -from ..utils.helpers import normalize_gadget_name, print_frida_connection_help, warn_about_older_operating_systems, \ - debug_print + + +def get_agent() -> Agent: + """ get_agent bootstraps an agent instance """ + + agent = Agent(AgentConfig( + name=state_connection.name, + host=state_connection.host, + port=state_connection.port, + device_type=state_connection.device_type, + device_id=state_connection.device_id, + spawn=state_connection.spawn, + foremost=state_connection.foremost, + debugger=state_connection.debugger, + pause=not state_connection.no_pause + )) + + agent.run() + + return agent # Start the Click command group @@ -26,13 +43,17 @@ @click.option('--port', '-p', required=False, default=27042, show_default=True) @click.option('--api-host', '-ah', default='127.0.0.1', show_default=True) @click.option('--api-port', '-ap', required=False, default=8888, show_default=True) -@click.option('--gadget', '-g', required=False, default='Gadget', - help='Name of the Frida Gadget/Process to connect to.', show_default=True) +@click.option('--name', '-n', required=False, + help='Name or bundle identifier to attach to.', show_default=True) @click.option('--serial', '-S', required=False, default=None, help='A device serial to connect to.') @click.option('--debug', '-d', required=False, default=False, is_flag=True, - help='Enable debug mode with verbose output. (Includes agent source map in stack traces)') + help='Enable debug mode with verbose output.') +@click.option('--spawn', '-s', required=False, is_flag=True, help='Spawn the target.') +@click.option('--no-pause', '-p', required=False, is_flag=True, help='Resume the target immediately.') +@click.option('--foremost', '-f', required=False, is_flag=True, help='Use the current foremost application.') +@click.option('--debugger', required=False, default=False, is_flag=True, help='Enable the Chrome debug port.') def cli(network: bool, host: str, port: int, api_host: str, api_port: int, - gadget: str, serial: str, debug: bool) -> None: + name: str, serial: str, debug: bool, spawn: bool, no_pause: bool, foremost: bool, debugger: bool) -> None: """ \b _ _ _ _ @@ -43,28 +64,28 @@ def cli(network: bool, host: str, port: int, api_host: str, api_port: int, \b Runtime Mobile Exploration by: @leonjza from @sensepost - - By default, communications will happen over USB, unless the --network - option is provided. """ if debug: app_state.debug = debug - # disable the usb comms if network is chosen. if network: state_connection.use_network() state_connection.host = host state_connection.port = port if serial: - state_connection.device_serial = serial + state_connection.device_id = serial # set api parameters app_state.api_host = api_host app_state.api_port = api_port - state_connection.gadget_name = normalize_gadget_name(gadget_name=gadget) + state_connection.name = name + state_connection.spawn = spawn + state_connection.no_pause = no_pause + state_connection.foremost = foremost + state_connection.debugger = debugger @cli.command() @@ -73,27 +94,18 @@ def api(): Start the objection API server in headless mode. """ - agent = Agent() - - try: - agent.inject() - except frida.ServerNotRunningError as e: - click.secho('Unable to connect to the frida server: {error}'.format(error=str(e)), fg='red') - return - + agent = get_agent() state_connection.set_agent(agent=agent) - click.secho('Starting API server on {host}:{port}'.format( - host=app_state.api_host, port=app_state.api_port), fg='yellow', bold=True) - + click.secho(f'Starting API server on {app_state.api_host}:{app_state.api_port}', fg='yellow', bold=True) api_state.start(app_state.api_host, app_state.api_port, app_state.debug) @cli.command() +@click.option('--plugin-folder', '-P', required=False, default=None, help='The folder to load plugins from.') +@click.option('--quiet', '-q', required=False, default=False, is_flag=True) @click.option('--startup-command', '-s', required=False, multiple=True, help='A command to run before the repl polls the device for information.') -@click.option('--quiet', '-q', required=False, default=False, is_flag=True, - help='Do not display the objection logo on startup.') @click.option('--file-commands', '-c', required=False, type=click.File('r'), help=('A file containing objection commands, separated by a ' 'newline, that will run before the repl polls the device for information.')) @@ -101,109 +113,67 @@ def api(): help='A script to import and run before the repl polls the device for information.') @click.option('--enable-api', '-a', required=False, default=False, is_flag=True, help='Start the objection API server.') -@click.option('--plugin-folder', '-P', required=False, default=None, help='The folder to load plugins from.') -def explore(startup_command: str, quiet: bool, file_commands, startup_script: click.File, enable_api: bool, - plugin_folder: str) -> None: +def start(plugin_folder: str, quiet: bool, startup_command: str, file_commands, startup_script: click.File, + enable_api: bool) -> None: """ - Start the objection exploration REPL. + Start a new session """ - agent = Agent() - - try: - agent.inject() - except (frida.ServerNotRunningError, frida.NotSupportedError, frida.ProtocolError) as e: - click.secho('Unable to connect to the frida server: {error}'.format(error=str(e)), fg='red') - return - - # set the frida agent - state_connection.set_agent(agent=agent) + agent = get_agent() + state_connection.set_agent(agent) # load plugins if plugin_folder: - folder = os.path.abspath(plugin_folder) - debug_print('[plugin] Plugins path is: {0}'.format(folder)) - - for p in os.scandir(folder): - # skip files and hidden directories + folder = Path(plugin_folder).resolve() + debug_print(f'[plugin] Plugins path is: {folder}') + for p in folder.iterdir(): if p.is_file() or p.name.startswith('.'): - debug_print('[plugin] Skipping {0}'.format(p.name)) + debug_print(f'[plugin] Skipping {p.name}') continue - debug_print('[plugin] Attempting to load plugin at {0}'.format(p.path)) - load_plugin([p.path]) - - # start the main REPL - r = Repl() + debug_print(f'[plugin] Attempting to load plugin at {p}') + load_plugin([p]) - # if we have a command to run, do that first before - # the call to get_device_info(). - if startup_command: - for command in startup_command: - click.secho('Running a startup command... {0}'.format(command), dim=True) - r.run_command(command) + repl = Repl() - # If we have a script, import and run that asap if startup_script: - click.secho('Importing and running startup script at: {location}'.format(location=startup_script), dim=True) - response = agent.single(startup_script.read()) - print(response) - - try: - - # poll the device for information. this method also sets - # the device type internally in state.device - device_info = get_device_info() + click.secho(f'Importing and running startup script at: {startup_script}', dim=True) + agent.attach_script(startup_script.read()) - except (frida.TimedOutError, frida.ServerNotRunningError, - frida.ProcessNotFoundError, frida.NotSupportedError) as e: - - click.secho('Could not connect with error: {0}'.format(str(e)), fg='red') - print_frida_connection_help() - - return + if startup_command: + for command in startup_command: + click.secho(f'Running a startup command... {command}', dim=True) + repl.run_command(command) - # process commands from a resource file if file_commands: click.secho('Running commands from file...', bold=True) for command in file_commands.readlines(): - # clean up newlines command = command.strip() - - # do nothing for empty lines if command == '': continue # run the command using the instantiated repl - click.secho('Running: \'{0}\':\n'.format(command), dim=True) - r.run_command(command) + click.secho(f'Running: \'{command}\':\n', dim=True) + repl.run_command(command) warn_about_older_operating_systems() # start the api server if enable_api: def api_thread(): - """ - Small method to run Flash non-blocking - - :return: - """ - + """ Small method to run Flask non-blocking """ api_state.start(app_state.api_host, app_state.api_port) - click.secho('Starting API server on {host}:{port}'.format( - host=app_state.api_host, port=app_state.api_port), fg='yellow', bold=True) - + click.secho(f'Starting API server on {app_state.api_host}:{app_state.api_port}', fg='yellow', bold=True) thread = threading.Thread(target=api_thread) thread.daemon = True thread.start() time.sleep(2) - # run the REPL and wait for more commands - r.set_prompt_tokens(device_info) - r.start_repl(quiet=quiet) + # drop into the repl + repl.run(quiet=quiet) @cli.command() @@ -222,20 +192,9 @@ def run(hook_debug: bool, command: tuple) -> None: # specify if hooks should be debugged app_state.debug_hooks = hook_debug - # Inject the agent - agent = Agent() - agent.inject() + agent = get_agent() state_connection.set_agent(agent=agent) - try: - - click.secho('Determining environment...', dim=True) - get_device_info() - - except (frida.TimedOutError, frida.ServerNotRunningError) as e: - click.secho('Error: {0}'.format(e), fg='red') - return - command = ' '.join(command) # use the methods in the main REPL to run the command @@ -252,40 +211,6 @@ def version() -> None: click.secho('objection: {0}'.format(__version__)) -@cli.command() -def device_type(): - """ - Get information about an attached device. - """ - - try: - - # Inject the agent - agent = Agent() - agent.inject() - state_connection.set_agent(agent=agent) - - device_name, system_name, model, system_version = get_device_info() - - except frida.ProcessNotFoundError as e: - - click.secho('Could not connect with error: {0}'.format(str(e)), fg='red') - print_frida_connection_help() - - return - - if state_connection.get_comms_type() == state_connection.TYPE_USB: - click.secho('Connection: USB') - - elif state_connection.get_comms_type() == state_connection.TYPE_REMOTE: - click.secho('Connection: Network') - - click.secho('Name: {0}'.format(device_name)) - click.secho('System: {0}'.format(system_name)) - click.secho('Model: {0}'.format(model)) - click.secho('Version: {0}'.format(system_version)) - - @cli.command() @click.option('--source', '-s', help='The source IPA to patch', required=True) @click.option('--gadget-version', '-V', help=('The gadget version to use. If not ' diff --git a/objection/console/commands.py b/objection/console/commands.py index bfb9f92f..916afdf1 100644 --- a/objection/console/commands.py +++ b/objection/console/commands.py @@ -67,6 +67,11 @@ 'exec': None, # handled in the Repl class itself }, + 'resume': { + 'meta': 'Resume the attached process', + 'exec': None + }, + 'import': { 'meta': 'Import fridascript from a full path and run it', 'exec': frida_commands.load_background diff --git a/objection/console/repl.py b/objection/console/repl.py index b7bdd43f..ec77370c 100644 --- a/objection/console/repl.py +++ b/objection/console/repl.py @@ -11,11 +11,9 @@ from prompt_toolkit.history import FileHistory from prompt_toolkit.styles import Style -from objection.utils.agent import Agent from .commands import COMMANDS from .completer import CommandCompleter from ..__init__ import __version__ -from ..commands.device import get_device_info from ..state.app import app_state from ..state.connection import state_connection from ..utils.helpers import get_tokens @@ -28,7 +26,6 @@ class Repl(object): def __init__(self) -> None: self.cli = None - self.prompt_tokens = [] self.completer = FuzzyCompleter(CommandCompleter()) self.commands_repository = COMMANDS @@ -68,31 +65,16 @@ def get_prompt_style() -> Style: # Prompt. 'applicationname': '#007cff', + 'status': '#717171', 'on': '#00aa00', 'devicetype': '#00ff48', 'version': '#00ff48', + 'jobs': '', # TODO 'connection': '#717171' }) - def set_prompt_tokens(self, device_info: tuple) -> None: - """ - Set prompt tokens sourced from a command.device.device_info() - call. - - :param device_info: - :return: - """ - device_name, system_name, model, system_version = device_info - - self.prompt_tokens = [ - ('class:applicationname', device_name), - ('class:on', ' on '), - ('class:devicetype', '(' + model + ': '), - ('class:version', system_version + ') '), - ('class:connection', '[' + state_connection.get_comms_type_string() + '] # '), - ] - - def get_prompt_message(self) -> list: + @staticmethod + def get_prompt_message() -> list: """ Return prompt tokens to use in the cli app. @@ -102,15 +84,17 @@ def get_prompt_message(self) -> list: :return: """ - if self.prompt_tokens: - return self.prompt_tokens + agent = state_connection.agent + dev = state_connection.get_agent().device + params = dev.query_system_parameters() return [ - ('class:applicationname', 'unknown application'), - ('class:on', ''), - ('class:devicetype', ''), - ('class:version', ' '), - ('class:connection', '[' + state_connection.get_comms_type_string() + '] # '), + ('class:applicationname', state_connection.name), + ('class:status', ' (' + ('run' if agent.resumed else 'pause') + ')'), + ('class:on', ' on '), + ('class:devicetype', '(' + params['os']['name'] + ': '), + ('class:version', params['os']['version'] + ') '), + ('class:connection', '[' + dev.type + '] # '), ] def run_command(self, document: str) -> None: @@ -294,7 +278,8 @@ def _find_command_help(self, tokens: list) -> str: return user_help - def handle_reconnect(self, document: str) -> bool: + @staticmethod + def handle_reconnect(document: str) -> bool: """ Handles a reconnection attempt to a device. @@ -310,13 +295,13 @@ def handle_reconnect(self, document: str) -> bool: click.secho('Reconnecting...', dim=True) try: - state_connection.agent.unload() + # TODO + # state_connection.a.unload() + # + # agent = OldAgent() + # agent.inject() + # state_connection.a = agent - agent = Agent() - agent.inject() - state_connection.agent = agent - - self.set_prompt_tokens(get_device_info()) click.secho('Reconnection successful!', fg='green') except (frida.ServerNotRunningError, frida.TimedOutError) as e: @@ -326,7 +311,7 @@ def handle_reconnect(self, document: str) -> bool: return False - def start_repl(self, quiet: bool) -> None: + def run(self, quiet: bool) -> None: """ Start the objection repl. """ @@ -359,6 +344,11 @@ def start_repl(self, quiet: bool) -> None: click.secho('Exiting...', dim=True) break + if document.strip() in ('resume', 'res'): + click.secho('Resuming attached process', dim=True) + state_connection.agent.resume() + continue + # if we got the reconnect command, handle just that if self.handle_reconnect(document): continue diff --git a/objection/state/connection.py b/objection/state/connection.py index 99b57d73..4e2ee13b 100644 --- a/objection/state/connection.py +++ b/objection/state/connection.py @@ -1,23 +1,24 @@ class StateConnection(object): """ A class controlling the connection state of a device. """ - TYPE_USB = 0 - TYPE_REMOTE = 1 - def __init__(self) -> None: """ Init a new connection state, defaulting to a USB connection. """ - self.usb = True self.network = False - self.host = '127.0.0.1' - self.port = 27042 - self._type = self.TYPE_USB - self.device_serial = None + self.host = None + self.port = None + self.device_type = 'usb' + self.device_id = None + + self.spawn = False + self.no_pause = False + self.foremost = False + self.debugger = False - self.gadget_name = 'Gadget' + self.name = None self.agent = None self.api = None @@ -29,8 +30,7 @@ def use_usb(self) -> None: """ self.network = False - self.usb = True - self._type = self.TYPE_USB + self.device_type = 'usb' def use_network(self) -> None: """ @@ -40,8 +40,7 @@ def use_network(self) -> None: """ self.network = True - self.usb = False - self._type = self.TYPE_REMOTE + self.device_type = 'remote' def get_comms_type(self) -> int: """ @@ -50,25 +49,6 @@ def get_comms_type(self) -> int: :return: """ - return self._type - - def get_comms_type_string(self) -> str: - """ - Returns the currently configured connection type as a string. - - :return: - """ - - t = self.get_comms_type() - - if t == self.TYPE_USB: - return 'usb' - - if t == self.TYPE_REMOTE: - return 'net' - - return '' - def get_api(self): """ Return a Frida RPC API session @@ -99,7 +79,7 @@ def get_agent(self): return self.agent def __repr__(self) -> str: - return ' None: - self.device = None - self.frida_version = None - self.os_version = None + platform: Device + version: str + + def set_version(self, v: str): + """ + Set the running OS version + + :param v: + :return: + """ + + self.version = v + + def set_platform(self, t: Device): + """ + Set's the device type + + :param t: + :return: + """ + + self.platform = t def __repr__(self) -> str: - return ''.format(self.device, self.frida_version, - self.os_version) + return f'' device_state = DeviceState() diff --git a/objection/utils/agent.py b/objection/utils/agent.py index 54e18b43..39321484 100644 --- a/objection/utils/agent.py +++ b/objection/utils/agent.py @@ -1,45 +1,52 @@ +import argparse import atexit import json -from pprint import pprint +import sys +from dataclasses import dataclass from pathlib import Path +from pprint import pprint import click import frida -from frida.core import ScriptExports +from objection.state.app import app_state +from objection.state.connection import state_connection +from objection.state.device import device_state, Ios, Android from objection.state.jobs import job_manager_state -from ..state.app import app_state -from ..state.connection import state_connection -from ..utils.helpers import debug_print +from objection.utils.helpers import debug_print -class Agent(object): - """ Class to manage the lifecycle of the Frida agent. """ +@dataclass +class AgentConfig(object): + """ Default configuration for an Agent instance """ - def __init__(self): - """ - Initialises a new Agent instance to run the Frida agent. - """ + name: str + host: str = None + port: int = None + device_type: str = 'usb' + device_id: str = None + foremost: bool = False + spawn: bool = False + pause: bool = True + debugger: bool = False - self.agent_path = Path(__file__).parent.parent / 'agent.js' - debug_print('Agent path is: {path}'.format(path=self.agent_path)) - self.session = None - self.script = None +class OutputHandlers(object): + """ Output handlers for an Agent instance """ - self.device = None - self.spawned_pid = None - self.resumed = False + def device_output(self): + pass - atexit.register(self.cleanup) + def device_lost(self): + pass @staticmethod - def on_message(message: dict, data): + def session_on_detached(message: str, crash): """ - The callback to run when a message is received from the agent. + The callback to run for the detach signal :param message: - :param data: + :param crash: :return: """ @@ -53,33 +60,32 @@ def on_message(message: dict, data): click.secho('- [./incoming message] ' + '-' * 16, dim=True) # process the response - if message and 'payload' in message: - if len(message['payload']) > 0: - if isinstance(message['payload'], dict): - click.secho('(agent) ' + json.dumps(message['payload'])) - elif isinstance(message['payload'], str): - click.secho('(agent) ' + message['payload']) - else: - click.secho('Dumping unknown agent message', fg='yellow') - pprint(message['payload']) + if message: + click.secho('(session detach message) ' + message, fg='red') + + # Frida 12.3 crash reporting + # https://www.nowsecure.com/blog/2019/02/07/frida-12-3-debuts-new-crash-reporting-feature/ + if crash: + click.secho('(process crash report)', fg='red') + click.secho('\n\t{0}'.format(crash.report), dim=True) except Exception as e: - click.secho('Failed to process an incoming message from agent: {0}'.format(e), fg='red', bold=True) + click.secho('Failed to process an incoming message for a session detach signal: {0}'.format(e), fg='red', + bold=True) raise e @staticmethod - def on_detach(message: str, crash): + def script_on_message(message: dict, data): """ - The callback to run for the detach signal + The callback to run when a message is received from the agent. :param message: - :param crash: + :param data: :return: """ try: - # log the hook response if needed if app_state.should_debug(): click.secho('- [incoming message] ' + '-' * 18, dim=True) @@ -87,226 +93,276 @@ def on_detach(message: str, crash): click.secho('- [./incoming message] ' + '-' * 16, dim=True) # process the response - if message: - click.secho('(session detach message) ' + message, fg='red') - - # Frida 12.3 crash reporting - # https://www.nowsecure.com/blog/2019/02/07/frida-12-3-debuts-new-crash-reporting-feature/ - if crash: - click.secho('(process crash report)', fg='red') - click.secho('\n\t{0}'.format(crash.report), dim=True) + if message and 'payload' in message: + if len(message['payload']) > 0: + if isinstance(message['payload'], dict): + click.secho('(agent) ' + json.dumps(message['payload'])) + elif isinstance(message['payload'], str): + click.secho('(agent) ' + message['payload']) + else: + click.secho('Dumping unknown agent message', fg='yellow') + pprint(message['payload']) except Exception as e: - click.secho('Failed to process an incoming message for a session detach signal: {0}'.format(e), fg='red', - bold=True) + click.secho('Failed to process an incoming message from agent: {0}'.format(e), fg='red', bold=True) raise e - @staticmethod - def _get_device() -> frida.core.Device: - """ - Attempt to get a handle on a device. - :return: - """ +class Agent(object): + """ Class to manage the lifecycle of the objection Frida agent """ - if state_connection.get_comms_type() == state_connection.TYPE_USB: + agent_path: Path = None + c: AgentConfig - if state_connection.device_serial: - device = frida.get_device(state_connection.device_serial) - click.secho('Using USB device `{n}`'.format(n=device.name), bold=True) + handlers: OutputHandlers - return device + device: frida.core.Device = None + session: frida.core.Session = None + script: frida.core.Script = None - else: - device = frida.get_usb_device(5) - click.secho('Using USB device `{n}`'.format(n=device.name), bold=True) + pid: int = None + resumed: bool = True - return device + def __init__(self, config: AgentConfig): + """ initialises the agent class """ - if state_connection.get_comms_type() == state_connection.TYPE_REMOTE: - device = frida.get_device_manager().add_remote_device('{host}:{port}'.format( - host=state_connection.host, port=state_connection.port)) - click.secho('Using networked device @`{n}`'.format(n=device.name), bold=True) + self.agent_path = Path(__file__).parent.parent / 'agent.js' + if not self.agent_path.exists(): + raise Exception(f'Unable to locate Objection agent sources at: {self.agent_path}. ' + 'If this is a development install, check the wiki for more ' + 'information on building the agent.') + debug_print('Agent path is: {path}'.format(path=self.agent_path)) - return device + self.config = config + debug_print(f'agent config: {self.config}') + self.handlers = OutputHandlers() - raise Exception('Failed to find a device to attach to!') + atexit.register(self.teardown) - def get_session(self) -> frida.core.Session: + def _get_agent_source(self) -> str: """ - Attempt to get a Frida session on a device. + Loads the frida-compiled agent from disk. + + :return: """ - if self.session: - return self.session + with open(self.agent_path, 'r', encoding='utf-8') as f: + src = f.readlines() - self.device = self._get_device() + return ''.join([str(x) for x in src]) - # try and get the target process. - try: + def set_device(self): + """ + Set's the target device to work with. - debug_print('Attempting to attach to process: `{process}`'.format( - process=state_connection.gadget_name)) - self.session = self.device.attach(state_connection.gadget_name) - debug_print('Process attached!') - self.resumed = True + :return: + """ - self.session.on('detached', self.on_detach) + if self.config.device_id is not None: + self.device = frida.get_device(self.config.device_id) - return self.session + elif (self.config.host is not None) or (self.config.device_type == 'remote'): + if self.config.host is None: + self.device = frida.get_remote_device() + else: + host = self.config.host + port = self.config.port + self.device = frida.get_device_manager() \ + .add_remote_device(f'{host}:{port}' if host is not None else f'127.0.0.1:{port}') - except frida.ProcessNotFoundError: - debug_print('Unable to find process: `{process}`, attempting spawn'.format( - process=state_connection.gadget_name)) + elif self.config.device_type is not None: + for dev in frida.enumerate_devices(): + if dev.type == self.config.device_type: + self.device = dev + else: + self.device = frida.get_local_device() - # TODO: Handle the fact that gadget mode can't spawn + # surely we have a device by now? + if self.device is None: + raise Exception('Unable to find a device') - self.spawned_pid = self.device.spawn(state_connection.gadget_name) - debug_print('PID `{pid}` spawned, attaching...'.format(pid=self.spawned_pid)) + self.device.on('output', self.handlers.device_output) + self.device.on('lost', self.handlers.device_lost) - self.session = self.device.attach(self.spawned_pid) - return self.session + debug_print(f'device determined as: {self.device}') - def _get_agent_source(self) -> str: + def set_target_pid(self): """ - Loads the frida-compiled agent from disk. + Set's the PID we should attach to. This method will spawn the + target if needed. The resumed value is also toggled here. + + Defaults: + resumed: bool = True :return: """ - if not self.agent_path.exists(): - raise Exception('Unable to locate Objection agent sources at: {location}. ' - 'If this is a development install, check the wiki for more ' - 'information on building the agent.'.format(location=self.agent_path)) + if (self.config.name is None) and (not self.config.foremost): + raise Exception('Need a target name to spawn/attach to') - with open(self.agent_path, 'r', encoding='utf-8') as f: - agent = f.readlines() + if self.config.foremost: + try: + app = self.device.get_frontmost_application() + except Exception as e: + raise Exception(f'Could not get foremost application on {self.device.name}: {e}') + + if app is None: + raise Exception(f'No foremost application on {self.device.name}') + + self.pid = app.pid + # update the global state for the prompt etc. + state_connection.name = app.identifier - # If we are not in debug mode, strip the source map - if not app_state.should_debug(): - agent = agent[:-1] + elif self.config.spawn: + self.pid = self.device.spawn(self.config.name) + self.resumed = False + else: + # check if the name is actually an integer. this way we can + # assume we got the target PID already + try: + self.pid = int(self.config.name) + except ValueError: + pass - return ''.join([str(x) for x in agent]) + if self.pid is None: + # last resort, maybe we have a process name + self.pid = self.device.get_process(self.config.name).pid - def inject(self): + debug_print(f'process PID determined as {self.pid}') + + def attach(self): """ - Injects the Objection Agent. + Attaches to an enumerated PID, injecting the objection agent. :return: """ - debug_print('Injecting agent...') - - session = self.get_session() - self.script = session.create_script(source=self._get_agent_source()) - self.script.on('message', self.on_message) - self.script.load() - - if not self.resumed: - debug_print('Resuming PID `{pid}`'.format(pid=self.spawned_pid)) - self.device.resume(self.spawned_pid) + if self.pid is None: + raise Exception('A PID needs to be set before attach()') - # ping the agent - if not self.exports().ping(): - click.secho('Failed to ping the agent', fg='red') - raise Exception('Failed to communicate with agent') + self.session = self.device.attach(self.pid) + self.session.on('detached', self.handlers.session_on_detached) - click.secho('Agent injected and responds ok!', fg='green', dim=True) + if self.config.debugger: + self.session.enable_debugger() - return self + self.script = self.session.create_script(source=self._get_agent_source()) + self.script.on('message', self.handlers.script_on_message) + self.script.load() - def single(self, source: str, unload=True) -> list: + def attach_script(self, source): """ - Executes a single adhoc script, capturing the output and returning it. + Attaches an arbitrary script session. + + # TODO: Implement some script management so we could unload these later. :param source: - :param unload: :return: """ - message_buffer = [] - - def on_message(message: str, data): - """ - Simple message buffer helper. - - :param message: - :param data: - :return: - """ - - message_buffer.append(message) - - session = self.get_session() + session = self.device.attach(self.pid) script = session.create_script(source=source) - script.on('message', on_message) + script.on('message', self.handlers.script_on_message) script.load() - if not self.resumed: - debug_print('Resuming PID `{pid}`'.format(pid=self.spawned_pid)) - self.device.resume(self.spawned_pid) + def update_device_state(self): + """ + Updates the device_state. Useful in other parts where we + need platform specific decisions. - if unload: - script.unload() + :return: + """ + + params = self.device.query_system_parameters() + + # set os platform + if params['os']['id'] == 'ios': + device_state.set_platform(Ios) + elif params['os']['id'] == 'android': + device_state.set_platform(Android) - return message_buffer + # set os version + device_state.set_version(params['os']['version']) - def background(self, source: str): + def resume(self): """ - Executes an artibrary Frida script in the background, using the - default on_message handler for incoming messages from the script. + Resume the target pid. - :param source: :return: """ - debug_print('Loading a background script') + if self.resumed: + return - session = self.get_session() - script = session.create_script(source=source) - script.on('message', self.on_message) - script.load() + if not self.pid: + raise Exception('Cannot resume without self.pid') - if not self.resumed: - debug_print('Resuming PID `{pid}`'.format(pid=self.spawned_pid)) - self.device.resume(self.spawned_pid) + self.device.resume(self.pid) + self.resumed = True - debug_print('Background script loaded') - - def exports(self) -> frida.core.ScriptExports: + def exports(self): """ - Get the exports of the agent. + Returns the RPC exports exposed by the Frida agent :return: """ + if not self.script: + raise Exception('Need a script created before reading exports()') + return self.script.exports - def unload(self) -> None: + def run(self): """ - Run cleanup routines on an agent. + Run the Agent by getting a device, setting the target pid and attaching. + If we should skip pausing, also resume the process. :return: """ - if self.script: - debug_print('Calling unload()') - self.script.unload() + self.set_device() + self.set_target_pid() + self.attach() - def cleanup(self) -> None: - """ - Cleanup an Agent + # internal state + self.update_device_state() - :return: - """ + if not self.config.pause: + debug_print('asked to run without pausing, so resuming in run()') + self.resume() + def teardown(self): try: - if self.script: click.secho('Asking jobs to stop...', dim=True) job_manager_state.cleanup() click.secho('Unloading objection agent...', dim=True) - self.unload() - + self.script.unload() except frida.InvalidOperationError as e: - click.secho('Unable to run cleanups: {error}'.format(error=str(e)), fg='yellow', dim=True) + click.secho(f'Unable to run cleanups: {e}', fg='yellow', dim=True) + + +if __name__ == '__main__': + + parser = argparse.ArgumentParser() + parser.add_argument('name', help='target app to attach/spawn. needs to be bundle ' + 'identifier for spawn') + parser.add_argument('--no-spawn', dest='no_spawn', default=True, action='store_false', + help='do not try and spawn the target app') + parser.add_argument('--no-pause', dest='no_pause', default=True, action='store_false', + help='resume the app after spawning') + parser.add_argument('--debug', default=False, action='store_true', help='print debug logging') + args = parser.parse_args() + + if args.name is None: + print('error: need a target app to attach/spawn') + sys.exit(1) + + if args.debug: + app_state.debug = True + + c = AgentConfig(name=args.name, spawn=args.no_spawn, pause=args.no_pause) + a = Agent(config=c) + a.run() + + print(a.exports().env_frida()) diff --git a/objection/utils/helpers.py b/objection/utils/helpers.py index d62b1772..79995e87 100644 --- a/objection/utils/helpers.py +++ b/objection/utils/helpers.py @@ -96,26 +96,6 @@ def get_tokens(text: str) -> list: return tokens -def normalize_gadget_name(gadget_name: str): - """ - Takes a string input and converts it into an integer - if possible. This helps the attach() process in the Frida - API determine if it should be attaching to a process name or a PID. - - :param gadget_name: - :return: - """ - - try: - - gadget_name = int(gadget_name) - - except ValueError: - pass - - return gadget_name - - def clean_argument_flags(args: list) -> list: """ Returns a list of arguments with flags removed. @@ -174,17 +154,17 @@ def warn_about_older_operating_systems() -> None: ios_supported = '9' # android & ios version warnings - if device_state.device_type == Android and ( - parse_version(device_state.os_version) < parse_version(android_supported)): + if device_state.platform == Android and ( + parse_version(device_state.version) < parse_version(android_supported)): click.secho('Warning: You appear to be running Android {0} which may result in ' 'some hooks failing.\nIt is recommended to use at least an Android ' - 'version {1} device with objection.'.format(device_state.os_version, android_supported), + 'version {1} device with objection.'.format(device_state.version, android_supported), fg='yellow') # android & ios version warnings - if device_state.device_type == Ios and ( - parse_version(device_state.os_version) < parse_version(ios_supported)): + if device_state.platform == Ios and ( + parse_version(device_state.version) < parse_version(ios_supported)): click.secho('Warning: You appear to be running iOS {0} which may result in ' 'some hooks failing.\nIt is recommended to use at least an iOS ' - 'version {1} device with objection.'.format(device_state.os_version, ios_supported), + 'version {1} device with objection.'.format(device_state.version, ios_supported), fg='yellow') diff --git a/objection/utils/plugin.py b/objection/utils/plugin.py index 6b062cae..7f149f3c 100644 --- a/objection/utils/plugin.py +++ b/objection/utils/plugin.py @@ -87,15 +87,13 @@ def inject(self) -> None: if not self.agent: self.agent = state_connection.get_agent() - if not self.session: - self.session = self.agent.get_session() - - if not self.script: - self.script = self.session.create_script(source=self.script_src) + self.session = self.agent.device.attach(self.agent.pid) + self.script = self.session.create_script(source=self.script_src) # check for a custom message handler, otherwise fallback # to the default objection handler - self.script.on('message', self.on_message_handler if self.on_message_handler else self.agent.on_message) + self.script.on('message', + self.on_message_handler if self.on_message_handler else self.agent.handlers.script_on_message) self.script.load() self.api = self.script.exports diff --git a/tests/commands/ios/test_plist.py b/tests/commands/ios/test_plist.py index 122082ff..af917d25 100644 --- a/tests/commands/ios/test_plist.py +++ b/tests/commands/ios/test_plist.py @@ -28,7 +28,7 @@ def test_cat_with_relative(self, mock_file_manager, mock_api): mock_file_manager.pwd.return_value = '/baz' mock_api.return_value.ios_plist_read.return_value = 'foobar' - device_state.device_type = Ios + device_state.platform = Ios with capture(cat, ['foo']) as o: output = o diff --git a/tests/commands/test_device.py b/tests/commands/test_device.py index 8dc98308..16769219 100644 --- a/tests/commands/test_device.py +++ b/tests/commands/test_device.py @@ -34,7 +34,7 @@ def test_gets_android_device_info(self, mock_api): @mock.patch('objection.commands.device._get_ios_environment') @mock.patch('objection.commands.device.device_state') def test_gets_environment_and_calls_ios_platform_specific_method(self, mock_device_state, mock_ios_environment): - type(mock_device_state).device_type = mock.PropertyMock(return_value=Ios) + type(mock_device_state).platform = mock.PropertyMock(return_value=Ios) get_environment() @@ -44,7 +44,7 @@ def test_gets_environment_and_calls_ios_platform_specific_method(self, mock_devi @mock.patch('objection.commands.device.device_state') def test_gets_environment_and_calls_android_platform_specific_method(self, mock_device_state, mock_android_environment): - type(mock_device_state).device_type = mock.PropertyMock(return_value=Android) + type(mock_device_state).platform = mock.PropertyMock(return_value=Android) get_environment() diff --git a/tests/commands/test_filemanager.py b/tests/commands/test_filemanager.py index e43a29ca..b186a89f 100644 --- a/tests/commands/test_filemanager.py +++ b/tests/commands/test_filemanager.py @@ -48,7 +48,7 @@ def test_cd_to_absoluate_ios_path(self, mock_path_exists_ios): mock_path_exists_ios.return_value = True file_manager_state.cwd = '/foo' - device_state.device_type = Ios + device_state.platform = Ios with capture(cd, ['/foo/bar/baz']) as o: output = o @@ -61,7 +61,7 @@ def test_cd_to_absoluate_android_path(self, mock_path_exists_android): mock_path_exists_android.return_value = True file_manager_state.cwd = '/foo' - device_state.device_type = Android + device_state.platform = Android with capture(cd, ['/foo/bar/baz']) as o: output = o @@ -74,7 +74,7 @@ def test_cd_to_absoluate_ios_path_that_does_not_exist(self, mock_path_exists_ios mock_path_exists_ios.return_value = False file_manager_state.cwd = '/foo' - device_state.device_type = Ios + device_state.platform = Ios with capture(cd, ['/foo/bar/baz']) as o: output = o @@ -87,7 +87,7 @@ def test_cd_to_relative_path_ios(self, mock_path_exists_ios): mock_path_exists_ios.return_value = True file_manager_state.cwd = '/foo' - device_state.device_type = Ios + device_state.platform = Ios with capture(cd, ['bar']) as o: output = o @@ -100,7 +100,7 @@ def test_cd_to_relative_path_android(self, mock_path_exists_android): mock_path_exists_android.return_value = True file_manager_state.cwd = '/foo' - device_state.device_type = Android + device_state.platform = Android with capture(cd, ['bar']) as o: output = o @@ -113,7 +113,7 @@ def test_cd_to_relative_path_ios_that_does_not_exist(self, mock_path_exists_ios) mock_path_exists_ios.return_value = False file_manager_state.cwd = '/foo' - device_state.device_type = Ios + device_state.platform = Ios with capture(cd, ['bar']) as o: output = o @@ -138,7 +138,7 @@ def test_rm_dispatcher_validates_arguments(self): @mock.patch('objection.commands.filemanager.click.confirm') @mock.patch('objection.commands.filemanager._rm_android') def test_rm_dispatcher_confirms_before_delete(self, mock_android_rm, mock_confirm): - device_state.device_type = Android + device_state.platform = Android file_manager_state.cwd = '/foo' mock_confirm.return_value = False @@ -153,7 +153,7 @@ def test_rm_dispatcher_confirms_before_delete(self, mock_android_rm, mock_confir @mock.patch('objection.commands.filemanager.click.confirm') @mock.patch('objection.commands.filemanager._rm_android') def test_rm_dispatcher_calls_android_rm_helper(self, mock_android_rm, mock_confirm): - device_state.device_type = Android + device_state.platform = Android mock_android_rm.return_value = True mock_confirm.return_value = True @@ -188,7 +188,7 @@ def test_returns_current_directory_via_helper_when_already_set(self): @mock.patch('objection.commands.filemanager._pwd_ios') def test_returns_current_directory_via_helper_for_ios(self, mock_pwd_ios): mock_pwd_ios.return_value = '/foo/bar' - device_state.device_type = Ios + device_state.platform = Ios self.assertEqual(pwd(), '/foo/bar') self.assertTrue(mock_pwd_ios.called) @@ -196,7 +196,7 @@ def test_returns_current_directory_via_helper_for_ios(self, mock_pwd_ios): @mock.patch('objection.commands.filemanager._pwd_android') def test_returns_current_directory_via_helper_for_android(self, mock_pwd_android): mock_pwd_android.return_value = '/foo/bar' - device_state.device_type = Android + device_state.platform = Android self.assertEqual(pwd(), '/foo/bar') self.assertTrue(mock_pwd_android.called) @@ -226,7 +226,7 @@ def test_get_android_pwd_via_helper(self, mock_api): @mock.patch('objection.commands.filemanager.pwd') @mock.patch('objection.commands.filemanager._ls_ios') def test_ls_gets_pwd_from_helper_with_no_argument(self, _, mock_pwd): - device_state.device_type = Ios + device_state.platform = Ios ls([]) @@ -234,7 +234,7 @@ def test_ls_gets_pwd_from_helper_with_no_argument(self, _, mock_pwd): @mock.patch('objection.commands.filemanager._ls_ios') def test_ls_calls_ios_helper_method(self, mock_ls_ios): - device_state.device_type = Ios + device_state.platform = Ios ls(['/foo/bar']) @@ -242,7 +242,7 @@ def test_ls_calls_ios_helper_method(self, mock_ls_ios): @mock.patch('objection.commands.filemanager._ls_android') def test_ls_calls_android_helper_method(self, mock_ls_android): - device_state.device_type = Android + device_state.platform = Android ls(['/foo/bar']) @@ -384,7 +384,7 @@ def test_download_platform_proxy_validates_arguments(self): @mock.patch('objection.commands.filemanager._download_ios') def test_download_platform_proxy_calls_ios_method(self, mock_download_ios): - device_state.device_type = Ios + device_state.platform = Ios download(['/foo', '/bar']) @@ -392,7 +392,7 @@ def test_download_platform_proxy_calls_ios_method(self, mock_download_ios): @mock.patch('objection.commands.filemanager._download_android') def test_download_platform_proxy_calls_android_method(self, mock_download_android): - device_state.device_type = Android + device_state.platform = Android download(['/foo', '/bar']) @@ -494,7 +494,7 @@ def test_file_upload_method_proxy_validates_arguments(self): @mock.patch('objection.commands.filemanager._upload_ios') def test_file_upload_method_proxy_calls_ios_helper_method(self, mock_upload_ios): - device_state.device_type = Ios + device_state.platform = Ios upload(['/foo', '/bar']) @@ -502,7 +502,7 @@ def test_file_upload_method_proxy_calls_ios_helper_method(self, mock_upload_ios) @mock.patch('objection.commands.filemanager._upload_android') def test_file_upload_method_proxy_calls_android_helper_method(self, mock_upload_android): - device_state.device_type = Android + device_state.platform = Android upload(['/foo', '/bar']) diff --git a/tests/commands/test_ui.py b/tests/commands/test_ui.py index e7686570..574a7821 100644 --- a/tests/commands/test_ui.py +++ b/tests/commands/test_ui.py @@ -9,11 +9,11 @@ class TestUI(unittest.TestCase): def tearDown(self): - device_state.device_type = None + device_state.platform = None @mock.patch('objection.commands.ui._alert_ios') def test_alert_helper_method_proxy_calls_ios(self, mock_alert_ios): - device_state.device_type = Ios() + device_state.platform = Ios() alert([]) @@ -21,7 +21,7 @@ def test_alert_helper_method_proxy_calls_ios(self, mock_alert_ios): @mock.patch('objection.commands.ui._alert_ios') def test_alert_helper_method_proxy_calls_ios_custom_message(self, mock_alert_ios): - device_state.device_type = Ios() + device_state.platform = Ios() alert(['foo']) diff --git a/tests/console/test_repl.py b/tests/console/test_repl.py index 856d4222..8ff825ed 100644 --- a/tests/console/test_repl.py +++ b/tests/console/test_repl.py @@ -171,7 +171,7 @@ def test_starts_repl_and_exists_cleanly_with_banner(self): self.repl.session = MagicMock(name='session') self.repl.session.prompt.return_value = 'exit' - with capture(self.repl.start_repl, False) as o: + with capture(self.repl.run, False) as o: output = o expected_output = (""" @@ -193,7 +193,7 @@ def test_starts_repl_and_exists_cleanly_with_banner_and_quiet_flag(self): self.repl.session = MagicMock(name='session') self.repl.session.prompt.return_value = 'exit' - with capture(self.repl.start_repl, True) as o: + with capture(self.repl.run, True) as o: output = o expected_output = 'Exiting...\n' diff --git a/tests/state/test_device.py b/tests/state/test_device.py index 52fc8833..3939a74e 100644 --- a/tests/state/test_device.py +++ b/tests/state/test_device.py @@ -7,6 +7,6 @@ class TestDevice(unittest.TestCase): def test_device_representation(self): device_state.device = Ios device_state.frida_version = '10.6.1' - device_state.os_version = '1' + device_state.version = '1' self.assertTrue('Frida Version:10.6.1 OS Version: 1' in repr(device_state) and 'Ios' in repr(device_state)) diff --git a/tests/utils/test_agent.py b/tests/utils/test_agent.py index 7d352503..2084dd5c 100644 --- a/tests/utils/test_agent.py +++ b/tests/utils/test_agent.py @@ -2,7 +2,7 @@ from pathlib import Path from unittest import mock -from objection.utils.agent import Agent +from objection.utils.agent_old import OldAgent class TestAgent(unittest.TestCase): @@ -16,7 +16,7 @@ def setUp(self): def test_agent_loads_from_disk_successfully_without_debug(self, mock_app_state): mock_app_state.should_debug.return_value = False - agent = Agent() + agent = OldAgent() source = agent._get_agent_source() self.assertTrue(mock_app_state.should_debug.called) @@ -26,7 +26,7 @@ def test_agent_loads_from_disk_successfully_without_debug(self, mock_app_state): def test_agent_loads_from_disk_successfully_with_debug(self, mock_app_state): mock_app_state.should_debug.return_value = True - agent = Agent() + agent = OldAgent() source = agent._get_agent_source() self.assertTrue(mock_app_state.should_debug.called) @@ -35,6 +35,6 @@ def test_agent_loads_from_disk_successfully_with_debug(self, mock_app_state): def test_agent_fails_to_load_throws_error(self): with self.assertRaises(Exception) as _: - agent = Agent() + agent = OldAgent() with mock.patch(agent, 'exists', False): agent._get_agent_source() diff --git a/tests/utils/test_helpers.py b/tests/utils/test_helpers.py index 4c1030b7..1a0fcea3 100644 --- a/tests/utils/test_helpers.py +++ b/tests/utils/test_helpers.py @@ -77,7 +77,7 @@ def test_prints_frida_connection_help(self): self.assertEqual(output, expected_output) def test_warns_about_operating_system_versions(self): - device_state.device_type = Ios + device_state.platform = Ios with capture(warn_about_older_operating_systems) as o: output = o