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 += 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 extract_providers(decompiled_app_path): """ Extracts provider paths from a decompiled app directory using grep :param str decompiled_app_path: the directory where to look for the providers :return: a sorted list of proviers """ from scrounger.utils.general import pretty_grep import re providers_regex = r"content://[a-zA-Z0-1.-@/]+" providers = [] grep_result = pretty_grep(providers_regex, decompiled_app_path) for filename in grep_result: for finding in grep_result[filename]: # needs regex search since grep returns the whole line provider_path = re.search(providers_regex, finding["details"]).group().split( "://", 1)[-1].strip() # make sure that every provider follows a standard and has no / # in the end if provider_path.endswith("/"): provider_path = provider_path[:-1] # TODO: translate @string to value providers.append(provider_path) # creates a set to make sure there are no duplicates and returns a sorted # list return sorted(set(providers))
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): 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 string(string_variable, resources_strings_xml_file): """ Looks for a string variable in the resources files :param str string_variable: the string variable to look for :param str resources_strings_xml_file: the strings.xml file to look for the string variable :return: the string value of the variable or `string_variable` if not found """ from scrounger.utils.general import pretty_grep #replace @string if in variable name string_variable = string_variable.replace("@string/", "") grep_result = pretty_grep(string_variable, resources_strings_xml_file) # if variable was not found if len(grep_result) == 0: return string_variable # get the string from grep result string = grep_result.popitem()[1][0]["details"] # get the string between tags return string.split(">", 1)[-1].split("<", 1)[0]
def public_resource(decompiled_app_path, resource_id): """ Looks for strings reference for the resource :param str decompiled_app_path: the directory with the decompiled app :param str resource_id: the resource to look for :return: a str witht he resource or the str with the resource id """ from scrounger.utils.general import pretty_grep # public xml file public_xml = "{}/res/values/public.xml".format(decompiled_app_path) grep_result = pretty_grep(resource_id, public_xml) # if variable was not found if len(grep_result) == 0: return resource_id # get the string from grep result string = grep_result.popitem()[1][0]["details"] # get the string between tags # <public type="string" name="action_update" id="0x7f0c0015" /> return string.split("name=\"", 1)[-1].split("\" ", 1)[0]
def run(self): result = { "title": "Application Does Not Implement SSL Pinning", "details": "", "severity": "High", "report": False } # preparing variable to run ssl_keywords = {} 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 for SSL evidences") for directory in dirs: smali = "{}/{}".format(self.decompiled_apk, directory) ssl_keywords.update(pretty_grep(self.regex, smali)) if not ssl_keywords: result.update({ "report": True, "details": "Found no evidences of a `TrustManager`." }) Log.info("Analysing SSL evidences") for filename in ssl_keywords: if any(filepath in filename for filepath in ignore): continue with open(filename, "r") as fp: smali = fp.read() if re.search(self.mock_check_server, smali): result.update({ "report": True, "details": "{}\n* {}:{}\n".format( result["details"], filename.replace(self.decompiled_apk, ""), extract_smali_method("checkServerTrusted", filename)) }) if re.search(self.mock_accepted_issuers, smali): result.update({ "report": True, "details": "{}\n* {}:{}\n".format( result["details"], filename.replace(self.decompiled_apk, ""), extract_smali_method("getAcceptedIssuers", filename)) }) return {"{}_result".format(self.name()): result}
def run(self): result = { "title": "Application Does Not Prevent Screenshots", "details": "", "severity": "Low", "report": False } # preparing variable to run report_activities = [] activities = {} 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) activities.update(pretty_grep(self.activities_regex, smali)) if activities: safe_activities = pretty_grep( self.regex, " ".join(list(activities))) report_activities = list(set(activities) - set(safe_activities)) if report_activities: result.update({ "report": True, "details": "* {}".format("\n* ".join( [activity.replace(self.decompiled_apk, "") for activity in report_activities if not any( i in activity for i in ignore) ]) ) }) return { "{}_result".format(self.name()): result }
def run(self): result = { "title": "Application's WebViews Are Vulnerable To Arbitrary \ Redirection", "details": "", "severity": "Low", "report": False } # preparing variable to run webview_files = {} overrride_url_files = {} 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) webview_files.update(pretty_grep(self.use_webview_regex, smali)) overrride_url_files.update(pretty_grep(self.regex, smali)) Log.info("Analysing WebViews") for filename in overrride_url_files: webview_files.pop(filename) if webview_files: result.update({ "report": True, "details": pretty_grep_to_str(webview_files, self.decompiled_apk, ignore) }) return {"{}_result".format(self.name()): result}
def run(self): result = { "title": "Application's WebViews Enable JavaScript", "details": "", "severity": "Medium", "report": False } # preparing variable to run filenames = {} 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) filenames.update(pretty_grep(self.regex, smali)) report = {} # check var setting for file_name in filenames: report[file_name] = [] for instance in filenames[file_name]: var_name = instance["details"].split( "}", 1)[0].split(",", 1)[-1].strip() var_setting = track_variable( var_name, instance["line"], file_name) for setting_line in var_setting: if "0x1" in setting_line["details"]: report[file_name] += var_setting for file_name in report: filenames[file_name] += report[file_name] if filenames: result.update({ "report": True, "details": pretty_grep_to_str( filenames, self.decompiled_apk, ignore) }) return { "{}_result".format(self.name()): result }
def run(self): result = { "title": "Application Does Not Delete Cached Files On Exit", "details": "", "severity": "Low", "report": False } # preparing variable to run webview_files = {} clear_cache = {} 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) webview_files.update(pretty_grep(self.use_webview_regex, smali)) clear_cache.update(pretty_grep(self.regex, smali)) Log.info("Analysing WebViews") for filename in clear_cache: webview_files.pop(filename) if webview_files: result.update({ "report": True, "details": pretty_grep_to_str(webview_files, self.decompiled_apk, ignore) }) return {"{}_result".format(self.name()): result}
def app_used_resources(decompiled_app_path, ignored, identifier=None): """ Returns the strings that correspond to the used resources :param str decompiled_app_path: the directory with the decompiled app :param list ignored: a list of paths to be ignored :param str identifier: if set it tries to identify only class names in the path equivalent to the identifier :return: list with strings """ from scrounger.utils.general import pretty_grep # prepare identifier paths identifier_paths = [] if identifier: identifier_path = identifier.replace(".", "/") while identifier_path.count("/") > 1: identifier_paths += [identifier_path] identifier_path = identifier_path.rsplit("/", 1)[0] lsmali_dirs = smali_dirs(decompiled_app_path) full_smali_path = [] for ldir in lsmali_dirs: full_smali_path += ["{}/{}".format(decompiled_app_path, ldir)] # find 0xXXXXX and look for it in xml files xml_references_regex = r"const .*0x[a-z0-9]{8}" grep_result = pretty_grep(xml_references_regex, " ".join(full_smali_path)) strings = [] # we want repeated string names for filename in grep_result: # check if path not to be ignored and filder identifier paths if not any([ignored_path in filename for ignored_path in ignored]) and \ (( identifier and \ any([id_path in filename for id_path in identifier_paths]) ) or not identifier): for finding in grep_result[filename]: resource_id = finding["details"].strip().rsplit(" ", 1)[-1] resource = public_resource(decompiled_app_path, resource_id) if resource_id != resource: strings += [resource] # return sorted classes but not unique return sorted(strings)
def app_strings(decompiled_app_path, ignored, identifier=None): """ Looks for strings in the smali code and xml files :param str decompiled_app_path: the directory with the decompiled app :param list ignored: a list of paths to be ignored :param str identifier: if set it tries to identify only class names in the path equivalent to the identifier :return: list with strings """ from scrounger.utils.general import pretty_grep # prepare identifier paths identifier_paths = [] if identifier: identifier_path = identifier.replace(".", "/") while identifier_path.count("/") > 1: identifier_paths += [identifier_path] identifier_path = identifier_path.rsplit("/", 1)[0] # grep class names from smali code string_regex = r"\".*?\"" lsmali_dirs = smali_dirs(decompiled_app_path) full_smali_path = [] for ldir in lsmali_dirs: full_smali_path += ["{}/{}".format(decompiled_app_path, ldir)] grep_result = pretty_grep(string_regex, " ".join(full_smali_path)) strings = [] # we want repeated string names for filename in grep_result: # check if path not to be ignored and filder identifier paths if not any([ignored_path in filename for ignored_path in ignored]) and \ (( identifier and \ any([id_path in filename for id_path in identifier_paths]) ) or not identifier): for finding in grep_result[filename]: # get string name name = finding["details"].split("\"")[1] strings += [name] return sorted(strings)
def run(self): result = { "title": "Application Communicates Over Unencrypted Channels", "details": "", "severity": "High", "report": False } # preparing variable to run pretty_result = "" ignore = [url.strip() for url 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) urls = pretty_grep(self.regex, smali) to_remove = [] for url in urls: for detail in urls[url]: if any(iurl in detail["details"] for iurl in ignore) or \ detail["details"] == "http://": urls[url].remove(detail) if not urls[url]: to_remove += [url] for filename in to_remove: urls.pop(filename) pretty_result += pretty_grep_to_str(urls, smali) if pretty_result: result.update({ "report": True, "details": pretty_result }) return { "{}_result".format(self.name()): result }
def class_names(decompiled_app_path, ignored, identifier=None): """ Looks for class names from the smali code :param str decompiled_app_path: the directory with the decompiled app :param list ignored: a list of paths to be ignored :param str identifier: if set it tries to identify only class names in the path equivalent to the identifier :return: list with class names """ from scrounger.utils.general import pretty_grep # prepare identifier paths identifier_paths = [] if identifier: identifier_path = identifier.replace(".", "/") while identifier_path.count("/") > 1: identifier_paths += [identifier_path] identifier_path = identifier_path.rsplit("/", 1)[0] # grep class names from smali code class_regex = r"\.class.*L.*" grep_result = pretty_grep(class_regex, decompiled_app_path) classes = [] # we want repeated class names for filename in grep_result: # check if path not to be ignored and filder identifier paths if not any([ignored_path in filename for ignored_path in ignored]) and \ (( identifier and \ any([id_path in filename for id_path in identifier_paths]) ) or not identifier): for finding in grep_result[filename]: # get class name name = finding["details"].rsplit("/", 1)[-1].rsplit(";", 1)[0] classes += [name] # return sorted classes but not unique return sorted(classes)
def run(self): result = { "title": "Application's WebViews Implement Javascript Bridges", "details": "", "severity": "Medium", "report": False } # preparing variable to run bridges = {} 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) bridges.update(pretty_grep(self.js_interface_regex, smali)) report = {} # check var setting for file_name in bridges: report[file_name] = [] for instance in bridges[file_name]: report[file_name] += method_name(instance["line"], file_name) bridges = {} # TODO: check this works for file_name in report: bridges[file_name] += report[file_name] if bridges: result.update({ "report": True, "details": pretty_grep_to_str(bridges, self.decompiled_apk, ignore) }) return {"{}_result".format(self.name()): result}
def run(self): result = { "title": "Application Communicates Over Insecure Channels", "details": "", "severity": "Medium", "report": False } Log.info("Getting application's strings") strs = strings(self.binary) Log.info("Analysing strings and class dump") if not re.search(self._regex, strs) and \ not pretty_grep(self._regex, self.class_dump): result.update({ "report": True, "details": "No evidence of secure channels being used." }) return { "{}_result".format(self.name()): result }
def run(self): result = { "title": "Application Is Vulnerable To Fragment Injection", "details": "", "severity": "Medium", "report": False } # create yaml apktool_module = YamlModule() apktool_module.decompiled_apk = self.decompiled_apk apktool = apktool_module.run() if "print" in apktool: return {"print": "Could not get the apktool yaml file"} apktool = apktool.popitem()[1] # preparing variable to run activities = {} 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 apktool yaml and smali") for directory in dirs: smali = "{}/{}".format(self.decompiled_apk, directory) activities.update(pretty_grep(self.regex, smali)) if activities and int(apktool.min_sdk()) < 18: result.update({ "report": True, "details": pretty_grep_to_str(activities, self.decompiled_apk, ignore) }) return {"{}_result".format(self.name()): result}
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)) }) if self.device and self.identifier and \ self.proxy_host != None and self.proxy_port != None: Log.info("Testing SSL Pinning using a proxy") Log.info( "Make sure your device trusts the CA in: {}/ca.crt".format( _CERT_PATH)) Log.info("Waiting for {} seconds to allow time to setup the \ proxy on the remote device".format(self.wait_time)) sleep(int(self.wait_time)) Log.info("Killing the application") self.device.stop(self.identifier) Log.info("Starting the SSL proxy") proxy_server = create_server(self.proxy_host, self.proxy_port, _CERT_PATH) Log.info("Starting the Application") self.device.start(self.identifier) Log.info("Waiting for the Application to start and make requests") sleep(10) pinned = list( set(proxy_server.server.connected) - set(proxy_server.server.requested)) if not proxy_server.server.connected: Log.error("No connections made by the application") if pinned: result.update({ "report": True, "details": "{}\n\nThe application started a connection but \ made no requests to the following domains:\n* {}".format( result["details"], "\n* ".join(pinned)) }) proxy_server.stop() return {"{}_result".format(self.name()): result}
def run(self): from time import sleep result = { "title": "Application Does Not Implement SSL Pinning", "details": "", "severity": "High", "report": False } # preparing variable to run ssl_keywords = {} 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 for SSL evidences") for directory in dirs: smali = "{}/{}".format(self.decompiled_apk, directory) ssl_keywords.update(pretty_grep(self.regex, smali)) if not ssl_keywords: result.update({ "report": True, "details": "Found no evidences of a `TrustManager`." }) Log.info("Analysing SSL evidences") for filename in ssl_keywords: if any(filepath in filename for filepath in ignore): continue with open(filename, "r") as fp: smali = fp.read() if re.search(self.mock_check_server, smali): result.update({ "report": True, "details": "{}\n* {}:{}\n".format( result["details"], filename.replace(self.decompiled_apk, ""), extract_smali_method("checkServerTrusted", filename)) }) if re.search(self.mock_accepted_issuers, smali): result.update({ "report": True, "details": "{}\n* {}:{}\n".format( result["details"], filename.replace(self.decompiled_apk, ""), extract_smali_method("getAcceptedIssuers", filename)) }) if self.device and self.identifier and \ self.proxy_host != None and self.proxy_port != None: Log.info("Testing SSL Pinning using a proxy") Log.info( "Make sure your device trusts the CA in: {}/ca.crt".format( _CERT_PATH)) Log.info("Waiting for {} seconds to allow time to setup the \ proxy on the remote device".format(self.wait_time)) sleep(int(self.wait_time)) Log.info("Killing the application") self.device.stop(self.identifier) Log.info("Starting the SSL proxy") proxy_server = create_server(self.proxy_host, self.proxy_port, _CERT_PATH) Log.info("Starting the Application") self.device.start(self.identifier) Log.info("Waiting for the Application to start and make requests") sleep(10) pinned = list( set(proxy_server.server.connected) - set(proxy_server.server.requested)) if not proxy_server.server.connected: Log.error("No connections made by the application") if pinned: result.update({ "title": "Application Implements SSL Pinning", "report": True, "details": "{}\n\nThe application started a connection but \ made no requests to the following domains:\n* {}".format( result["details"], "\n* ".join(pinned)) }) proxy_server.stop() return {"{}_result".format(self.name()): result}
def run(self): result = { "title": "Application Does Not Detect Emulators", "details": "", "severity": "Medium", "report": True } # preparing variable to run emulator_detection = {} ignore = [filepath.strip() for filepath in self.ignore.split(";")] Log.info("Identifying smali directories") dirs = smali_dirs(self.decompiled_apk) Log.info("Analysing smali code for emulator detection mechanisms") for directory in dirs: smali = "{}/{}".format(self.decompiled_apk, directory) emulator_detection.update(pretty_grep(self.regex, smali)) if emulator_detection: result = { "title": "Application Detects Emulators", "details": "{}\n\n{}".format( result["details"], pretty_grep_to_str(emulator_detection, self.decompiled_apk, ignore)), "severity": "Medium", "report": True } # dynamic testing Log.info("Checking requirements for dynamic testing") if hasattr(self, "apk") and hasattr(self, "avd") and \ hasattr(self, "identifier") and self.identifier and \ self.apk and self.avd: # get available devices before starting the emulator available_devices = devices() # start emulator Log.info("Starting the emulator") emulator_process = process("emulator -avd {}".format(self.avd)) # wait for emulator to start sleep(60) # diff devices -> get emulator emulator_id = list(set(devices()) - set(available_devices)) if len(emulator_id) != 1: Log.warn("Could not find the emulator in the device list") emulator_process.kill() return { "{}_result".format(self.name()): result, "print": "Coud not start emulator or find defined avd" } device = AndroidDevice(emulator_id) Log.info("Installing the apk in the device") device.install(self.apk) if device.installed(self.identifier): while not device.unlocked(): Log.info("Please unlock the emulator") sleep(5) Log.info("Starting the application") device.start(identifier) sleep(15) if self.identifier not in device.processes(): result.update({"report": False}) emulator_process.kill() return {"{}_result".format(self.name()): result}
def run(self): result = { "title": "Application Does Not Detect Debuggers", "details": "", "severity": "Medium", "report": False } ignore = [filepath.strip() for filepath in self.ignore.split(";")] Log.info("Identifying smali directories") dirs = smali_dirs(self.decompiled_apk) Log.info("Looking for evidence in smali code") debug_evidence = {} for directory in dirs: smali = "{}/{}".format(self.decompiled_apk, directory) debug_evidence.update(pretty_grep(self.debug_regex, smali)) if debug_evidence: result.update({ "title": "Application Destects Debuggers", "report": True, "details": "The following evidence was found in the smali \ code:\n{}".format( pretty_grep_to_str(debug_evidence, self.decompiled_apk, ignore)) }) else: result.update({ "details": "No evidence of debug detection was found in the \ smali code.", "report": True }) if self.repackage: Log.info("Trying to modify the application to be debuggable") # make the application debuggable debug_module = DModule() debug_module.decompiled_apk = self.decompiled_apk debug_module.device = self.device debug_module.output = None # will default to /tmp debug_module.install = True debug_module.run() Log.info("Starting the application and identifying the process ID") self.device.start(self.identifier) pid = self.device.pid(self.identifier) if pid: Log.info("Forwarding local ports") forward(54321, pid) Log.info("Starting JDB") jdb = JDB("127.0.0.1", 54321) if not jdb.running(): result.update({ "report": True, "details": "{}\n\nScrounger was unable to attach a debugger\ to the application.".format(result["details"]) }) else: result.update({ "report": True, "details": "{}\n\nScrounger was able to attach a debugger \ to the application:\n\n{}".format(result["details"], jdb.read()) }) Log.info("Removing forwarded ports and exiting jdb") remove_forward() jdb.exit() else: Log.error("The application is not running") return {"{}_result".format(self.name()): result}