def analyzeAPK(self): """ Uses androguard to retrieve metadata about the app e.g. activities, permissions, intent filters, etc. """ try: prettyPrint("Analyzing app") logEvent("Analyzing app: \"%s\"" % self.APKPath) # 1. Load the APK using androguard analysisSession = Session() analysisSession.add(self.APKPath, open(self.APKPath).read()) # 2. Retrieve handles to APK and its dex code self.APK = analysisSession.analyzed_apk.values()[0] self.DEX = analysisSession.analyzed_dex.values()[0][0] self.VMAnalysis = analysisSession.analyzed_dex.values()[0][1] # 3. Retrieve information for each activity prettyPrint("Analyzing activities") self.activitiesInfo = analyzeActivities(self.APK, self.DEX) # 4. Do the same for services and broadcast receivers prettyPrint("Analyzing services") self.servicesInfo = analyzeServices(self.APK, self.DEX) prettyPrint("Analyzing broadcast receivers") self.receiversInfo = analyzeReceivers(self.APK, self.DEX) except Exception as e: prettyPrintError(e) return False prettyPrint("Success") return True
def testTypes(self): s = Session() with open(TEST_CASE, "rb") as fd: digest, d, dx = s.addDEX(TEST_CASE, fd.read()) for method in d.get_methods(): key = method.get_class_name() + " " + method.get_name( ) + " " + method.get_descriptor() if key not in VALUES: continue print("METHOD", method.get_class_name(), method.get_name( ), method.get_descriptor()) code = method.get_code() bc = code.get_bc() idx = 0 for i in bc.get_instructions(): if "const" in i.get_name(): i.show(0) formatted_operands = i.get_formatted_operands() print(formatted_operands) if not formatted_operands: VALUES[key].pop(0) else: for f in formatted_operands: self.assertAlmostEqual(f, VALUES[key].pop(0), places=4) idx += i.get_length()
def testTypes(self): s = Session() with open(TEST_CASE, "rb") as fd: digest, d, dx = s.addDEX(TEST_CASE, fd.read()) for method in d.get_methods(): key = method.get_class_name() + " " + method.get_name( ) + " " + method.get_descriptor() if key not in VALUES: continue print("METHOD", method.get_class_name(), method.get_name(), method.get_descriptor()) code = method.get_code() bc = code.get_bc() idx = 0 for i in bc.get_instructions(): if "const" in i.get_name(): i.show(0) formatted_operands = i.get_formatted_operands() print(formatted_operands) if not formatted_operands: VALUES[key].pop(0) else: for f in formatted_operands: self.assertAlmostEqual(f, VALUES[key].pop(0), places=4) idx += i.get_length()
def setupSession(self): self.session = Session() self.fileLoadingThread = FileLoadingThread(self.session) self.connect(self.fileLoadingThread, QtCore.SIGNAL("loadedFile(bool)"), self.loadedFile)
def interact(session=False, apk=None): """ Start an interactive shell :param session: :param apk: :return: """ from androguard.core.androconf import ANDROGUARD_VERSION, CONF from IPython.terminal.embed import InteractiveShellEmbed from traitlets.config import Config from androguard.misc import init_print_colors, AnalyzeAPK from androguard.session import Session if session: CONF["SESSION"] = Session(export_ipython=True) if apk: print("Loading apk {}...".format(os.path.basename(apk))) print("Please be patient, this might take a while.") # TODO we can export fancy aliases for those as well... a, d, dx = AnalyzeAPK(apk) cfg = Config() _version_string = "Androguard version {}".format(ANDROGUARD_VERSION) ipshell = InteractiveShellEmbed( config=cfg, banner1="{} started".format(_version_string)) init_print_colors() ipshell()
def interact(): CONF["SESSION"] = Session(True) cfg = Config() ipshell = InteractiveShellEmbed(config=cfg, banner1="Androguard version %s" % ANDROGUARD_VERSION) init_print_colors() ipshell()
def get_default_session(): """ Return the default Session from the configuration or create a new one, if the session in the configuration is None. """ if androconf.CONF["SESSION"] is None: androconf.CONF["SESSION"] = Session() return androconf.CONF["SESSION"]
def testTypes(self): s = Session() with open(TEST_CASE, "rb") as fd: digest, d, dx = s.addDEX(TEST_CASE, fd.read()) for method in filter(lambda x: x.full_name in VALUES, d.get_methods()): print("METHOD", method.full_name) for i in filter(lambda x: 'const' in x.get_name(), method.get_instructions()): i.show(0) # ins should only have one literal self.assertEquals(len(i.get_literals()), 1) fmt, value = VALUES[method.full_name].pop(0) converted = format_value(i.get_literals()[0], i, fmt) print(i.get_literals(), fmt, value, converted) self.assertEqual(converted, value) print()
def process_files(apk1, apk2, should_multiprocess=True): """ Similar issues to load_androguard. Serialization issue prevents sending this object (multiple Gbs in RAM) through a multiprocessing mechanism such as Pipes (or anything build on top of it, i. e. Queues). """ file_paths = (apk1, apk2) if not should_multiprocess: s = Session() return tuple( map(lambda f: load_androguard(f, True, False, s=s), file_paths)) parent_conn, child_conn = multiprocessing.Pipe(False) def post_result(file_path, conn): value = load_androguard(file_path, True, False) conn.send((file_path, value)) ps = [ multiprocessing.Process(target=post_result, args=(f, child_conn)) for f in file_paths ] def apply_map(f, i): for x in i: f(x) assert len(file_paths) == 2 print("Starting multiprocessing Files") # Serialization with Pickle requires higher recursion limit import sys previous_recursion = sys.getrecursionlimit() sys.setrecursionlimit(50000) apply_map(multiprocessing.Process.start, ps) values = (parent_conn.recv(), parent_conn.recv()) r = tuple( map(lambda x: x[1], sorted(values, key=lambda x: file_paths.index(x[0])))) apply_map(multiprocessing.Process.join, ps) print("Finished all processes") sys.setrecursionlimit(previous_recursion) return r
def interact(session=False, apk=None): """ Start an interactive shell :param session: :param apk: :return: """ if session: CONF["SESSION"] = Session(export_ipython=True) if apk: print("Loading apk {}...".format(os.path.basename(apk))) print("Please be patient, this might take a while.") # TODO we can export fancy aliases for those as well... a, d, dx = AnalyzeAPK(apk) cfg = Config() ipshell = InteractiveShellEmbed(config=cfg, banner1="{} started".format(_version_string)) init_print_colors() ipshell()
def load_androguard(file_path, force_reload=False, write_session=True, session_file=None, s=None): """ Should handle saving and loading sessions automatically. Writing and Loading sessions currently cause a Kernel Disconnect or an EOF Error (Pickle serialization issues) """ if (not force_reload) and path.exists(session_file): print("Loading Existing Session") s = Load(session_file) a = d = dx = None else: print("Loading Session from Apk at", file_path) if s is None: s = Session() a, d, dx = AnalyzeAPK(file_path, s) if write_session: print("Saving Loaded Session to", session_file) s.add(file_path, dx=dx) Save(s, session_file) return a, d, dx
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') fh.setFormatter(formatter) ch.setFormatter(formatter) # add the handlers to the logger logger.addHandler(fh) logger.addHandler(ch) if not os.path.isfile(path): logger.info('{path} is not a valid file'.format(path=path)) sys.exit() if not output_file_path: output_file_path = os.path.splitext(os.path.basename(path))[0] + '.json' logger.info('output file defaulting to: {path}'.format(path=output_file_path)) start = time.time() a, d, dx = AnalyzeAPK(path, session=Session()) end = time.time() logger.info('androguard time: {t}'.format(t=(end-start))) #a = <class 'androguard.core.bytecodes.apk.APK'> #d = list <class 'androguard.core.bytecodes.dvm.DalvikVMFormat'> a list of DalvikVMFormat objects, each pertaining to a single classes.dex #dx = list <class 'androguard.core.analysis.analysis.Analysis'> A list of Analyses objects, each pertaining to a single classes.dex logger.info(len(d)) logger.info(len(dx)) start = time.time() result = [] for __d in d:#all dex print(len(__d.get_classes())) continue
def extractStaticFeatures(apkPath): """Extracts static numerical features from APK using Androguard""" try: features = [[], [], [], []] # Tuples are immutable if os.path.exists(apkPath.replace(".apk",".static")): prettyPrint("Found a pre-computed static features file") bFeatures, pFeatures, aFeatures, allFeatures = [], [], [], [] try: possibleExtensions = [".basic", ".perm", ".api", ".static"] for ext in possibleExtensions: if os.path.exists(apkPath.replace(".apk", ext)): content = open(apkPath.replace(".apk", ext)).read() if len(content) > 0: features[possibleExtensions.index(ext)] = [float(f) for f in content[1:-1].split(',') if len(f) > 0] return tuple(features) except Exception as e: prettyPrintError(e) prettyPrint("Could not extract features from \".static\" file. Continuing as usual", "warning") if verboseON(): prettyPrint("Starting analysis on \"%s\"" % apkPath, "debug") analysisSession = Session() if not os.path.exists(apkPath): prettyPrint("Could not find the APK file \"%s\"" % apkPath, "warning") return [], [], [], [] # 1. Analyze APK and retrieve its components #t = threading.Timer(300.0, returnEmptyFeatures) # Guarantees not being stuck on analyzing an APK #t.start() analysisSession.add(apkPath, open(apkPath).read()) if type(analysisSession.analyzed_apk.values()) == list: apk = analysisSession.analyzed_apk.values()[0][0] else: apk = analysisSession.analyzed_apk.values()[0] dex = analysisSession.analyzed_dex.values()[0][0] vm = analysisSession.analyzed_dex.values()[0][1] # 2. Add features to the features vector basicFeatures, permissionFeatures, apiCallFeatures, allFeatures = [], [], [], [] # 2.a. The APK-related features if verboseON(): prettyPrint("Extracting basic features", "debug") minSDKVersion = 0.0 if not apk.get_min_sdk_version() else float(apk.get_min_sdk_version()) maxSDKVersion = 0.0 if not apk.get_max_sdk_version() else float(apk.get_max_sdk_version()) basicFeatures.append(minSDKVersion) basicFeatures.append(maxSDKVersion) basicFeatures.append(float(len(apk.get_activities()))) # No. of activities basicFeatures.append(float(len(apk.get_services()))) # No. of services basicFeatures.append(float(len(apk.get_receivers()))) # No. of broadcast receivers basicFeatures.append(float(len(apk.get_providers()))) # No. of providers # 2.b. Harvest permission-related features if verboseON(): prettyPrint("Extracting permissions-related features", "debug") aospPermissions = float(len(apk.get_requested_aosp_permissions())) # Android permissions requested by the app declaredPermissions = float(len(apk.get_declared_permissions())) # Custom permissions declared by the app dangerousPermissions = float(len([p for p in apk.get_requested_aosp_permissions_details().values() if p["protectionLevel"] == "dangerous"])) totalPermissions = float(len(apk.get_permissions())) permissionFeatures.append(totalPermissions) # No. of permissions if totalPermissions > 0: permissionFeatures.append(aospPermissions/totalPermissions) # AOSP permissions : Total permissions permissionFeatures.append(declaredPermissions/totalPermissions) # Third-party permissions : Total permissions permissionFeatures.append(dangerousPermissions/totalPermissions) # Dangerous permissions : Total permissions else: permissionFeatures.append(0.0) permissionFeatures.append(0.0) permissionFeatures.append(0.0) # 2.c. The DEX-related features (API calls) if verboseON(): prettyPrint("Extracting API calls from dex code", "debug") apiCallFeatures.append(float(len(dex.get_classes()))) # Total number of classes apiCallFeatures.append(float(len(dex.get_strings()))) # Total number of strings apiCategories = sensitiveAPICalls.keys() apiCategoryCount = [0.0] * len(apiCategories) for c in dex.classes.get_names(): currentClass = dex.get_class(c) if not currentClass: continue code = currentClass.get_source() if len(code) < 1: continue for category in apiCategories: if code.find(category) != -1: for call in sensitiveAPICalls[category]: apiCategoryCount[apiCategories.index(category)] += float(len(re.findall(call, code))) apiCallFeatures += apiCategoryCount except Exception as e: prettyPrintError(e) return [], [], [], [] allFeatures = basicFeatures + permissionFeatures + apiCallFeatures return basicFeatures, permissionFeatures, apiCallFeatures, allFeatures
def run(self): """ Runs the Droidutan test against the [processTarget] for [processDuration] """ try: # A timer to guarante the process exits if verboseON(): prettyPrint( "Setting timer for %s seconds" % str(float(self.processDuration) * 5.0), "debug") t = threading.Timer(float(self.processDuration) * 5.0, self.stop) t.start() # Step 1. Analyze APK #APKType = "malware" if self.processTarget.find("malware") != -1 else "goodware" if verboseON(): prettyPrint("Analyzing APK: \"%s\"" % self.processTarget, "debug") s = Session() s.add(self.processTarget, open(self.processTarget).read()) if len(s.analyzed_apk.values()) > 0: apk = s.analyzed_apk.values()[0] if type(apk) == list: apk = s.analyzed_apk.values()[0][0] else: prettyPrint("Could not retrieve an APK to analyze. Skipping", "warning") return False # Step 2. Get the Ip address assigned to the AVD getAVDIPCmd = [ "VBoxManage", "guestproperty", "enumerate", self.processVM ] avdIP = "" result = subprocess.Popen( getAVDIPCmd, stderr=subprocess.STDOUT, stdout=subprocess.PIPE).communicate()[0].replace(' ', '') if result.lower().find("error") != -1: prettyPrint("Unable to retrieve the IP address of the AVD", "error") print result return False index = result.find("androvm_ip_management,value:") + len( "androvm_ip_management,value:") while result[index] != ',': avdIP += result[index] index += 1 adbID = "%s:5555" % avdIP # Step 3. Define frequently-used commands droidbotOut = self.processTarget.replace(".apk", "_droidbot") droidbotCmd = [ "droidbot", "-d", adbID, "-a", self.processTarget, "-o", droidbotOut, "-timeout", str(self.processDuration), "-random", "-keep_env", "-grant_perm" ] # Step 4. Test the APK using Droidbot (Assuming machine is already on) prettyPrint("Testing the APK \"%s\" using Droidbot" % apk.package) # 4.a. Start Droidbot status = subprocess.Popen(droidbotCmd, stderr=subprocess.STDOUT, stdout=subprocess.PIPE).communicate()[0] # 4.b. Check for existence of output directory if not os.path.exists(droidbotOut): prettyPrint( "No output folder found for \"%s\"" % self.processTarget, "warning") return False # 4.c. Filter the logcat dumped by droidbot logFile = open("%s/logcat_filtered.log" % droidbotOut, "w") catlog = subprocess.Popen(("cat", "%s/logcat.txt" % droidbotOut), stdout=subprocess.PIPE) output = subprocess.check_output( ("grep", "-i", "droidmon-apimonitor-%s" % apk.package), stdin=catlog.stdout) logFile.write(output) logFile.close() except subprocess.CalledProcessError as cpe: prettyPrint( "Unable to find the tag \"Droidmon-apimonitor-%s\" in the log file" % apk.package, "warning") except Exception as e: prettyPrintError(e) return False return True
def display_PERMISSION(a, x, classes): # Show methods used by permission perms_access = x.get_tainted_packages().get_permissions([]) for perm in perms_access: print("PERM : ", perm) analysis.show_Paths(a, perms_access[perm]) def display_OBJECT_CREATED(a, x, class_name): print("Search object", class_name) analysis.show_Paths(a, x.get_tainted_packages().search_objects(class_name)) s = Session() with open(TEST, "r") as fd: s.add(TEST, fd.read()) a, d, dx = s.get_objects_apk(TEST) print(d.get_strings()) print(d.get_regex_strings("access")) print(d.get_regex_strings("(long).*2")) print(d.get_regex_strings(".*(t\_t).*")) classes = d.get_classes_names() display_CFG(d, dx, classes) display_STRINGS(dx) display_FIELDS(d, dx)
32768.5, 32767.5, 32766.5, -5, -65535, -65536, -123456789123456789.555555555, -123456789123456789.555555555, -606384730, -123456790519087104, -606384730, 3.5 ], } def test(got, expected): if got == expected: prefix = ' OK ' else: prefix = ' X ' print('%s got: %s expected: %s' % (prefix, repr(got), repr(expected))) s = Session() with open(TEST_CASE, "r") as fd: digest, d, dx = s.addDEX(TEST_CASE, fd.read()) for method in d.get_methods(): key = method.get_class_name() + " " + method.get_name( ) + " " + method.get_descriptor() if key not in VALUES: continue print("METHOD", method.get_class_name(), method.get_name( ), method.get_descriptor()) code = method.get_code() bc = code.get_bc()
def androlyze_main(session, filename): """ Start an interactive shell :param session: Session file to load :param filename: File to analyze, can be APK or DEX (or ODEX) """ from androguard.core.androconf import ANDROGUARD_VERSION, CONF from IPython.terminal.embed import InteractiveShellEmbed from traitlets.config import Config from androguard.session import Session, Load from colorama import Fore import colorama import atexit # Import commonly used classes, for further usage... from androguard.core.bytecodes.apk import APK from androguard.core.bytecodes.dvm import DalvikVMFormat from androguard.core.analysis.analysis import Analysis from androguard.misc import AnalyzeAPK colorama.init() if session: print("Restoring session '{}'...".format(session)) s = CONF['SESSION'] = Load(session) print("Successfully restored {}".format(s)) # TODO Restore a, d, dx etc... else: s = CONF["SESSION"] = Session(export_ipython=True) if filename: ("Loading apk {}...".format(os.path.basename(filename))) print("Please be patient, this might take a while.") filetype = androconf.is_android(filename) print("Found the provided file is of type '{}'".format(filetype)) if filetype not in ['DEX', 'DEY', 'APK']: print(Fore.RED + "This file type is not supported by androlyze for auto loading right now!" + Fore.RESET, file=sys.stderr) print("But your file is still available:") print(">>> filename") print(repr(filename)) print() else: with open(filename, "rb") as fp: raw = fp.read() h = s.add(apk, raw) print("Added file to session: SHA256::{}".format(h)) if filetype == 'APK': print("Loaded APK file...") a, d, dx = s.get_objects_apk(digest=h) print(">>> a") print(a) print(">>> d") print(d) print(">>> dx") print(dx) print() elif filetype in ['DEX', 'DEY']: print("Loaded DEX file...") for h_, d, dx in s.get_objects_dex(): if h == h_: break print(">>> d") print(d) print(">>> dx") print(dx) print() def shutdown_hook(): """Save the session on exit, if wanted""" if not s.isOpen(): return try: res = input("Do you want to save the session? (y/[n])?").lower() except (EOFError, KeyboardInterrupt): pass else: if res == "y": # TODO: if we already started from a session, probably we want to save it under the same name... # TODO: be able to take any filename you want fname = s.save() print("Saved Session to file: '{}'".format(fname)) cfg = Config() _version_string = "Androguard version {}".format(ANDROGUARD_VERSION) ipshell = InteractiveShellEmbed(config=cfg, banner1="{} started" .format(_version_string)) atexit.register(shutdown_hook) ipshell()
class MainView(QtGui.QMainWindow): def __init__(self, parent=None): super(MainView, self).__init__(parent) self.ui = Ui_MainWindow() self.ui.setupUi(self) self.__logger = Logger(self.ui.logArea) self.__logger.log(Logger.INFO, '') self.init_actions() self.setup_fileloading() self.apk_path = '' self.apk = None self.x = self.d = self.a = None self.manifest = None self.show_sample_question() def setup_fileloading(self): self.session = Session() self.fileLoadingThread = FileLoadingThread(self.session) self.connect(self.fileLoadingThread,QtCore.SIGNAL("loadedFile(bool)"),self.loadedApk) def show_sample_question(self): self.sample_dialog = SampleDialog(self) self.sample_dialog.exec_() def get_logger(self): return self.__logger; def loadedApk(self, success): self.sample_dialog.close() if not success: self.__logger.log(Logger.ERROR,"Analysis of %s failed :(" % str(self.fileLoadingThread.file_path)) self.set_loading_progressbar_disabled() return self.d = self.session.get_DalvikForm() self.d.create_python_export() self.x = uVMAnalysis(self.d) self.setupTree() self.setupResourceController() self.load_app_info_table() self.load_permissions() try: self.__logger.log(Logger.INFO,"Analysis of %s done!" % str(self.apk.get_app_name())) self.ui.loadedAPK_label.setText("Loaded: "+str(self.apk.get_app_name())) except UnicodeEncodeError: self.ui.loadedAPK_label.setText("APK Loaded! (non-ascii chars detected)") self.__logger.log(Logger.WARNING,"Non ascii code characters detected, some strings are possibly not displayed properly.") self.set_loading_progressbar_disabled() def setupResourceController(self): self.resourcecontroller = ResourceFileController(self.apk, self.ui.resourceFileTable, self.ui.graphicview, self.ui.fileResourceInfo) def get_android_manifest_xml(self): self.set_loading_progressbar_text("Decompiling AndroidManifest.xml") buff = self.apk.get_android_manifest_xml().toprettyxml(encoding="utf-8") doc = QtGui.QTextEdit() doc.setWindowTitle("AndroidManifest.xml") hl = XMLHighlighter(doc.document()) doc.setPlainText(str(buff).strip()) return doc def setupTree(self): try: self.ui.tree_area.layout().deleteLater() except AttributeError: pass self.tree = TreeWindow(self,self,session=self.session) self.tree.setWindowTitle("Tree model") layout = QtGui.QVBoxLayout() layout.addWidget(self.tree) self.ui.tree_area.setLayout(layout) self.tree.fill() self.setupCentral() def setupCentral(self): self.central = QtGui.QTabWidget() self.central.setTabBar(CustomTabBar()) self.central.setTabsClosable(True) self.central.tabCloseRequested.connect(self.tabCloseRequestedHandler) self.central.currentChanged.connect(self.currentTabChanged) layout = QtGui.QVBoxLayout() layout.addWidget(self.central) self.ui.sourceTextWidget.setLayout(layout) def tabCloseRequestedHandler(self, index): self.central.removeTab(index) def currentTabChanged(self, index): if index == -1: return # all tab closed def openSourceWindow(self, current_class, method=None): sourcewin = self.getMeSourceWindowIfExists(current_class) if not sourcewin: current_filename = self.session.get_filename_by_class(current_class) current_digest = self.session.get_digest_by_class(current_class) sourcewin = SourceWindow(win=self, current_class=current_class, current_title=current_class.current_title, current_filename=current_filename, current_digest=current_digest, session=self.session) sourcewin.reload_java_sources() self.central.addTab(sourcewin, sourcewin.title) self.central.setTabToolTip(self.central.indexOf(sourcewin), current_class.get_name()) if method: sourcewin.browse_to_method(method) self.central.setCurrentWidget(sourcewin) def openManifestWindow(self): manifest_tab = self.get_android_manifest_xml() self.central.addTab(manifest_tab,"AndroidManifest.xml") self.central.setCurrentWidget(manifest_tab) def openStringsWindow(self): stringswin = StringsWindow(win=self, session=self.session) self.central.addTab(stringswin, stringswin.title) self.central.setTabToolTip(self.central.indexOf(stringswin), stringswin.title) self.central.setCurrentWidget(stringswin) def openBytecodeWindow(self, current_class, method=None): byte_code_str = '' for clazz in self.d.get_classes(): if clazz.get_name() == current_class.get_name(): for method in clazz.get_methods(): byte_code_str += "# "+method.get_name()+" "+method.get_descriptor()+"\n" byte_code = method.get_code() if byte_code != None: byte_code = byte_code.get_bc() idx = 0 for i in byte_code.get_instructions(): byte_code_str += "\t, %x " % (idx)+i.get_name()+" "+i.get_output()+"\n" idx += i.get_length() bytecode_tab = self.get_bytecode_window(byte_code_str) self.central.addTab(bytecode_tab,"Bytecode: "+current_class.get_name()) self.central.setCurrentWidget(bytecode_tab) def showStatus(self,text): QtGui.QMessageBox.information(self, "Info", text) def get_bytecode_window(self,byte_code): return BytecodeWindow(byte_code,self) def getMeSourceWindowIfExists(self, path): '''Helper for openSourceWindow''' for idx in range(self.central.count()): if path == self.central.tabToolTip(idx): return self.central.widget(idx) return None def load_app_info_table(self): self.info = {} self.info["Application Name"] = self.apk.get_app_name() self.info["Application Size"] = util.sizeof_fmt(os.path.getsize(self.apk_path)) self.info["Android Version Name"] = self.apk.get_androidversion_name() self.info["Android Version Code"] = self.apk.get_androidversion_code() self.info["Android Package Name"] = self.apk.get_package() self.info["Signature Name"] = self.apk.get_signature_name() self.info["Uses Dynamic Code Loading"] = str(analysis.is_dyn_code(self.x)) self.info["Uses Reflection"] = str(analysis.is_reflection_code(self.x)) self.info["Uses Crypto"] = str(analysis.is_crypto_code(self.x)) self.info["Privacy Leaks"] = str(len(self.get_privacy_leaks())) self.info["Number of Providers"] = str(len(self.apk.get_providers())) self.info["Number of Activities"] = str(len(self.apk.get_activities())) self.info["Number of Services"] = str(len(self.apk.get_services())) self.info["Number of Libraries"] = str(len(self.apk.get_libraries())) self.info["Number of Permissions"] = str(len(self.get_uses_permissions())) self.info_actions = {} self.info_actions["Application Name"] = None self.info_actions["Application Size"] = None self.info_actions["Android Version Name"] = None self.info_actions["Android Version Code"] = None self.info_actions["Android Package Name"] = None self.info_actions["Signature Name"] = self.show_signature self.info_actions["Uses Dynamic Code Loading"] = self.show_dyncode self.info_actions["Uses Reflection"] = self.show_reflection self.info_actions["Uses Crypto"] = self.show_cryptocode self.info_actions["Privacy Leaks"] = self.show_privacy_leaks self.info_actions["Number of Providers"] = self.show_providers self.info_actions["Number of Activities"] = self.show_activities self.info_actions["Number of Services"] = self.show_services self.info_actions["Number of Libraries"] = self.show_libraries self.info_actions["Number of Permissions"] = self.show_permissions info_table = self.ui.appInfoTable info_table.setRowCount(len(self.info)) info_table.setColumnWidth(1, 200) info_table.horizontalHeader().setResizeMode(0, QtGui.QHeaderView.Stretch) row = 0 for key in sorted(self.info): action = self.info_actions[key] action_button = None if action is not None: action_button = QtGui.QPushButton() action_button.setText("Show") action_button.clicked.connect(action) key_item = QtGui.QTableWidgetItem(key) value_item = QtGui.QTableWidgetItem(self.info[key]) info_table.setItem(row,0,key_item) info_table.setItem(row,1,value_item) if action_button is not None: info_table.setCellWidget(row,2,action_button) row += 1 def show_providers(self): self.__logger.log_with_title("Providers",self.apk.get_providers()) def show_signature(self): certname = "" signature_expr = re.compile("^(META-INF/)(.*)(\.RSA|\.DSA)$") for i in self.apk.get_files(): if signature_expr.search(i): f = tempfile.NamedTemporaryFile(delete=False) f.write(self.apk.get_file(i)) f.close() certname = f.name cmd = ["keytool","-printcert","-file",certname] proc = subprocess.Popen(cmd,stdout=subprocess.PIPE) (out,err) = proc.communicate() self.__logger.log_with_title("Signature",out) def show_services(self): self.__logger.log_with_title("Services",'\n\t -'+'\n\t -'.join(self.apk.get_services())) def show_permissions(self): self.__logger.log(Logger.INFO,"Searching for permission usage, this can take a while depending on the size of the app.") p = self.x.get_permissions( [] ) self.__logger.log_with_title("Permissions Usage","") for i in p: self.__logger.log_with_color(Logger.WARNING,"\n\t======="+i+"=======\n") for j in p[i]: self.__logger.log_with_color(Logger.INFO,"\t\t -"+self.show_Path(self.x.get_vm(), j )+"\n") def show_cryptocode(self): if analysis.is_crypto_code(self.x) is False: self.__logger.log(Logger.WARNING,"No crypto code was found! Maybe it uses adapted or obfuscated encryption code.") return paths = [] paths.extend(self.x.get_tainted_packages().search_methods("Ljavax/crypto/.", ".", ".")) paths.extend(self.x.get_tainted_packages().search_methods("Ljava/security/spec/.", ".", ".")) str_info_dyn="\t" for path in paths: str_info_dyn += (self.show_Path(self.x.get_vm(), path )+"\n\n\t") self.__logger.log_with_title("Usage of Crypto Code",str_info_dyn) def show_dyncode(self): if analysis.is_dyn_code(self.x) is False: self.__logger.log(Logger.WARNING,"No dynamic code was found!") return paths = [] paths.extend(self.x.get_tainted_packages().search_methods("Ldalvik/system/BaseDexClassLoader;", "<init>", ".")) paths.extend(self.x.get_tainted_packages().search_methods("Ldalvik/system/PathClassLoader;", "<init>", ".")) paths.extend(self.x.get_tainted_packages().search_methods("Ldalvik/system/DexClassLoader;", "<init>", ".")) paths.extend(self.x.get_tainted_packages().search_methods("Ldalvik/system/DexFile;", "<init>", ".")) paths.extend(self.x.get_tainted_packages().search_methods("Ldalvik/system/DexFile;", "loadDex", ".")) str_info_dyn="\t" for path in paths: str_info_dyn += (self.show_Path(self.x.get_vm(), path )+"\n\n\t") self.__logger.log_with_title("Usage of Dynamic Code",str_info_dyn) def get_privacy_leaks(self): paths = [] paths.extend(self.x.get_tainted_packages().search_methods(".", "getDeviceId", ".")) paths.extend(self.x.get_tainted_packages().search_methods(".", "getSubscriberId", ".")) paths.extend(self.x.get_tainted_packages().search_methods(".", "getSimSerialNumber", ".")) paths.extend(self.x.get_tainted_packages().search_methods(".", "getLine1Number", ".")) paths.extend(self.x.get_tainted_packages().search_methods(".", "getAddress", ".")) paths.extend(self.x.get_tainted_packages().search_methods(".", "getMacAddress", ".")) paths.extend(self.x.get_tainted_packages().search_methods(".", "getSSID", ".")) paths.extend(self.x.get_tainted_packages().search_methods(".", "getCid", ".")) paths.extend(self.x.get_tainted_packages().search_methods(".", "getAccounts", ".")) paths.extend(self.x.get_tainted_packages().search_methods(".", "getTimeZone", ".")) paths.extend(self.x.get_tainted_packages().search_methods(".", "getAllBookmarks", ".")) paths.extend(self.x.get_tainted_packages().search_methods(".", "getAllVisitedUrls", ".")) return paths def show_privacy_leaks(self): paths = self.get_privacy_leaks() if len(paths) == 0: self.__logger.log(Logger.WARNING,"No privacy leaks were found!") return str_info_privacy="\t" for path in paths: str_info_privacy += (self.show_Path(self.x.get_vm(), path )+"\n\n\t") self.__logger.log_with_title("Possible Privacy Leaks",str_info_privacy) def show_reflection(self): if analysis.is_reflection_code(self.x) is False: self.__logger.log(Logger.WARNING,"No reflection code was found!") return paths = self.x.get_tainted_packages().search_methods("Ljava/lang/reflect/Method;", ".", ".") str_info_reflection="" for path in paths: str_info_reflection += (self.show_Path(self.x.get_vm(), path )+"\n\n\t") self.__logger.log_with_title("Usage of Reflection",str_info_reflection) def show_activities(self): self.__logger.log_with_title("Activities",'\n\t -'+'\n\t -'.join(self.apk.get_activities())) def show_libraries(self): if len(self.apk.get_libraries()) == 0: self.__logger.log(Logger.WARNING,"No libraries were found.") return self.__logger.log(Logger.INFO,"Libraries Used: "+str(self.apk.get_libraries())) def init_actions(self): self.ui.saveLogBtn.clicked.connect(self.__logger.saveLog) self.ui.clearLogBtn.clicked.connect(self.__logger.clearLog) self.ui.showStringsBtn.clicked.connect(self.openStringsWindow) self.ui.showManifestBtn.clicked.connect(self.openManifestWindow) def load_apk_from_device(self): table = DeviceTable(self,parent=self) self.sample_dialog.close() table.show_data() table.exec_() def load_apk(self,path=None): if not path: path = QtGui.QFileDialog.getOpenFileName(self, "Open File",'', "APK Files (*.apk);;Androguard Session (*.ag)") path = str(path[0]) self.apk_path = path if path: self.set_loading_progressbar("Analyzing APK","Please wait while the APK is being dissected") self.__logger.log(Logger.INFO,"Analyzing %s..." % str(path)) self.apk = apk.APK(path) self.manifest = self.apk.get_AndroidManifest().getElementsByTagName("manifest")[0] self.fileLoadingThread.load(path) def load_permissions(self): perms = self.get_uses_permissions() self.ui.permsTable.setRowCount(len(perms)) self.ui.permsTable.horizontalHeader().setStretchLastSection(True) for i, uses_permission in enumerate(perms): newitem = QtGui.QTableWidgetItem(uses_permission) self.ui.permsTable.setItem(0, i, newitem) def get_uses_permissions(self): permissions = [] for uses_permission in self.manifest.getElementsByTagName("uses-permission"): permissions.append(uses_permission.attributes["android:name"].value) return permissions def set_loading_progressbar(self,title,text): self.dialog = QtGui.QProgressDialog(self) self.dialog.setMinimum(0) self.dialog.setLabelText(text) self.dialog.setMaximum(0) self.dialog.setWindowTitle(title) self.dialog.setCancelButton(None) self.dialog.setModal(True) self.dialog.show() def set_loading_progressbar_text(self,text): if self.dialog is not None: self.dialog.setLabelText(text); def set_loading_progressbar_disabled(self): self.dialog.hide() def show_Path(self,vm, path): cm = vm.get_class_manager() if isinstance(path, analysis.PathVar): dst_class_name, dst_method_name, dst_descriptor = path.get_dst( cm ) info_var = path.get_var_info() return "%s %s (0x%x) ---> %s->%s%s " % (path.get_access_flag(), info_var, path.get_idx(), dst_class_name, dst_method_name, dst_descriptor) else: if path.get_access_flag() == analysis.TAINTED_PACKAGE_CALL: src_class_name, src_method_name, src_descriptor = path.get_src( cm ) dst_class_name, dst_method_name, dst_descriptor = path.get_dst( cm ) return "%d %s->%s%s (0x%x) ---> %s->%s%s" % (path.get_access_flag(), src_class_name, src_method_name, src_descriptor, path.get_idx(), dst_class_name, dst_method_name, dst_descriptor) else: src_class_name, src_method_name, src_descriptor = path.get_src( cm ) return "%d %s->%s%s (0x%x)" % (path.get_access_flag(), src_class_name, src_method_name, src_descriptor, path.get_idx())
def run(self): app.logger.info('new analysis') s = Session() self.status = 'Analyzing APK' a, d, dx = AnalyzeAPK(self.target_file, session=s) #APK,list[DalvikVMFormat],Analysis print(type(a), type(d[0]), type(dx)) #cache activities, receivers, services, and providers, because for some reason, saving the Session causes a bug, breaking getters """i.e. bytecodes/apk.py", line 582, in get_elements for item in self.xml[i].findall('.//' + tag_name): TypeError: string indices must be integers """ activities = a.get_activities() receivers = a.get_receivers() services = a.get_services() providers = a.get_providers() self.main_activity = a.get_main_activity() if self.session_save_file: sys.setrecursionlimit(100000000) self.status = 'Saving session file' Save(s, self.session_save_file) cached_analyses.append({'md5': self.md5, 'analysis': (a, d, dx)}) #gather all classes from dexs 'd' #classes = get_all_classes_from_dexs(d) classes = dx.classes total_num = len(classes) done = 0 #num of done classes #result_classes contains the completed analysis info for each class run through the ClassAnalysis object result_classes = [] analysis_start_time = time.time() self.status = 'Getting all classes' for c_name, c_analysis in classes.items(): ca = ClassAnalysis(c_name, c_analysis, activities, receivers, services, providers) ca_result = ca.run() result_classes.append(ca_result) done += 1 if done % ceil(total_num / 100) == 0: self.progress += 1 #app.logger.info(self.progress) # with app.test_request_context('/'): # socketio.emit('newstatus', {'data':self.progress}, namespace='/status') analysis_end_time = time.time() analysis_total_time = analysis_end_time - analysis_start_time #debugging: self.status = 'Writing beforenetworkx debugging JSON' with open(self.graph_out_path + '.beforenetworkx', 'w') as f: json.dump(result_classes, f, indent=4, separators=(',', ': '), sort_keys=True) #create a networkx graph given the completed analyses in result_classess create_graph_start_time = time.time() self.status = 'Creating graph out of {} classes analyzed'.format( len(result_classes)) graph = create_graph(classes=result_classes) create_graph_end_time = time.time() create_graph_total_time = create_graph_end_time - create_graph_start_time #write graph to file: graph_out_path write_graph_start_time = time.time() self.status = 'Writing graph to disk' write_graph(graph, self.graph_out_path) write_graph_end_time = time.time() write_graph_total_time = write_graph_end_time - write_graph_start_time #build and write another graph that contains only providers,receivers,activities, and services if self.component_subgraph_out_path: component_names = [] self.status = 'Getting component nodes from graph' for node in graph: node_tmp = graph.node[node] if node_tmp[ 'component_type'] != NonComponentType.EXTERNAL and node_tmp[ 'component_type'] != NonComponentType.INTERNAL: component_names.append(node_tmp['name']) self.status = 'Creating subgraph containing only components' subgraph = get_class_subgraph(graph, class_names=component_names) self.status = 'Writing subgraph to disk' write_graph(subgraph, self.component_subgraph_out_path) #app metadata for misc/debugging apk_size = os.path.getsize(self.target_file) self.status = 'Writing metadata' self.write_app_metadata(result_classes, a, analysis_total_time, apk_size, create_graph_total_time, write_graph_total_time) #debugging # with open(self.graph_out_path+'.runmetrics', 'w') as f: # json.dump() self.progress = 100 self.status = 'Done' self.paused.wait( ) #wait for caller to collect last status and reset event before finishing app.logger.info('done')
from pprint import pprint from androguard.session import Session from androguard.misc import AnalyzeAPK a, d, dx = AnalyzeAPK('/home/branden/apks/dendroid.apk', session=Session()) print('here') acts = a.get_activities() print('here2') print(acts)
parser = argparse.ArgumentParser(description="Androguard GUI") parser.add_argument("-d", "--debug", action="store_true", default=False) parser.add_argument("-i", "--input_file", default=None) parser.add_argument("-c", "--console", action="store_true", default=False) args = parser.parse_args() if args.debug: androconf.set_debug() # We need that to save huge sessions when leaving and avoid # RuntimeError: maximum recursion depth exceeded while pickling an object # or # RuntimeError: maximum recursion depth exceeded in cmp # http://stackoverflow.com/questions/2134706/hitting-maximum-recursion-depth-using-pythons-pickle-cpickle sys.setrecursionlimit(50000) session = Session(export_ipython=args.console) console = None if args.console: console = IpythonConsole() console.start() app = QtGui.QApplication(sys.argv) window = MainWindow(session=session, input_file=args.input_file) window.resize(1024, 768) window.show() sys.exit(app.exec_())
#!/usr/bin/env python from __future__ import print_function import sys, hashlib PATH_INSTALL = "./" sys.path.append(PATH_INSTALL) from androguard.session import Session from androguard.core.bytecodes import dvm TEST = 'examples/android/TestsAndroguard/bin/classes.dex' s = Session() with open(TEST, "r") as fd: digest, d, dx = s.addDEX(TEST, fd.read()) # CFG for method in d.get_methods(): g = dx.get_method(method) # Display only methods with exceptions if method.get_code() == None: continue if method.get_code().tries_size <= 0: continue print(method.get_class_name(), method.get_name(), method.get_descriptor( ), method.get_code().get_length(), method.get_code().registers_size)
32768.5, 32767.5, 32766.5, -5, -65535, -65536, -123456789123456789.555555555, -123456789123456789.555555555, -606384730, -123456790519087104, -606384730, 3.5 ], } def _test(got, expected): if got == expected: prefix = ' OK ' else: prefix = ' X ' print('%s got: %s expected: %s' % (prefix, repr(got), repr(expected))) s = Session() with open(TEST_CASE, "rb") as fd: digest, d, dx = s.addDEX(TEST_CASE, fd.read()) for method in d.get_methods(): key = method.get_class_name() + " " + method.get_name( ) + " " + method.get_descriptor() if key not in VALUES: continue print("METHOD", method.get_class_name(), method.get_name(), method.get_descriptor()) code = method.get_code() bc = code.get_bc()
class MainView(QtGui.QMainWindow): def __init__(self, parent=None): super(MainView, self).__init__(parent) self.ui = Ui_MainWindow() self.ui.setupUi(self) self.__logger = Logger(self.ui.logArea) self.__logger.log(Logger.INFO, '') self.init_actions() self.setup_fileloading() self.apk_path = '' self.apk = None self.x = self.d = self.a = None self.manifest = None self.show_sample_question() def setup_fileloading(self): self.session = Session() self.fileLoadingThread = FileLoadingThread(self.session) self.connect(self.fileLoadingThread, QtCore.SIGNAL("loadedFile(bool)"), self.loadedApk) def show_sample_question(self): self.sample_dialog = SampleDialog(self) self.sample_dialog.exec_() def get_logger(self): return self.__logger def loadedApk(self, success): self.sample_dialog.close() if not success: self.__logger.log( Logger.ERROR, "Analysis of %s failed :(" % str(self.fileLoadingThread.file_path)) self.set_loading_progressbar_disabled() return self.d = self.session.get_DalvikForm() self.d.create_python_export() self.x = uVMAnalysis(self.d) self.setupTree() self.setupResourceController() self.load_app_info_table() self.load_permissions() try: self.__logger.log( Logger.INFO, "Analysis of %s done!" % str(self.apk.get_app_name())) self.ui.loadedAPK_label.setText("Loaded: " + str(self.apk.get_app_name())) except UnicodeEncodeError: self.ui.loadedAPK_label.setText( "APK Loaded! (non-ascii chars detected)") self.__logger.log( Logger.WARNING, "Non ascii code characters detected, some strings are possibly not displayed properly." ) self.set_loading_progressbar_disabled() def setupResourceController(self): self.resourcecontroller = ResourceFileController( self.apk, self.ui.resourceFileTable, self.ui.graphicview, self.ui.fileResourceInfo) def get_android_manifest_xml(self): self.set_loading_progressbar_text("Decompiling AndroidManifest.xml") buff = self.apk.get_android_manifest_xml().toprettyxml( encoding="utf-8") doc = QtGui.QTextEdit() doc.setWindowTitle("AndroidManifest.xml") hl = XMLHighlighter(doc.document()) doc.setPlainText(str(buff).strip()) return doc def setupTree(self): try: self.ui.tree_area.layout().deleteLater() except AttributeError: pass self.tree = TreeWindow(self, self, session=self.session) self.tree.setWindowTitle("Tree model") layout = QtGui.QVBoxLayout() layout.addWidget(self.tree) self.ui.tree_area.setLayout(layout) self.tree.fill() self.setupCentral() def setupCentral(self): self.central = QtGui.QTabWidget() self.central.setTabBar(CustomTabBar()) self.central.setTabsClosable(True) self.central.tabCloseRequested.connect(self.tabCloseRequestedHandler) self.central.currentChanged.connect(self.currentTabChanged) layout = QtGui.QVBoxLayout() layout.addWidget(self.central) self.ui.sourceTextWidget.setLayout(layout) def tabCloseRequestedHandler(self, index): self.central.removeTab(index) def currentTabChanged(self, index): if index == -1: return # all tab closed def openSourceWindow(self, current_class, method=None): sourcewin = self.getMeSourceWindowIfExists(current_class) if not sourcewin: current_filename = self.session.get_filename_by_class( current_class) current_digest = self.session.get_digest_by_class(current_class) sourcewin = SourceWindow(win=self, current_class=current_class, current_title=current_class.current_title, current_filename=current_filename, current_digest=current_digest, session=self.session) sourcewin.reload_java_sources() self.central.addTab(sourcewin, sourcewin.title) self.central.setTabToolTip(self.central.indexOf(sourcewin), current_class.get_name()) if method: sourcewin.browse_to_method(method) self.central.setCurrentWidget(sourcewin) def openManifestWindow(self): manifest_tab = self.get_android_manifest_xml() self.central.addTab(manifest_tab, "AndroidManifest.xml") self.central.setCurrentWidget(manifest_tab) def openStringsWindow(self): stringswin = StringsWindow(win=self, session=self.session) self.central.addTab(stringswin, stringswin.title) self.central.setTabToolTip(self.central.indexOf(stringswin), stringswin.title) self.central.setCurrentWidget(stringswin) def openBytecodeWindow(self, current_class, method=None): byte_code_str = '' for clazz in self.d.get_classes(): if clazz.get_name() == current_class.get_name(): for method in clazz.get_methods(): byte_code_str += "# " + method.get_name( ) + " " + method.get_descriptor() + "\n" byte_code = method.get_code() if byte_code != None: byte_code = byte_code.get_bc() idx = 0 for i in byte_code.get_instructions(): byte_code_str += "\t, %x " % (idx) + i.get_name( ) + " " + i.get_output() + "\n" idx += i.get_length() bytecode_tab = self.get_bytecode_window(byte_code_str) self.central.addTab( bytecode_tab, "Bytecode: " + current_class.get_name()) self.central.setCurrentWidget(bytecode_tab) def showStatus(self, text): QtGui.QMessageBox.information(self, "Info", text) def get_bytecode_window(self, byte_code): return BytecodeWindow(byte_code, self) def getMeSourceWindowIfExists(self, path): '''Helper for openSourceWindow''' for idx in range(self.central.count()): if path == self.central.tabToolTip(idx): return self.central.widget(idx) return None def load_app_info_table(self): self.info = {} self.info["Application Name"] = self.apk.get_app_name() self.info["Application Size"] = util.sizeof_fmt( os.path.getsize(self.apk_path)) self.info["Android Version Name"] = self.apk.get_androidversion_name() self.info["Android Version Code"] = self.apk.get_androidversion_code() self.info["Android Package Name"] = self.apk.get_package() self.info["Signature Name"] = self.apk.get_signature_name() self.info["Uses Dynamic Code Loading"] = str( analysis.is_dyn_code(self.x)) self.info["Uses Reflection"] = str(analysis.is_reflection_code(self.x)) self.info["Uses Crypto"] = str(analysis.is_crypto_code(self.x)) self.info["Privacy Leaks"] = str(len(self.get_privacy_leaks())) self.info["Number of Providers"] = str(len(self.apk.get_providers())) self.info["Number of Activities"] = str(len(self.apk.get_activities())) self.info["Number of Services"] = str(len(self.apk.get_services())) self.info["Number of Libraries"] = str(len(self.apk.get_libraries())) self.info["Number of Permissions"] = str( len(self.get_uses_permissions())) self.info_actions = {} self.info_actions["Application Name"] = None self.info_actions["Application Size"] = None self.info_actions["Android Version Name"] = None self.info_actions["Android Version Code"] = None self.info_actions["Android Package Name"] = None self.info_actions["Signature Name"] = self.show_signature self.info_actions["Uses Dynamic Code Loading"] = self.show_dyncode self.info_actions["Uses Reflection"] = self.show_reflection self.info_actions["Uses Crypto"] = self.show_cryptocode self.info_actions["Privacy Leaks"] = self.show_privacy_leaks self.info_actions["Number of Providers"] = self.show_providers self.info_actions["Number of Activities"] = self.show_activities self.info_actions["Number of Services"] = self.show_services self.info_actions["Number of Libraries"] = self.show_libraries self.info_actions["Number of Permissions"] = self.show_permissions info_table = self.ui.appInfoTable info_table.setRowCount(len(self.info)) info_table.setColumnWidth(1, 200) info_table.horizontalHeader().setResizeMode(0, QtGui.QHeaderView.Stretch) row = 0 for key in sorted(self.info): action = self.info_actions[key] action_button = None if action is not None: action_button = QtGui.QPushButton() action_button.setText("Show") action_button.clicked.connect(action) key_item = QtGui.QTableWidgetItem(key) value_item = QtGui.QTableWidgetItem(self.info[key]) info_table.setItem(row, 0, key_item) info_table.setItem(row, 1, value_item) if action_button is not None: info_table.setCellWidget(row, 2, action_button) row += 1 def show_providers(self): self.__logger.log_with_title("Providers", self.apk.get_providers()) def show_signature(self): certname = "" signature_expr = re.compile("^(META-INF/)(.*)(\.RSA|\.DSA)$") for i in self.apk.get_files(): if signature_expr.search(i): f = tempfile.NamedTemporaryFile(delete=False) f.write(self.apk.get_file(i)) f.close() certname = f.name cmd = ["keytool", "-printcert", "-file", certname] proc = subprocess.Popen(cmd, stdout=subprocess.PIPE) (out, err) = proc.communicate() self.__logger.log_with_title("Signature", out) def show_services(self): self.__logger.log_with_title( "Services", '\n\t -' + '\n\t -'.join(self.apk.get_services())) def show_permissions(self): self.__logger.log( Logger.INFO, "Searching for permission usage, this can take a while depending on the size of the app." ) p = self.x.get_permissions([]) self.__logger.log_with_title("Permissions Usage", "") for i in p: self.__logger.log_with_color(Logger.WARNING, "\n\t=======" + i + "=======\n") for j in p[i]: self.__logger.log_with_color( Logger.INFO, "\t\t -" + self.show_Path(self.x.get_vm(), j) + "\n") def show_cryptocode(self): if analysis.is_crypto_code(self.x) is False: self.__logger.log( Logger.WARNING, "No crypto code was found! Maybe it uses adapted or obfuscated encryption code." ) return paths = [] paths.extend(self.x.get_tainted_packages().search_methods( "Ljavax/crypto/.", ".", ".")) paths.extend(self.x.get_tainted_packages().search_methods( "Ljava/security/spec/.", ".", ".")) str_info_dyn = "\t" for path in paths: str_info_dyn += (self.show_Path(self.x.get_vm(), path) + "\n\n\t") self.__logger.log_with_title("Usage of Crypto Code", str_info_dyn) def show_dyncode(self): if analysis.is_dyn_code(self.x) is False: self.__logger.log(Logger.WARNING, "No dynamic code was found!") return paths = [] paths.extend(self.x.get_tainted_packages().search_methods( "Ldalvik/system/BaseDexClassLoader;", "<init>", ".")) paths.extend(self.x.get_tainted_packages().search_methods( "Ldalvik/system/PathClassLoader;", "<init>", ".")) paths.extend(self.x.get_tainted_packages().search_methods( "Ldalvik/system/DexClassLoader;", "<init>", ".")) paths.extend(self.x.get_tainted_packages().search_methods( "Ldalvik/system/DexFile;", "<init>", ".")) paths.extend(self.x.get_tainted_packages().search_methods( "Ldalvik/system/DexFile;", "loadDex", ".")) str_info_dyn = "\t" for path in paths: str_info_dyn += (self.show_Path(self.x.get_vm(), path) + "\n\n\t") self.__logger.log_with_title("Usage of Dynamic Code", str_info_dyn) def get_privacy_leaks(self): paths = [] paths.extend(self.x.get_tainted_packages().search_methods( ".", "getDeviceId", ".")) paths.extend(self.x.get_tainted_packages().search_methods( ".", "getSubscriberId", ".")) paths.extend(self.x.get_tainted_packages().search_methods( ".", "getSimSerialNumber", ".")) paths.extend(self.x.get_tainted_packages().search_methods( ".", "getLine1Number", ".")) paths.extend(self.x.get_tainted_packages().search_methods( ".", "getAddress", ".")) paths.extend(self.x.get_tainted_packages().search_methods( ".", "getMacAddress", ".")) paths.extend(self.x.get_tainted_packages().search_methods( ".", "getSSID", ".")) paths.extend(self.x.get_tainted_packages().search_methods( ".", "getCid", ".")) paths.extend(self.x.get_tainted_packages().search_methods( ".", "getAccounts", ".")) paths.extend(self.x.get_tainted_packages().search_methods( ".", "getTimeZone", ".")) paths.extend(self.x.get_tainted_packages().search_methods( ".", "getAllBookmarks", ".")) paths.extend(self.x.get_tainted_packages().search_methods( ".", "getAllVisitedUrls", ".")) return paths def show_privacy_leaks(self): paths = self.get_privacy_leaks() if len(paths) == 0: self.__logger.log(Logger.WARNING, "No privacy leaks were found!") return str_info_privacy = "\t" for path in paths: str_info_privacy += (self.show_Path(self.x.get_vm(), path) + "\n\n\t") self.__logger.log_with_title("Possible Privacy Leaks", str_info_privacy) def show_reflection(self): if analysis.is_reflection_code(self.x) is False: self.__logger.log(Logger.WARNING, "No reflection code was found!") return paths = self.x.get_tainted_packages().search_methods( "Ljava/lang/reflect/Method;", ".", ".") str_info_reflection = "" for path in paths: str_info_reflection += (self.show_Path(self.x.get_vm(), path) + "\n\n\t") self.__logger.log_with_title("Usage of Reflection", str_info_reflection) def show_activities(self): self.__logger.log_with_title( "Activities", '\n\t -' + '\n\t -'.join(self.apk.get_activities())) def show_libraries(self): if len(self.apk.get_libraries()) == 0: self.__logger.log(Logger.WARNING, "No libraries were found.") return self.__logger.log(Logger.INFO, "Libraries Used: " + str(self.apk.get_libraries())) def init_actions(self): self.ui.saveLogBtn.clicked.connect(self.__logger.saveLog) self.ui.clearLogBtn.clicked.connect(self.__logger.clearLog) self.ui.showStringsBtn.clicked.connect(self.openStringsWindow) self.ui.showManifestBtn.clicked.connect(self.openManifestWindow) def load_apk_from_device(self): table = DeviceTable(self, parent=self) self.sample_dialog.close() table.show_data() table.exec_() def load_apk(self, path=None): if not path: path = QtGui.QFileDialog.getOpenFileName( self, "Open File", '', "APK Files (*.apk);;Androguard Session (*.ag)") self.apk_path = path self.set_loading_progressbar( "Analyzing APK", "Please wait while the APK is being dissected") self.__logger.log(Logger.INFO, "Analyzing %s..." % str(path)) self.apk = apk.APK(self.apk_path) self.manifest = self.apk.get_AndroidManifest().getElementsByTagName( "manifest")[0] self.fileLoadingThread.load(str(path)) def load_permissions(self): perms = self.get_uses_permissions() self.ui.permsTable.setRowCount(len(perms)) self.ui.permsTable.horizontalHeader().setStretchLastSection(True) for i, uses_permission in enumerate(perms): newitem = QtGui.QTableWidgetItem(uses_permission) self.ui.permsTable.setItem(0, i, newitem) def get_uses_permissions(self): permissions = [] for uses_permission in self.manifest.getElementsByTagName( "uses-permission"): permissions.append( uses_permission.attributes["android:name"].value) return permissions def set_loading_progressbar(self, title, text): self.dialog = QtGui.QProgressDialog(self) self.dialog.setMinimum(0) self.dialog.setLabelText(text) self.dialog.setMaximum(0) self.dialog.setWindowTitle(title) self.dialog.setCancelButton(None) self.dialog.setModal(True) self.dialog.show() def set_loading_progressbar_text(self, text): if self.dialog is not None: self.dialog.setLabelText(text) def set_loading_progressbar_disabled(self): self.dialog.hide() def show_Path(self, vm, path): cm = vm.get_class_manager() if isinstance(path, analysis.PathVar): dst_class_name, dst_method_name, dst_descriptor = path.get_dst(cm) info_var = path.get_var_info() return "%s %s (0x%x) ---> %s->%s%s " % ( path.get_access_flag(), info_var, path.get_idx(), dst_class_name, dst_method_name, dst_descriptor) else: if path.get_access_flag() == analysis.TAINTED_PACKAGE_CALL: src_class_name, src_method_name, src_descriptor = path.get_src( cm) dst_class_name, dst_method_name, dst_descriptor = path.get_dst( cm) return "%d %s->%s%s (0x%x) ---> %s->%s%s" % ( path.get_access_flag(), src_class_name, src_method_name, src_descriptor, path.get_idx(), dst_class_name, dst_method_name, dst_descriptor) else: src_class_name, src_method_name, src_descriptor = path.get_src( cm) return "%d %s->%s%s (0x%x)" % (path.get_access_flag(), src_class_name, src_method_name, src_descriptor, path.get_idx())
class MainWindow(QtGui.QMainWindow): '''Main window: self.central: QTabWidget in center area self.dock: QDockWidget in left area self.tree: TreeWindow(QTreeWidget) in self.dock ''' def __init__(self, parent=None, input_file=None): super(MainWindow, self).__init__(parent) self.session = None self.setupSession() self.setupFileMenu() self.setupViewMenu() self.setupHelpMenu() self.setupCentral() self.setupEmptyTree() self.setupDock() self.setWindowTitle("Androguard GUI") self.showStatus("Androguard GUI") if input_file != None: self.openFile(input_file) def showStatus(self, msg): '''Helper function called by any window to display a message in status bar. ''' androconf.debug(msg) self.statusBar().showMessage(msg) def about(self): '''User clicked About menu. Display a Message box.''' QtGui.QMessageBox.about(self, "About Androguard GUI", "<p><b>Androguard GUI</b> is basically a GUI for Androguard :)." \ "<br>Have fun !</p>") def setupSession(self): self.session = Session() self.fileLoadingThread = FileLoadingThread(self.session) self.connect(self.fileLoadingThread, QtCore.SIGNAL("loadedFile(bool)"), self.loadedFile) def loadedFile(self, success): if not success: self.showStatus("Analysis of %s failed :(" % str(self.fileLoadingThread.file_path)) return self.updateDockWithTree() self.cleanCentral() self.showStatus("Analysis of %s done!" % str(self.fileLoadingThread.file_path)) def openFile(self, path=None): '''User clicked Open menu. Display a Dialog to ask which file to open.''' self.session.reset() if not path: path = QtGui.QFileDialog.getOpenFileName(self, "Open File", '', "Android Files (*.apk *.jar *.dex *.odex *.dey);;Androguard Session (*.ag)") path = str(path[0]) if path: self.setupTree() self.showStatus("Analyzing %s..." % str(path)) self.fileLoadingThread.load(path) def addFile(self, path=None): '''User clicked Open menu. Display a Dialog to ask which APK to open.''' if not self.session.isOpen(): return if not path: path = QtGui.QFileDialog.getOpenFileName(self, "Add File", '', "Android Files (*.apk *.jar *.dex *.odex *.dey)") path = str(path[0]) if path: self.showStatus("Analyzing %s..." % str(path)) self.fileLoadingThread.load(path) def saveFile(self, path=None): '''User clicked Save menu. Display a Dialog to ask whwre to save.''' if not path: path = QtGui.QFileDialog.getSaveFileName(self, "Save File", '', "Androguard Session (*.ag)") path = str(path[0]) if path: self.showStatus("Saving %s..." % str(path)) self.saveSession(path) def saveSession(self, path): '''Save androguard session.''' try: self.session.save(path) except RuntimeError as e: androconf.error(str(e)) # http://stackoverflow.com/questions/2134706/hitting-maximum-recursion-depth-using-pythons-pickle-cpickle androconf.error("Try increasing sys.recursionlimit") os.remove(path) androconf.warning("Session not saved") def quit(self): '''Clicked in File menu to exit or CTRL+Q to close main window''' QtGui.qApp.quit() def closeEvent(self, event): '''Clicked [x] to close main window''' event.accept() def setupEmptyTree(self): '''Setup empty Tree at startup. ''' if hasattr(self, "tree"): del self.tree self.tree = QtGui.QTreeWidget(self) self.tree.header().close() def setupDock(self): '''Setup empty Dock at startup. ''' self.dock = QtGui.QDockWidget("Classes", self) self.dock.setWidget(self.tree) self.dock.setFeatures(QtGui.QDockWidget.NoDockWidgetFeatures) self.addDockWidget(QtCore.Qt.LeftDockWidgetArea, self.dock) def setupTree(self): androconf.debug("Setup Tree") self.tree = TreeWindow(win=self, session=self.session) self.tree.setWindowTitle("Tree model") self.dock.setWidget(self.tree) def setupCentral(self): '''Setup empty window supporting tabs at startup. ''' self.central = QtGui.QTabWidget() self.central.setTabsClosable(True) self.central.tabCloseRequested.connect(self.tabCloseRequestedHandler) self.central.currentChanged.connect(self.currentTabChanged) self.setCentralWidget(self.central) def tabCloseRequestedHandler(self, index): self.central.removeTab(index) def currentTabChanged(self, index): androconf.debug("curentTabChanged -> %d" % index) if index == -1: return # all tab closed def cleanCentral(self): #TOFIX: Removes all the pages, but does not delete them. self.central.clear() def setupFileMenu(self): fileMenu = QtGui.QMenu("&File", self) self.menuBar().addMenu(fileMenu) fileMenu.addAction("&Open...", self.openFile, "Ctrl+O") fileMenu.addAction("&Add...", self.addFile, "Ctrl+A") fileMenu.addAction("&Save...", self.saveFile, "Ctrl+S") fileMenu.addAction("E&xit", self.quit, "Ctrl+Q") def setupViewMenu(self): viewMenu = QtGui.QMenu("&View", self) self.menuBar().addMenu(viewMenu) viewMenu.addAction("&Strings...", self.openStringsWindow) def setupHelpMenu(self): helpMenu = QtGui.QMenu("&Help", self) self.menuBar().addMenu(helpMenu) helpMenu.addAction("&About", self.about) helpMenu.addAction("About &Qt", QtGui.qApp.aboutQt) def updateDockWithTree(self, empty=False): '''Update the classes tree. Called when - a new APK has been imported - a classe has been renamed (displayed in the tree) ''' self.setupTree() self.tree.fill() def openStringsWindow(self): stringswin = StringsWindow(win=self, session=self.session) self.central.addTab(stringswin, stringswin.title) self.central.setTabToolTip(self.central.indexOf(stringswin), stringswin.title) self.central.setCurrentWidget(stringswin) def openBytecodeWindow(self, current_class, method=None): pass#self.central.setCurrentWidget(sourcewin) def openSourceWindow(self, current_class, method=None): '''Main function to open a .java source window It checks if it already opened and open that tab, otherwise, initialize a new window. ''' androconf.debug("openSourceWindow for %s" % current_class) sourcewin = self.getMeSourceWindowIfExists(current_class) if not sourcewin: current_filename = self.session.get_filename_by_class(current_class) current_digest = self.session.get_digest_by_class(current_class) sourcewin = SourceWindow(win=self, current_class=current_class, current_title=current_class.current_title, current_filename=current_filename, current_digest=current_digest, session=self.session) sourcewin.reload_java_sources() self.central.addTab(sourcewin, sourcewin.title) self.central.setTabToolTip(self.central.indexOf(sourcewin), current_class.get_name()) if method: sourcewin.browse_to_method(method) self.central.setCurrentWidget(sourcewin) def getMeSourceWindowIfExists(self, current_class): '''Helper for openSourceWindow''' for idx in range(self.central.count()): if current_class.get_name() == self.central.tabToolTip(idx): androconf.debug("Tab %s already opened at: %d" % (current_class.get_name(), idx)) return self.central.widget(idx) return None def doesClassExist(self, path): arg = class2func(path) try: getattr(self.d, arg) except AttributeError: return False return True
class MainWindow(QtGui.QMainWindow): '''Main window: self.central: QTabWidget in center area self.dock: QDockWidget in left area self.tree: TreeWindow(QTreeWidget) in self.dock ''' def __init__(self, parent=None, input_file=None): super(MainWindow, self).__init__(parent) self.session = None self.setupSession() self.setupFileMenu() self.setupViewMenu() self.setupHelpMenu() self.setupCentral() self.setupEmptyTree() self.setupDock() self.setWindowTitle("Androguard GUI") self.showStatus("Androguard GUI") if input_file != None: self.openFile(input_file) def showStatus(self, msg): '''Helper function called by any window to display a message in status bar. ''' androconf.debug(msg) self.statusBar().showMessage(msg) def about(self): '''User clicked About menu. Display a Message box.''' QtGui.QMessageBox.about(self, "About Androguard GUI", "<p><b>Androguard GUI</b> is basically a GUI for Androguard :)." "<br>Have fun !</p>") def setupSession(self): self.session = Session() self.fileLoadingThread = FileLoadingThread(self.session) self.connect(self.fileLoadingThread, QtCore.SIGNAL("loadedFile(bool)"), self.loadedFile) def loadedFile(self, success): if not success: self.showStatus("Analysis of %s failed :(" % str(self.fileLoadingThread.file_path)) return self.updateDockWithTree() self.cleanCentral() self.showStatus("Analysis of %s done!" % str(self.fileLoadingThread.file_path)) def openFile(self, path=None): '''User clicked Open menu. Display a Dialog to ask which file to open.''' self.session.reset() if not path: path = QtGui.QFileDialog.getOpenFileName(self, "Open File", '', "Android Files (*.apk *.jar *.dex *.odex *.dey);;Androguard Session (*.ag)") path = str(path[0]) if path: self.setupTree() self.showStatus("Analyzing %s..." % str(path)) self.fileLoadingThread.load(path) def addFile(self, path=None): '''User clicked Open menu. Display a Dialog to ask which APK to open.''' if not self.session.isOpen(): return if not path: path = QtGui.QFileDialog.getOpenFileName(self, "Add File", '', "Android Files (*.apk *.jar *.dex *.odex *.dey)") path = str(path[0]) if path: self.showStatus("Analyzing %s..." % str(path)) self.fileLoadingThread.load(path) def saveFile(self, path=None): '''User clicked Save menu. Display a Dialog to ask whwre to save.''' if not path: path = QtGui.QFileDialog.getSaveFileName(self, "Save File", '', "Androguard Session (*.ag)") path = str(path[0]) if path: self.showStatus("Saving %s..." % str(path)) self.saveSession(path) def saveSession(self, path): '''Save androguard session.''' try: self.session.save(path) except RuntimeError as e: androconf.error(str(e)) # http://stackoverflow.com/questions/2134706/hitting-maximum-recursion-depth-using-pythons-pickle-cpickle androconf.error("Try increasing sys.recursionlimit") os.remove(path) androconf.warning("Session not saved") def quit(self): '''Clicked in File menu to exit or CTRL+Q to close main window''' QtGui.qApp.quit() def closeEvent(self, event): '''Clicked [x] to close main window''' event.accept() def setupEmptyTree(self): '''Setup empty Tree at startup. ''' if hasattr(self, "tree"): del self.tree self.tree = QtGui.QTreeWidget(self) self.tree.header().close() def setupDock(self): '''Setup empty Dock at startup. ''' self.dock = QtGui.QDockWidget("Classes", self) self.dock.setWidget(self.tree) self.dock.setFeatures(QtGui.QDockWidget.NoDockWidgetFeatures) self.addDockWidget(QtCore.Qt.LeftDockWidgetArea, self.dock) def setupTree(self): androconf.debug("Setup Tree") self.tree = TreeWindow(win=self, session=self.session) self.tree.setWindowTitle("Tree model") self.dock.setWidget(self.tree) def setupCentral(self): '''Setup empty window supporting tabs at startup. ''' self.central = QtGui.QTabWidget() self.central.setTabsClosable(True) self.central.tabCloseRequested.connect(self.tabCloseRequestedHandler) self.central.currentChanged.connect(self.currentTabChanged) self.setCentralWidget(self.central) def tabCloseRequestedHandler(self, index): self.central.removeTab(index) def currentTabChanged(self, index): androconf.debug("curentTabChanged -> %d" % index) if index == -1: return # all tab closed def cleanCentral(self): # TOFIX: Removes all the pages, but does not delete them. self.central.clear() def setupFileMenu(self): fileMenu = QtGui.QMenu("&File", self) self.menuBar().addMenu(fileMenu) fileMenu.addAction("&Open...", self.openFile, "Ctrl+O") fileMenu.addAction("&Add...", self.addFile, "Ctrl+A") fileMenu.addAction("&Save...", self.saveFile, "Ctrl+S") fileMenu.addAction("E&xit", self.quit, "Ctrl+Q") def setupViewMenu(self): viewMenu = QtGui.QMenu("&View", self) self.menuBar().addMenu(viewMenu) viewMenu.addAction("&Strings...", self.openStringsWindow) def setupHelpMenu(self): helpMenu = QtGui.QMenu("&Help", self) self.menuBar().addMenu(helpMenu) helpMenu.addAction("&About", self.about) helpMenu.addAction("About &Qt", QtGui.qApp.aboutQt) def updateDockWithTree(self, empty=False): '''Update the classes tree. Called when - a new APK has been imported - a classe has been renamed (displayed in the tree) ''' self.setupTree() self.tree.fill() def openStringsWindow(self): stringswin = StringsWindow(win=self, session=self.session) self.central.addTab(stringswin, stringswin.title) self.central.setTabToolTip( self.central.indexOf(stringswin), stringswin.title) self.central.setCurrentWidget(stringswin) def openBytecodeWindow(self, current_class, method=None): pass # self.central.setCurrentWidget(sourcewin) def openSourceWindow(self, current_class, method=None): '''Main function to open a .java source window It checks if it already opened and open that tab, otherwise, initialize a new window. ''' androconf.debug("openSourceWindow for %s" % current_class) sourcewin = self.getMeSourceWindowIfExists(current_class) if not sourcewin: current_filename = self.session.get_filename_by_class( current_class) current_digest = self.session.get_digest_by_class(current_class) sourcewin = SourceWindow(win=self, current_class=current_class, current_title=current_class.current_title, current_filename=current_filename, current_digest=current_digest, session=self.session) sourcewin.reload_java_sources() self.central.addTab(sourcewin, sourcewin.title) self.central.setTabToolTip(self.central.indexOf( sourcewin), current_class.get_name()) if method: sourcewin.browse_to_method(method) self.central.setCurrentWidget(sourcewin) def getMeSourceWindowIfExists(self, current_class): '''Helper for openSourceWindow''' for idx in range(self.central.count()): if current_class.get_name() == self.central.tabToolTip(idx): androconf.debug("Tab %s already opened at: %d" % (current_class.get_name(), idx)) return self.central.widget(idx) return None def doesClassExist(self, path): arg = class2func(path) try: getattr(self.d, arg) except AttributeError: return False return True