def var_class(args: list) -> None: search = str(search) if len(search) == 0: click.secho('Usage: android hooking search classes <name>', bold=True) return runner = FridaRunner() runner.set_hook_with_data(android_hook('hooking/search-class'), search=search) runner.run() response = runner.get_last_message() if not response.is_successful(): click.secho('Failed to search for classes with error: {0}'.format( response.error_reason), fg='red') return None if response.data: # dump the classes to screen for classname in response.data: click.secho(classname) click.secho('\nFound {0} classes'.format(len(response.data)), bold=True) else: click.secho('No classes found')
def search_method(args: list) -> None: """ Search for Objective-C methods by name. :param args: :return: """ if len(clean_argument_flags(args)) < 1: click.secho('Usage: ios hooking search methods <name>', bold=True) return search = args[0] runner = FridaRunner() runner.set_hook_with_data(ios_hook('hooking/search-method'), search=search) runner.run() response = runner.get_last_message() if not response.is_successful(): click.secho('Failed to search for methods with error: {0}'.format(response.error_reason), fg='red') return None if response.data: # dump the methods to screen for method in response.data: click.secho(method) click.secho('\nFound {0} methods'.format(len(response.data)), bold=True) else: click.secho('No methods found')
def show_android_class_methods(args: list = None) -> None: """ Shows the methods available on an Android class. :param args: :return: """ if len(clean_argument_flags(args)) <= 0: click.secho('Usage: android hooking list class_methods <class name>', bold=True) return class_name = args[0] runner = FridaRunner() runner.set_hook_with_data(android_hook('hooking/list-class-methods'), class_name=class_name) runner.run() response = runner.get_last_message() if not response.is_successful(): click.secho('Failed to list class methods with error: {0}'.format( response.error_reason), fg='red') return None # print the enumerated classes for class_name in sorted(response.data): click.secho(class_name) click.secho('\nFound {0} method(s)'.format(len(response.data)), bold=True)
def search_class(args: list) -> None: """ Searching for Objective-C classes in the current application by name. :param args: :return: """ if len(clean_argument_flags(args)) < 1: click.secho('Usage: ios hooking search classes <name>', bold=True) return search = args[0] runner = FridaRunner() runner.set_hook_with_data(ios_hook('hooking/search-class'), search=search) runner.run() response = runner.get_last_message() if not response.is_successful(): click.secho('Failed to search for classes with error: {0}'.format(response.error_reason), fg='red') return None if response.data: # dump the classes to screen for classname in response.data: click.secho(classname) click.secho('\nFound {0} classes'.format(len(response.data)), bold=True) else: click.secho('No classes found')
def watch_class_method(args: list) -> None: """ Watches for invocations of an Android Java class method. All overloads are watched. :param args: :return: """ if len(args) < 2: click.secho( ('Usage: android hooking watch class_method <class> <method>' ' (eg: com.example.test dologin)'), bold=True) return target_class = args[0] target_method = args[1] runner = FridaRunner() runner.set_hook_with_data(android_hook('hooking/watch-method'), target_class=target_class, target_method=target_method) runner.run_as_job(name='watch-java-method')
def watch_class_method(args: list) -> None: """ Starts an objection jon that hooks into a specific class method and reports on invocations. :param args: :return: """ if len(args) <= 0: click.secho(( 'Usage: ios hooking watch method <selector>' ' (eg: -[ClassName methodName:]) (optional: --include-backtrace)'), bold=True) return selector = args[0] runner = FridaRunner() runner.set_hook_with_data( ios_hook('hooking/watch-method'), selector=selector, include_backtrace=_should_include_backtrace(args)) runner.run_as_job(name='watch-method')
def execute(args: list) -> None: """ Runs a shell command on an Android device. :param args: :return: """ command = ' '.join(args) click.secho('Running command: {0}\n'.format(command), dim=True) runner = FridaRunner() runner.set_hook_with_data(android_hook('command/exec'), command=command) runner.run() response = runner.get_last_message() if not response.is_successful(): click.secho('Failed to run command with error: {0}'.format( response.error_reason), fg='red') return if response.stdout: click.secho(response.stdout, bold=True) if response.stderr: click.secho(response.stderr, bold=True, fg='red')
def watch_class_method(args: list) -> None: """ Watches for invocations of an Android Java class method. All overloads for the same method are also watched. Optionally, this method will dump the watched methods arguments, backtrace as well as return value. :param args: :return: """ if len(clean_argument_flags(args)) < 2: click.secho( ('Usage: android hooking watch class_method <class> <method> ' '(eg: com.example.test dologin) ' '(optional: --dump-args) ' '(optional: --dump-backtrace) ' '(optional: --dump-return)'), bold=True) return target_class = args[0] target_method = args[1] runner = FridaRunner() runner.set_hook_with_data(android_hook('hooking/watch-method'), target_class=target_class, target_method=target_method, dump_args=_should_dump_args(args), dump_return=_should_dump_return_value(args), dump_backtrace=_should_dump_backtrace(args)) runner.run_as_job(name='watch-java-method', args=args)
def watch_class_methods_var_returns(args: list) -> None: """ Starts an objection jon that hooks into a specific all classes and methods and reports on invocations when specifi args and return values reached. :param args: :return: """ if len(clean_argument_flags(args)) <= 0: click.secho(('Usage: ios hooking watch var_and_returns <classPattern> <methodPattern> <argsPattern> <returnPattern> (eg: Controller login [email protected] false) ' '(optional: --dump-backtrace) ' '(optional: --dump-args) ' '(optional: --dump-return)'), bold=True) return classes_Pattern = args[0] methods_Pattern = args[1] args_Pattern = args[2] returns_Pattern = args[3] runner = FridaRunner() runner.set_hook_with_data(ios_hook('hooking/watch-class-methods-var-returns'), classes_Pattern=classes_Pattern, methods_Pattern=methods_Pattern, args_Pattern=args_Pattern, returns_Pattern=returns_Pattern, dump_backtrace=_should_dump_backtrace(args), dump_args=_should_dump_args(args), dump_return=_should_dump_return_value(args)) runner.run_as_job(name='watch-class-methods-var-returns', args=args)
def cat(args: list = None) -> None: """ Parses a plist on an iOS device and echoes it in a more human readable way. :param args: :return: """ if len(args) <= 0: click.secho('Usage: ios plist cat <remote_plist>', bold=True) return plist = args[0] if not os.path.isabs(plist): pwd = filemanager.pwd() plist = os.path.join(pwd, plist) runner = FridaRunner() runner.set_hook_with_data(ios_hook('plist/get'), plist=plist) runner.run() response = runner.get_last_message() if not response.is_successful(): click.secho('Failed to get plist with error: {0}'.format( response.error_reason), fg='red') return click.secho(response.data, bold=True)
def search_class(args: list) -> None: """ Searches the current Android application for instances of a class. :param args: :return: """ if len(args) < 1: click.secho('Usage: android hooking search classes <name>', bold=True) return search = args[0] runner = FridaRunner() runner.set_hook_with_data(android_hook('hooking/search-class'), search=search) runner.run() response = runner.get_last_message() if not response.is_successful(): click.secho('Failed to search for classes with error: {0}'.format(response.error_reason), fg='red') return None if response.data: # dump the classes to screen for classname in response.data: click.secho(classname) click.secho('\nFound {0} classes'.format(len(response.data)), bold=True) else: click.secho('No classes found')
def watch_class_method(args: list) -> None: """ Starts an objection jon that hooks into a specific class method and reports on invocations. :param args: :return: """ if len(clean_argument_flags(args)) <= 0: click.secho(('Usage: ios hooking watch method <selector> (eg: -[ClassName methodName:]) ' '(optional: --dump-backtrace) ' '(optional: --dump-args) ' '(optional: --dump-return)'), bold=True) return selector = args[0] argument_count = selector.count(':') runner = FridaRunner() runner.set_hook_with_data(ios_hook('hooking/watch-method'), selector=selector, argument_count=argument_count, dump_backtrace=_should_dump_backtrace(args), dump_args=_should_dump_args(args), dump_return=_should_dump_return_value(args)) runner.run_as_job(name='watch-method', args=args)
def set_method_return_value(args: list = None) -> None: """ Sets a Java methods return value to a specified boolean. :param args: :return: """ if len(clean_argument_flags(args)) < 2: click.secho( ('Usage: android hooking set return_value ' '"<fully qualified class>" (eg: "com.example.test") ' '"<method (with overload if needed)>" (eg: see help for details) ' '<true/false>'), bold=True) return class_name = args[0] method_name = args[1].replace('\'', '"') # fun! retval = args[2] runner = FridaRunner() runner.set_hook_with_data(android_hook('hooking/set-return'), class_name=class_name, method_name=method_name, retval=retval) runner.run_as_job(name='set-return-value', args=args)
def watch_class(args: list) -> None: """ Watches for invocations of all methods in an Android Java class. All overloads for methods found are also watched. :param args: :return: """ if len(clean_argument_flags(args)) < 1: click.secho( 'Usage: android hooking watch class <class> ' '(eg: com.example.test) ' '(optional: --dump-args) ' '(optional: --dump-backtrace) ' '(optional: --dump-return)', bold=True) return target_class = args[0] runner = FridaRunner() runner.set_hook_with_data(android_hook('hooking/watch-class-methods'), target_class=target_class, dump_args=_should_dump_args(args), dump_return=_should_dump_return_value(args), dump_backtrace=_should_dump_backtrace(args)) runner.run_as_job(name='watch-java-class', args=args)
def add(args: list) -> None: """ Adds a new keychain entry to the keychain :param args: :return: """ if not _has_minimum_flags_to_add_item(args): click.secho( 'Usage: ios keychain add --key <key name> --data <entry data>', bold=True) return key = _get_flag_value(args, '--key') value = _get_flag_value(args, '--data') click.secho('Adding a new entry to the iOS keychain...', dim=True) click.secho('Key: {0}'.format(key), dim=True) click.secho('Value: {0}'.format(value), dim=True) runner = FridaRunner() runner.set_hook_with_data(ios_hook('keychain/add')) api = runner.rpc_exports() if api.add(key, value): click.secho('Successfully added the keychain item', fg='green') return click.secho('Failed to add the keychain item', fg='red')
def show_ios_class_methods(args: list) -> None: """ Displays the methods available in a class. :param args: :return: """ if len(args) <= 0: click.secho('Usage: ios hooking list class_methods <class name> (--include-parents)', bold=True) return classname = args[0] runner = FridaRunner() runner.set_hook_with_data( ios_hook('hooking/list-class-methods'), classname=classname, include_parents=_should_include_parent_methods(args)) runner.run() response = runner.get_last_message() if not response.is_successful(): click.secho('Failed to list classes with error: {0}'.format(response.error_reason), fg='red') return None # dump the methods to screen for method in response.data: click.secho(method)
def entries(args: list = None) -> None: """ Lists entries in the Android KeyStore :param args: :return: """ runner = FridaRunner() runner.set_hook_with_data(android_hook('keystore/list')) runner.run() response = runner.get_last_message() if not response.is_successful(): click.secho('Failed to list KeyStore items with error: {0}'.format( response.error_reason), fg='red') return None if not response.data: click.secho('No keystore items were found', fg='yellow') return None output = [[x['alias'], x['is_key'], x['is_certificate']] for x in response.data] click.secho(tabulate(output, headers=['Alias', 'Is Key', 'Is Certificate']))
def disable(args: list = None) -> None: """ Performs a generic anti root detection. :param args: :return: """ runner = FridaRunner() runner.set_hook_with_data(android_hook('root/disable')) runner.run_as_job(name='root-disable')
def simulate(args: list = None) -> None: """ Simulate a rooted environment. :param args: :return: """ runner = FridaRunner() runner.set_hook_with_data(android_hook('root/simulate')) runner.run_as_job(name='root-simulate')
def android_disable(args: list = None) -> None: """ Starts a new objection job that hooks common classes and functions, applying new logic in an attempt to bypass SSL pinning. :param args: :return: """ hook = android_hook('pinning/disable') runner = FridaRunner() runner.set_hook_with_data(hook=hook, quiet=_should_be_quiet(args)) runner.run_as_job(name='pinning-disable')
def dump_ios_method_args(args: list) -> None: """ Starts an objection job that hooks into a class method and dumps the argument values as the method is invoked. :param args: :return: """ # small helper method to reduce copy/paste code for the usage info def usage(): click.secho( 'Usage: ios hooking dump method_args <+/-> <class_name> <method_name>', bold=True) if len(args) < 3: usage() return class_instance = args[0] class_name = args[1] method_name = args[2] if class_instance not in ['-', '+']: click.secho( 'Specify a class method (+) or instance method (-) with either a "+" or a "-"', fg='red') usage() return full_method = '{0}[{1} {2}]'.format(class_instance, class_name, method_name) argument_count = full_method.count(':') click.secho('Full method: {0} ({1} arguments)'.format( full_method, argument_count)) # prepare a runner for the arg dump hook runner = FridaRunner() runner.set_hook_with_data(ios_hook('hooking/dump-arguments'), method=full_method, argument_count=argument_count) runner.run_as_job(name='dump-arguments')
def launch_service(args: list) -> None: """ Launches an exported service using an Android Intent :param args: :return: """ if len(args) < 1: click.secho('Usage: android intent launch_service <service_class>', bold=True) return intent_class = args[0] click.secho('Launching Service: {0}...'.format(intent_class), dim=True) runner = FridaRunner() runner.set_hook_with_data(android_hook('intent/start-service'), intent_class=intent_class) runner.run()
def launch_activity(args: list) -> None: """ Launces an activity class using an Android Intent :param args: :return: """ if len(args) < 1: click.secho('Usage: android intent launch_activity <activity_class>', bold=True) return intent_class = args[0] click.secho('Launching Activity: {0}...'.format(intent_class), dim=True) runner = FridaRunner() runner.set_hook_with_data(android_hook('intent/start-activity'), intent_class=intent_class) runner.run()
def watch_class(args: list) -> None: """ Starts an objection job that hooks into all of the methods available in a class and reports on invocations. :param args: :return: """ if len(args) <= 0: click.secho('Usage: ios hooking watch class <class_name> (--include-parents)', bold=True) return class_name = args[0] runner = FridaRunner() runner.set_hook_with_data( ios_hook('hooking/watch-class-methods'), class_name=class_name, include_parents=_should_include_parent_methods(args)) runner.run_as_job(name='watch-class-methods')
def clear(args: list = None) -> None: """ Clears out an Android KeyStore :param args: :return: """ runner = FridaRunner() runner.set_hook_with_data(android_hook('keystore/clear')) runner.run() response = runner.get_last_message() if not response.is_successful(): click.secho('Failed to clear the KeyStore error: {0}'.format( response.error_reason), fg='red') return None click.secho('Cleared the KeyStore', fg='green')
def set_method_return_value(args: list) -> None: """ Make an Objective-C method return a specific boolean value, always. :param args: :return: """ if len(args) < 2: click.secho('Usage: ios hooking set_method_return "<selector>" (eg: "-[ClassName methodName:]") <true/false>', bold=True) return selector = args[0] retval = args[1] runner = FridaRunner() runner.set_hook_with_data( ios_hook('hooking/set-return'), selector=selector, retval=_string_is_true(retval)) runner.run_as_job(name='set-return-value')
def var_class(args: list) -> None: if len(clean_argument_flags(args)) < 2: click.secho(('Usage: android hooking watch var <class> <var> ' '(eg: com.example.test [email protected]) ' '(optional: --dump-args) ' '(optional: --dump-backtrace) ' '(optional: --dump-return)'), bold=True) return search_class = args[0] search_var = args[1] runner = FridaRunner() runner.set_hook_with_data(android_hook('hooking/var-class'), search_class=search_class, search_var=search_var, dump_args=_should_dump_args(args), dump_return=_should_dump_return_value(args), dump_backtrace=_should_dump_backtrace(args)) runner.run_as_job(name='watch-java-var', args=args)
def dump_android_method_args(args: list) -> None: """ Starts an objection job that hooks into a class method and dumps the argument values as the method is invoked. :param args: :return: """ if len(args) < 2: click.secho('Usage: android hooking dump_args <class> <method>', bold=True) return target_class = args[0] target_method = args[1] # prepare a runner for the arg dump hook runner = FridaRunner() runner.set_hook_with_data(android_hook('hooking/dump-arguments'), target_class=target_class, target_method=target_method) runner.run_as_job(name='dump-arguments')
class TestFridaRunner(unittest.TestCase): def setUp(self): self.runner = FridaRunner() self.successful_message = { 'payload': { 'type': 'send', 'status': 'success', 'error_reason': None, 'data': 'data for unittest' } } self.error_message = { 'payload': { 'type': 'send', 'status': 'error', 'error_reason': 'error_message', 'data': 'error data for unittest' } } self.sample_hook = """// this is a comment var response = { status: 'success', error_reason: NaN, type: 'file-readable', data: { path: '{{ path }}', readable: Boolean(file.canRead()) } }; send(response);""" def test_init_runner_without_hook(self): runner = FridaRunner() self.assertEqual(runner.messages, []) self.assertIsNone(runner.script) def test_init_runner_with_hook(self): runner = FridaRunner('test') self.assertEqual(runner.hook, 'test') def test_handles_incoming_success_message_and_adds_message(self): self.runner._on_message(self.successful_message, None) self.assertEqual(len(self.runner.messages), 1) def test_handles_incoming_error_message_and_warns_while_adding_message( self): with capture(self.runner._on_message, self.error_message, None) as o: output = o expected_output = '[hook failure] error_message\n' self.assertEqual(output, expected_output) self.assertEqual(len(self.runner.messages), 1) @mock.patch('objection.utils.frida_transport.app_state.should_debug_hooks') def test_handles_incoming_success_message_and_prints_debug_output( self, should_debug_hooks): should_debug_hooks.return_value = True with capture(self.runner._on_message, self.successful_message, None) as o: output = o expected_output = """- [response] ------------------ { "payload": { "data": "data for unittest", "error_reason": null, "status": "success", "type": "send" } } - [./response] ---------------- """ self.assertEqual(output, expected_output) def test_hook_processor_beautifies_javascript_output_from_hook_property( self): self.runner.hook = self.sample_hook hook = self.runner._hook_processor() expected_outut = """var response = { status: 'success', error_reason: NaN, type: 'file-readable', data: { path: '', readable: Boolean(file.canRead()) } }; send(response);""" self.assertEqual(hook, expected_outut) def test_can_fetch_last_message_with_multiple_messages_received(self): # ignore the output we get from the error message with capture(self.runner._on_message, self.error_message, None) as _: pass self.runner._on_message(self.successful_message, None) last_message = self.runner.get_last_message() self.assertEqual(last_message.data, self.successful_message['payload']['data']) def test_sets_hook_with_data(self): self.runner.set_hook_with_data('{{ test }}', test='testing123') self.assertEqual(self.runner.hook, 'testing123')