Пример #1
0
    def __init__(self, apk):
        """

        :param apk: the filename of the apk.
        """

        self.apkinfo = Apkinfo(apk)

        self.pre_method0 = []
        self.pre_method1 = []

        self.same_sequence_show_up = []
        self.same_operation = []

        # Pretty Table Output
        self.tb = PrettyTable()
        self.tb.field_names = ["Rule", "Confidence", "Score", "Weight"]
        self.tb.align = "l"

        # Sum of the each weight
        self.weight_sum = 0
        # Sum of the each rule
        self.score_sum = 0

        self.level_2_reuslt = []
Пример #2
0
    def __init__(self, apk):
        """

        :param apk: the filename of the apk.
        """
        self.apkinfo = Apkinfo(apk)

        self.quark_analysis = QuarkAnalysis()
Пример #3
0
def apkinfo(scope="function"):
    r = requests.get(APK_SOURCE, allow_redirects=True)
    open(APK_FILENAME, "wb").write(r.content)

    apk_file = APK_FILENAME
    apkinfo = Apkinfo(apk_file)
    yield apkinfo
Пример #4
0
class XRule:
    """XRule is used to test quark's five-stage theory"""

    def __init__(self, apk):
        """

        :param apk: the filename of the apk.
        """

        self.apkinfo = Apkinfo(apk)

        self.pre_method0 = []
        self.pre_method1 = []

        self.same_sequence_show_up = []
        self.same_operation = []

        # Pretty Table Output
        self.tb = PrettyTable()
        self.tb.field_names = ["Rule", "Confidence", "Score", "Weight"]
        self.tb.align = "l"

        # Sum of the each weight
        self.weight_sum = 0
        # Sum of the each rule
        self.score_sum = 0

    def find_previous_method(self, base_method, top_method, pre_method_list):
        """
        Find the previous method based on base method before top method.
        This will append the method into pre_method_list.

        :param base_method: the base function which needs to be searched.
        :param top_method: the top-level function which calls the basic function.
        :param pre_method_list: list is used to track each function.
        :return: None
        """
        class_name, method_name = base_method
        method_set = self.apkinfo.upperfunc(class_name, method_name)

        if method_set is not None:

            if top_method in method_set:
                pre_method_list.append(base_method)
            else:
                for item in method_set:
                    # prevent some functions from looking for themselves.
                    if item == base_method:
                        continue
                    self.find_previous_method(
                        item, top_method, pre_method_list)

    def find_intersection(self, list1, list2, depth=1):
        """
        Find the list1 ∩ list2. list1 & list2 are list within tuple, for example,
        [("class_name","method_name"),...]

        :param list1: first list that contains each method.
        :param list2: second list that contains each method.
        :param depth: maximum number of recursive search functions.
        :return: a set of list1 ∩ list2 or None.
        """
        # Check both lists are not null
        if len(list1) > 0 and len(list2) > 0:

            # find ∩
            result = set(list1).intersection(list2)
            if len(result) > 0:

                return result
            else:
                # Not found same method usage, try to find the next layer.
                depth += 1
                if depth > MAX_SEARCH_LAYER:
                    return None

                # Append first layer into next layer.
                next_list1 = copy.deepcopy(list1)
                next_list2 = copy.deepcopy(list2)

                # Extend the upper function into next layer.
                for item in list1:
                    if self.apkinfo.upperfunc(item[0], item[1]) is not None:
                        next_list1.extend(
                            self.apkinfo.upperfunc(
                                item[0], item[1]))
                for item in list2:
                    if self.apkinfo.upperfunc(item[0], item[1]) is not None:
                        next_list2.extend(
                            self.apkinfo.upperfunc(
                                item[0], item[1]))

                return self.find_intersection(next_list1, next_list2, depth)
        else:
            raise ValueError("List is Null")

    def check_sequence(self, same_method, first_func, second_func):
        """
        Check if the first function appeared before the second function.

        :param same_method: function that call the first function and second functions at the same time.
        :param first_func: the first show up function, which is (class_name, method_name)
        :param second_func: the second show up function, which is (class_name, method_name)
        :return: True or False
        """
        same_class_name, same_method_name = same_method
        first_class_name, first_method_name = first_func
        second_class_name, second_method_name = second_func

        method_set = self.apkinfo.find_method(
            same_class_name, same_method_name)
        seq_table = []

        if method_set is not None:
            for method in method_set:
                for _, call, number in method.get_xref_to():

                    to_md_name = str(call.name)

                    if (to_md_name == first_method_name) or (
                            to_md_name == second_method_name):
                        seq_table.append((call.name, number))

            # sorting based on the value of the number
            if len(seq_table) < 2:
                # Not Found sequence in same_method
                return False
            seq_table.sort(key=operator.itemgetter(1))

            idx = 0
            length = len(seq_table)
            f_func_val = None
            s_func_val = None
            while idx < length:
                if seq_table[idx][0] == first_method_name:
                    f_func_val = idx
                    break
                idx += 1
            while length > 0:
                if seq_table[length - 1][0] == second_method_name:
                    s_func_val = length - 1
                    break
                length -= 1

            if s_func_val > f_func_val:
                # print("Found sequence in :" + repr(same_method))
                return True
            else:
                return False
        else:
            return False

    def check_parameter(self, common_method,
                        first_method_name, second_method_name):
        """
        check the usage of the same parameter between two method.

        :param common_method: function that call the first function and second functions at the same time.
        :param first_method_name: function which calls before the second method.
        :param second_method_name: function which calls after the first method.
        :return: True or False
        """

        pyeval = PyEval()
        # Check if there is an operation of the same register
        state = False

        common_class_name, common_method_name = common_method

        for bytecode_obj in self.apkinfo.get_method_bytecode(
                common_class_name, common_method_name
        ):
            # ['new-instance', 'v4', Lcom/google/progress/SMSHelper;]
            instruction = [bytecode_obj.mnemonic]
            if bytecode_obj.registers is not None:
                instruction.extend(bytecode_obj.registers)
            if bytecode_obj.parameter is not None:
                instruction.append(bytecode_obj.parameter)

            # for the case of MUTF8String
            instruction = [str(x) for x in instruction]

            if instruction[0] in pyeval.eval.keys():
                pyeval.eval[instruction[0]](instruction)

        for table in pyeval.show_table():
            for val_obj in table:
                matchers = [first_method_name, second_method_name]
                matching = [
                    s for s in val_obj.called_by_func if all(xs in s for xs in matchers)
                ]
                if len(matching) > 0:
                    state = True
                    break
        return state

    def run(self, rule_obj):
        """
        Run the five levels check to get the y_score.

        :param rule_obj: the instance of the RuleObject.
        :return: None
        """

        # Level 1
        if set(rule_obj.x1_permission).issubset(set(self.apkinfo.permissions)):
            rule_obj.check_item[0] = True

        # Level 2
        test_md0 = rule_obj.x2n3n4_comb[0]["method"]
        test_cls0 = rule_obj.x2n3n4_comb[0]["class"]
        if self.apkinfo.find_method(test_cls0, test_md0) is not None:
            rule_obj.check_item[1] = True
            # Level 3
            test_md1 = rule_obj.x2n3n4_comb[1]["method"]
            test_cls1 = rule_obj.x2n3n4_comb[1]["class"]
            if self.apkinfo.find_method(test_cls1, test_md1) is not None:
                rule_obj.check_item[2] = True

                # Level 4
                # [('class_a','method_a'),('class_b','method_b')]
                # Looking for the first layer of the upperfunction
                upperfunc0 = self.apkinfo.upperfunc(test_cls0, test_md0)
                upperfunc1 = self.apkinfo.upperfunc(test_cls1, test_md1)

                same = self.find_intersection(upperfunc0, upperfunc1)
                if same is not None:

                    # Clear the results from the previous rule
                    self.same_sequence_show_up.clear()
                    self.same_operation.clear()

                    for common_method in same:

                        base_method_0 = (test_cls0, test_md0)
                        base_method_1 = (test_cls1, test_md1)
                        # Clear the results from the previous common_method
                        self.pre_method0.clear()
                        self.pre_method1.clear()
                        self.find_previous_method(
                            base_method_0, common_method, self.pre_method0)
                        self.find_previous_method(
                            base_method_1, common_method, self.pre_method1)
                        # TODO It may have many previous method in
                        # self.pre_method
                        pre_0 = self.pre_method0[0]
                        pre_1 = self.pre_method1[0]

                        if self.check_sequence(common_method, pre_0, pre_1):
                            rule_obj.check_item[3] = True
                            self.same_sequence_show_up.append(common_method)

                            # Level 5
                            if self.check_parameter(
                                    common_method, str(pre_0[1]), str(pre_1[1])
                            ):
                                rule_obj.check_item[4] = True
                                self.same_operation.append(common_method)

    def show_summary_report(self, rule_obj):
        """
        Show the summary report.

        :param rule_obj: the instance of the RuleObject.
        :return: None
        """
        # Count the confidence
        confidence = str(rule_obj.check_item.count(True) * 20) + "%"
        conf = rule_obj.check_item.count(True)
        weight = rule_obj.get_score(conf)
        score = rule_obj.yscore

        self.tb.add_row([green(rule_obj.crime), yellow(
            confidence), score, red(weight)])

        # add the weight
        self.weight_sum += weight
        # add the score
        self.score_sum += score

    def show_detail_report(self, rule_obj):
        """
        Show the detail report.

        :param rule_obj: the instance of the RuleObject.
        :return: None
        """

        # Count the confidence
        print("")
        print(f"Confidence: {rule_obj.check_item.count(True) * 20}%")
        print("")

        if rule_obj.check_item[0]:

            print(red(CHECK_LIST), end="")
            print(green(bold("1.Permission Request")), end="")
            print("")

            for permission in rule_obj.x1_permission:
                print("\t\t" + permission)
        if rule_obj.check_item[1]:
            print(red(CHECK_LIST), end="")
            print(green(bold("2.Native API Usage")), end="")
            print("")
            print("\t\t" + rule_obj.x2n3n4_comb[0]["method"])
        if rule_obj.check_item[2]:
            print(red(CHECK_LIST), end="")
            print(green(bold("3.Native API Combination")), end="")

            print("")
            print("\t\t" + rule_obj.x2n3n4_comb[0]["method"])
            print("\t\t" + rule_obj.x2n3n4_comb[1]["method"])
        if rule_obj.check_item[3]:

            print(red(CHECK_LIST), end="")
            print(green(bold("4.Native API Sequence")), end="")

            print("")
            print("\t\t" + "Sequence show up in:")
            for seq_methon in self.same_sequence_show_up:
                print("\t\t" + repr(seq_methon))
        if rule_obj.check_item[4]:

            print(red(CHECK_LIST), end="")
            print(green(bold("5.Native API Use Same Parameter")), end="")
            print("")
            for seq_operation in self.same_operation:
                print("\t\t" + repr(seq_operation))
