def push_and_start_frida_server(adb: ADB): """ Push and start adb server on device Parameters ---------- adb Returns ------- """ frida_server = os.path.join(os.getcwd(), "resources", "frida-server", "frida-server") try: adb.execute(['root']) except Exception as e: adb.kill_server() logger.error("Error on adb {}".format(e)) logger.info("Push frida server") try: adb.push_file(frida_server, "/data/local/tmp") except Exception as e: pass logger.info("Add execution permission to frida-server") chmod_frida = ["chmod 755 /data/local/tmp/frida-server"] adb.shell(chmod_frida) logger.info("Start frida server") start_frida = ["cd /data/local/tmp && ./frida-server &"] adb.shell(start_frida, is_async=True) time.sleep(4)
def push_api_monitor_xposed(adb: ADB, package_name: str, dir_hook_file: str): """ push file on emulator needed to api monitor Parameters ---------- adb package_name dir_hook_file Returns ------- """ logger.info("Push files needed to API Monitor") adb.push_file(os.path.join(dir_hook_file, "hooks.json"), "/data/local/tmp") adb.shell(['echo', '"{0}"'.format(package_name), '>', '/data/local/tmp/package.name'])
def ping(): trytime = 30 success = 0 adb = ADB() cmd = "ping -c 4 {0} | grep -q 'ttl=' && echo '{0} ok' || echo '{0} failed'".format( '61.135.169.125') print(cmd) for i in range(trytime): result = adb.shell(cmd) if 'ok' in result[1]: success += 1 print(result) return success
def push_and_start_frida_server(adb: ADB): """ Push and start adb server on device Parameters ---------- adb Returns ------- """ frida_server = os.path.join(os.path.dirname(__file__), "resources", "frida-server", "frida-server") cmd_output = adb.shell("ps -e | grep frida".split()) if "frida-server" in cmd_output: logger.warning("[*] frida-server is already running on device") return try: adb.execute(["root"]) except Exception as e: adb.kill_server() logger.error("Error on adb {}".format(e)) logger.info("[*] Push frida server") try: adb.push_file(frida_server, "/data/local/tmp") except Exception as e: pass logger.info("[*] Add execution permission to frida-server") chmod_frida = ["chmod 755 /data/local/tmp/frida-server"] adb.shell(chmod_frida) logger.info("Start frida server") start_frida = ["cd /data/local/tmp && ./frida-server &"] adb.shell(start_frida, is_async=True) time.sleep(4)
def loopInit(): powerUsbController.OPEN_MAIN_POWER() powerUsbController.OPEN_KL15() powerUsbController.USB_ON() time.sleep(20) logging.info("swith to Diagnostics Mode") openADB(TBOX_IP, TBOX_DIAG_KEY) time.sleep(20) powerUsbController.SHUTDOWN_KL15() time.sleep(2) adb = ADB() #adb.stopAdb() #adb.startAdb() logging.info( "adb shell zte_topsw_mcutest 29 02000600060002000200020004000000") ret = adb.shell("zte_topsw_mcutest 29 02000600060002000200020004000000", TBOX_ADB_SN) if (0 != ret[0]): raise Exception("send adb shell zte_topsw_mcutest failed! " + ret[2]) logging.info(ret[1]) time.sleep(1)
def push_and_start_frida_server_google_emulator(adb: ADB): """ Parameters ---------- adb Returns ------- """ frida_server = os.path.join(os.path.dirname(__file__), "resources", "frida-server-15-1-17", "frida-server") logger.info("[*] Checking if frida-server is already running") cmd_output = adb.shell("ps -e | grep frida") if "frida-server" in cmd_output: logger.warning("[*] frida-server is already running on device") return logger.info("[*] Push frida-server (google-emulator)") try: adb.push_file(frida_server, "/sdcard") adb.shell_su("mv /sdcard/frida-server /data/local/tmp/frida-server") except Exception as e: pass cmd_set_enforce = "setenforce 0" adb.shell_su(cmd_set_enforce) cmd_enforce_echo = "echo 0 > /sys/fs/selinux/enforce" adb.shell_su(cmd_enforce_echo) chmod_frida = "chmod 755 /data/local/tmp/frida-server" adb.shell_su(chmod_frida) logger.info("[*] Start frida server") start_frida = "/data/local/tmp/frida-server &" adb.shell_su(start_frida, is_async=True) time.sleep(4)
def start_analysis(list_apps: list, timeout_privacy: int, max_actions: int, type: str, emulator_name: str): logger.info("Start Analysis of {} apps".format(len(list_apps))) start = time.time() stats = Statistic(type) if type == "random": type = start_appium_node(type) logger.info("Upload model p3 detector") # upload model pdetector = PredictionModel() logger.info("P3detector model uploaded") # start analysis count = 0 tentative = 0 num_log = len(glob.glob(os.path.join(os.getcwd(), "logs", "log_analysis_*"))) log_analysis_file = os.path.join(os.getcwd(), "logs", "log_analysis_{}.json".format(num_log + 1)) log_permission_file = os.path.join(os.getcwd(), "logs", "permissions_stats_{}.json".format(num_log + 1)) log_trackers_file = os.path.join(os.getcwd(), "logs", "tracker_stats_{}.json".format(num_log + 1)) while count < len(list_apps): app = list_apps[count] dict_analysis_app = {} md5_app = md5(app) dir_result = os.path.join(os.getcwd(), "logs", md5_app) if not os.path.exists(dir_result): os.makedirs(dir_result) try: dict_analysis_app["md5"] = md5_app apk_object = APK(app) dict_analysis_app["package_name"] = apk_object.get_package() if not check_app_already_analyzed_md5(md5_app) or tentative > 0: logger.info("3PDroid start Analysis {}".format(app)) write_package_name_and_md5(apk_object.get_package(), md5_app, os.path.join(os.getcwd(), "logs", "package_md5.txt")) # start emulator r_start_emulator = requests.get("{}/start/{}".format(LOCAL_URL_EMULATOR, emulator_name)) # if the emulator star ok if r_start_emulator.status_code == 200: # get trackers libraries and list permissions logger.info("Start emulator ok") logger.info("Get application information") app_trackers, app_permissions_list, api_to_monitoring_trackers, application, dict_analysis_app = app_analyzer. \ analyze_apk_androguard(app, md5_app, dict_analysis_app) # get permissions privacy relevant logger.info("Package name {}".format(application.get_package())) dict_analysis_app["package_name"] = application.get_package() logger.info("MD5 {}".format(md5_app)) logger.info("Get permission-api mapping") permissions_api_mapping = app_analyzer.get_api_related_to_permission_privacy_relevant() logger.info("Creation list api to be monitored during dynamic analysis") list_api_to_monitoring = app_analyzer.create_list_api_to_monitoring_from_file( permissions_api_mapping, app_permissions_list, app_trackers) # if API == 0 --> app is cleaned if len(list_api_to_monitoring) == 0: logger.info("Application does not need privacy policy page, " "close the emulator and " "pass to the next application") # write on file file_name = md5_app dir_result = os.path.join(os.getcwd(), "logs", file_name) dict_analysis_app["dynamic_analysis_needed"] = False with open(os.path.join(dir_result, "{}.json".format(file_name)), "w") as json_file: json.dump(dict_analysis_app, json_file, indent=4) count += 1 r_stop_emulator = requests.get("{}/stop/{}".format(LOCAL_URL_EMULATOR, emulator_name)) # UPDATE STATS logger.info("Update stats") stats.update_stats_permission(app_permissions_list) stats.update_stats_trackers(app_trackers) stats.add_app_cleaned() # STORE INFORMATION logger.info(stats.stats_trackers) logger.info(stats.stats_permission) # write on file stats.write_on_file(log_analysis_file, count) stats.write_stats_permissions(log_permission_file) stats.write_stats_trackers(log_trackers_file) shutil.move(app, os.path.join(os.getcwd(), "apps_analyzed")) continue # APP should be analyzed in a dynamic way logger.info("Number of APIs to monitoring: {}".format(len(list_api_to_monitoring))) file_name = md5_app dir_result = os.path.join(os.getcwd(), "logs", file_name) dict_analysis_app["api_to_monitoring_all"] = len(list_api_to_monitoring) with open(os.path.join(dir_result, "{}.json".format(file_name)), "w") as json_file: json.dump(dict_analysis_app, json_file, indent=4) # disable verify installer and set correct time time.sleep(5) logger.info("Set correct time on emulator") adb = ADB() adb.kill_server() adb.connect() time.sleep(2) try: command_settings_verify = ["settings put global verifier_verify_adb_installs 0"] adb.shell(command_settings_verify) date_command = ['su 0 date {0}; am broadcast -a android.intent.action.TIME_SET'. format(time.strftime('%m%d%H%M%Y.%S'))] adb.shell(date_command) except Exception as e: logger.error("Exception as e {}, restart and re-connect to emulator".format(e)) adb.kill_server() adb.connect() command_settings_verify = ["settings put global verifier_verify_adb_installs 0"] adb.shell(command_settings_verify) date_command = ['su 0 date {0}; am broadcast -a android.intent.action.TIME_SET'. format(time.strftime('%m%d%H%M%Y.%S'))] adb.shell(date_command) dir_hook_file = os.path.join(os.getcwd(), "hook", md5_app) logger.info("Creation hook dir frida") if not os.path.exists(dir_hook_file): os.makedirs(dir_hook_file) hook_is_created = app_analyzer.create_api_list_frida(list_api_to_monitoring, os.path.join(dir_hook_file, "frida_api.txt")) frida_monitoring.push_and_start_frida_server(adb) frida_monitoring.set_file_log_frida(os.path.join(os.getcwd(), "logs", md5_app, "monitoring_api_{}.json".format(md5_app))) frida_monitoring.clean_list_json_api_invoked() signal.signal(signal.SIGALRM, handler_timeout) signal.alarm(MAX_TIME_ANALYSIS) result_app, dict_analysis_app = dynamic_testing_environment.start_analysis(type_analysis=type, app=app, max_actions=max_actions, timeout_privacy=timeout_privacy, pdetector=pdetector, md5_app=md5_app, frida_monitoring=frida_monitoring, dict_analysis_app=dict_analysis_app) # END DYNAMIC ANALYSIS NOW STORE DATA result_directory = os.path.join(os.getcwd(), "logs", md5_app) if not os.path.exists(result_directory): os.makedirs(result_directory) logger.info("Analysis api invoked during dynamic analysis") # Get API Invoked list_json_api_invoked = frida_monitoring.get_list_api_invoked() # set_list_json_api_invoked = list(set(list_json_api_invoked)) logger.info("Api invoked during dynamic analysis {}".format(len(list_json_api_invoked))) # store on json file the api invoked if len(list_json_api_invoked) > 0: file_log_frida = frida_monitoring.get_file_log_frida() with open(file_log_frida, "w") as outfile_api: json.dump(list_json_api_invoked, outfile_api, indent=4) # Action not needed # DETECT IF THE APP IS COMPLIANT OR NOT WITH GOOGLE PLAY STORE app_is_compliant = result_app.detected and not (result_app.back_button_change_page or result_app.home_button_change_page or len(list_json_api_invoked) > 0 ) dict_analysis_app["api_invoked_during_dynamic_analysis"] = len(list_json_api_invoked) if app_is_compliant: stats.add_app_compliant() else: stats.add_app_not_compliant() dict_analysis_app["app_is_compliant_with_google_play_store"] = app_is_compliant dict_analysis_app["app_analyzed"] = True dict_analysis_app["num_tentative"] = tentative + 1 write_json_file_log(md5_app, dict_analysis_app) stats.add_api_privacy_relevant_invoked(len(list_json_api_invoked)) # r_reset_emulator = requests.get("{}/reset/{}".format(LOCAL_URL_EMULATOR, emulator_name)) r_stop_emulator = requests.get("{}/stop/{}".format(LOCAL_URL_EMULATOR, emulator_name)) # UPDATE stats analysis logger.info("Update stats") stats.list_max_actions.append(result_app.max_actions) stats.update_value_dynamic_analysis(result_app) stats.update_stats_permission(app_permissions_list) stats.update_stats_trackers(app_trackers) # debug logger.info(stats.stats_trackers) logger.info(stats.stats_permission) # write on file count += 1 tentative = 0 stats.write_on_file(log_analysis_file, count) stats.write_stats_permissions(log_permission_file) stats.write_stats_trackers(log_trackers_file) logger.info("End update stats") logger.info("3PDroid end analysis") str_end_file = "*" * 20 logger.info("{}\n\n".format(str_end_file)) shutil.move(app, os.path.join(os.getcwd(), "apps_analyzed")) else: tentative += 1 if tentative < MAX_TENTATIVE: logger.error("Unable to start emulator, try again this app") str_end_file = "*" * 20 logger.info("{}\n\n".format(str_end_file)) r_stop_emulator = requests.get("{}/stop/{}".format(LOCAL_URL_EMULATOR, emulator_name)) else: tentative = 0 count += 1 r_stop_emulator = requests.get("{}/stop/{}".format(LOCAL_URL_EMULATOR, emulator_name)) stats.add_app_not_analyzed() dict_analysis_app["app_analyzed"] = False write_json_file_log(md5_app, dict_analysis_app) logger.error("Unable to start emulator, pass to next app") str_end_file = "*" * 20 logger.info("{}\n\n".format(str_end_file)) shutil.move(app, os.path.join(os.getcwd(), "apps_analyzed")) else: logger.info("App already analyzed, pass to next app") shutil.move(app, os.path.join(os.getcwd(), "apps_analyzed")) count += 1 except MyTimeoutExcpetion as e: tentative += 1 if tentative < MAX_TENTATIVE: logger.error("Exception stop emulator, Exception: {}".format(e)) str_end_file = "*" * 20 logger.info("{}\n\n".format(str_end_file)) r_stop_emulator = requests.get("{}/stop/{}".format(LOCAL_URL_EMULATOR, emulator_name)) else: tentative = 0 count += 1 r_stop_emulator = requests.get("{}/stop/{}".format(LOCAL_URL_EMULATOR, emulator_name)) stats.add_app_not_analyzed() logger.error("Exception stop emulator pass to next apps, Exception: {}".format(e)) dict_analysis_app["app_analyzed"] = False write_json_file_log(md5_app, dict_analysis_app) str_end_file = "*" * 20 logger.info("{}\n\n".format(str_end_file)) shutil.move(app, os.path.join(os.getcwd(), "apps_analyzed")) except Exception as e: tentative += 1 if tentative < MAX_TENTATIVE: logger.error("Exception stop emulator, Exception: {}".format(e)) str_end_file = "*" * 20 logger.info("{}\n\n".format(str_end_file)) r_stop_emulator = requests.get("{}/stop/{}".format(LOCAL_URL_EMULATOR, emulator_name)) else: tentative = 0 count += 1 r_stop_emulator = requests.get("{}/stop/{}".format(LOCAL_URL_EMULATOR, emulator_name)) stats.add_app_not_analyzed() logger.error("Exception stop emulator pass to next apps, Exception: {}".format(e)) dict_analysis_app["app_analyzed"] = False write_json_file_log(md5_app, dict_analysis_app) str_end_file = "*" * 20 logger.info("{}\n\n".format(str_end_file)) shutil.move(app, os.path.join(os.getcwd(), "apps_analyzed")) end = time.time() logger.info("\n\n") logger.info("Execution time: {}, mean: {}".format(end - start, (end - start) / count))
from adb import ADB debug = ADB() print debug.devices() debug.shell("input keyevent 26") debug.shell("input keyevent 82") debug.shell("input swipe 50 50 800 50") #debug.screenShot("./ss.png")
class Device(object): def __init__(self, device=None): self.logger = logging.getLogger(self.__class__.__name__) if device is None: from .DeviceHelper import get_available_devices all_devices = get_available_devices() if len(all_devices) == 0: self.logger.warning("ERROR: No device connected.") sys.exit(-1) device = all_devices[0] if "emulator" in device: self.is_emulator = True else: self.is_emulator = False self.device = device self.grant_perm = False ## Grant all permissions while installing. Useful for Android 6.0+ # basic device information self.display_info = None self.sdk_version = None self.release_version = None self.ro_debuggable = None self.ro_secure = None self.connected = True # adapters self.adb = ADB(device=self) self.adapters = { self.adb: True, # self.telnet: False, } def set_up(self): """ Set connections on this device """ # wait for emulator to start self.wait_for_device() for adapter in self.adapters: adapter_enabled = self.adapters[adapter] if not adapter_enabled: continue adapter.set_up() def connect(self): """ establish connections on this device :return: """ for adapter in self.adapters: adapter_enabled = self.adapters[adapter] if not adapter_enabled: continue adapter.connect() self.get_sdk_version() self.get_release_version() self.get_ro_secure() self.get_ro_debuggable() self.get_display_info() self.unlock() self.check_connectivity() self.connected = True def disconnect(self): """ disconnect current device :return: """ self.connected = False for adapter in self.adapters: adapter_enabled = self.adapters[adapter] if not adapter_enabled: continue adapter.disconnect() def tear_down(self): for adapter in self.adapters: adapter_enabled = self.adapters[adapter] if not adapter_enabled: continue adapter.tear_down() def install_app(self, app): """ install an app to device @param app: instance of App @return: """ assert isinstance(app, App) # subprocess.check_call(["adb", "-s", self.serial, "uninstall", app.get_package_name()], # stdout=subprocess.PIPE, stderr=subprocess.PIPE) package_name = app.get_package_name() if package_name not in self.adb.get_installed_apps(): install_cmd = ["adb", "-s", self.device, "install", "-r"] if self.grant_perm and self.get_sdk_version() >= 23: install_cmd.append("-g") install_cmd.append(app.app_path) install_p = subprocess.Popen(install_cmd, stdout=subprocess.PIPE) count = 0 while self.connected and package_name not in self.adb.get_installed_apps( ): print("Please wait while installing the app...") count += 1 if count > 30: break time.sleep(2) if not self.connected: install_p.terminate() return install_p.terminate() # dumpsys_p = subprocess.Popen(["adb", "-s", self.serial, "shell", # "dumpsys", "package", package_name], stdout=subprocess.PIPE) # dumpsys_lines = [] # while True: # line = dumpsys_p.stdout.readline() # if not line: # break # if not isinstance(line, str): # line = line.decode() # dumpsys_lines.append(line) # if self.output_dir is not None: # package_info_file_name = "%s/dumpsys_package_%s.txt" % (self.output_dir, app.get_package_name()) # package_info_file = open(package_info_file_name, "w") # package_info_file.writelines(dumpsys_lines) # package_info_file.close() # app.dumpsys_main_activity = self.__parse_main_activity_from_dumpsys_lines(dumpsys_lines) self.logger.info("App installed: %s" % package_name) self.logger.info("Main activity: %s" % app.get_main_activity()) def uninstall_app(self, app): """ Uninstall an app from device. :param app: an instance of App or a package name """ if isinstance(app, App): package_name = app.get_package_name() else: package_name = app if package_name in self.adb.get_installed_apps(): uninstall_cmd = [ "adb", "-s", self.device, "uninstall", package_name ] uninstall_p = subprocess.Popen(uninstall_cmd, stdout=subprocess.PIPE) while package_name in self.adb.get_installed_apps(): print("Please wait while uninstalling the app...") time.sleep(2) uninstall_p.terminate() def start_app(self, app): """ start an app on the device :param app: instance of App, or str of package name :return: """ if isinstance(app, str): package_name = app elif isinstance(app, App): package_name = app.get_package_name() if app.get_main_activity(): package_name += "/%s" % app.get_main_activity() else: self.logger.warning("unsupported param " + app + " with type: ", type(app)) return intent = Intent(suffix=package_name) self.send_intent(intent) def pull_file(self, remote_file, local_file): self.adb.run_cmd(["pull", remote_file, local_file]) def push_file(self, local_file, remote_dir="/sdcard/"): """ push file/directory to target_dir :param local_file: path to file/directory in host machine :param remote_dir: path to target directory in device :return: """ if not os.path.exists(local_file): self.logger.warning("push_file file does not exist: %s" % local_file) self.adb.run_cmd(["push", local_file, remote_dir]) def shutdown(self): self.adb.shell("reboot -p") def wait_for_device(self): """ wait until the device is fully booted :return: """ self.logger.info("waiting for device") try: subprocess.check_call( ["adb", "-s", self.device, "wait-for-device"]) except: self.logger.warning("error waiting for device") def get_sdk_version(self): """ Get version of current SDK """ if self.sdk_version is None: self.sdk_version = self.adb.get_sdk_version() return self.sdk_version def get_release_version(self): """ Get version of current SDK """ if self.release_version is None: self.release_version = self.adb.get_release_version() return self.release_version def get_ro_secure(self): if self.ro_secure is None: self.ro_secure = self.adb.get_ro_secure() return self.ro_secure def get_ro_debuggable(self): if self.ro_debuggable is None: self.ro_debuggable = self.adb.get_ro_debuggable() return self.ro_debuggable def get_display_info(self, refresh=True): """ get device display information, including width, height, and density :param refresh: if set to True, refresh the display info instead of using the old values :return: dict, display_info """ if self.display_info is None or refresh: self.display_info = self.adb.get_display_info() return self.display_info def unlock(self): """ unlock screen skip first-use tutorials etc :return: """ self.adb.unlock() def check_connectivity(self): """ check if the device is available """ for adapter in self.adapters: adapter_name = adapter.__class__.__name__ adapter_enabled = self.adapters[adapter] if not adapter_enabled: print("[CONNECTION] %s is not enabled." % adapter_name) else: if adapter.check_connectivity(): print("[CONNECTION] %s is enabled and connected." % adapter_name) else: print("[CONNECTION] %s is enabled but not connected." % adapter_name) def send_intent(self, intent): """ send an intent to device via am (ActivityManager) :param intent: instance of Intent or str :return: """ assert self.adb is not None assert intent is not None if isinstance(intent, Intent): cmd = intent.get_cmd() else: cmd = intent return self.adb.shell(cmd) def start_activity_via_monkey(self, package): """ use monkey to start activity @param package: package name of target activity """ cmd = 'monkey' if package: cmd += ' -p %s' % package out = self.adb.shell(cmd) if re.search(r"(Error)|(Cannot find 'App')", out, re.IGNORECASE | re.MULTILINE): raise RuntimeError(out) def get_package_path(self, package_name): """ get installation path of a package (app) :param package_name: :return: package path of app in device """ dat = self.adb.shell('pm path %s' % package_name) package_path_re = re.compile('^package:(.+)$') m = package_path_re.match(dat) if m: path = m.group(1) return path.strip() return None def app_is_running(self, packageName): """ Get app's pid information USER PID PPID VSIZE RSS WCHAN PC NAME u0_a1104 10881 870 1646504 57404 SyS_epoll_ 0000000000 S com.phikal.regex :return: """ cmd = "adb -s " + self.device + " shell \"ps|grep " + packageName + "\"" proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) (stdout, stderr) = proc.communicate() vtLine = stdout.split(os.linesep) if len(vtLine) > 1: for line in vtLine: vtLine = line.strip().split() if len(vtLine) == 9 and vtLine[8] == packageName: return True proc.terminate() return False def get_top_activity_name(self): """ Get current activity """ r = self.adb.shell("dumpsys activity activities") activity_line_re = re.compile( '\* Hist #\d+: ActivityRecord{[^ ]+ [^ ]+ ([^ ]+) t(\d+)}') m = activity_line_re.search(r) if m: return m.group(1).split('/')[0] # return m.group(1).replace("/","") # data = self.adb.shell("dumpsys activity top").splitlines() # regex = re.compile("\s*ACTIVITY ([A-Za-z0-9_.]+)/([A-Za-z0-9_.]+)") # m = regex.search(data[1]) # if m: # return m.group(1) + "/" + m.group(2) self.logger.warning("Unable to get top activity name.") return None @staticmethod def __parse_main_activity_from_dumpsys_lines(lines): main_activity = None activity_line_re = re.compile("[^ ]+ ([^ ]+)/([^ ]+) filter [^ ]+") action_re = re.compile("Action: \"([^ ]+)\"") category_re = re.compile("Category: \"([^ ]+)\"") activities = {} cur_package = None cur_activity = None cur_actions = [] cur_categories = [] for line in lines: line = line.strip() m = activity_line_re.match(line) if m: activities[cur_activity] = { "actions": cur_actions, "categories": cur_categories } cur_package = m.group(1) cur_activity = m.group(2) if cur_activity.startswith("."): cur_activity = cur_package + cur_activity cur_actions = [] cur_categories = [] else: m1 = action_re.match(line) if m1: cur_actions.append(m1.group(1)) else: m2 = category_re.match(line) if m2: cur_categories.append(m2.group(1)) if cur_activity is not None: activities[cur_activity] = { "actions": cur_actions, "categories": cur_categories } for activity in activities: if "android.intent.action.MAIN" in activities[activity]["actions"] \ and "android.intent.category.LAUNCHER" in activities[activity]["categories"]: main_activity = activity return main_activity