Beispiel #1
0
def getRC4Key(apk):
    a, d, dx = AnalyzeAPK(apk)
    classes = dx.get_classes()
    for clazz in classes:
        methods = clazz.get_methods()
        for method in methods:
            '''
            First step is to find the generator function
            '''
            caller_method = method.get_method()
            if caller_method.get_descriptor() != "([B [B)V":
                continue

            source = caller_method.get_source()
            if "length" not in source:
                continue
            '''
            Then we can find the key instantiation method by looking at the
            XRef method form the generator
            '''
            caller_xrefs = method.get_xref_from()
            if len(list(caller_xrefs)) != 1:
                print("Error : No XRefs found for PRNG")
                sys.exit(-1)

            key_method = list(caller_xrefs)[0][1]
            key_method_source = key_method.get_source()
            '''
            Quick regex to find the key
            '''
            keys = re.findall(r"= {(?P<key>(\s*-?\d+,?)+)};",
                              key_method_source)
            if (keys == None):
                print("Error: No key found")
                sys.exit(-1)

            for key in keys:
                key = list(map(lambda x: int(x) & 0xff, key[0].split(",")))
                # print(f"Key : {key}")
                key = bytes(key)
                '''
                Second step : we decrypt the good asset containing the payload
                '''
                resources = a.get_files()
                for res in resources:
                    if (res.startswith("res/") or res.startswith("META-INF/")
                            or res == "resource.arsc" or res == "classes.dex"):
                        continue
                    else:
                        content = a.get_file(res)
                        rc4 = ARC4.new(key)
                        unciphered_file = rc4.decrypt(content[4:])
                        magic_number = unciphered_file[:2]
                        if (magic_number == b'PK'):
                            payload_size = int.from_bytes(content[0:4],
                                                          byteorder='little')
                            # print(f"Filesize : {payload_size}")
                            extract_name = apk + ".payload.apk"
                            f = open(extract_name, "wb")
                            f.write(unciphered_file[:payload_size])
                            f.close()
                            print(f"Saved to : {extract_name}")
                            return extract_name
Beispiel #2
0
class AndroHelper:

    def __init__(self, apk_path, output_dir):
        self.apk_path = apk_path
        # output directory
        self.output_dir = output_dir + "/"
        self.packed_files = dict()
        self.a, self.d, self.dx = AnalyzeAPK(self.apk_path)
        self.detected_malware = dict()

    def analyse(self):
        self.packed_files = dict()
        self.malware_detect()

        for file in self.a.get_files():
            file_type = check_header(self.a.get_file(file)[0:4].hex())

            if file_type == "JAR":

                if not os.path.isdir(self.output_dir):
                    os.makedirs(self.output_dir)

                f = open(self.output_dir + file.split("/")[-1], 'wb')
                f.write(self.a.get_file(file))
                f.close()
                try:
                    a, d, dx = AnalyzeAPK(self.output_dir + file.split("/")[-1])

                    if a.get_package():
                        self.packed_files[self.a.get_package()] = {file: {}}
                    else:
                        continue
                except Exception as e:
                    # not an APK file
                    continue

                with open(PERMISSIONS_FILE) as json_file:
                    permissions = json.load(json_file)

                perms_desc = {}
                dangerous_perms = {}

                if a.get_permissions():
                    for perm in a.get_permissions():
                        try:
                            mapped = list(filter(lambda x: x["permission"] == perm, permissions))
                            perms_desc[perm] = {"desc": mapped[0]["desc"], "level": mapped[0]["protection_lvl"]}
                            if any(re.findall(r'dangerous', mapped[0]["protection_lvl"], re.IGNORECASE)):
                                # Permission is flagged as dangerous
                                dangerous_perms[mapped[0]["permission"]] = mapped[0]["desc"]

                        except Exception as e:
                            continue

                self.packed_files[self.a.get_package()][file] = dangerous_perms

        return {"packed_file": self.packed_files, "detected_malware": self.detected_malware}

    def malware_detect(self):
        action_spy = ActionSpy(apk_path=self.apk_path, output_dir=self.output_dir)
        succeeded_test = action_spy.check()
        self.detected_malware["actionspy"] = succeeded_test

        wolf_rat = WolfRat(apk_path=self.apk_path, output_dir=self.output_dir)
        succeeded_test = wolf_rat.check()
        self.detected_malware["wolfrat"] = succeeded_test

        anubis = Anubis(apk_path=self.apk_path, output_dir=self.output_dir)
        succeeded_test = anubis.check()
        self.detected_malware["anubis"] = succeeded_test