Пример #5
0
def apkinfo(scope="function"):
    apk_file = "quark/sample/13667fe3b0ad496a0cd157f34b7e0c991d72a4db.apk"
    apkinfo = Apkinfo(apk_file)
    yield apkinfo
Пример #6
0
class Quark:
    """Quark module is used to check quark's five-stage theory"""
    def __init__(self, apk):
        """

        :param apk: the filename of the apk.
        """
        self.apkinfo = Apkinfo(apk)

        self.quark_analysis = QuarkAnalysis()

    def find_previous_method(self,
                             base_method,
                             parent_function,
                             wrapper,
                             visited_methods=None):
        """
        Find the method under the parent function, based on base_method before to parent_function.
        This will append the method into wrapper.

        :param base_method: the base function which needs to be searched.
        :param parent_function: the top-level function which calls the basic function.
        :param wrapper: list is used to track each function.
        :param visited_methods: set with tested method.
        :return: None
        """
        if visited_methods is None:
            visited_methods = set()

        method_set = self.apkinfo.upperfunc(base_method)
        visited_methods.add(base_method)

        if method_set is not None:

            if parent_function in method_set:
                wrapper.append(base_method)
            else:
                for item in method_set:
                    # prevent to test the tested methods.
                    if item in visited_methods:
                        continue
                    self.find_previous_method(item, parent_function, wrapper,
                                              visited_methods)

    def find_intersection(self, first_method_set, second_method_set, depth=1):
        """
        Find the first_method_list ∩ second_method_list.
        [MethodAnalysis, MethodAnalysis,...]

        :param first_method_set: first list that contains each MethodAnalysis.
        :param second_method_set: second 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.
        """
        # Check both lists are not null

        if first_method_set and second_method_set:

            # find ∩
            result = first_method_set & second_method_set
            if result:
                return result
            else:
                # Not found same method usage, try to find the next layer.
                depth += 1
                if depth > MAX_SEARCH_LAYER:
                    return None

                # Append first layer into next layer.
                next_level_set_1 = first_method_set.copy()
                next_level_set_2 = second_method_set.copy()

                # Extend the xref from function into next layer.
                for method in first_method_set:
                    if self.apkinfo.upperfunc(method):
                        next_level_set_1 = self.apkinfo.upperfunc(
                            method) | next_level_set_1
                for method in second_method_set:
                    if self.apkinfo.upperfunc(method):
                        next_level_set_2 = self.apkinfo.upperfunc(
                            method) | next_level_set_2

                return self.find_intersection(next_level_set_1,
                                              next_level_set_2, depth)
        else:
            raise ValueError("Set is Null")

    def check_sequence(self, mutual_parent, first_method_list,
                       second_method_list):
        """
        Check if the first function appeared before the second function.

        :param mutual_parent: function that call the first function and second functions at the same time.
        :param first_method_list: the first show up function, which is a MethodAnalysis
        :param second_method_list: the second show up function, which is a MethodAnalysis
        :return: True or False
        """
        state = False

        for first_call_method in first_method_list:
            for second_call_method in second_method_list:

                seq_table = []

                for _, call, number in mutual_parent.get_xref_to():

                    if call in (first_call_method, second_call_method):
                        seq_table.append((call, number))

                # sorting based on the value of the number
                if len(seq_table) < 2:
                    # Not Found sequence in same_method
                    continue
                seq_table.sort(key=operator.itemgetter(1))
                # seq_table would look like: [(getLocation, 1256), (sendSms, 1566), (sendSms, 2398)]

                method_list_need_check = [x[0] for x in seq_table]
                sequence_pattern_method = [
                    first_call_method, second_call_method
                ]

                if tools.contains(sequence_pattern_method,
                                  method_list_need_check):
                    state = True

        return state

    def check_parameter(self, parent_function, first_method_list,
                        second_method_list):
        """
        Check the usage of the same parameter between two method.

        :param parent_function: function that call the first function and second functions at the same time.
        :param first_method_list: function which calls before the second method.
        :param second_method_list: function which calls after the first method.
        :return: True or False
        """
        state = False

        for first_call_method in first_method_list:
            for second_call_method in second_method_list:

                pyeval = PyEval()
                # Check if there is an operation of the same register

                for bytecode_obj in self.apkinfo.get_method_bytecode(
                        parent_function):
                    # ['new-instance', 'v4', Lcom/google/progress/SMSHelper;]
                    instruction = [bytecode_obj.mnemonic]
                    if bytecode_obj.registers is not None:
                        instruction.extend(bytecode_obj.registers)
                    if bytecode_obj.parameter is not None:
                        instruction.append(bytecode_obj.parameter)

                    # for the case of MUTF8String
                    instruction = [str(x) for x in instruction]

                    if instruction[0] in pyeval.eval.keys():
                        pyeval.eval[instruction[0]](instruction)

                for table in pyeval.show_table():
                    for val_obj in table:

                        for c_func in val_obj.called_by_func:

                            first_method_pattern = f"{first_call_method.class_name}->{first_call_method.name}{first_call_method.descriptor}"
                            second_method_pattern = f"{second_call_method.class_name}->{second_call_method.name}{second_call_method.descriptor}"

                            if first_method_pattern in c_func and second_method_pattern in c_func:
                                state = True

                # Build for the call graph
                if state:
                    call_graph_analysis = {
                        "parent": parent_function,
                        "first_call": first_call_method,
                        "second_call": second_call_method,
                        "apkinfo": self.apkinfo,
                        "first_api": self.quark_analysis.first_api,
                        "second_api": self.quark_analysis.second_api,
                        "crime": self.quark_analysis.crime_description,
                    }
                    self.quark_analysis.call_graph_analysis_list.append(
                        call_graph_analysis)

        return state

    def run(self, rule_obj):
        """
        Run the five levels check to get the y_score.

        :param rule_obj: the instance of the RuleObject.
        :return: None
        """
        self.quark_analysis.clean_result()
        self.quark_analysis.crime_description = rule_obj.crime

        # Level 1: Permission Check
        if set(rule_obj.x1_permission).issubset(set(self.apkinfo.permissions)):
            rule_obj.check_item[0] = True
        else:
            # Exit if the level 1 stage check fails.
            return

        # Level 2: Single Native API Check
        api_1_method_name = rule_obj.x2n3n4_comb[0]["method"]
        api_1_class_name = rule_obj.x2n3n4_comb[0]["class"]
        api_1_descriptor = rule_obj.x2n3n4_comb[0]["descriptor"]

        api_2_method_name = rule_obj.x2n3n4_comb[1]["method"]
        api_2_class_name = rule_obj.x2n3n4_comb[1]["class"]
        api_2_descriptor = rule_obj.x2n3n4_comb[1]["descriptor"]

        first_api = self.apkinfo.find_method(api_1_class_name,
                                             api_1_method_name,
                                             api_1_descriptor)
        second_api = self.apkinfo.find_method(api_2_class_name,
                                              api_2_method_name,
                                              api_2_descriptor)

        if first_api is not None or second_api is not None:
            rule_obj.check_item[1] = True

            if first_api is not None:
                first_api = self.apkinfo.find_method(api_1_class_name,
                                                     api_1_method_name,
                                                     api_1_descriptor)
                self.quark_analysis.level_2_result.append(first_api)
            if second_api is not None:
                second_api = self.apkinfo.find_method(api_2_class_name,
                                                      api_2_method_name,
                                                      api_2_descriptor)
                self.quark_analysis.level_2_result.append(second_api)
        else:
            # Exit if the level 2 stage check fails.
            return

        # Level 3: Both Native API Check
        if first_api is not None and second_api is not None:
            self.quark_analysis.first_api = first_api
            self.quark_analysis.second_api = second_api
            rule_obj.check_item[2] = True

        else:
            # Exit if the level 3 stage check fails.
            return

        # Level 4: Sequence Check
        # Looking for the first layer of the upper function
        first_api_xref_from = self.apkinfo.upperfunc(first_api)
        second_api_xref_from = self.apkinfo.upperfunc(second_api)

        mutual_parent_function_list = self.find_intersection(
            first_api_xref_from, second_api_xref_from)

        if mutual_parent_function_list is not None:

            for parent_function in mutual_parent_function_list:
                first_wrapper = []
                second_wrapper = []

                self.find_previous_method(first_api, parent_function,
                                          first_wrapper)
                self.find_previous_method(second_api, parent_function,
                                          second_wrapper)

                if self.check_sequence(parent_function, first_wrapper,
                                       second_wrapper):
                    rule_obj.check_item[3] = True
                    self.quark_analysis.level_4_result.append(parent_function)

                    # Level 5: Handling The Same Register Check
                    if self.check_parameter(parent_function, first_wrapper,
                                            second_wrapper):
                        rule_obj.check_item[4] = True
                        self.quark_analysis.level_5_result.append(
                            parent_function)

        else:
            # Exit if the level 4 stage check fails.
            return

    def get_json_report(self):
        """
        Get quark report including summary and detail with json format.

        :return: json report
        """

        w = Weight(self.quark_analysis.score_sum,
                   self.quark_analysis.weight_sum)
        warning = w.calculate()

        # Filter out color code in threat level
        for level in ["Low Risk", "Moderate Risk", "High Risk"]:
            if level in warning:
                warning = level

        json_report = {
            "md5": self.apkinfo.md5,
            "apk_filename": self.apkinfo.filename,
            "size_bytes": self.apkinfo.filesize,
            "threat_level": warning,
            "total_score": self.quark_analysis.score_sum,
            "crimes": self.quark_analysis.json_report,
        }

        return json_report

    def generate_json_report(self, rule_obj):
        """
        Show the json report.

        :param rule_obj: the instance of the RuleObject
        :return: None
        """
        # Count the confidence
        confidence = str(rule_obj.check_item.count(True) * 20) + "%"
        conf = rule_obj.check_item.count(True)
        weight = rule_obj.get_score(conf)
        score = rule_obj.yscore

        # Assign level 1 examine result
        permissions = []
        if rule_obj.check_item[0]:
            permissions = rule_obj.x1_permission

        # Assign level 2 examine result
        api = []
        if rule_obj.check_item[1]:
            for item2 in self.quark_analysis.level_2_result:
                api.append({
                    "class": repr(item2.class_name),
                    "method": repr(item2.name),
                })

        # Assign level 3 examine result
        combination = []
        if rule_obj.check_item[2]:
            combination = rule_obj.x2n3n4_comb

        # Assign level 4 - 5 examine result if exist
        sequnce_show_up = []
        same_operation_show_up = []

        # Check examination has passed level 4
        if self.quark_analysis.level_4_result and rule_obj.check_item[3]:
            for item4 in self.quark_analysis.level_4_result:
                sequnce_show_up.append({
                    "class": repr(item4.class_name),
                    "method": repr(item4.name),
                })

            # Check examination has passed level 5
            if self.quark_analysis.level_5_result and rule_obj.check_item[4]:
                for item5 in self.quark_analysis.level_5_result:
                    same_operation_show_up.append({
                        "class":
                        repr(item5.class_name),
                        "method":
                        repr(item5.name),
                    })

        crime = {
            "crime": rule_obj.crime,
            "score": score,
            "weight": weight,
            "confidence": confidence,
            "permissions": permissions,
            "native_api": api,
            "combination": combination,
            "sequence": sequnce_show_up,
            "register": same_operation_show_up,
        }
        self.quark_analysis.json_report.append(crime)

        # add the weight
        self.quark_analysis.weight_sum += weight
        # add the score
        self.quark_analysis.score_sum += score

    def show_summary_report(self, rule_obj):
        """
        Show the summary report.

        :param rule_obj: the instance of the RuleObject.
        :return: None
        """
        # Count the confidence
        confidence = str(rule_obj.check_item.count(True) * 20) + "%"
        conf = rule_obj.check_item.count(True)
        weight = rule_obj.get_score(conf)
        score = rule_obj.yscore

        self.quark_analysis.summary_report_table.add_row([
            green(rule_obj.crime),
            yellow(confidence, ),
            score,
            red(weight),
        ])

        # add the weight
        self.quark_analysis.weight_sum += weight
        # add the score
        self.quark_analysis.score_sum += score

    def show_detail_report(self, rule_obj):
        """
        Show the detail report.

        :param rule_obj: the instance of the RuleObject.
        :return: None
        """

        # Count the confidence
        print("")
        print(f"Confidence: {rule_obj.check_item.count(True) * 20}%")
        print("")

        if rule_obj.check_item[0]:

            print(red(CHECK_LIST), end="")
            print(green(bold("1.Permission Request")), end="")
            print("")

            for permission in rule_obj.x1_permission:
                print(f"\t\t {permission}")
        if rule_obj.check_item[1]:
            print(red(CHECK_LIST), end="")
            print(green(bold("2.Native API Usage")), end="")
            print("")

            for api in self.quark_analysis.level_2_result:
                print(f"\t\t ({api.class_name}, {api.name})")
        if rule_obj.check_item[2]:
            print(red(CHECK_LIST), end="")
            print(green(bold("3.Native API Combination")), end="")

            print("")
            print(
                f"\t\t ({rule_obj.x2n3n4_comb[0]['class']}, {rule_obj.x2n3n4_comb[0]['method']})",
            )
            print(
                f"\t\t ({rule_obj.x2n3n4_comb[1]['class']}, {rule_obj.x2n3n4_comb[1]['method']})",
            )
        if rule_obj.check_item[3]:

            print(red(CHECK_LIST), end="")
            print(green(bold("4.Native API Sequence")), end="")

            print("")
            print(f"\t\t Sequence show up in:")
            for seq_method in self.quark_analysis.level_4_result:
                print(f"\t\t {seq_method.full_name}")
        if rule_obj.check_item[4]:

            print(red(CHECK_LIST), end="")
            print(green(bold("5.Native API Use Same Parameter")), end="")
            print("")
            for seq_operation in self.quark_analysis.level_5_result:
                print(f"\t\t {seq_operation.full_name}")

    def show_call_graph(self):
        print_info("Creating Call Graph...")
        for call_graph_analysis in self.quark_analysis.call_graph_analysis_list:
            call_graph(call_graph_analysis)
        print_success("Call Graph Completed")

    def show_rule_classification(self):
        print_info("Rules Classification")
        output_parent_function_table(
            self.quark_analysis.call_graph_analysis_list)
        output_parent_function_json(
            self.quark_analysis.call_graph_analysis_list)
