def run(self): result = { "title": "Application Does Not Check For Third-Party Keyboards", "details": "", "severity": "Medium", "report": False } symb_module = SymbolsModule() symb_module.binary = self.binary symbols_result, symbols = symb_module.run(), None for key in symbols_result: if key.endswith("_symbols"): symbols = symbols_result[key] if not symbols: return {"print": "Couldn't get symbols from binary."} Log.info("Analysing Symbols") if not re.search(self._regex, symbols): result.update({ "report": True, "details": "No evidence of third party keyboard detection \ functions found." }) return {"{}_result".format(self.name()): result}
def run(self): result = { "title": "Application Was Compiled Without ARC Support", "details": "", "severity": "Medium", "report": False } symb_module = SymbolsModule() symb_module.binary = self.binary symbols_result, symbols = symb_module.run(), None for key in symbols_result: if key.endswith("_symbols"): symbols = symbols_result[key] if not symbols: return {"print": "Couldn't get symbols from binary."} Log.info("Analysing Symbols") if not re.search(self._regex, symbols): result.update({ "report": True, "details": "No evidence of ARC functions found." }) return {"{}_result".format(self.name()): result}
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("Preparing to re-compile the application") # get identifier manifest_module = ManifestModule() manifest_module.decompiled_apk = self.decompiled_apk self.manifest = manifest_module.run() if "print" not in self.manifest: identifier = self.manifest.popitem()[1].package() recompiled_apk = "{}/{}-recompiled.apk".format(self.output, identifier) # unzip Log.info("Re-compiling application") output = recompile(self.decompiled_apk, recompiled_apk) if "Exception" in output: return { "print": "Failed to re-compile the application:\n{}".format(output) } return { "{}_recompiled".format(identifier): recompiled_apk, "print": "Application re-compiled to {}".format(recompiled_apk) }
def run(self): result = { "title": "Application Uses Deprecated Ciphers", "details": "", "severity": "Low", "report": False } # preparing variable to run ciphers = {} ignore = [filepath.strip() for filepath in self.ignore.split(";")] Log.info("Identifying smali directories") dirs = smali_dirs(self.decompiled_apk) Log.info("Analysing application's smali code") for directory in dirs: smali = "{}/{}".format(self.decompiled_apk, directory) #ssl_socket_files = pretty_grep(self.file_regex, smali) ciphers.update(pretty_grep(self.regex, smali)) if ciphers: result.update({ "report": True, "details": pretty_grep_to_str(ciphers, self.decompiled_apk, ignore) }) return {"{}_result".format(self.name()): result}
def run(self): result = { "title": "Application Uses Native Libraries", "details": "", "severity": "Low", "report": False } Log.info("Identifying application's libraries") libs_path = [lib.strip() for lib in self.libs.split(";")] libraries = [] if self.device.installed(self.identifier): app_path = self.device.packages()[self.identifier].rsplit("/", 1)[0] libs_path_str = " ".join( ["{}/{}".format(app_path, lib) for lib in libs_path]) libraries = [ lib for lib in self.device.root_execute("ls {}".format( libs_path_str)).split("\n") if lib and "No such file or directory" not in lib ] if libraries: result.update({ "report": True, "details": "* {}".format("\n* ".join(libraries)) }) return {"{}_result".format(self.name()): result}
def _start_connection(self): """ Starts an SSH connection to the remote device """ from scrounger.utils.ssh import SSHClient from scrounger.utils.general import process from scrounger.utils.config import SSH_COMMAND_TIMEOUT from scrounger.utils.config import SSH_SESSION_TIMEOUT # setup if not self._ssh_session: # this was breaking #self._iproxy_process = process( # 'iproxy 2222 22 {}'.format(self._device_id)) self._iproxy_process = process("iproxy 2222 22") self._ssh_session = SSHClient("127.0.0.1", 2222, self._username, self._password, SSH_COMMAND_TIMEOUT) self._ssh_session.connect() # Log a new sessions _Log.debug("new ssh session started.") # start a tiemout for the connection from threading import Timer if self._timer: self._timer.cancel() # cancel old timer and start new one self._timer = Timer(SSH_SESSION_TIMEOUT, self._stop_connection) self._timer.start()
def run(self): result = { "title": "Application Has Browsable Activities", "details": "", "severity": "Informational", "report": False } # create manifest manifest_module = ManifestModule() manifest_module.decompiled_apk = self.decompiled_apk self.manifest = manifest_module.run() if "print" in self.manifest: return {"print": "Could not get the manifest"} self.manifest = self.manifest.popitem()[1] Log.info("Getting browsable activities and uris") browsable_classes = self.manifest.browsable_activities() browsable_uris = self.manifest.browsable_uris() if browsable_classes or browsable_uris: details = "* URIs:\n * {}".format("\n * ".join(browsable_uris)) details += "\n\n* Classes:\n * {}".format( "\n * ".join(browsable_classes)) result.update({"report": True, "details": details}) return { "{}_result".format(self.name()): result, "{}_browsable_classes".format(self.manifest.package()): browsable_classes, "{}_browsable_uris".format(self.manifest.package()): browsable_uris }
def run(self): result = { "title": "Application Does Not Implement SSL Pinning", "details": "", "severity": "Medium", "report": False } Log.info("Getting application's strings") strs = strings(self.binary) Log.info("Analysing strings and class dump") matches = re.findall(self._regex, strs) evidence = pretty_grep(self._regex, self.class_dump) if matches: result.update({ "report": True, "details": "The following strings were found:\n* {}".format("\n* ".join( sorted(set(matches)))) }) if evidence: result.update({ "report": True, "details": "{}\nThe following was found in the class dump:\n\ {}".format(result["details"], pretty_grep_to_str(evidence, self.class_dump)) }) return {"{}_result".format(self.name()): 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): result = { "title": "Application Has Insecure ATS Configurations", "details": "", "severity": "Medium", "report": False } info_content = plist(self.info) Log.info("Parsing Info.plist file contents") ats_xml = plist_dict_to_xml(info_content, self._ats_key) Log.info("Analysing Info.plist file") if self._ats_key not in info_content or not info_content[ self._ats_key]: result.update({ "report": True, "details": "No evidence of ATS being implemented found." }) if any(option in ats_xml for option in self._insecure_options): result.update({ "report": True, "details": "The following insecure ATS configuration was \ found : {}".format(ats_xml) }) return {"{}_result".format(self.name()): result}
def run(self): result = { "title": "Application Does Not Implement Root Detection", "details": "", "severity": "High", "report": True } # preparing variable to run root_detection = {} ignore = [filepath.strip() for filepath in self.ignore.split(";")] Log.info("Identifying smali directories") dirs = smali_dirs(self.decompiled_apk) Log.info("Analysing application's smali code") for directory in dirs: smali = "{}/{}".format(self.decompiled_apk, directory) root_detection.update(pretty_grep(self.regex, smali)) if root_detection: result = { "title": "Application Has Root Detection", "details": pretty_grep_to_str(root_detection, self.decompiled_apk, ignore), "severity": "Low", "report": True } return {"{}_result".format(self.name()): result}
def run(self): result = { "title": "Application Does Not Check For Third-Party Keyboards", "details": "", "severity": "Low", "report": False } # preparing variable to run third_party_keyboard_evidence = {} ignore = [filepath.strip() for filepath in self.ignore.split(";")] Log.info("Identifying smali directories") dirs = smali_dirs(self.decompiled_apk) Log.info("Analysing application's smali code") for directory in dirs: smali = "{}/{}".format(self.decompiled_apk, directory) third_party_keyboard_evidence.update(pretty_grep( self.regex, smali)) # remove ignored paths to_remove = [] for filename in third_party_keyboard_evidence: if any(filepath in filename for filepath in ignore): to_remove += [filename] for filename in to_remove: third_party_keyboard_evidence.pop(filename) if not third_party_keyboard_evidence: result.update({"report": True}) return {"{}_result".format(self.name()): result}
def run(self): result = { "title": "Application Does Not Use Prepared Statements", "details": "", "severity": "Low", "report": False } symb_module = SymbolsModule() symb_module.binary = self.binary symbols_result, symbols = symb_module.run(), None for key in symbols_result: if key.endswith("_symbols"): symbols = symbols_result[key] if not symbols: return {"print": "Couldn't get symbols from binary."} Log.info("Analysing Symbols") sqlite_matches = re.findall(self._sqlite_regex, symbols) matches = re.findall(self._regex, symbols) if sqlite_matches and not matches: result.update({ "report": True, "details": "Evidences of SQLite being used were found but no \ evidence of prepared statements being used was found." }) return {"{}_result".format(self.name()): result}
def run(self): Log.info("Installing binaries") binaries_to_install = self.binaries.split(";") packages_to_install = self.packages.split(";") repositories_to_install = self.repositories.split(";") binaries_local_path = "{}/bin/ios".format(_SCROUNGER_HOME) for binary in binaries_to_install: installed = self.device.install_binary("{}/{}".format( binaries_local_path, binary)) if not installed: Log.error("Could not install {}".format(binary)) scrounger_apt_list = "/etc/apt/sources.list.d/scrounger.list" repositories_list = " ".join(self.device.repositories()) Log.info("Adding repositories") for repository in repositories_to_install: if repository not in repositories_list: Log.info("Adding {} repository".format(repository)) if not repository.endswith("/"): repository = "{}/".format(repository) self.device.execute("echo deb {} ./ >> {}".format( repository, scrounger_apt_list)) self.device.execute("apt update") packages = " ".join(packages_to_install) Log.info("Trying to install {}".format(packages)) self.device.execute( "apt -y --allow-unauthenticated install {}".format(packages)) return { "print": "Binaries installed." }
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 _start_connection(self): """ Starts an SSH connection to the remote device """ from scrounger.utils.ssh import SSHClient from scrounger.utils.config import SSH_COMMAND_TIMEOUT from scrounger.utils.config import SSH_SESSION_TIMEOUT from scrounger.utils.config import _SCROUNGER_HOME from scrounger.lib.tcprelay import create_server # setup if not self._ssh_session: self._relay_process = create_server() self._ssh_session = SSHClient("127.0.0.1", 2222, self._username, self._password, SSH_COMMAND_TIMEOUT) self._ssh_session.connect() # Log a new sessions _Log.debug("new ssh session started.") # add scrounger's key key_path = "{}/bin/ios/scrounger.pub".format(_SCROUNGER_HOME) if not self._ssh_session.add_key(key_path): _Log.debug("Scrounger's ssh key not in authorized_keys") # start a tiemout for the connection from threading import Timer if self._timer: self._timer.cancel() # cancel old timer and start new one self._timer = Timer(SSH_SESSION_TIMEOUT, self._stop_connection) self._timer.start()
def run(self): result = { "title": "Application Does Not Disable Clipboard Access", "details": "", "severity": "Medium", "report": False } symb_module = SymbolsModule() symb_module.binary = self.binary symbols_result, symbols = symb_module.run(), None for key in symbols_result: if key.endswith("_symbols"): symbols = symbols_result[key] if not symbols: return {"print": "Couldn't get symbols from binary."} Log.info("Analysing Symbols") if not re.search(self._regex, symbols): result.update({ "report": True, "details": "No evidence of the application trying to disable \ clipboard access." }) return {"{}_result".format(self.name()): result}
def run(self): result = { "title": "Application Communicates Over Unencrypted Channels", "details": "", "severity": "Medium", "report": False } ignore = [url.strip() for url in self.ignore.split(";")] Log.info("Getting application's strings") strs = strings(self.binary) Log.info("Analysing strings") report_matches = [] matches = re.finditer(self._regex, strs) for item in matches: match = item.group() if any(iurl in match for iurl in ignore) or match == "http://": continue report_matches += [match] if report_matches: result.update({ "report": True, "details": "The following strings were found:\n* {}".format("\n* ".join( sorted(set(report_matches)))) }) return {"{}_result".format(self.name()): result}
def run(self): result = { "title": "Application Makes Insecure Function Calls", "details": "", "severity": "Medium", "report": False } symb_module = SymbolsModule() symb_module.binary = self.binary symbols_result, symbols = symb_module.run(), None for key in symbols_result: if key.endswith("_symbols"): symbols = symbols_result[key] if not symbols: return {"print": "Couldn't get symbols from binary."} Log.info("Analysing Symbols") matches = re.findall(self.function_calls, symbols) if matches: result.update({ "report": True, "details": "The following function symbols were \ found: * {}".format("\n* ".join(sorted(set(matches)))) }) return {"{}_result".format(self.name()): result}
def run(self): result = { "title": "Application Has World Writable Files", "details": "", "severity": "Medium", "report": False } if not self.device.installed(self.identifier): return {"print": "Application not installed"} Log.info("Starting the application") self.device.start(self.identifier) sleep(5) Log.info("Analysing application's data") target_paths = self.device.data_paths(self.identifier) world_writable_files = [] for data_path in target_paths: world_writable_files += self.device.world_files(data_path, "w") if world_writable_files: result.update({ "report": True, "details": "* World Writable Files:\n * {}".format( "\n* ".join(world_writable_files)) }) return {"{}_result".format(self.name()): result}
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): result = { "title": "Application Has Secret Codes", "details": "", "severity": "Low", "report": False } # create manifest manifest_module = ManifestModule() manifest_module.decompiled_apk = self.decompiled_apk self.manifest = manifest_module.run() if "print" in self.manifest: return {"print": "Could not get the manifest"} self.manifest = self.manifest.popitem()[1] Log.info("Analysing application's manifest for secret codes") secret_codes = self.manifest.secret_codes() if secret_codes: details = "* Secret Codes:\n * {}".format( "\n * ".join(secret_codes)) details += """ * Test the secret codes using the following command: adb shell su -c "am broadcast -a {} -d android_secret_code://CODE" """.format(self._secret_code_activity) result.update({"report": True, "details": details}) return { "{}_result".format(self.name()): result, "{}_secret_codes".format(self.manifest.package()): secret_codes }
def run(self): result = { "title": "Application Does Not Check If A Passcode Is Set", "details": "", "severity": "Low", "report": False } symb_module = SymbolsModule() symb_module.binary = self.binary symbols_result, symbols = symb_module.run(), None for key in symbols_result: if key.endswith("_symbols"): symbols = symbols_result[key] if not symbols: return {"print": "Couldn't get symbols from binary."} Log.info("Analysing Symbols") matches = re.findall(self._regex, symbols) if not matches: result.update({ "report": True, "details": "No evidence of checking for passcode set found." }) return {"{}_result".format(self.name()): result}
def run(self): Log.info("Preparing to sign the application") binaries_local_path = "{}/bin/android".format(_SCROUNGER_HOME) signjar = "{}/signapk.jar".format(binaries_local_path) key = "{}/key.pk8".format(binaries_local_path) cert = "{}/cert.x509.pem".format(binaries_local_path) # get identifier identifier = self.recompiled_apk.rsplit("/",1)[-1].rsplit(".", 1)[0] signed_apk = "{}/{}-signed.apk".format(self.output, identifier) # sign Log.info("Signinging application") output = sign(self.recompiled_apk, signed_apk, signjar, cert, key) if output and "Exception" in output: return { "print": "Failed to sign the application:\n{}".format(output) } return { "{}_signed".format(identifier): signed_apk, "print": "Application signed to {}".format(signed_apk) }
def run(self): result = { "title": "Application Uses Weak Random Functions", "details": "", "severity": "Low", "report": False } symb_module = SymbolsModule() symb_module.binary = self.binary symbols_result, symbols = symb_module.run(), None for key in symbols_result: if key.endswith("_symbols"): symbols = symbols_result[key] if not symbols: return {"print": "Couldn't get symbols from binary."} Log.info("Analysing Symbols") matches = re.findall(self._regex, symbols) if matches: result.update({ "report": True, "details": "The following evidence were found:\n* {}".format("\n* ".join( sorted(set(matches)))) }) return {"{}_result".format(self.name()): result}
def run(self): result = { "title": "Application's Providers Are Vulnerable To Path Traversal", "details": "", "severity": "High", "report": False } Log.info("Extracting and translating providers") providers = parsed_providers(self.decompiled_apk) Log.info("Analysing providers") vulnerable_providers = [] example_result = None for provider in providers: exec_result = self.device.read_provider(provider, self.exploit_path) if exec_result and "Exception" not in exec_result: vulnerable_providers += [provider] example_result = exec_result if vulnerable_providers: details = "* Vulnerable Providers:\n* {}".format( "\n* ".join(vulnerable_providers)) details += "\n\nAn example of exploitation success:\n{}".format( example_result) result.update({"report": True, "details": details}) return {"{}_result".format(self.name()): result}
def run(self): result = { "title": "Application Allows Backups", "details": "", "severity": "Low", "report": False } # create manifest manifest_module = ManifestModule() manifest_module.decompiled_apk = self.decompiled_apk manifest = manifest_module.run() if "print" in manifest: return {"print": "Could not get the manifest"} manifest = manifest.popitem()[1] Log.info("Analysing application's manifest") if manifest.allow_backup(): result.update({"report": True}) return { "{}_result".format(self.name()): result }
def run(self): result = { "title": "Application Allows Files To Be Backed Up", "details": "", "severity": "Low", "report": False } Log.info("Checking if the application is installed") installed_apps = self.device.apps() if self.identifier not in installed_apps: return {"print": "Application not installed."} remote_data_path = installed_apps[self.identifier]["data"] files = self.device.find_files(remote_data_path) report_files = [] for file_path in files: if file_path: protection = self.device.backup_flag(file_path) if protection and "0" in protection: report_files += [file_path] if report_files: result.update({ "report": True, "details": "The following files were found to have the \ backup flag:\n* {}".format("\n* ".join(report_files)) }) return {"{}_result".format(self.name()): result}
def run(self): result = { "title": "Application Logs To Syslog", "details": "", "severity": "Low", "report": True } Log.info("Getting executable's name") info_module = InfoModule() info_module.identifier = self.identifier info_module.device = self.device info_result, info = info_module.run(), None for key in info_result: if key.endswith("_info"): info = info_result[key] if not info: return {"print": "Couldn't get Info from device."} executable_name = info["CFBundleExecutable"] Log.info("Getting application's logs") logs = self.device.logs(executable_name) if logs: result.update({ "report": True, "details": "The following logs were found:\n{}".format(logs) }) return {"{}_result".format(self.name()): result}