class SmartInput(object):

    default = '123456'

    type_class = {
        'TYPE_NULL': '',
        'TYPE_CLASS_TEXT': 'example',
        'TYPE_CLASS_NUMBER': '1',
        'TYPE_CLASS_PHONE': '3453453456',
        'TYPE_CLASS_DATETIME': '03032015'
    }

    type_variation = {
        'TYPE_TEXT_VARIATION_NORMAL': 'example',
        'TYPE_TEXT_VARIATION_URI': 'https://www.example.com',
        'TYPE_TEXT_VARIATION_EMAIL_ADDRESS': '*****@*****.**',
        'TYPE_TEXT_VARIATION_EMAIL_SUBJECT': 'Example Email Subject',
        'TYPE_TEXT_VARIATION_SHORT_MESSAGE': 'Example Short Message',
        'TYPE_TEXT_VARIATION_LONG_MESSAGE':
        'This is an example of a very long message for an input text.',
        'TYPE_TEXT_VARIATION_PERSON_NAME': 'John Smith',
        'TYPE_TEXT_VARIATION_POSTAL_ADDRESS': '16100',
        'TYPE_TEXT_VARIATION_PASSWORD': '******',
        'TYPE_TEXT_VARIATION_VISIBLE_PASSWORD': '******',
        'TYPE_TEXT_VARIATION_WEB_EDIT_TEXT': '',
        'TYPE_TEXT_VARIATION_FILTER': '',
        'TYPE_TEXT_VARIATION_PHONETIC': '',
        'TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS': '*****@*****.**',
        'TYPE_TEXT_VARIATION_WEB_PASSWORD': '******',
        'TYPE_NUMBER_VARIATION_NORMAL': '3453453456',
        'TYPE_NUMBER_VARIATION_PASSWORD': '******',
        'TYPE_DATETIME_VARIATION_NORMAL': '03032015',
        'TYPE_DATETIME_VARIATION_DATE': '03032015',
        'TYPE_DATETIME_VARIATION_TIME': '000000'
    }

    def __init__(self, apk_path: str):
        self.logger = logging.getLogger('{0}.{1}'.format(
            __name__, self.__class__.__name__))

        self.logger.info('Smart input generation')

        self.smart_inputs = {}

        self.apk: APK = None
        self.dx: Analysis = None

        self.apk, _, self.dx = AnalyzeAPK(apk_path)

        tmp_edit_text_classes = self.get_subclass_names(
            'Landroid/widget/EditText;')

        # Convert EditText classes to dot notation ('EditText' is a built-in class so the prefix is not needed).
        # This notation will be used when looking for text inputs in the xml layout files.
        self.edit_text_classes = {'EditText'}
        for clazz in tmp_edit_text_classes:
            self.edit_text_classes.add(
                re.search('L(.*);', clazz).group(1).replace('/', '.'))

        try:
            self.class_object_list = [
                clazz.get_vm_class()
                for clazz in self.dx.get_internal_classes()
            ]

            self.classes_dict = self.get_class_dict()

            # Find the R$id classes.
            self.resource_ids = self.get_resource_ids(self.class_object_list)

            # Find the R$layout classes.
            self.resource_layouts = self.get_resource_layouts(
                self.class_object_list)

            self.field_refs = get_field_refs(self.resource_ids)

            self.find_text_fields()
        except Exception as e:
            self.logger.error(
                'Error during smart input generation: {0}'.format(e))
            raise

    def get_subclass_names(self, class_name: str):
        subclass_names = set()
        edit_text_class = self.dx.get_class_analysis(class_name)
        if edit_text_class:
            for clazz in edit_text_class.get_xref_from():
                if clazz.get_vm_class().get_superclassname() == class_name:
                    subclass_name = clazz.get_vm_class().get_name()
                    subclass_names.add(subclass_name)
                    subclass_names.update(
                        self.get_subclass_names(subclass_name))
        return subclass_names

    # Return a dict with the class names and the class objects.
    def get_class_dict(self):
        classes = {}
        for clazz in self.class_object_list:
            # Get the name of the class using the dot notation.
            clazz_name = re.search('L(.*);',
                                   clazz.get_name()).group(1).replace(
                                       '/', '.')
            classes[clazz_name] = clazz

        return classes

    # Get R$id classes.
    def get_resource_ids(self, classes):
        resource_ids = []
        for clazz in classes:
            if clazz.get_name().endswith('R$id;'):
                self.logger.debug('Found R$id class at {0}'.format(
                    clazz.get_name()))
                resource_ids.append(clazz)
        return resource_ids

    # Get R$layout classes.
    def get_resource_layouts(self, classes):
        resource_layouts = []
        for clazz in classes:
            if clazz.get_name().endswith('R$layout;'):
                self.logger.debug('Found R$layout class at {0}'.format(
                    clazz.get_name()))
                resource_layouts.append(clazz)
        return resource_layouts

    def get_xml_from_file(self, xml_file):
        ap = AXMLPrinter(self.apk.get_file(xml_file))
        return minidom.parseString(ap.get_buff())

    # Return every instance of an EditText field and their inputType in the XML.
    # Not all EditText fields will have an inputType specified in the XML.
    def get_input_fields_with_input_types_from_xml(self, xml_file):
        input_fields = {}
        xml_content = self.get_xml_from_file(xml_file)
        for edit_text_tag in self.edit_text_classes:
            for item in xml_content.getElementsByTagName(edit_text_tag):
                android_id = None
                input_type = {'type': None, 'is_password': False}
                for k, v in item.attributes.itemsNS():
                    if k[1] == 'id':
                        android_id = v[1:]
                    if k[1] == 'inputType':
                        input_type['type'] = v
                    if k[1] == 'password':
                        # Deprecated, only inputType should be used, but some apps still use this.
                        input_type['is_password'] = True if v.lower(
                        ) == 'true' else False

                if android_id:
                    input_fields[hex(int(android_id, 16))] = input_type

        return input_fields

    def parse_move(self, bc, index):
        i = bc.get_instruction(index)
        register = i.get_output().split(',')[1].strip()
        for x in range(index - 1, -1, -1):
            i = bc.get_instruction(x)
            if 'const' in i.get_name() and register in i.get_output():
                return parse_const(bc.get_instruction(x))

    def get_activity_xml(self, activity_class):
        # Build a list of every layout hex value referenced in activity's bytecode.
        hex_codes = []
        for method in activity_class.get_methods():
            if method.get_name() == 'onCreate':
                try:
                    for index, instruction in enumerate(
                            method.get_instructions()):
                        # Find setContentView, then parse the passed value from the previous
                        # const or const/high16 instruction.
                        if 'setContentView' in instruction.show_buff(0):
                            instruction = method.get_code().get_bc(
                            ).get_instruction(index - 1)
                            if 'const' in instruction.get_name():
                                hex_codes.append(parse_const(instruction))
                            elif 'move' in instruction.get_name():
                                hex_codes.append(
                                    self.parse_move(method.get_code().get_bc(),
                                                    index - 1))
                except Exception:
                    pass

        # Cross check the list of hex codes with R$layout to retrieve XML layout file name.
        for layout in self.resource_layouts:
            for field in layout.get_fields():
                if hex(field.get_init_value().get_value()) in hex_codes:
                    return 'res/layout/{0}.xml'.format(field.get_name())

        return None

    def get_input_field_from_code(self, class_object: ClassDefItem,
                                  field_id: str):
        self.logger.debug('Analyzing field {0}'.format(field_id))

        for method in class_object.get_methods():
            instructions = iter(method.get_instructions())
            for instruction in instructions:
                if ('const' == instruction.get_name() or 'const/high16' == instruction.get_name()) \
                        and field_id == parse_const(instruction):
                    # Get the register in which the constant is assigned.
                    register = instruction.get_output().split(',')[0].strip()

                    while True:
                        try:
                            last_instruction = instruction
                            instruction = next(instructions)
                        except StopIteration:
                            self.logger.debug(
                                'Could not get input field {0} from code'.
                                format(field_id))
                            return None

                        # Follow the register to the next invoke-virtual of findViewById...
                        if (register in instruction.get_output() and 'findViewById' in instruction.get_output()) \
                                and 'invoke-virtual' in instruction.get_name():
                            # ...and get the register of that output.
                            register = instruction.get_output().split(
                                ',')[1].strip()

                        elif instruction.get_name() == 'move-result-object' and \
                                'invoke-virtual' in last_instruction.get_name():
                            register = instruction.get_output().strip()

                        elif (instruction.get_name() == 'iput-object' or instruction.get_name() == 'sput-object') and \
                                register in instruction.get_output().split(',')[0].strip():
                            out_sp = re.search(
                                r'.*, (.*)->(\b[\w]*\b) (.*)',
                                instruction.get_output()).groups()

                            try:
                                field_analysis = list(
                                    self.dx.find_fields(
                                        out_sp[0], out_sp[1], out_sp[2]))
                                if field_analysis:
                                    return field_analysis[0]
                                else:
                                    for field in self.dx.get_class_analysis(
                                            out_sp[0]).get_vm_class(
                                            ).get_fields():
                                        if field.get_name() == out_sp[
                                                1] and field.get_descriptor(
                                                ) == out_sp[2]:
                                            return FieldClassAnalysis(field)
                            except Exception:
                                return None
        return None

    def find_text_fields(self):
        try:
            # Get all the input fields from the xml layout files.

            input_fields = {}
            for xml_layout_file in filter(lambda x: x.startswith('res/layout'),
                                          self.apk.get_files()):
                try:
                    input_fields.update(
                        self.get_input_fields_with_input_types_from_xml(
                            xml_layout_file))
                except Exception:
                    pass

            # Combine all information into a TextField dict.
            text_fields = {}

            for field_id in input_fields:
                text_fields[field_id] = TextField(
                    field_id,
                    self.field_refs[field_id].get_name(),
                    input_fields[field_id]['type'],
                    self.field_refs[field_id],
                    is_password=input_fields[field_id]['is_password'])

            self.smart_inputs['all'] = list(text_fields.values())

            # Group input fields by activity (if possible).

            for activity_name in self.apk.get_activities():
                self.logger.debug(
                    'Analyzing activity {0}'.format(activity_name))

                if activity_name in self.classes_dict:
                    # Include also the internal classes of the activity.
                    class_objects = [
                        self.classes_dict[dot_class_name]
                        for dot_class_name in self.classes_dict
                        if dot_class_name == activity_name or
                        dot_class_name.startswith('{0}$'.format(activity_name))
                    ]

                    input_types_for_fields = {}
                    for class_object in class_objects:
                        # Find all XML layouts referenced in setContentView in activity bytecode.
                        activity_xml_file = self.get_activity_xml(class_object)

                        if not activity_xml_file:
                            continue

                        try:
                            input_types_for_fields.update(
                                self.
                                get_input_fields_with_input_types_from_xml(
                                    activity_xml_file))
                        except Exception:
                            pass

                    if not input_types_for_fields:
                        self.logger.debug(
                            'No XMLs found for activity {0}'.format(
                                activity_name))
                        continue

                    # Combine all information into a TextField dict.
                    text_fields = {}

                    for field_id in input_types_for_fields:
                        for class_object in class_objects:
                            field = self.get_input_field_from_code(
                                class_object, field_id)

                            if field:
                                tf = TextField(
                                    field_id,
                                    self.field_refs[field_id].get_name(),
                                    input_types_for_fields[field_id]['type'],
                                    self.field_refs[field_id],
                                    field,
                                    is_password=input_types_for_fields[
                                        field_id]['is_password'])
                                text_fields[field_id] = tf
                            else:
                                tf = TextField(
                                    field_id,
                                    self.field_refs[field_id].get_name(),
                                    input_types_for_fields[field_id]['type'],
                                    self.field_refs[field_id],
                                    is_password=input_types_for_fields[
                                        field_id]['is_password'])
                                if field_id not in text_fields:
                                    text_fields[field_id] = tf

                    if not text_fields:
                        self.logger.debug(
                            'No text fields found for activity {0}'.format(
                                activity_name))
                    else:
                        self.smart_inputs[activity_name] = list(
                            text_fields.values())

        except Exception as e:
            self.logger.warning(
                'There was a problem during the search for text fields: {0}'.
                format(e))

        finally:
            if len(self.smart_inputs) > 0:
                self.logger.debug('{0} text fields identified'.format(
                    len(self.smart_inputs)))

        return self.smart_inputs

    def get_smart_input_for_id(self, input_id: str):
        # No id was provided, return the default text.
        if not input_id:
            return self.default

        to_return = None
        item = None
        if 'all' in self.smart_inputs:
            for item in self.smart_inputs['all']:
                if item.name == input_id:
                    if item.type_variation in self.type_variation:
                        to_return = self.type_variation[item.type_variation]
                        break
                    if item.type_class in self.type_class:
                        to_return = self.type_class[item.type_class]
                        break
        if to_return and item:
            # This field requires a specific input.
            self.logger.info('Possible input for Editable({0}): "{1}"'.format(
                item, to_return))
        elif 'username' in input_id.lower() or 'email' in input_id.lower(
        ) or 'user' in input_id.lower():
            # Maybe this is a username field.
            to_return = self.type_variation[
                'TYPE_TEXT_VARIATION_EMAIL_ADDRESS']
            self.logger.info(
                'Using username input for Editable(id={0}): "{1}"'.format(
                    input_id, to_return))
        elif 'password' in input_id.lower() or 'pwd' in input_id.lower(
        ) or 'secret' in input_id.lower():
            # Maybe this is a password field.
            to_return = self.type_variation['TYPE_TEXT_VARIATION_PASSWORD']
            self.logger.info(
                'Using password input for Editable(id={0}): "{1}"'.format(
                    input_id, to_return))
        else:
            # No hint for this field, using the default text.
            to_return = self.default
            self.logger.info(
                'Using default input for Editable(id={0}): "{1}"'.format(
                    input_id, to_return))
        return to_return