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 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: """ 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 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 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 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_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 test_finds_and_compiles_ios_hooks_without_an_exception_handler(self): hook = ios_hook('filesystem/pwd', skip_trycatch=True) expected_output = """// Determines the current working directory, based // on the main bundles path on the iOS device. var NSBundle = ObjC.classes.NSBundle; var BundleURL = NSBundle.mainBundle().bundlePath(); var response = { status: 'success', error_reason: NaN, type: 'current-working-directory', data: { cwd: BundleURL.toString() } }; send(response); // -- Sample Objective-C // // NSURL *bundleURL = [[NSBundle mainBundle] bundleURL]; """ self.assertEqual(hook, expected_output)
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 test_finds_and_compiles_ios_hooks_with_an_exception_handler(self): hook = ios_hook('filesystem/pwd') expected_output = """if (ObjC.available) { try { // Determines the current working directory, based // on the main bundles path on the iOS device. var NSBundle = ObjC.classes.NSBundle; var BundleURL = NSBundle.mainBundle().bundlePath(); var response = { status: 'success', error_reason: NaN, type: 'current-working-directory', data: { cwd: BundleURL.toString() } }; send(response); // -- Sample Objective-C // // NSURL *bundleURL = [[NSBundle mainBundle] bundleURL]; } catch (err) { var response = { status: 'error', error_reason: err.message, type: 'global-exception', data: {} }; send(response); } } else { var response = { status: 'error', error_reason: 'Objective-C runtime is not available.', type: 'global-exception', data: {} }; send(response); } """ self.assertEqual(hook, expected_output)
def disable(args: list = None) -> None: """ Attempts to disable jailbreak detection. :param args: :return: """ hook = ios_hook('jailbreak/disable') runner = FridaRunner(hook=hook) runner.run_as_job(name='disable-jailbreak-detection')
def simulate(args: list = None) -> None: """ Attempts to simulate a Jailbroken environment :param args: :return: """ hook = ios_hook('jailbreak/simulate') runner = FridaRunner(hook=hook) runner.run_as_job(name='simulate-jailbroken-environment')
def dump(args: list = None) -> None: """ Dump the iOS keychain :param args: :return: """ if _should_output_json(args) and len(args) < 2: click.secho('Usage: ios keychain dump (--json <local destination>)', bold=True) return click.secho('Note: You may be asked to authenticate using the devices passcode or TouchID') if not _should_output_json(args): click.secho('Get all of the attributes by adding `--json keychain.json` to this command', dim=True) click.secho('Reading the iOS keychain...', dim=True) hook = ios_hook('keychain/dump') runner = FridaRunner(hook=hook) runner.run() response = runner.get_last_message() if not response.is_successful(): click.secho('Failed to get keychain items with error: {0}'.format(response.error_message), fg='red') return if _should_output_json(args): destination = args[1] click.secho('Writing full keychain as json to {0}...'.format(destination), dim=True) with open(destination, 'w') as f: f.write(json.dumps(response.data, indent=2)) click.secho('Dumped full keychain to: {0}'.format(destination), fg='green') return # refer to hooks/ios/keychain/dump.js for a key,value reference data = [] if response.data: for entry in response.data: data.append([entry['item_class'], entry['account'], entry['service'], entry['generic'], entry['data'], ]) click.secho('') click.secho(tabulate(data, headers=['Class', 'Account', 'Service', 'Generic', 'Data'])) else: click.secho('No keychain data could be found', fg='yellow')
def monitor(args: list = None) -> None: """ Starts a new objection job that monitors the iOS pasteboard and reports on new strings found. :param args: :return: """ hook = ios_hook('pasteboard/monitor') runner = FridaRunner(hook=hook) runner.run_as_job(name='pasteboard-monitor')
def ios_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 = ios_hook('pinning/disable') runner = FridaRunner(hook=hook) runner.run_as_job(name='pinning-disable')
def ios_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 = ios_hook('pinning/disable') runner = FridaRunner() runner.set_hook_with_data( hook=hook, ignore_ios10_tls_helper=_should_ignore_ios10_tls_helper_hook(args), quiet=_should_be_quiet(args)) runner.run_as_job(name='pinning-disable')
def get(args: list) -> None: """ Gets cookies using the iOS NSHTTPCookieStorage sharedHTTPCookieStorage and prints them to the screen. :param args: :return: """ hook = ios_hook('binarycookie/get') runner = FridaRunner(hook=hook) runner.run() response = runner.get_last_message() if not response.is_successful(): click.secho('Failed to get cookies with error: {0}'.format( response.error_reason), fg='red') return if not response.data: click.secho('No cookies found') return if _should_dump_json(args): print(json.dumps(response.data, indent=4)) return data = [] for cookie in response.data: data.append([ cookie['name'], cookie['value'], cookie['expiresDate'], cookie['domain'], cookie['path'], cookie['isSecure'], cookie['isHTTPOnly'] ]) click.secho(tabulate(data, headers=[ 'Name', 'Value', 'Expires', 'Domain', 'Path', 'Secure', 'HTTPOnly' ]), bold=True)
def _get_ios_classes() -> list: """ Gets a list of all of the classes available in the current Objective-C runtime. :return: """ hook = ios_hook('hooking/list-classes') runner = FridaRunner(hook=hook) 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 return response.data
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 clear(args: list = None) -> None: """ Clear the iOS keychain. :param args: :return: """ click.secho('Clearing the keychain...', dim=True) hook = ios_hook('keychain/clear') runner = FridaRunner(hook=hook) runner.run() response = runner.get_last_message() if not response.is_successful(): click.secho('Failed to clear keychain items with error: {0}'.format(response.error_message), fg='red') return click.secho('Keychain cleared', fg='green')
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 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 get(args: list = None) -> None: """ Gets all of the values stored in NSUserDefaults and prints them to screen. :param args: :return: """ hook = ios_hook('nsuserdefaults/get') runner = FridaRunner(hook=hook) runner.run() response = runner.get_last_message() if not response.is_successful(): click.secho('Failed to get nsuserdefaults with error: {0}'.format( response.error_reason), fg='red') return click.secho(response.data, bold=True)
def dump(args: list = None) -> None: """ Dumps credentials stored in NSURLCredentialStorage :param args: :return: """ hook = ios_hook('nsurlcredentialstorage/dump') runner = FridaRunner(hook=hook) api = runner.rpc_exports() data = api.dump() runner.unload_script() if not data: click.secho('No credentials found using NSURLCredentialStorage') click.secho('') click.secho(tabulate(data, headers="keys")) click.secho('') click.secho('Found {count} credentials'.format(count=len(data)), bold=True)