Пример #7
0
class Quark:
    """XRule is used to test quark's five-stage theory"""

    def __init__(self, apk):
        """

        :param apk: the filename of the apk.
        """
        self.apkinfo = Apkinfo(apk)

        self.pre_method0 = []
        self.pre_method1 = []

        self.same_sequence_show_up = []
        self.same_operation = []

        # Json report
        self.json_report = []

        # Pretty Table Output
        self.tb = PrettyTable()
        self.tb.field_names = ["Rule", "Confidence", "Score", "Weight"]
        self.tb.align = "l"

        # Sum of the each weight
        self.weight_sum = 0
        # Sum of the each rule
        self.score_sum = 0

        self.level_2_reuslt = []

    def find_previous_method(self, base_method, top_method, pre_method_list, visited_methods=None):
        """
        Find the previous method based on base method before top method.
        This will append the method into pre_method_list.

        :param base_method: the base function which needs to be searched.
        :param top_method: the top-level function which calls the basic function.
        :param pre_method_list: list is used to track each function.
        :param visited_methods: set with tested method.
        :return: None
        """
        if visited_methods is None:
            visited_methods = set()

        class_name, method_name = base_method
        method_set = self.apkinfo.upperfunc(class_name, method_name)
        visited_methods.add(base_method)

        if method_set is not None:

            if top_method in method_set:
                pre_method_list.append(base_method)
            else:
                for item in method_set:
                    # prevent to test the tested methods.
                    if item in visited_methods:
                        continue
                    self.find_previous_method(
                        item, top_method, pre_method_list, visited_methods,
                    )

    def find_intersection(self, list1, list2, depth=1):
        """
        Find the list1 ∩ list2. list1 & list2 are list within tuple, for example,
        [("class_name","method_name"),...]

        :param list1: first list that contains each method.
        :param list2: second list that contains each method.
        :param depth: maximum number of recursive search functions.
        :return: a set of list1 ∩ list2 or None.
        """
        # Check both lists are not null
        if list1 and list2:

            # find ∩
            result = set(list1).intersection(list2)
            if result:

                return result
            else:
                # Not found same method usage, try to find the next layer.
                depth += 1
                if depth > MAX_SEARCH_LAYER:
                    return None

                # Append first layer into next layer.
                next_list1 = copy.deepcopy(list1)
                next_list2 = copy.deepcopy(list2)

                # Extend the upper function into next layer.
                for item in list1:
                    if self.apkinfo.upperfunc(item[0], item[1]) is not None:
                        next_list1.extend(
                            self.apkinfo.upperfunc(
                                item[0], item[1],
                            ),
                        )
                for item in list2:
                    if self.apkinfo.upperfunc(item[0], item[1]) is not None:
                        next_list2.extend(
                            self.apkinfo.upperfunc(
                                item[0], item[1],
                            ),
                        )

                return self.find_intersection(next_list1, next_list2, depth)
        else:
            raise ValueError("List is Null")

    def check_sequence(self, same_method, first_func, second_func):
        """
        Check if the first function appeared before the second function.

        :param same_method: function that call the first function and second functions at the same time.
        :param first_func: the first show up function, which is (class_name, method_name)
        :param second_func: the second show up function, which is (class_name, method_name)
        :return: True or False
        """
        same_class_name, same_method_name = same_method
        first_class_name, first_method_name = first_func
        second_class_name, second_method_name = second_func

        method_set = self.apkinfo.find_method(
            same_class_name, same_method_name,
        )
        seq_table = []

        if method_set is not None:
            for method in method_set:
                for _, call, number in method.get_xref_to():

                    to_md_name = str(call.name)

                    if (to_md_name == first_method_name) or (
                            to_md_name == second_method_name
                    ):
                        seq_table.append((call.name, number))

            # sorting based on the value of the number
            if len(seq_table) < 2:
                # Not Found sequence in same_method
                return False
            seq_table.sort(key=operator.itemgetter(1))
            # seq_table would look like: [(getLocation, 1256), (sendSms, 1566), (sendSms, 2398)]

            method_list = [x[0] for x in seq_table]
            check_sequence_method = [first_method_name, second_method_name]

            return tools.contains(check_sequence_method, method_list)
        else:
            return False

    def check_parameter(
            self, common_method,
            first_method_name, second_method_name,
    ):
        """
        check the usage of the same parameter between two method.

        :param common_method: function that call the first function and second functions at the same time.
        :param first_method_name: function which calls before the second method.
        :param second_method_name: function which calls after the first method.
        :return: True or False
        """

        pyeval = PyEval()
        # Check if there is an operation of the same register
        state = False

        common_class_name, common_method_name = common_method

        for bytecode_obj in self.apkinfo.get_method_bytecode(
                common_class_name, common_method_name,
        ):
            # ['new-instance', 'v4', Lcom/google/progress/SMSHelper;]
            instruction = [bytecode_obj.mnemonic]
            if bytecode_obj.registers is not None:
                instruction.extend(bytecode_obj.registers)
            if bytecode_obj.parameter is not None:
                instruction.append(bytecode_obj.parameter)

            # for the case of MUTF8String
            instruction = [str(x) for x in instruction]

            if instruction[0] in pyeval.eval.keys():
                pyeval.eval[instruction[0]](instruction)

        for table in pyeval.show_table():
            for val_obj in table:
                matchers = [first_method_name, second_method_name]
                matching = [
                    s for s in val_obj.called_by_func if all(xs in s for xs in matchers)
                ]
                if matching:
                    state = True
                    break
        return state

    def run(self, rule_obj):
        """
        Run the five levels check to get the y_score.

        :param rule_obj: the instance of the RuleObject.
        :return: None
        """

        # Level 1
        if set(rule_obj.x1_permission).issubset(set(self.apkinfo.permissions)):
            rule_obj.check_item[0] = True
        else:
            # Exit if the level 1 stage check fails.
            return

        # Level 2
        test_md0 = rule_obj.x2n3n4_comb[0]["method"]
        test_cls0 = rule_obj.x2n3n4_comb[0]["class"]
        test_md1 = rule_obj.x2n3n4_comb[1]["method"]
        test_cls1 = rule_obj.x2n3n4_comb[1]["class"]

        first_method_result = self.apkinfo.find_method(test_cls0, test_md0)
        second_method_result = self.apkinfo.find_method(test_cls1, test_md1)

        self.level_2_reuslt.clear()

        if first_method_result is not None or second_method_result is not None:
            rule_obj.check_item[1] = True

            if first_method_result is not None:
                self.level_2_reuslt.append((test_cls0, test_md0))
            if second_method_result is not None:
                self.level_2_reuslt.append((test_cls1, test_md1))
        else:
            # Exit if the level 2 stage check fails.
            return

        # Level 3
        if first_method_result is not None and second_method_result is not None:
            rule_obj.check_item[2] = True
        else:
            # Exit if the level 3 stage check fails.
            return

        # Level 4
        # [('class_a','method_a'),('class_b','method_b')]
        # Looking for the first layer of the upperfunction
        upperfunc0 = self.apkinfo.upperfunc(test_cls0, test_md0)
        upperfunc1 = self.apkinfo.upperfunc(test_cls1, test_md1)

        same = self.find_intersection(upperfunc0, upperfunc1)

        if same is not None:

            # Clear the results from the previous rule
            self.same_sequence_show_up.clear()
            self.same_operation.clear()

            for common_method in same:

                base_method_0 = (test_cls0, test_md0)
                base_method_1 = (test_cls1, test_md1)
                # Clear the results from the previous common_method
                self.pre_method0.clear()
                self.pre_method1.clear()
                self.find_previous_method(
                    base_method_0, common_method, self.pre_method0,
                )
                self.find_previous_method(
                    base_method_1, common_method, self.pre_method1,
                )
                # TODO It may have many previous method in
                # self.pre_method
                pre_0 = self.pre_method0[0]
                pre_1 = self.pre_method1[0]

                if self.check_sequence(common_method, pre_0, pre_1):
                    rule_obj.check_item[3] = True
                    self.same_sequence_show_up.append(common_method)

                    # Level 5
                    if self.check_parameter(common_method, str(pre_0[1]), str(pre_1[1])):
                        rule_obj.check_item[4] = True
                        self.same_operation.append(common_method)

        else:
            # Exit if the level 4 stage check fails.
            return

    def get_json_report(self):
        """
        Get quark report including summary and detail with json format.

        :return: json report
        """

        w = Weight(self.score_sum, self.weight_sum)
        warning = w.calculate()

        # Filter out color code in threat level
        for level in ["Low Risk", "Moderate Risk", "High Risk"]:
            if level in warning:
                warning = level

        json_report = {
            "md5": self.apkinfo.md5,
            "apk_filename": self.apkinfo.filename,
            "size_bytes": self.apkinfo.filesize,
            "threat_level": warning,
            "total_score": self.score_sum,
            "crimes": self.json_report,
        }

        return json_report

    def generate_json_report(self, rule_obj):
        """
        Show the json report.

        :param rule_obj: the instance of the RuleObject
        :return: None
        """
        # Count the confidence
        confidence = str(rule_obj.check_item.count(True) * 20) + "%"
        conf = rule_obj.check_item.count(True)
        weight = rule_obj.get_score(conf)
        score = rule_obj.yscore

        # Assign level 1 examine result
        permissions = []
        if rule_obj.check_item[0]:
            permissions = rule_obj.x1_permission

        # Assign level 2 examine result
        api = []
        if rule_obj.check_item[1]:
            for class_name, method_name in self.level_2_reuslt:
                api.append({
                    "class": class_name,
                    "method": method_name,
                })

        # Assign level 3 examine result
        combination = []
        if rule_obj.check_item[2]:
            combination = rule_obj.x2n3n4_comb

        # Assign level 4 - 5 examine result if exist
        sequnce_show_up = []
        same_operation_show_up = []

        # Check examination has passed level 4
        if self.same_sequence_show_up and rule_obj.check_item[3]:
            for same_sequence_cls, same_sequence_md in self.same_sequence_show_up:
                sequnce_show_up.append({
                    "class": repr(same_sequence_cls),
                    "method": repr(same_sequence_md),
                })

            # Check examination has passed level 5
            if self.same_operation and rule_obj.check_item[4]:
                for same_operation_cls, same_operation_md in self.same_operation:
                    same_operation_show_up.append({
                        "class": repr(same_operation_cls),
                        "method": repr(same_operation_md),
                    })

        crime = {
            "crime": rule_obj.crime,
            "score": score,
            "weight": weight,
            "confidence": confidence,
            "permissions": permissions,
            "native_api": api,
            "combination": combination,
            "sequence": sequnce_show_up,
            "register": same_operation_show_up,
        }
        self.json_report.append(crime)

        # add the weight
        self.weight_sum += weight
        # add the score
        self.score_sum += score

    def show_summary_report(self, rule_obj):
        """
        Show the summary report.

        :param rule_obj: the instance of the RuleObject.
        :return: None
        """
        # Count the confidence
        confidence = str(rule_obj.check_item.count(True) * 20) + "%"
        conf = rule_obj.check_item.count(True)
        weight = rule_obj.get_score(conf)
        score = rule_obj.yscore

        self.tb.add_row([
            green(rule_obj.crime), yellow(
                confidence,
            ), score, red(weight),
        ])

        # add the weight
        self.weight_sum += weight
        # add the score
        self.score_sum += score

    def show_detail_report(self, rule_obj):
        """
        Show the detail report.

        :param rule_obj: the instance of the RuleObject.
        :return: None
        """

        # Count the confidence
        print("")
        print(f"Confidence: {rule_obj.check_item.count(True) * 20}%")
        print("")

        if rule_obj.check_item[0]:

            print(red(CHECK_LIST), end="")
            print(green(bold("1.Permission Request")), end="")
            print("")

            for permission in rule_obj.x1_permission:
                print(f"\t\t {permission}")
        if rule_obj.check_item[1]:
            print(red(CHECK_LIST), end="")
            print(green(bold("2.Native API Usage")), end="")
            print("")

            for class_name, method_name in self.level_2_reuslt:
                print(f"\t\t ({class_name}, {method_name})")
        if rule_obj.check_item[2]:
            print(red(CHECK_LIST), end="")
            print(green(bold("3.Native API Combination")), end="")

            print("")
            print(
                f"\t\t ({rule_obj.x2n3n4_comb[0]['class']}, {rule_obj.x2n3n4_comb[0]['method']})",
            )
            print(
                f"\t\t ({rule_obj.x2n3n4_comb[1]['class']}, {rule_obj.x2n3n4_comb[1]['method']})",
            )
        if rule_obj.check_item[3]:

            print(red(CHECK_LIST), end="")
            print(green(bold("4.Native API Sequence")), end="")

            print("")
            print(f"\t\t Sequence show up in:")
            for seq_method in self.same_sequence_show_up:
                print(f"\t\t {repr(seq_method)}")
        if rule_obj.check_item[4]:

            print(red(CHECK_LIST), end="")
            print(green(bold("5.Native API Use Same Parameter")), end="")
            print("")
            for seq_operation in self.same_operation:
                print(f"\t\t {repr(seq_operation)}")
