def run(self): Log.info("Dumping classes with otool") try: class_dump = otool_class_dump_to_dict(otool_class_dump( self.binary)) except Exception as e: Log.error("An error ocurred when trying to use otool") Log.debug(e) Log.info("Trying jtool") class_dump = jtool_class_dump_to_dict(jtool_class_dump( self.binary)) dump_name = self.binary.rsplit("/", 1)[-1].replace(" ", ".") result = { "{}_class_dump".format(dump_name.replace(".", "_")): class_dump } if hasattr(self, "output") and self.output: Log.info("Saving classes to file") dump_path = "{}/{}.class.dump".format(self.output, dump_name) # create output folder execute("mkdir -p {}".format(dump_path)) save_class_dump(class_dump, dump_path) result.update({ "{}_dump_path".format(dump_name.replace(".", "_")): dump_path, "print": "Dump saved in {}.".format(dump_path) }) return result
def run(self): Log.info("Creating output directories") execute("mkdir -p {}".format(self.output)) Log.info("Pulling keychain data") keychain_data = self.device.keychain_data() result = {"keychain_data": keychain_data} if hasattr(self, "output") and self.output: filename = "{}/keychain.json".format(self.output) Log.info("Saving keychain data") with open(filename, "w") as fp: fp.write(dumps(keychain_data, ensure_ascii=False)) result.update({ "keychain_file": filename, "print": "Keychain data saved in {}.".format(filename) }) return result
def run(self): # get the jar first jar_module = JarModule() jar_module.output = self.output jar_module.apk = self.apk result = jar_module.run() self.jar = [result[jar] for jar in result if jar.endswith("_jar")][0] if not self.jar: return {"print": "Could not decompile the application"} Log.info("Creating source directory") # create source directory identifier = self.jar.rsplit("/", 1)[-1].lower().rsplit(".", 1)[0] output_path = "{}/{}.source".format(self.output, identifier) execute("mkdir -p {}".format(output_path)) # unzip Log.info("Getting application's source") source(self.jar, output_path) return { "{}_source".format(identifier): output_path, "print": "Application source reversed to {}".format(output_path) }
def run(self): # create unzipped directory identifier = self.ipa.rsplit("/", 1)[-1].lower().rsplit(".", 1)[0] Log.info("Crating output directories") output_path = "{}/{}.unzipped".format(self.output, identifier) execute("mkdir -p {}".format(output_path)) # unzip Log.info("Unzipping application") unzip(self.ipa, output_path) # get new identifier app_path = application_path(output_path) # get info info_module = InfoModule() info_module.unzipped_ipa = output_path info = info_module.run() for key in info: if key.endswith("_info"): info = info[key] break # move to new directory if "CFBundleIdentifier" in info: identifier = info["CFBundleIdentifier"] old_output_path = output_path output_path = "{}/{}.unzipped".format(self.output, identifier) execute("mv {} {}".format(old_output_path, output_path)) return { "{}_unzipped".format(identifier): output_path, "print": "Application unzipped to {}".format(output_path) }
def run(self): Log.info("Creating decompilation directory") # create decompiled directory identifier = self.apk.rsplit("/", 1)[-1].lower().rsplit(".", 1)[0] output_path = "{}/{}.decompiled".format(self.output, identifier) execute("mkdir -p {}".format(output_path)) # unzip Log.info("Decompiling application") decompile(self.apk, output_path) # get identifier manifest_module = ManifestModule() manifest_module.decompiled_apk = output_path self.manifest = manifest_module.run() if "print" not in self.manifest: identifier = self.manifest.popitem()[1].package() # move decompiled app to new path old_output_path = output_path output_path = "{}/{}.decompiled".format(self.output, identifier) execute("mv {} {}".format(old_output_path, output_path)) return { "{}_decompiled".format(identifier): output_path, "print": "Application decompiled to {}".format(output_path) }
def run(self): Log.info("Dumping classes with otool") class_dump = otool_class_dump_to_dict( self.device.otool("-ov", self.binary)[0]) # stdout dump_name = self.binary.rsplit("/", 1)[-1].replace(" ", ".") result = { "{}_class_dump".format(dump_name.replace(".", "_")): class_dump } if hasattr(self, "output") and self.output: Log.info("Saving classes to file") dump_path = "{}/{}.class.dump".format(self.output, dump_name) # create output folder execute("mkdir -p {}".format(dump_path)) save_class_dump(class_dump, dump_path) result.update({ "{}_dump_path".format(dump_name.replace(".", "_")): dump_path, "print": "Dump saved in {}.".format(dump_path) }) return result
def run(self): # create decompiled directory identifier = self.apk.rsplit("/", 1)[-1].lower().rsplit(".", 1)[0] Log.info("Creating output directory") filename = "{}/{}.jar".format(self.output, identifier) execute("mkdir -p {}".format(self.output)) # get jar Log.info("Getting application's jar") jar(self.apk, filename) return { "{}_jar".format(identifier): filename, "print": "Application JAR saved to {}".format(filename) }
def complete_set(self, text, line, start_index, end_index): from scrounger.utils.general import execute CMD_LEN = (3, 4) commands = line.split(" ") options = [] if "global" in line: CMD_LEN = (4, 5) if len(commands) < CMD_LEN[0]: # if setting var name or global if "global" not in line: options = ["global"] options += [option for option in self._session.global_options ] + [option for option in self._session.options] elif len(commands) < CMD_LEN[1]: # if setting value options = [""] # add None as 1 of the options options += [ "result:{}".format(name) for name in self._session.results ] # looks in the file system if text.startswith("./") or text.startswith("/") or \ text.startswith("~/"): options += [ f.replace(_HOME, "~") for f in execute("ls -d {}*".format( text.replace("~", _HOME))).split("\n") ] options = list(set(options)) return [option for option in options if option.startswith(text)]
def run(self): results = [] exceptions = [] # run all modules Log.info("Running all Android analysis modules") for module in self._analysis_modules: instance = module.Module() for option in self.options: if hasattr(self, option["name"]): setattr(instance, option["name"], getattr(self, option["name"])) try: Log.debug("Validating and Running: {}".format(instance.name())) instance.validate_options() run_result = instance.run() for key in run_result: if key.endswith("_result") and validate_analysis_result( run_result[key]) and run_result[key]["report"]: results += [run_result[key]] except Exception as e: exceptions += [{"module": instance.name(), "exception": e}] # setup output folders Log.info("Creating output folders") output_directory = "{}{}".format(self.output, self._output_directory) execute("mkdir -p {}".format(output_directory)) output_file = "{}/results.json".format(output_directory) # write results to json file Log.info("Writing results to file") with open(output_file, "w") as fp: fp.write(dumps(results)) return { "android_analysis": results, "exceptions": exceptions, "print": "The following issues were found:\n* {}".format("\n* ".join( [result["title"] for result in results])) }
def preloop(self): from os import popen, path from scrounger.utils.general import execute import scrounger.modules _Cmd.preloop(self) ## sets up command completion self._rows, self._columns = popen('stty size', 'r').read().split() self._rows, self._columns = int(self._rows), int(self._columns) if self._columns < 128: self._columns = 128 # need to add / to then replace it modules_path = "{}/".format(scrounger.modules.__path__[0]) modules = execute("find {} -name '*.py'".format(modules_path)) self._available_modules = [ module.replace(modules_path, "").replace(".py", "") for module in modules.split("\n") if module and "__" not in module ] # add custom modules modules_path = "{}/modules/".format(_SCROUNGER_HOME) modules = execute("find {} -name \"*.py\"".format(modules_path)) # add path to sys.path _path.append(modules_path) #self._custom_modules = [ self._available_modules += [ module.replace(modules_path, "").replace(".py", "") for module in modules.split("\n") if module and "__" not in module ] # fix for macos self._available_modules = [ module[1:] if module.startswith("/") else module for module in sorted(self._available_modules) ] execute("mkdir -p {}".format(self._global_options["output"])) readline.set_completer_delims(' \t\n') if path.exists(_HISTORY_FILE): readline.read_history_file(_HISTORY_FILE)
def _set_var(self, options, variable): if not " " in variable: key = variable.strip() value = None else: key, value = variable.split(" ", 1) if value == "None" or value == None: value = "" if key == "output": from scrounger.utils.general import execute execute("mkdir -p {}".format(value)) if key.lower() == "debug" and value.lower() == "true": import logging as _logging Log.setLevel(_logging.DEBUG) options[key] = value
def _stop_connection(self): """ Stops the SSH connection with the remote device """ from scrounger.utils.general import execute # cleanup if self._timer: self._timer.cancel() self._timer = None if self._ssh_session: self._ssh_session.disconnect() #self._iproxy_process.kill() self._iproxy_process = self._ssh_session = None execute('killall iproxy') # make sure iproxy is killed # Log session stop _Log.debug("ssh session killed.")
def plist(self, plist_file_path): """ Returns the contents of a plist file on the remote device :param str plist_file_path: the plist file to be read :return: returns a dict with the plist contents """ from scrounger.utils.ios import plist from scrounger.utils.general import execute # get local file local_file = "/tmp/Info.plist" self.get(plist_file_path, local_file) plist_content = plist(local_file) # clean up tmp file execute("rm -rf {}".format(local_file)) return plist_content """
def __init__(self): import scrounger.modules.analysis.android as android_analysis all_modules = android_analysis.__all__ # add custom modules modules_path = "{}/modules/".format(_SCROUNGER_HOME) modules = execute("find {} -name \"*.py\"".format(modules_path)) # add path to sys.path _path.append(modules_path) modules = [ module.replace(modules_path, "").replace(".py", "") for module in modules.split("\n") if module and "__" not in module ] all_modules += [ module for module in modules if module.startswith("custom/analysis/android") ] for module in all_modules: if module.startswith("custom/"): module_class = __import__("{}".format(module.replace("/", ".")), fromlist=["Module"]) else: module_class = __import__( "scrounger.modules.analysis.android.{}".format(module), fromlist=["Module"]) # avoid running full_analysis again if self.__module__ == module_class.__name__: continue self._analysis_modules += [module_class] self.options += module_class.Module.options sanitized_options = {} for option in self.options: if option["name"] not in sanitized_options: sanitized_options[option["name"]] = option elif option["name"] in sanitized_options and \ not sanitized_options[option["name"]]["required"] and \ option["required"]: sanitized_options[option["name"]]["required"] = True self.options = sanitized_options.values() super(Module, self).__init__()
def get(self, remote_file_path, local_file_path): """ Retrieves a file from the remote device :param str remote_file_path: the path on the remote device :param str local_file_path: the path on the local host to copy the file to (it needs to contain the file name too) :return: returns nothing """ from scrounger.utils.general import execute # start a connection if there is none self._start_connection() # logging files _Log.debug("copying {} to {}.".format(remote_file_path, local_file_path)) # create local file path if not exists execute("mkdir -p {}".format(local_file_path.split("/", 1)[0])) # get file self._ssh_session.get_file(remote_file_path, local_file_path)
def __init__(self, name): from os import popen, path # helper functions from scrounger.utils.general import execute # used to find the available modules import scrounger.modules self._name = name self._rows, self._columns = popen('stty size', 'r').read().split() self._rows, self._columns = int(self._rows), int(self._columns) if self._columns < 128: self._columns = 128 # need to add / to then replace it modules_path = "{}/".format(scrounger.modules.__path__[0]) modules = execute("find {} -name '*.py'".format(modules_path)) self._available_modules = [ module.replace(modules_path, "").replace(".py", "") for module in modules.split("\n") if module and "__" not in module ] # add custom modules modules_path = "{}/modules/".format(_SCROUNGER_HOME) modules = execute("find {} -name \"*.py\"".format(modules_path)) # add path to sys.path _path.append(modules_path) self._available_modules += [ module.replace(modules_path, "").replace(".py", "") for module in modules.split("\n") if module and "__" not in module ] # fix for macos self._available_modules = [ module[1:] if module.startswith("/") else module for module in sorted(self._available_modules) ] # public vars to be used by calling modules self.options = {} self.global_options = { "debug": "False", "device": "", "output": "", "verbose": "False" } self.devices = {} self.results = {} self.exceptions = [] # unused self.prompt = None # initialize private vars self._module_instance = None self._current_module = None self._module_class = None
def _create_custom_modules_paths(): from scrounger.utils.general import execute from os import path current_path = path.realpath(__file__).rsplit('/', 1)[0] modules_path = "{}/scrounger/modules/".format(current_path) # create custom module paths module_types = execute("find {} -type d".format(modules_path)) for module_type in module_types.split("\n"): custom_path = "{}/modules/custom/{}".format(_SCROUNGER_HOME, module_type.replace(modules_path, "")) execute("mkdir -p {}".format(custom_path)) # add __init__.py to be able to import modules execute("touch {}/__init__.py".format(custom_path)) # copy ios binaries ios_binaries_path = "{}/bin/ios/".format(current_path) installed_path = "{}/bin/ios".format(_SCROUNGER_HOME) execute("mkdir -p {}".format(installed_path)) binaries = execute("find {} -type f".format(ios_binaries_path)) for binary in binaries.split("\n"): execute("cp {} {}".format(binary, installed_path)) # copy android binaries android_binaries_path = "{}/bin/android/".format(current_path) installed_path = "{}/bin/android".format(_SCROUNGER_HOME) execute("mkdir -p {}".format(installed_path)) binaries = execute("find {} -type f".format(android_binaries_path)) for binary in binaries.split("\n"): execute("cp {} {}".format(binary, installed_path)) # change scrounger's private key perms execute("chmod 600 {}/bin/ios/scrounger.key".format(_SCROUNGER_HOME)) # generate keys for SSL proxy execute("mkdir -p {}/certs".format(_CERT_PATH)) execute("openssl genrsa -out {}/ca.key 2048".format(_CERT_PATH)) execute("openssl req -new -x509 -days 3650 -key {}/ca.key \ -out {}/ca.crt -subj \"/CN=proxy2 CA\"".format(_CERT_PATH, _CERT_PATH)) execute("openssl genrsa -out {}/cert.key 2048".format(_CERT_PATH))
def _create_custom_modules_paths(): from scrounger.utils.general import execute from os import path current_path = path.realpath(__file__).rsplit('/', 1)[0] modules_path = "{}/scrounger/modules/".format(current_path) # create custom module paths module_types = execute("find {} -type d".format(modules_path)) for module_type in module_types.split("\n"): custom_path = "{}/modules/custom/{}".format( _SCROUNGER_HOME, module_type.replace(modules_path, "")) execute("mkdir -p {}".format(custom_path)) # add __init__.py to be able to import modules execute("touch {}/__init__.py".format(custom_path)) # copy ios binaries ios_binaries_path = "{}/bin/ios/".format(current_path) installed_path = "{}/bin/ios".format(_SCROUNGER_HOME) execute("mkdir -p {}".format(installed_path)) binaries = execute("find {} -type f".format(ios_binaries_path)) for binary in binaries.split("\n"): execute("cp {} {}".format(binary, installed_path)) # copy android binaries ios_binaries_path = "{}/bin/android/".format(current_path) installed_path = "{}/bin/android".format(_SCROUNGER_HOME) execute("mkdir -p {}".format(installed_path)) binaries = execute("find {} -type f".format(ios_binaries_path)) for binary in binaries.split("\n"): execute("cp {} {}".format(binary, installed_path))
def run(self): Log.info("Checking output folders") if not self.output: self.output = "/tmp/scrounger-tmp" # create the required dirs execute("mkdir -p {}".format(self.output)) # get identifier manifest_module = ManifestModule() manifest_module.decompiled_apk = self.decompiled_apk manifest = manifest_module.run() if "print" not in manifest: manifest = manifest.popitem()[1] identifier = manifest.package() # set filenames debuggable_apk = "{}/{}-debuggable.apk".format(self.output, identifier) # read manifest content Log.info("Modifying AndroidManifest.xml") with open(manifest.file_path(), "r") as fd: manifest_content = fd.read() # look for <application> and modify debuggable new_manifest_content = "" for line in manifest_content.split("\n"): if "<application" in line: if "debuggable" in line: line = line.replace("android:debuggable=\"false\"", "android:debuggable=\"true\"") else: line_split = line.split(">", 1) line = "{} android:debuggable=\"true\">{}".format( line_split[0], line_split[1]) new_manifest_content = "{}\n{}".format(new_manifest_content, line) new_manifest_content = "\n".join(new_manifest_content.split("\n")[1:]) # overwrite the manifest with the new content with open(manifest.file_path(), "w") as fd: fd.write(new_manifest_content) # recompiled the application Log.info("Re-compiling application") output = recompile(self.decompiled_apk, debuggable_apk) if "Exception" in output: return { "print": "Failed to re-compile the application:\n{}".format(output) } # signing the application sign_module, signed_apk = SignApkModule(), None sign_module.recompiled_apk = debuggable_apk sign_module.output = self.output sign_result = sign_module.run() for key in sign_result: if key.endswith("_signed"): signed_apk = sign_result[key] if signed_apk: execute("mv {} {}".format(signed_apk, debuggable_apk)) # install the application if self.install: Log.info("Uninstalling previously installed application") self.device.uninstall(identifier) Log.info("Installing new debuggable application") self.device.install(debuggable_apk) return { "{}_debuggable".format(identifier): debuggable_apk, "print": "Application re-compiled and signed to {}".format(debuggable_apk) }
def __init__(self): """ Creates a module object and checks if the required variables and methods are defined """ # check for meta and options variables first REQUIRED_VARIABLES = ["meta", "options"] for variable in REQUIRED_VARIABLES: if not hasattr(self, variable): raise MissingFieldException( "Missing the `{}` variable when defining a module".format( variable)) # check for requires methods REQUIRED_METHODS = ["run", "validate_options"] for method in REQUIRED_METHODS: if not hasattr(self, method): raise MissingFieldException( "Missing the `{}` method when defining a module".format( method)) # check if meta variable has all the necessary values and are valid REQUIRED_META_FIELDS = { "author": str, "description": str, "certainty": int } for field in REQUIRED_META_FIELDS: # check if any missing fields if field not in self.meta: raise MisconfiguredVariable( "Field `{}` is missing from the `meta` variable".format( field)) # check if values are valid if not isinstance(self.meta[field], REQUIRED_META_FIELDS[field]): from scrounger.utils.general import execute execute("echo Meta: {} >> /tmp/debug.log".format(self.meta)) raise MisconfiguredVariable( "Metafield `{}` is not of type `{}`".format( field, REQUIRED_META_FIELDS[field])) # check if certainty is > 0 and <= 100 if self.meta["certainty"] < 0 or self.meta["certainty"] > 100: raise MisconfiguredVariable( "Certainty must be a value between 0 and 100") # check if additional options are valid required_fields = ["name", "description", "required", "default"] for option in self.options: for field in required_fields: if field not in option: raise MisconfiguredVariable( "Field `{}` not found in option `{}`".format( field, option)) # check if var name has spaces if " " in option["name"]: raise MisconfiguredVariable( "Option `{}` contains spaces".format(option["name"])) self._init_called = True