From 06e5313438c42555850754996324d2375bc41a5d Mon Sep 17 00:00:00 2001 From: Mohamed Ali Date: Fri, 9 Feb 2024 17:31:42 +0300 Subject: [PATCH] feat: Add MacOS support to Objection This commit extends the functionality of Objection to include support for MacOS, enabling security researchers and developers to utilize the toolkit for macOS applications in addition to iOS and Android. By introducing MacOS support, this enhancement broadens the scope of Objection, allowing users to perform runtime exploration and security assessments on applications within the MacOS environment. With this addition, Objection provides a unified platform for analyzing mobile and desktop applications across different operating systems. This commit aims to enhance the versatility and usability of Objection, further empowering users in their security assessment efforts by offering support for MacOS alongside existing iOS and Android capabilities. --- objection/__init__.py | 2 +- objection/commands/device.py | 22 +++- objection/console/cli.py | 7 +- objection/console/commands.py | 212 +++++++++++++++++++++++++++++++++- objection/state/connection.py | 11 ++ objection/state/device.py | 7 ++ objection/utils/agent.py | 5 +- 7 files changed, 259 insertions(+), 7 deletions(-) diff --git a/objection/__init__.py b/objection/__init__.py index 5924159f..4ae4a189 100644 --- a/objection/__init__.py +++ b/objection/__init__.py @@ -1,6 +1,6 @@ import sys -__version__ = '1.11.0' +__version__ = '1.11.1' # helper containing a python 3 related warning # if this is run with python 2 diff --git a/objection/commands/device.py b/objection/commands/device.py index 31c3a8c1..8b8a8363 100644 --- a/objection/commands/device.py +++ b/objection/commands/device.py @@ -2,7 +2,7 @@ from tabulate import tabulate from ..state.connection import state_connection -from ..state.device import device_state, Android, Ios +from ..state.device import device_state, Android, Ios, Macos def get_environment(args: list = None) -> None: @@ -22,6 +22,9 @@ def get_environment(args: list = None) -> None: if device_state.platform == Android: _get_android_environment() + if device_state.platform == Macos: + _get_macos_environment() + def _get_ios_environment() -> None: """ @@ -51,3 +54,20 @@ def _get_android_environment() -> None: click.secho('') click.secho(tabulate(paths.items(), headers=['Name', 'Path'])) + + +def _get_macos_environment() -> None: + """ + Prints information about the macOS environment. + + This includes the current OS version as well as directories + of interest for the current applications Documents, Library and + main application bundle. + + :return: + """ + + paths = state_connection.get_api().env_ios_paths() + + click.secho('') + click.secho(tabulate(paths.items(), headers=['Name', 'Path'])) diff --git a/objection/console/cli.py b/objection/console/cli.py index 8b0c9ed2..9f6efc20 100644 --- a/objection/console/cli.py +++ b/objection/console/cli.py @@ -39,6 +39,8 @@ def get_agent() -> Agent: @click.group() @click.option('--network', '-N', is_flag=True, help='Connect using a network connection instead of USB.', show_default=True) +@click.option('--desktop', '-D', is_flag=True, help='Connect using a local connection for desktop.', + show_default=True) @click.option('--host', '-h', default='127.0.0.1', show_default=True) @click.option('--port', '-p', required=False, default=27042, show_default=True) @click.option('--api-host', '-ah', default='127.0.0.1', show_default=True) @@ -53,7 +55,7 @@ def get_agent() -> Agent: @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.') @click.option('--uid', required=False, default=None, help='Specify the uid to run as (Android only).') -def cli(network: bool, host: str, port: int, api_host: str, api_port: int, +def cli(network: bool, desktop: bool, host: str, port: int, api_host: str, api_port: int, name: str, serial: str, debug: bool, spawn: bool, no_pause: bool, foremost: bool, debugger: bool, uid: int) -> None: """ @@ -79,6 +81,9 @@ def cli(network: bool, host: str, port: int, api_host: str, api_port: int, if serial: state_connection.device_id = serial + if desktop: + state_connection.use_desktop() + # set api parameters app_state.api_host = api_host app_state.api_port = api_port diff --git a/objection/console/commands.py b/objection/console/commands.py index 1326bd85..60930dac 100644 --- a/objection/console/commands.py +++ b/objection/console/commands.py @@ -499,7 +499,7 @@ }, }, # ios commands - 'ios': { + 'ios': { 'meta': 'Commands specific to iOS', 'commands': { 'info': { @@ -742,7 +742,215 @@ }, } }, - + # macos commands + 'macos': { + 'meta': 'Commands specific to iOS', + 'commands': { + 'info': { + 'meta': 'Get macOS and application related information', + 'commands': { + 'binary': { + 'meta': 'Get information about application binaries and dylibs', + 'exec': binary.info + } + } + }, + 'keychain': { + 'meta': 'Work with the macOS keychain', + 'commands': { + 'dump': { + 'meta': 'Dump the keychain for the current app\'s entitlement group', + 'flags': ['--json', '--smart'], + 'exec': keychain.dump + }, + 'dump_raw': { + 'meta': 'Dump raw, unprocessed keychain entries (advanced)', + 'exec': keychain.dump_raw + }, + 'clear': { + 'meta': 'Delete all keychain entries for the current app\'s entitlement group', + 'exec': keychain.clear + }, + 'add': { + 'meta': 'Add an entry to the iOS keychain', + 'flags': ['--account', '--service', '--data'], + 'exec': keychain.add + } + } + }, + 'plist': { + 'meta': 'Work with macOS Plists', + 'commands': { + 'cat': { + 'meta': 'Cat a plist', + 'dynamic': filemanager.list_files_in_current_fm_directory, + 'exec': plist.cat + } + } + }, + 'bundles': { + 'meta': 'Work with macOS Bundles', + 'commands': { + 'list_frameworks': { + 'meta': 'Lists all of the application\'s bundles that represent frameworks', + 'flags': ['--include-apple-frameworks', '--full-path'], + 'exec': bundles.show_frameworks + }, + 'list_bundles': { + 'meta': 'Lists all of the application\'s non framework bundles', + 'flags': ['--full-path'], + 'exec': bundles.show_bundles + } + } + }, + 'nsuserdefaults': { + 'meta': 'Work with NSUserDefaults', + 'commands': { + 'get': { + 'meta': 'Get all of the entries', + 'exec': nsuserdefaults.get + } + } + }, + 'nsurlcredentialstorage': { + 'meta': 'Work with the shared NSURLCredentialStorage', + 'commands': { + 'dump': { + 'meta': 'Dump all of the credentials in the shared NSURLCredentialStorage', + 'exec': nsurlcredentialstorage.dump + } + } + }, + 'cookies': { + 'meta': 'Work with shared cookies', + 'commands': { + 'get': { + 'meta': 'Get the current apps shared cookies', + 'flags': ['--json'], + 'exec': cookies.get + } + } + }, + 'heap': { + 'meta': 'Commands to work with the macOS heap', + 'commands': { + 'print': { + 'meta': 'Print information about objects on the iOS heap', + 'commands': { + 'ivars': { + 'meta': 'Print instance variables for an Objective-C object', + 'flags': ['--to-utf8'], + 'exec': ios_heap.ivars + }, + 'methods': { + 'meta': 'Print instance methods for an Objective-C object', + 'flags': ['--without-arguments'], + 'exec': ios_heap.methods + } + } + }, + 'search': { + 'meta': 'Search for information about the current macOS heap', + 'commands': { + 'instances': { + 'meta': 'Search for live instances of a particular class', + 'exec': ios_heap.instances + } + } + }, + 'execute': { + 'meta': 'Execute methods on objects on the macOS heap', + 'flags': ['--return-string'], + 'exec': ios_heap.execute + }, + 'evaluate': { + 'meta': 'Evaluate JavaScript on objects on the macOS heap', + 'flags': ['--inline'], + 'exec': ios_heap.evaluate + } + } + }, + 'hooking': { + 'meta': 'Commands used for hooking methods in iOS', + 'commands': { + 'list': { + 'meta': 'Lists various bits of information', + 'commands': { + 'classes': { + 'meta': 'List classes available in the current application', + 'exec': ios_hooking.show_ios_classes + }, + 'class_methods': { + 'meta': 'List the methods in a class', + 'flags': ['--include-parents'], + 'exec': ios_hooking.show_ios_class_methods + } + } + }, + 'watch': { + 'meta': 'Watch invocations of classes and methods', + 'exec': ios_hooking.watch, + 'flags': ['--dump-args', '--dump-backtrace', '--dump-return'], + }, + 'set': { + 'meta': 'Set various values', + 'commands': { + 'return_value': { + 'meta': 'Set a methods return value. Supports only boolean returns', + 'exec': ios_hooking.set_method_return_value + } + } + }, + 'search': { + 'meta': 'Search for various classes and or methods', + 'exec': ios_hooking.search, + 'flags': ['--json', '--only-classes'] + }, + 'generate': { + 'meta': 'Generate Frida hooks for macOS', + 'commands': { + 'class': { + 'meta': 'A generic hook manager for Classes', + 'exec': ios_generate.clazz + }, + 'simple': { + 'meta': 'Simple hooks for each Class method', + 'exec': ios_generate.simple + } + }, + } + } + }, + 'pasteboard': { + 'meta': 'Work with the macOS pasteboard', + 'commands': { + 'monitor': { + 'meta': 'Monitor the macOS pasteboard', + 'exec': pasteboard.monitor + } + } + }, + 'sslpinning': { + 'meta': 'Work with macOS SSL pinning', + 'commands': { + 'disable': { + 'meta': 'Attempt to disable SSL pinning in various iOS libraries/classes', + 'flags': ['--quiet'], + 'exec': ios_pinning.ios_disable + } + } + }, + 'monitor': { + 'meta': 'Commands to work with macOS function monitoring', + 'commands': { + 'crypto': { + 'meta': 'Monitor CommonCrypto operations', + 'exec': ios_crypto.crypto_enable + } + }, + }, + } + }, 'exit': { 'meta': 'Exit', }, diff --git a/objection/state/connection.py b/objection/state/connection.py index 93929ac6..2dacc192 100644 --- a/objection/state/connection.py +++ b/objection/state/connection.py @@ -8,6 +8,7 @@ def __init__(self) -> None: """ self.network = False + self.desktop = False self.host = None self.port = None self.device_type = 'usb' @@ -43,6 +44,16 @@ def use_network(self) -> None: self.network = True self.device_type = 'remote' + def use_desktop(self) -> None: + """ + Sets the values required to have a Network connection. + + :return: + """ + + self.desktop = True + self.device_type = 'local' + def get_comms_type(self) -> int: """ Returns the currently configured connection type. diff --git a/objection/state/device.py b/objection/state/device.py index 46ee51af..5319414d 100644 --- a/objection/state/device.py +++ b/objection/state/device.py @@ -17,6 +17,13 @@ class Ios(Device): path_separator = '/' +class Macos(Device): + """ Represents macOS specific configurations. """ + + name = 'macos' + path_separator = '/' + + class DeviceState(object): """ A class representing the state of a device and its runtime. """ diff --git a/objection/utils/agent.py b/objection/utils/agent.py index 679df1e6..0b411fae 100644 --- a/objection/utils/agent.py +++ b/objection/utils/agent.py @@ -11,7 +11,7 @@ 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.device import device_state, Ios, Android, Macos from objection.state.jobs import job_manager_state from objection.utils.helpers import debug_print @@ -288,7 +288,8 @@ def update_device_state(self): device_state.set_platform(Ios) elif params['os']['id'] == 'android': device_state.set_platform(Android) - + elif params['os']['id'] == 'macos': + device_state.set_platform(Macos) # set os version device_state.set_version(params['os']['version'])