Пример #8
0
 def __init__(self, apkpath):
     self.apk = Apkinfo(apkpath)
     self.all_strings = self.apk.get_strings()
Пример #9
0
class Forensic:
    __slots__ = ["apk", "all_strings"]

    def __init__(self, apkpath):
        self.apk = Apkinfo(apkpath)
        self.all_strings = self.apk.get_strings()

    def get_all_strings(self):
        """
        Return all the strings inside the APK with a set.
        :return: a set of strings containing all strings
        """

        return self.all_strings

    def get_url(self):
        """
        Return all the url strings inside the APK with a set.
        :return: a set of strings containing the url
        """

        url = set()

        for string in self.all_strings:

            if extract_url(string):
                for url_string in extract_url(string):
                    url.add(url_string)

        return url

    def get_ip(self):
        """
        Return all the ip address strings inside the APK with a set.
        :return: a set of strings containing the ip address
        """

        ip = set()

        for string in self.all_strings:

            if extract_ip(string):

                for ip_string in extract_ip(string):
                    ip.add(ip_string)

        return ip

    def get_content(self):
        """
        Return all the content strings inside the APK with a set.
        :return: a set of strings containing "content://"
        """

        content = set()

        for string in self.all_strings:

            if extract_content(string):
                content.add(string)

        return content

    def get_file(self):
        """
        Return all the file strings inside the APK with a set.
        :return: a set of strings containing "file://"
        """

        file = set()

        for string in self.all_strings:

            if extract_file(string):
                file.add(string)

        return file

    def get_base64(self):
        """
        Return all possible Base64-encoded strings in the APK.
        :return: a set of strings containing possible Base64-encoded string
        """

        base64 = set()

        for string in self.all_strings:

            if validate_base64(string):
                base64.add(string)

        return base64