diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d3841a..2909fdb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,14 +11,12 @@ This project adheres to [Semantic Versioning](http://semver.org/). -## Unreleased -#### Added - +## [1.3.2] - 2017-07-13 #### Fixed +- **[CORE]** Search PID for System Apps - **[MODULE]** Keychain extraction of data not encodable in UTF8 _[from @federicodotta]_ - **[MODULE]** Improved jailbreak detection bypass (`dynamic/detection/script_jailbreak-detection-bypass.py`) - -#### Removed +- **[MODULE]** Improved certificate pinning bypass (`comms/proxy/pinning_bypass_frida`) @@ -57,6 +55,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). - **[CORE]** Remove infinite loop from `Retry` decorator, which attempts to restore a connection with the device if it fails - **[CORE]** Metadata parsing for app extensions - **[CORE]** Re-added support on iOS for: `storage/data/keychain_dump`, `binary/reversing/strings`, `binary/reversing/class_dump` +- **[CORE]** Use unquote to convert spaces. Fixes Issue #15 _[from @ccsplit]_ diff --git a/README.md b/README.md index 93245ed..b3a9268 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ ![Needle](https://labs.mwrinfosecurity.com/assets/needle-logo-blue.jpg) [![Black Hat Arsenal](https://www.toolswatch.org/badges/arsenal/2016.svg)](https://www.blackhat.com/us-16/arsenal.html#needle) +[![Black Hat Arsenal](https://rawgit.com/toolswatch/badges/master/arsenal/2017.svg)](https://www.blackhat.com/us-17/arsenal.html#needle) _Needle_ is an open source, modular framework to streamline the process of conducting security assessments of iOS apps. @@ -9,7 +10,11 @@ _Needle_ is an open source, modular framework to streamline the process of condu Assessing the security of an iOS application typically requires a plethora of tools, each developed for a specific need and all with different modes of operation and syntax. The Android ecosystem has tools like "[drozer](https://mwr.to/drozer)" that have solved this problem and aim to be a ‘one stop shop’ for the majority of use cases, however iOS does not have an equivalent. -Needle is an open source modular framework which aims to streamline the entire process of conducting security assessments of iOS applications, and acts as a central point from which to do so. Given its modular approach, Needle is easily extensible and new modules can be added in the form of python scripts. Needle is intended to be useful not only for security professionals, but also for developers looking to secure their code. A few examples of testing areas covered by Needle include: data storage, inter-process communication, network communications, static code analysis, hooking and binary protections.​ The only requirement in order to run Needle effectively is a jailbroken device. +[Needle](https://github.com/mwrlabs/needle) is the MWR's iOS Security Testing Framework, released at Black Hat USA in August 2016. It is an open source modular framework which aims to streamline the entire process of conducting security assessments of iOS applications, and acts as a central point from which to do so. Needle is intended to be useful not only for security professionals, but also for developers looking to secure their code. A few examples of testing areas covered by Needle include: data storage, inter-process communication, network communications, static code analysis, hooking and binary protections. The only requirement in order to run Needle effectively is a jailbroken device. + +The release of version 1.0.0 provided a major overhaul of its core and the introduction of a new native agent, written entirely in Objective-C. The new [NeedleAgent](https://github.com/mwrlabs/needle-agent) is an open source iOS app complementary to Needle, that allows to programmatically perform tasks natively on the device, eliminating the need for third party tools.  + +Needle has been presented at and used by workshops in various international conferences like Black Hat USA/EU, OWASP AppSec and DEEPSEC. It was also included by ToolsWatch in the shortlist for the [Top Security Tools of 2016](http://www.toolswatch.org/2017/02/2016-top-security-tools-as-voted-by-toolswatch-org-readers/), and it is featured in the [OWASP Mobile Testing Guide](https://github.com/OWASP/owasp-mstg). Needle is open source software, maintained by [MWR InfoSecurity](https://www.mwrinfosecurity.com/). diff --git a/needle/core/device/app.py b/needle/core/device/app.py index e18c984..30adcc7 100644 --- a/needle/core/device/app.py +++ b/needle/core/device/app.py @@ -22,7 +22,7 @@ def _retrieve_metadata(self): metadata_agent = self.__parse_from_agent() # Content of the app's local Info.plist - plist_info_path = Utils.escape_path('%s/Info.plist' % metadata_agent['binary_directory']) + plist_info_path = Utils.escape_path('%s/Info.plist' % metadata_agent['binary_directory'], escape_accent=True) plist_info = self._device.remote_op.parse_plist(plist_info_path) metadata_info = self.__parse_plist_info(plist_info) @@ -54,9 +54,9 @@ def __parse_from_agent(self): name = self.__extract_field(agent_info, 'DisplayName').encode('ascii','replace') bundle_type = self.__extract_field(agent_info, 'BundleType') bundle_id = self.__extract_field(agent_info, 'BundleIdentifier') - data_directory = self.__extract_field(agent_info, 'DataContainer', path=True) - bundle_directory = self.__extract_field(agent_info, 'BundleContainer', path=True) - binary_directory = self.__extract_field(agent_info, 'BundleURL', path=True) + data_directory = self.__extract_field(agent_info, 'DataContainer', path=True, urldecode=True) + bundle_directory = self.__extract_field(agent_info, 'BundleContainer', path=True, urldecode=True) + binary_directory = self.__extract_field(agent_info, 'BundleURL', path=True, urldecode=True) app_version = self.__extract_field(agent_info, 'BundleVersion') sdk_version = self.__extract_field(agent_info, 'SDKVersion') entitlements = self.__extract_field(agent_info, 'Entitlements') @@ -120,14 +120,21 @@ def __detect_architectures(self, binary): res = msg.rsplit(': ')[-1].split(' ') return res - def __extract_field(self, data, field, path=False): + def __extract_field(self, data, field, path=False, urldecode=False): """Extract the specified entry from the plist file. Returns empty string if not present.""" try: temp = data[field] + if urldecode: + try: + from urllib.parse import unquote + except ImportError: + # Python 2.x + from urllib import unquote + temp = unquote(temp) if path: prefix = 'file://' if temp.startswith(prefix): - temp = temp[len(prefix):] + temp = Utils.escape_path(temp[len(prefix):]) return temp except: return "" @@ -179,7 +186,7 @@ def search_pid(self, binary_name): cmd = "ps ax | grep -i '{binary_name}'".format(binary_name=binary_name) out = self._device.remote_op.command_blocking(cmd) try: - process_list = filter(lambda x: '/var/mobile' in x, out) + process_list = filter(lambda x: 'grep' not in x, out) if not process_list: process_list = filter(lambda x: '/var/containers' in x, out) process = process_list[0].strip() diff --git a/needle/core/framework/module.py b/needle/core/framework/module.py index cb79888..0d7613d 100644 --- a/needle/core/framework/module.py +++ b/needle/core/framework/module.py @@ -286,7 +286,7 @@ class FridaScript(FridaModule): def __init__(self, params): FridaModule.__init__(self, params) # Add option for launch mode - opt = ('spawn', True, True, 'If set to True, Frida will be used to spawn the app. ' + opt = ('spawn', False, True, 'If set to True, Frida will be used to spawn the app. ' 'If set to False, the app will be launched and Frida will be attached to the running instance') self.register_option(*opt) opt = ('resume', True, True, 'If set to True, Frida will resume the application process after spawning it (recommended)') diff --git a/needle/core/utils/constants.py b/needle/core/utils/constants.py index 005ca6a..ec7604d 100644 --- a/needle/core/utils/constants.py +++ b/needle/core/utils/constants.py @@ -12,7 +12,7 @@ class Constants(object): AUTHOR = 'MWR InfoSecurity (@MWRLabs) - Marco Lancini (@LanciniMarco)' EMAIL = 'marco.lancini@mwrinfosecurity.com' WEBSITE = 'mwr.to/needle' - VERSION = '1.3.1' + VERSION = '1.3.2' VERSION_CHECK = 'https://raw.githubusercontent.com/mwrlabs/needle/master/needle/core/utils/constants.py' # Name variables @@ -50,6 +50,7 @@ class Constants(object): # AGENT CONSTANTS AGENT_TAG = "[AGENT]" AGENT_WELCOME = "Welcome to Needle Agent" + AGENT_BUNDLE_ID = "mwr.needle.agent" AGENT_VERSION_MARK = "VERSION: " AGENT_OUTPUT_END = " :OUTPUT_END:" AGENT_TIMEOUT_READ = 5 @@ -62,6 +63,8 @@ class Constants(object): '10': [ 'binary/installation/install', 'binary/installation/pull_ipa', + 'binary/reversing/class_dump', + 'binary/reversing/strings' ] } diff --git a/needle/core/utils/utils.py b/needle/core/utils/utils.py index a5fb29e..7915539 100644 --- a/needle/core/utils/utils.py +++ b/needle/core/utils/utils.py @@ -17,11 +17,15 @@ class Utils(object): # PATH UTILS # ================================================================================================================== @staticmethod - def escape_path(path): + def escape_path(path, escape_accent=False): """Escape the given path.""" import pipes path = path.strip() # strip path = path.strip(''''"''') # strip occasional single/double quotes from both sides + if escape_accent: + # Find the accents/backquotes that do not have a backslash + # in front of them and escape them. + path = re.sub('(?= 0) { - this.path_to_hide = true; - send("Hooking fileExistsAtPath to return false"); - } - }, - onLeave: function (retval) { - if (this.path_to_hide) { - retval.replace(0x0); - } - } -}); - var resolver = new ApiResolver('objc'); resolver.enumerateMatches('*[* is*ailbroken]', { onMatch: function (match) { diff --git a/needle/modules/storage/data/keychain_dump_frida.py b/needle/modules/storage/data/keychain_dump_frida.py index f94d846..f54708f 100644 --- a/needle/modules/storage/data/keychain_dump_frida.py +++ b/needle/modules/storage/data/keychain_dump_frida.py @@ -124,7 +124,7 @@ class Module(FridaScript): for (var i = 0; i < result.count(); i++){ var entry = result.objectAtIndex_(i); send(JSON.stringify({ - Data: bytesToHex(Memory.readByteArray(entry.objectForKey_("v_Data").bytes(),entry.objectForKey_("v_Data").length())) + + Data: bytesToHex(Memory.readByteArray(entry.objectForKey_("v_Data").bytes(),entry.objectForKey_("v_Data").length())) + ( ObjC.classes.NSString.stringWithUTF8String_(entry.objectForKey_("v_Data").bytes()) ? " (UTF8 String: '" + ObjC.classes.NSString.stringWithUTF8String_(entry.objectForKey_("v_Data").bytes()).valueOf() + "')": "" ), EntitlementGroup: entry.objectForKey_("agrp").valueOf(), Protection: constants[entry.objectForKey_("pdmn")].valueOf(), @@ -167,7 +167,9 @@ def module_run(self): self.printer.warning(e) def module_post(self): - self.printer.info("Keychain Items:") - self.print_cmd_output() - self.add_issue('Keychain items detected ({})'.format(len(self.results)), None, 'INVESTIGATE', self.options['output']) - + if self.results: + self.printer.info("Keychain Items:") + self.print_cmd_output() + self.add_issue('Keychain items detected ({})'.format(len(self.results)), None, 'INVESTIGATE', self.options['output']) + else: + self.printer.warning("No items found.") \ No newline at end of file