From 7eeb533c25f711d7951d534f352cc26c40f40d2d Mon Sep 17 00:00:00 2001 From: 3rm-z Date: Mon, 2 Mar 2026 10:41:30 +0100 Subject: [PATCH 1/2] Implement reconnect command. --- objection/console/repl.py | 37 ++++++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/objection/console/repl.py b/objection/console/repl.py index 7f16da7b..076b4db0 100644 --- a/objection/console/repl.py +++ b/objection/console/repl.py @@ -292,23 +292,30 @@ def handle_reconnect(document: str) -> bool: """ if document.strip() in ('reconnect', 'reset'): - - click.secho('Reconnecting...', dim=True) + click.secho('Performing soft-restart...', fg='yellow') try: - # TODO - # state_connection.a.unload() - # - # agent = OldAgent() - # agent.inject() - # state_connection.a = agent - - click.secho('Not yet implemented!', fg='yellow') - - except (frida.ServerNotRunningError, frida.TimedOutError) as e: - click.secho('Failed to reconnect with error: {0}'.format(e), fg='red') - - return True + from objection.state.connection import state_connection + from objection.console.cli import get_agent + + if state_connection.agent: + state_connection.agent.jobs = [] # Empty cleanup of jobs to avoid loop on exit + + state_connection.agent = None + state_connection.session = None + + click.secho(f'Re-attaching to {state_connection.name}...', dim=True) + + new_agent = get_agent() + state_connection.agent = new_agent + + click.secho('Reconnection successful!', fg='green') + + except Exception as e: + click.secho(f'Reconnection failed: {e}', fg='red') + click.secho('Ensure the application is running and the device is connected.', dim=True) + + return True return False From 391b3c924ede451143d476dbc2549036670b3f83 Mon Sep 17 00:00:00 2001 From: Megladon <30530996+IPMegladon@users.noreply.github.com> Date: Wed, 11 Mar 2026 20:42:55 +0200 Subject: [PATCH 2/2] (feat) Added reconnect_spawn and some stability. Co-authored-by: Shlomo isaacs <59068386+panguin6010@users.noreply.github.com> --- objection/console/commands.py | 5 +++- objection/console/repl.py | 43 +++++++++++++++++++++++++++-------- objection/utils/agent.py | 21 ++++++++++++++--- 3 files changed, 55 insertions(+), 14 deletions(-) diff --git a/objection/console/commands.py b/objection/console/commands.py index 51885422..6a773ddb 100644 --- a/objection/console/commands.py +++ b/objection/console/commands.py @@ -62,10 +62,13 @@ }, 'reconnect': { - 'meta': 'Reconnect to the current device', + 'meta': 'Reconnect to the current app', 'exec': None, # handled in the Repl class itself }, + 'reconnect_spawn': { + 'meta': 'Respawn the current app', + 'resume': { 'meta': 'Resume the attached process', 'exec': None diff --git a/objection/console/repl.py b/objection/console/repl.py index 076b4db0..005c4c1f 100644 --- a/objection/console/repl.py +++ b/objection/console/repl.py @@ -291,21 +291,44 @@ def handle_reconnect(document: str) -> bool: :return: """ - if document.strip() in ('reconnect', 'reset'): - click.secho('Performing soft-restart...', fg='yellow') - + if document.strip() in ('reconnect', 'reset', 'reconnect_spawn'): try: - from objection.state.connection import state_connection - from objection.console.cli import get_agent - - if state_connection.agent: - state_connection.agent.jobs = [] # Empty cleanup of jobs to avoid loop on exit + from .cli import get_agent + + reconnect_spawn = document.strip() == 'reconnect_spawn' + if reconnect_spawn: + click.secho('Performing full-restart...', fg='yellow') + state_connection.spawn = True + state_connection.no_pause = True + else: + click.secho('Performing soft-restart...', fg='yellow') + state_connection.spawn = False + + curr_agent = state_connection.agent + + # Cleanup current agent (ignore errors if already destroyed) + click.secho('Unloading current agent...', dim=True) + try: + if curr_agent.script: + curr_agent.script.unload() + + except (frida.InvalidOperationError, Exception): + pass # Script already destroyed or detached + + try: + if curr_agent.session: + curr_agent.session.detach() + except (frida.InvalidOperationError, Exception): + pass # Session already detached + # Need to clear because destructor will attempt to clear script/session again. + curr_agent.script = None state_connection.agent = None state_connection.session = None click.secho(f'Re-attaching to {state_connection.name}...', dim=True) - + + # Try respawn the agent. new_agent = get_agent() state_connection.agent = new_agent @@ -315,7 +338,7 @@ def handle_reconnect(document: str) -> bool: click.secho(f'Reconnection failed: {e}', fg='red') click.secho('Ensure the application is running and the device is connected.', dim=True) - return True + return True return False diff --git a/objection/utils/agent.py b/objection/utils/agent.py index 431d266b..5cda4883 100644 --- a/objection/utils/agent.py +++ b/objection/utils/agent.py @@ -223,7 +223,21 @@ def set_target_pid(self): raise Exception('--uid flag can only be used on Android.') self.pid = self.device.spawn(self.config.name, uid=int(self.config.uid)) else: - self.pid = self.device.spawn(self.config.name) + try: + self.pid = self.device.spawn(self.config.name) + except frida.InvalidArgumentError: + pass + + # Maybe we have an app name and not identifier + app_list = self.device.enumerate_applications() + app_name_lc = self.config.name.lower() + + matching_app = [app for app in app_list if app.name.lower() == app_name_lc] + # Don't care about matching_app[0].pid not in (0, None), if already running we restart anyway. + if len(matching_app) == 1: + debug_print("Found app by name instead of package, spawning.") + self.pid = self.device.spawn(matching_app[0].identifier) + self.resumed = False else: # check if the name is actually an integer. this way we can @@ -241,11 +255,12 @@ def set_target_pid(self): pass if self.pid is None: - # maybe we have an app identifier + # maybe we have an app identifier/package name app_list = self.device.enumerate_applications() app_name_lc = self.config.name.lower() matching_app = [app for app in app_list if app.identifier.lower() == app_name_lc] - if len(matching_app) == 1 and matching_app[0].pid is not None: + if len(matching_app) == 1 and matching_app[0].pid not in (0, None): + debug_print("Found app by package name.") self.pid = matching_app[0].pid elif len(matching_app) > 1: app_list_str = ', '.join([f"{app.identifier}: {app.pid}" for app in matching_app])