def module_run(self, verbose=False): logs = "" vulnerable = False providers = self.get_providers() z = self.apk.vm_analysis.tainted_packages.search_methods( ".", "addURI", ".") for p in z: method = self.apk.dalvik_vm_format.get_method_by_idx( p.get_src_idx()) if method.get_code() is None: continue mx = self.apk.vm_analysis.get_method(method) ms = decompile.DvMethod(mx) try: ms.process() except AttributeError as e: self.warning( "Error while processing disassembled Dalvik method: %s" % e.message) source = ms.get_source() matches = re.findall(r'addURI\("([^"]*)", "([^"]*)"', source) for match in matches: for provider in providers: if provider["authorities"] == match[0]: provider["uris"].add("uri://%s/%s" % (match[0], match[1])) for provider in providers: provider["uris"] = list(provider["uris"]) if provider["exported"] and provider["permission"] is None and provider["read_permission"] is None\ and provider["write_permission"] is None: #content was introduced in honeycomb if self.avd is not None and self.avd.target >= 16: for uri in provider["uris"]: logs += "$ adb shell content query --uri %s\n" % uri logs += self.avd.shell("content query --uri %s" % uri) provider["vulnerable"] = True vulnerable = True else: provider["vulnerable"] = False if verbose: print logs return { "results": providers, "vulnerabilities": [ framework.Vulnerability( "Exported content provider.", "The following application provider is exported, which means that any application can access it" " without the need for any custom permission.", framework.Vulnerability.MEDIUM, resources=[p for p in providers if p["vulnerable"]], logs=logs).__dict__ ] if vulnerable else [] }
def module_run(self, verbose=False): logs = "" receivers = self.get_receivers() vulnerable = False #for each action by categories, send intent and see what happen for receiver in receivers: receiver["vulnerable"] = False if receiver["exported"] and receiver["permission"] is None: receiver["vulnerable"] = True #1. Get exposed broadcast receivers from results if self.avd is not None: for intent in receiver["intent_filters"]: if intent['category'] is not None: output = self.avd.shell("am broadcast -a %s -c %s -n %s/%s" % (intent['action'], intent['category'], self.apk.get_package(), receiver["name"])) else: output = self.avd.shell("am broadcast -a %s -n %s/%s" % (intent['action'], self.apk.get_package(), receiver["name"])) if "Broadcast completed" not in output: logs += "$ adb shell am broadcast -a %s -c %s -n %s/%s\n%s\n" % \ (intent['action'], intent["category"], self.apk.get_package(), receiver["name"], output) else: receiver["vulnerable"] = False if not len(receiver["intent_filters"]) and self.avd is not None: for category in categories: for action in actions: #2. Fuzz receivers with a set of intents (Null intents, malformed, ...) output = self.avd.shell("am broadcast -a %s -c %s -n %s/%s" % (action, category, self.apk.get_package(), receiver["name"])) if "Broadcast completed" not in output: print output logs += "$ adb shell am broadcast -a %s -c %s -n %s/%s\n%s\n" % \ (action, category, self.apk.get_package(), receiver["name"], output) receiver["vulnerable"] = True if receiver["vulnerable"] is True: vulnerable = True return { "results": receivers, "vulnerabilities": [ framework.Vulnerability( "Unprotected broadcast receiver.", "The following broadcast receivers were found to be vulnerable.", framework.Vulnerability.LOW, resources=[r for r in receivers if r["vulnerable"]], logs=logs ).__dict__] if vulnerable else [] }
def module_run(self, verbose=False): results = [] z = self.apk.vm_analysis.tainted_packages.search_methods( ".", "rawQuery", ".") z += self.apk.vm_analysis.tainted_packages.search_methods( ".", "query", ".") for p in z: method = self.apk.dalvik_vm_format.get_method_by_idx( p.get_src_idx()) if method.get_code() is None: continue mx = self.apk.vm_analysis.get_method(method) if self.apk.get_package() in method.get_class_name().replace( "/", "."): ms = decompile.DvMethod(mx) try: ms.process() except AttributeError as e: self.warning( "Error while processing disassembled Dalvik method: %s" % e.message) if method.get_class_name()[1:-1] not in [ x["file"] for x in results ]: results.append({ "file": method.get_class_name()[1:-1], "lines": [method.get_debug().get_line_start()] }) else: for r in results: if r["file"] == method.get_class_name()[1:-1]: if method.get_debug().get_line_start( ) not in r["lines"]: r["lines"].append( method.get_debug().get_line_start()) return { "results": results, "vulnerabilities": [ framework.Vulnerability( "Multiple SQL injection vectors.", "The application do not make use of prepared statement which could lead to SQL injection vulnerabilities.\n" "Review the results to see if these raw queries can be exploited.", framework.Vulnerability.MEDIUM, resources=results).__dict__ ] if len(results) else [] }
def module_run(self, verbose=False): logs = "" activities = self.get_activities() vulnerable = False for activity in activities: launcher = False activity["vulnerable"] = False for intfilter in activity["intent_filters"]: if intfilter["action"] == "android.intent.action.MAIN" and \ intfilter["category"] == "android.intent.category.LAUNCHER": launcher = True if not launcher and (activity["exported"] and activity["permission"] is None): activity["vulnerable"] = True vulnerable = True if self.avd is not None: output = self.avd.shell( "am start -n %s/%s" % (self.apk.get_package(), activity["name"])) time.sleep(1) self.avd.screenshot("%s/analysis/%s/screenshots/%s.png" % (self.root_dir, self.apk.get_package(), activity["name"])) activity["screenshot"] = "screenshots/%s.png" % ( activity["name"]) logs += "$ adb shell am start -n %s/%s\n%s\n" % ( self.apk.get_package(), activity["name"], output) if "Error" in output: activity["vulnerable"] = False return { "results": activities, "vulnerabilities": [ framework.Vulnerability( "Potentially vulnerable activity components.", "The following activities were found to be vulnerable.", framework.Vulnerability.LOW, resources=[a for a in activities if a["vulnerable"]], logs=logs).__dict__ ] if vulnerable else [] }
def module_run(self, verbose=False): services = self.get_services() vulnerable = False logs = "" for service in services: service["vulnerable"] = False if service[ "exported"] and service["permission"] is None and not len( service['intent_filters']): if self.avd is not None: output = self.avd.shell( "am startservice -n %s/%s" % (self.apk.get_package(), service["name"])) logs += "$ adb shell am startservice -n %s/%s\n%s\n" % ( self.apk.get_package(), service["name"], output) if "Error: Not found; no service started." not in output: service["vulnerable"] = True vulnerable = True #force-stop was only introduced in honeycomb if self.avd.target > 12: output = self.avd.shell("am force-stop %s" % (self.apk.get_package())) logs += "$ adb shell am force-stop %s \n%s" % ( self.apk.get_package(), output) if verbose: print logs return { "results": services, "vulnerabilities": [ framework.Vulnerability( "Potentially vulnerable service component.", "The following services were found to be vulnerable.", framework.Vulnerability.LOW, resources=[s for s in services if s["vulnerable"]], logs=logs).__dict__ ] if vulnerable else [] }
def module_run(self, verbose=False): webviews = [] vulnerable = False funcs = [{ "name": "setJavaScriptEnabled", "default": False, "description": "Allows the WebView to execute Javascript." }, { "name": "setPluginsEnabled" if self.apk.get_min_sdk_version() < 8 else "setPluginState", "default": True, "description": "Allow the loading of plugins (ie. Flash)." }, { "name": "setAllowContentAccess", "default": True, "description": "WebView has access to content providers on the system." }, { "name": "setAllowFileAccess", "default": True, "description": "Allows a WebView to load content from the filesystem using file:// scheme." }, { "name": "setAllowFileAccessFromFileURLS", "default": True if self.apk.get_min_sdk_version() <= 15 else False, "description": "Allows the HTML file that was loaded using file:// scheme to access " "other files on the system" }, { "name": "setAllowUniversalAccessFromFilesURLS", "default": True if self.apk.get_min_sdk_version() <= 15 else False, "description": "Allows the HTML file that was loaded using file:// to " "access content from any origin (including other files)." }, { "name": "setSavePassword", "default": True, "description": "The WebView will save entered passwords." }] z = self.apk.vm_analysis.tainted_packages.search_packages("WebView") for p in z: method = self.apk.dalvik_vm_format.get_method_by_idx( p.get_src_idx()) if method.get_code() is None: continue if method.get_class_name()[1:-1] not in [ x["file"] for x in webviews ]: webview = { "file": method.get_class_name()[1:-1], "line": method.get_debug().get_line_start() } mx = self.apk.vm_analysis.get_method(method) ms = decompile.DvMethod(mx) try: ms.process() except AttributeError as e: self.warning( "Error while processing disassembled Dalvik method: %s" % e.message) source = ms.get_source() for func in funcs: matches = re.findall(r'%s\((.*?)\);' % func["name"], source) if len(matches) == 1: webview[func["name"]] = True if matches[ 0] == "1" or matches[0] == "true" else False else: webview[func["name"]] = func["default"] webviews.append(webview) for webview in webviews: if webview["setJavaScriptEnabled"]: vulnerable = True return { "results": webviews, "logs": "", "vulnerabilities": [ framework.Vulnerability( "Explicitly enabled Javascript in WebViews", "The application explicitly enable Javascript for multiple webviews.", framework.Vulnerability.MEDIUM, resources=webviews).__dict__ ] if vulnerable else [] }
def module_run(self, verbose=False): result = None file_list = self.apk.zip.namelist() p_find_cert = re.compile('^(META-INF/.*?(RSA$|DSA$))$') cert_found = '' for i in file_list: if p_find_cert.match(i): cert_found = p_find_cert.match(i).groups()[0] if cert_found: with open("/tmp/%s.cert" % self.apk.get_package(), "wb") as f: rsa = self.apk.get_file(cert_found) f.write(rsa) p = os.popen( "openssl pkcs7 -inform DER -in /tmp/%s.cert -out /tmp/%s.pem -outform PEM -print_certs" % (self.apk.get_package(), self.apk.get_package())) output = p.read() p.close() if not output: x509 = X509.load_cert('/tmp/%s.pem' % self.apk.get_package()) dates = re.findall( r'Not Before: ([^\n]*)\n Not After : ([^\n]*)\n', x509.as_text()) result = { "fingerprint": x509.get_fingerprint("sha1"), "issuer": x509.get_issuer().as_text(), 'not_before': dates[0][0], 'not_after': dates[0][1], "pubkey": x509.get_pubkey().get_rsa().as_pem() if cert_found.endswith("RSA") else None, "serial_number": x509.get_serial_number(), "subject": x509.get_subject().as_text(), "version": x509.get_version(), "text": x509.as_text(), "verified": x509.verify() } else: self.error("An error occured while parsing the file.") else: self.error("Certificate not found.") if verbose: print "\n%s" % result["text"] vulnerabilities = [] d = datetime.datetime.strptime(result["not_after"], "%b %d %H:%M:%S %Y %Z") t = calendar.timegm(d.timetuple()) if t < int(time.time()): vulnerabilities.append( framework.Vulnerability( "Certificate has expired.", "The application certificate has expired.", framework.Vulnerability.INFO).__dict__) if result is not None and result["verified"] is False: vulnerabilities.append( framework.Vulnerability( "Certificate is not verified.", "The application certificate could not be verified.", framework.Vulnerability.INFO).__dict__) if result is not None and "Android Debug" in result["issuer"]: vulnerabilities.append( framework.Vulnerability( "Debug certificate.", "The application has been packaged with a debug certificate.", framework.Vulnerability.INFO, logs=result["issuer"]).__dict__) if result is not None and result["verified"] is True: vulnerabilities.append( framework.Vulnerability( "Certificate is verified.", "The application certificate has been verified.", framework.Vulnerability.INFO).__dict__) return {"results": result, "vulnerabilities": vulnerabilities}
def module_run(self, verbose=False): logs = "" xml = self.apk.get_android_manifest_xml() debuggable = False for a in xml.getElementsByTagName("application"): if a.getAttribute('android:debuggable') is not None: debuggable = True if a.getAttribute( 'android:debuggable') == 'true' else False if debuggable and self.avd is not None: #launch the app activities = self.get_activities() for activity in activities: for intfilter in activity["intent_filters"]: if intfilter["action"] == "android.intent.action.MAIN" and \ intfilter["category"] == "android.intent.category.LAUNCHER": output = self.avd.shell( "am start -n %s/%s" % (self.apk.get_package(), activity["name"])) logs += "$ adb shell am start -n %s/%s\n%s\n" % ( self.apk.get_package(), activity["name"], output) break #find application's process pid = 0 output = self.avd.shell("ps | grep %s" % self.apk.get_package()) logs += "$ adb shell ps | grep %s\n %s\n" % ( self.apk.get_package(), output) for line in output.split("\n"): s = line.split() if len(s) == 9 and s[8] == self.apk.get_package(): pid = int(s[1]) if pid is None: self.error( "The application is not running. Can't confirm debuggable status." ) else: #forward jdwp and connect remotely with jdb logs += "$ adb forward tcp:54321 jdwp:%d\n" % pid p = os.popen( "adb -s emulator-%d forward tcp:54321 jdwp:%d" % (self.avd._id, pid), ) output = p.read() p.close() if "Error:" in output: raise Exception(output) else: p = os.popen( "echo 'classes' | jdb -attach localhost:54321 | grep %s" % self.apk.get_package(), ) output = p.read() p.close() logs += "$ jdb -attach localhost:54321\n> classes\n%s" % output if "Error" in output: debuggable = False else: if "Unable to attach to target VM." in output: debuggable = False if verbose: print logs return { "results": debuggable, "vulnerabilities": [ framework.Vulnerability( "The application is debuggable", "The application is set to debuggable. A malicious application can subvert the integrity and " "confidentiality of the vulnerable application by connecting to its debug port.\n" "The application can be easily reversed engineered.", framework.Vulnerability.HIGH, logs=logs).__dict__ ] if debuggable else [] }
def module_run(self, verbose=False): xml = self.apk.get_android_manifest_xml() results = {"allow_backup": False, "backup_agent": None} for a in xml.getElementsByTagName("application"): if a.getAttribute('android:allowBackup') is not None: results["allow_backup"] = True if a.getAttribute( 'android:allowBackup') == 'true' else False if results["allow_backup"]: results["backup_agent"] = a.getAttribute( 'android:backupAgent') if results["allow_backup"] and self.avd is not None: try: self.output("Application allow backup. Backing up data ...") if not self.avd.headless: backup_location = "%s/analysis/%s/storage/backup" % ( self.root_dir, self.apk.get_package()) if not os.path.exists(backup_location): os.mkdir(backup_location) if self.avd.backup(self.apk.get_package(), location="%s/backup.ab" % backup_location): self.output( str("Package backed up to %s, decompressing ..." % backup_location)) #zlib decompression ab_file = open("%s/backup.ab" % backup_location, "rb") tar_file = open("%s/backup.tar" % backup_location, "wb") ab_file.read(24) tar_file.write(zlib.decompress(ab_file.read())) tar_file.close() ab_file.close() #tar decompression tar = tarfile.open("%s/backup.tar" % backup_location) tar.extractall(path=backup_location) tar.close() else: self.warning( "An error occured when trying to backup %s" % self.apk.get_package()) else: self.warning( "Emulator running in headless mode. Can't run automated backup script." ) except Exception as e: self.warning(str(e.message)) return { "results": results, "vulnerabilities": [ framework.Vulnerability( "The application allow backups.", "The allowBackup attribute determines if an application's data can be backed up and restored.\n" "By default, this flag is set to true. When this flag is set to true, application data can be backed up" " and restored by the user using adb backup and adb restore. This may have security consequences for an" " application. adb backup allows users who have enabled USB debugging to copy application data off of" " the device. Once backed up, all application data can be read by the user. adb restore allows creation" " of application data from a source specified by the user. Following a restore, applications should not" " assume that the data, file permissions, and directory permissions were created by the application" " itself. Setting allowBackup=\"false\" opts an application out of both backup and restore.", framework.Vulnerability.LOW).__dict__ ] if results["allow_backup"] is True else [] }
def module_run(self, verbose=False): #proguard detection proguard = False for root, dirs, files in os.walk( "%s/analysis/%s/code/decompiled/%s" % (self.root_dir, self.apk.get_package(), "/".join( self.apk.get_package().split(".")))): for f in files: if f in ["%s.java" % x for x in string.ascii_lowercase]: proguard = True #dexguard detection #1. use of unicode/chinese characters chinese_filenames = 0 for root, dirs, files in os.walk( "%s/analysis/%s/smali" % (self.root_dir, self.apk.get_package())): for f in files: for c in f: if u'\u4e00' <= c <= u'\u9fff': chinese_filenames += 1 chinese_chars = 0 for root, dirs, files in os.walk( "%s/analysis/%s/smali" % (self.root_dir, self.apk.get_package())): for filename in files: with codecs.open(os.path.join(root, filename), "rb", "utf-8") as f: for c in f.read(): if u'\u4e00' <= c <= u'\u9fff': chinese_chars += 1 #2. Usage of huge arrays (> 1900 bytes) huge_arrays = 0 for root, dirs, files in os.walk( "%s/analysis/%s/smali" % (self.root_dir, self.apk.get_package())): for filename in files: with open(os.path.join(root, filename), 'rb') as f: matches = re.findall( r'new-array ([^,]*),([^,]*),([^\n]*)\n', f.read()) if len(matches): if matches[0][1] > 1900: huge_arrays += 1 #3. Heavy use of reflection reflection = 0 for root, dirs, files in os.walk( "%s/analysis/%s/smali" % (self.root_dir, self.apk.get_package())): for filename in files: with open(os.path.join(root, filename), 'rb') as f: matches = re.findall('r(Ljava/lang/reflect/[^;];)', f.read()) reflection += len(matches) #4. Dynamic Code Loading and Executing dexclassloader = 0 for root, dirs, files in os.walk( "%s/analysis/%s/smali" % (self.root_dir, self.apk.get_package())): for filename in files: with open(os.path.join(root, filename), 'rb') as f: matches = re.findall('r(Ldalvik/system/DexClassLoader;)', f.read()) dexclassloader += len(matches) #5. Heavy use of Java’s encryption classes #APKProtect detection # The string "APKProtected" is present in the dex apkprotect = False for root, dirs, files in os.walk( "%s/analysis/%s/smali" % (self.root_dir, self.apk.get_package())): for filename in files: with open(os.path.join(root, filename), 'rb') as f: if "APKProtected" in f.read(): apkprotect = True dexguard = (dexclassloader > 0 and chinese_chars > 0 and chinese_filenames > 0) obfuscator = None if dexguard: obfuscator = "Dexguard" if proguard: obfuscator = "Proguard" if apkprotect: obfuscator = "APKProtect" if verbose and obfuscator is not None: print "Obfuscator : %s" % obfuscator return { "results": obfuscator, "logs": "", "vulnerabilities": [ framework.Vulnerability( "Lack of Code Obfuscation", "Obfuscation raise the bar for third parties that would want to determine how your application is " "working and protect your application against piracy or unwanted clones on the market." "Multiple solutions exists but we recommend you to use Proguard as it is well integrated into the " "Android Studio IDE.", framework.Vulnerability.LOW).__dict__ ] if obfuscator is None else [] }