def __init__(self, apk_path, target_method, output_dir, max_search_layer=3): self.quark = Quark(apk_path) self.apkinfo = self.quark.apkinfo # Parse smali code into classname methodname and descriptor. classname = target_method.split("->")[0] methodname = target_method.split("->")[1].split("(")[0] descriptor = "(" + target_method.split("->")[1].split("(")[1] self.method = self.apkinfo.find_method( class_name=classname, method_name=methodname, descriptor=descriptor, ) if self.method is None: raise ValueError("Target method not found!") self.output_dir = output_dir self.api_set = set() self.max_search_layer = max_search_layer return
def analysis(self, apk, rule, core_library="androguard"): """ The main function of Quark-Engine analysis, the analysis is based on the provided APK file. :param core_library: the library to analysis binary :param apk: the APK file :param rule: the rule to be checked, it could be a directory or a single json rule :return: None """ self.quark = Quark(apk, core_library) if os.path.isdir(rule): rules_list = os.listdir(rule) for single_rule in rules_list: if single_rule.endswith("json"): rule_path = os.path.join(rule, single_rule) rule_checker = RuleObject(rule_path) # Run the checker self.quark.run(rule_checker) # Generate json report self.quark.generate_json_report(rule_checker) elif os.path.isfile(rule): if rule.endswith("json"): rule = RuleObject(rule) # Run checker self.quark.run(rule) # Generate json report self.quark.generate_json_report(rule)
class Report: """ This module is for users who want to use quark as a Python module. """ def __init__(self): self.quark = None def analysis(self, apk, rule, core_library="androguard"): """ The main function of Quark-Engine analysis, the analysis is based on the provided APK file. :param core_library: the library to analysis binary :param apk: the APK file :param rule: the rule to be checked, it could be a directory or a single json rule :return: None """ self.quark = Quark(apk, core_library) if os.path.isdir(rule): rules_list = os.listdir(rule) for single_rule in rules_list: if single_rule.endswith("json"): rule_path = os.path.join(rule, single_rule) rule_checker = RuleObject(rule_path) # Run the checker self.quark.run(rule_checker) # Generate json report self.quark.generate_json_report(rule_checker) elif os.path.isfile(rule): if rule.endswith("json"): rule = RuleObject(rule) # Run checker self.quark.run(rule) # Generate json report self.quark.generate_json_report(rule) def get_report(self, report_type): """ Output the Quark-Engine report according to the report_type argument. :param report_type: string of the report format :return: string of the quark report with the format you specify """ if report_type == "json": return self.quark.get_json_report() raise ValueError( "The format are not supported, please refer to the Quark manual." )
def main(apk, rule, thershold, output, summary): logging.basicConfig(level=logging.WARN) print(f'Apk File: {apk}') print(f'Rule Directory: {rule}', ) if output: print(f'Generate Json File: {output.name}') if thershold: print(f'Thershold:{thershold}%') counter = 0 displayed = 0 quark = Quark(apk) # Analyzing for rule_file in tqdm.tqdm(get_rules_from_directory(rule)): rule = QuarkRule(rule_file) quark.analysis_rule(rule) counter = counter + 1 # Summarizing if summary: for rule, behavior_list in quark.report.passed_behaviors.items(): max_stage = max( [behavior.reached_stage for behavior in behavior_list]) if thershold and max_stage*20 < thershold: continue print(f'Crime @ {rule.crime:<80} {max_stage*20:>3}%') displayed = displayed + 1 print(f'Total: {displayed}/{counter}') else: print(f'Total: {counter}') # Generate json file if output: json.dump(quark.get_json_report(), output, indent=4)
def _worker_initializer(apk, core_library): global _quark _quark = Quark(apk, core_library)
def entry_point( summary, detail, rule_generation, apk, rule, output, webreport, graph, classification, threshold, list, permission, label, comparison, core_library, num_of_process, ): """Quark is an Obfuscation-Neglect Android Malware Scoring System""" # Load rules rule_buffer_list = [] rule_filter = summary or detail # Determine the location of rules if rule_filter and rule_filter.endswith("json"): if not os.path.isfile(rule_filter): print_warning( f"Specified rule not found.\n" f"If you want to specify one of the rules of Quark-Rule, " f"use {yellow(f'{config.DIR_PATH}/{rule_filter}')} " f"as an argument.") return rule_path_list = [rule_filter] else: rule_path_list = [ os.path.join(dir_path, file) for dir_path, _, file_list in os.walk(rule) for file in file_list if file.endswith("json") ] # Load rules into memory update_rule_buffer(rule_buffer_list, rule_path_list) # Determine if the user provide a rule label if (rule_filter and rule_filter != "all_rules" and not rule_filter.endswith("json")): rule_checker_list = [ rule_checker for rule_checker in rule_buffer_list if rule_filter in rule_checker.label ] else: rule_checker_list = rule_buffer_list if comparison: # selection of labels on which it will be done the comparison on radar chart # first look for all label found in the rule list all_labels = set() # array type, e.g. ['network', 'collection'] for rule_checker in rule_checker_list: all_labels.update(rule_checker.label) # let user choose a list of label on which it will be performed the analysis selected_label = np.array( select_label_menu(all_labels, min_labels=5, max_labels=15)) # perform label based analysis on the apk_ malware_confidences = {} for apk_ in apk: data = (ParallelQuark(apk_, core_library, num_of_process) if num_of_process > 1 else Quark(apk_, core_library)) all_labels = {} # dictionary containing # key: label # value: list of confidence values # $ print(all_rules["accessibility service"]) # > [60, 40, 60, 40, 60, 40] # analyse malware only on rules where appears label selected rule_checker_list = [ rule_checker for rule_checker in rule_buffer_list if len(np.intersect1d(rule_checker.label, selected_label)) != 0 ] if num_of_process > 1: data.apply_rules(rule_checker_list) for rule_checker in tqdm(rule_checker_list): # Run the checker data.run(rule_checker) confidence = rule_checker.check_item.count(True) * 20 labels = (rule_checker.label ) # array type, e.g. ['network', 'collection'] for single_label in labels: if single_label in all_labels: all_labels[single_label].append(confidence) else: all_labels[single_label] = [confidence] # extrapolate data used to plot radar chart radar_data = {} for _label in selected_label: confidences = np.array(all_labels[_label]) # on radar data use the maximum confidence for a certain label radar_data[_label] = np.max(confidences) radar_confidence = [ value_ for _label, value_ in radar_data.items() ] malware_confidences[apk_.split("/")[-1]] = radar_confidence show_comparison_graph( title=f"Malicious Actions Comparison Between {len(apk)} Malwares", lables=selected_label, malware_confidences=malware_confidences, font_size=22, ) return # Load APK data = (ParallelQuark(apk[0], core_library, num_of_process) if num_of_process > 1 else Quark(apk[0], core_library)) if label: all_labels = {} # dictionary containing # key: label # value: list of confidence values # $ print(all_rules["accessibility service"]) # > [60, 40, 60, 40, 60, 40] if num_of_process > 1: data.apply_rules(rule_buffer_list) for rule_checker in tqdm(rule_buffer_list): # Run the checker data.run(rule_checker) confidence = rule_checker.check_item.count(True) * 20 labels = (rule_checker.label ) # array type, e.g. ['network', 'collection'] for single_label in labels: if single_label in all_labels: all_labels[single_label].append(confidence) else: all_labels[single_label] = [confidence] # get how many label with max confidence >= 80% counter_high_confidence = sum( max(value) >= 80 for single_label, value in all_labels.items()) print_info(f"Total Label found: {yellow(len(all_labels))}") print_info( f"Rules with label which max confidence >= 80%: {yellow(counter_high_confidence)}" ) data.show_label_report(rule, all_labels, label) print(data.quark_analysis.label_report_table) # Show summary report if summary: if isinstance(data, ParallelQuark): data.apply_rules(rule_checker_list) for rule_checker in tqdm(rule_checker_list): # Run the checker data.run(rule_checker) data.show_summary_report(rule_checker, threshold) w = Weight(data.quark_analysis.score_sum, data.quark_analysis.weight_sum) print_warning(w.calculate()) print_info(f"Total Score: {data.quark_analysis.score_sum}") print(data.quark_analysis.summary_report_table) if classification: data.show_rule_classification() if graph: data.show_call_graph(graph) # Show detail report if detail: threshold_number = int(threshold) if threshold else 0 if isinstance(data, ParallelQuark): data.apply_rules(rule_checker_list) for rule_checker in tqdm(rule_checker_list): # Run the checker data.run(rule_checker) confidence = rule_checker.check_item.count(True) * 20 if confidence >= threshold_number: print(f"Rulepath: " f"{os.path.join(rule, rule_checker.rule_filename)}") print(f"Rule crime: {rule_checker.crime}") data.show_detail_report(rule_checker) print_success("OK") if classification: data.show_rule_classification() if graph: data.show_call_graph() if rule_generation: generator = RuleGeneration(apk[0], rule_generation) if webreport: if ".html" not in webreport: webreport = f"{webreport}.html" webreport_file = os.path.join(rule_generation, webreport) generator.generate_rule(web_editor=webreport_file) else: generator.generate_rule() # Show JSON report if output: if isinstance(data, ParallelQuark): data.apply_rules(rule_buffer_list) for rule_checker in tqdm(rule_buffer_list): # Run the checker data.run(rule_checker) data.generate_json_report(rule_checker) json_report = data.get_json_report() with open(output, "w") as file: json.dump(json_report, file, indent=4) file.close() # Generate web report if webreport: if summary or detail: for rule_checker in tqdm(rule_buffer_list): data.generate_json_report(rule_checker) json_report = data.get_json_report() report_html = ReportGenerator( json_report).get_analysis_report_html() if ".html" not in webreport: webreport = f"{webreport}.html" with open(webreport, "w") as file: file.write(report_html) file.close() if list: if list == "all": for all_method in data.apkinfo.all_methods: print(all_method.full_name) if list == "native": for api in data.apkinfo.android_apis: print(api.full_name) if list == "custom": for custom_method in data.apkinfo.custom_methods: print(custom_method.full_name) if permission: for p in data.apkinfo.permissions: print(p) if isinstance(data, ParallelQuark): data.close()
def simple_quark_obj(): r = requests.get(APK_SOURCE, allow_redirects=True) open(APK_FILENAME, "wb").write(r.content) apk_file = APK_FILENAME return Quark(apk_file)
class RadioContrast: """ This module is for generating rules with the APIs in a specific method. """ def __init__(self, apk_path, target_method, output_dir, max_search_layer=3): self.quark = Quark(apk_path) self.apkinfo = self.quark.apkinfo # Parse smali code into classname methodname and descriptor. classname = target_method.split("->")[0] methodname = target_method.split("->")[1].split("(")[0] descriptor = "(" + target_method.split("->")[1].split("(")[1] self.method = self.apkinfo.find_method( class_name=classname, method_name=methodname, descriptor=descriptor, ) if self.method is None: raise ValueError("Target method not found!") self.output_dir = output_dir self.api_set = set() self.max_search_layer = max_search_layer return def method_recursive_search(self, method_set, depth=1): """ Find all APIs in the target method. :param method_set: the list that contains each MethodAnalysis. :param depth: maximum number of recursive search functions. :return: a set of first_method_list ∩ second_method_list or None. """ # Not found same method usage, try to find the next layer. depth += 1 if depth > self.max_search_layer: return # Append first layer into next layer. next_level_set = method_set.copy() # Extend the xref from function into next layer. for md in next_level_set: if md[0].is_android_api(): self.api_set.add(md[0]) continue self.method_recursive_search(self.apkinfo.lowerfunc(md[0]), depth) def generate_rule(self, percentile_rank=0.2, web_editor=None): """ Generate rules and export them to the output directory. :param percentile_rank: The percentile rank for filter APIs by used count. :param web_editor: The path of the web editor html file. :return: None """ # Rescursive search for apis in target method. lower_funcs = set(self.apkinfo.lowerfunc(self.method)) self.method_recursive_search(lower_funcs) self.api_set, _ = filter_api_by_usage_count( self.apkinfo, self.api_set, percentile_rank=percentile_rank) first_apis_pool = list(self.api_set) second_apis_pool = list(self.api_set) # Setup progress bar. second_api_pool_num = len(second_apis_pool) outter_loop = tqdm(first_apis_pool) self.generated_result = list() # The number of rule file. rule_number = 1 for api1 in first_apis_pool: outter_loop.update(1) for num, api2 in enumerate(second_apis_pool, start=1): inner_desc = f"{num}/{second_api_pool_num}" outter_loop.set_postfix(inner_loop=inner_desc, refresh=True) # Skip the case of same method. if api2.name == api1.name: continue generated_rule = { "crime": "", "permission": [], "api": [ { "class": api1.class_name, "method": api1.name, "descriptor": api1.descriptor, }, { "class": api2.class_name, "method": api2.name, "descriptor": api2.descriptor, }, ], "score": 1, "label": [], } comb = RuleObject("test", json_data=generated_rule) try: self.quark.run(comb) except KeyboardInterrupt: raise except Exception as e: tqdm.write( "{} and {} combination has some error when analyzing,\ ERROR MESSAGE: {}".format( api1, api2, e, ), ) continue if comb.check_item[4]: continue if web_editor: generated_rule["number"] = rule_number self.generated_result.append(generated_rule) rule_number += 1 else: rule_name = f"{rule_number}.json" rule_path = os.path.join(self.output_dir, rule_name) with open(rule_path, "w") as rule_file: json.dump(generated_rule, rule_file, indent=4) rule_number += 1 if web_editor: web_editor_data = { "apk_filename": self.quark.apkinfo.filename, "md5": self.quark.apkinfo.md5, "size_bytes": self.quark.apkinfo.filesize, "result": self.generated_result } editor_html = ReportGenerator( web_editor_data).get_rule_generate_editor_html() if ".html" not in web_editor: web_editor = f"{web_editor}.html" with open(web_editor, "w") as file: file.write(editor_html) file.close() # Clear progress bar outter_loop.clear() outter_loop.close() return