Esempio n. 1
0
 def rename_method_invocations(self, smali_files: List[str], methods_to_rename: Set[str],
                               android_class_names: Set[str], interactive: bool = False):
     for smali_file in util.show_list_progress(smali_files,
                                               interactive=interactive,
                                               description='Renaming method invocations'):
         with util.inplace_edit_file(smali_file) as current_file:
             for line in current_file:
                 # Method invocation.
                 invoke_match = util.invoke_pattern.match(line)
                 if invoke_match:
                     method = '{method_name}({method_param}){method_return}'.format(
                         method_name=invoke_match.group('invoke_method'),
                         method_param=invoke_match.group('invoke_param'),
                         method_return=invoke_match.group('invoke_return')
                     )
                     invoke_type = invoke_match.group('invoke_type')
                     class_name = invoke_match.group('invoke_object')
                     # Rename the method invocation only if is direct or static (we are renaming only direct methods)
                     # and if is called from a class that is not an Android API class.
                     if ('direct' in invoke_type or 'static' in invoke_type) and method in methods_to_rename and \
                             class_name not in android_class_names:
                         method_name = invoke_match.group('invoke_method')
                         print(line.replace(
                             '{0}('.format(method_name),
                             '{0}('.format(self.rename_method(method_name))
                         ), end='')
                     else:
                         print(line, end='')
                 else:
                     print(line, end='')
Esempio n. 2
0
    def obfuscate(self, obfuscation_info: Obfuscation):
        self.logger.info('Running "{0}" obfuscator'.format(self.__class__.__name__))

        try:
            op_codes = util.get_nop_valid_op_codes()
            pattern = re.compile(r'\s+(?P<op_code>\S+)')

            for smali_file in util.show_list_progress(obfuscation_info.get_smali_files(),
                                                      interactive=obfuscation_info.interactive,
                                                      description='Inserting "nop" instructions in smali files'):
                self.logger.debug('Inserting "nop" instructions in file "{0}"'.format(smali_file))
                with util.inplace_edit_file(smali_file) as current_file:
                    for line in current_file:

                        # Print original instruction.
                        print(line, end='')

                        # Check if this line contains an op code at the beginning of the string.
                        match = pattern.match(line)
                        if match:
                            op_code = match.group('op_code')
                            # If this is a valid op code, insert some nop instructions after it.
                            if op_code in op_codes:
                                nop_count = util.get_random_int(1, 5)
                                print('\tnop\n' * nop_count, end='')

        except Exception as e:
            self.logger.error('Error during execution of "{0}" obfuscator: {1}'.format(self.__class__.__name__, e))
            raise

        finally:
            obfuscation_info.used_obfuscators.append(self.__class__.__name__)
Esempio n. 3
0
    def update_method(self, smali_file: str, new_method: StringIO):
        with util.inplace_edit_file(smali_file) as current_file:
            class_name = None
            for line in current_file:

                if not class_name:
                    class_match = util.class_pattern.match(line)
                    if class_match:
                        class_name = class_match.group('class_name')
                        print(line, end='')
                        continue

                invoke_match = util.invoke_pattern.match(line)
                if invoke_match:
                    if not self.is_init(invoke_match.group('invoke_method')):
                        # The following function will write into the file the new method invocation.
                        self.change_method_call(
                            invoke_match.group('invoke_type'),
                            invoke_match.group('invoke_pass'),
                            invoke_match.group('invoke_object'),
                            invoke_match.group('invoke_method'),
                            invoke_match.group('invoke_param'),
                            invoke_match.group('invoke_return'), class_name,
                            new_method)
                    else:
                        print(line, end='')
                else:
                    print(line, end='')
Esempio n. 4
0
 def rename_field_references(self,
                             fields_to_rename: Set[str],
                             smali_files: List[str],
                             sdk_classes: Set[str],
                             interactive: bool = False):
     for smali_file in util.show_list_progress(
             smali_files,
             interactive=interactive,
             description='Renaming field references'):
         with util.inplace_edit_file(smali_file) as current_smali_file:
             for line in current_smali_file:
                 # Field usage.
                 field_usage_match = util.field_usage_pattern.match(line)
                 if field_usage_match:
                     field = '{field_name}:{field_type}'.format(
                         field_name=field_usage_match.group('field_name'),
                         field_type=field_usage_match.group('field_type'))
                     class_name = field_usage_match.group('field_object')
                     field_name = field_usage_match.group('field_name')
                     if field in fields_to_rename and \
                             (not class_name.startswith(('Landroid', 'Ljava')) or class_name in sdk_classes):
                         # Rename field usage.
                         print(line.replace(
                             '{0}:'.format(field_name),
                             '{0}:'.format(self.rename_field(field_name))),
                               end='')
                     else:
                         print(line, end='')
                 else:
                     print(line, end='')
Esempio n. 5
0
    def rename_class_usages_in_smali(self, smali_files: List[str], rename_transformations: dict,
                                     interactive: bool = False):
        dot_rename_transformations = self.slash_to_dot_notation_for_classes(rename_transformations)

        # Add package name.
        dot_rename_transformations[self.package_name] = self.encrypted_package_name

        for smali_file in util.show_list_progress(smali_files,
                                                  interactive=interactive,
                                                  description='Renaming class usages in smali files'):
            with util.inplace_edit_file(smali_file) as current_file:
                for line in current_file:
                    # Rename classes used as strings with . instead of /.
                    string_match = self.string_pattern.search(line)
                    if string_match and string_match.group('string_value') in dot_rename_transformations:
                        line = line.replace(string_match.group('string_value'),
                                            dot_rename_transformations[string_match.group('string_value')])

                    # Sometimes classes are used in annotations as strings without trailing ;
                    if string_match and '{0};'.format(string_match.group('string_value')) in rename_transformations:
                        line = line.replace(
                            string_match.group('string_value'),
                            rename_transformations['{0};'.format(string_match.group('string_value'))][:-1])

                    # Rename classes used with the "classic" syntax (leading L and trailing ;).
                    class_names = util.class_name_pattern.findall(line)
                    for class_name in class_names:
                        if class_name in rename_transformations:
                            line = line.replace(class_name, rename_transformations[class_name])

                    print(line, end='')
Esempio n. 6
0
    def update_method(self, smali_file: str, new_method: StringIO):
        with util.inplace_edit_file(smali_file) as (in_file, out_file):
            class_name = None
            for line in in_file:

                if not class_name:
                    class_match = util.class_pattern.match(line)
                    if class_match:
                        class_name = class_match.group("class_name")
                        out_file.write(line)
                        continue

                invoke_match = util.invoke_pattern.match(line)
                if invoke_match:
                    if not self.is_init(invoke_match.group("invoke_method")):
                        # The following function will write into the file the new
                        # method invocation.
                        self.change_method_call(
                            invoke_match.group("invoke_type"),
                            invoke_match.group("invoke_pass"),
                            invoke_match.group("invoke_object"),
                            invoke_match.group("invoke_method"),
                            invoke_match.group("invoke_param"),
                            invoke_match.group("invoke_return"),
                            class_name,
                            new_method,
                            out_file,
                        )
                    else:
                        out_file.write(line)
                else:
                    out_file.write(line)
Esempio n. 7
0
    def obfuscate(self, obfuscation_info: Obfuscation):
        self.logger.info('Running "{0}" obfuscator'.format(self.__class__.__name__))

        try:
            for smali_file in util.show_list_progress(obfuscation_info.get_smali_files(),
                                                      interactive=obfuscation_info.interactive,
                                                      description='Inserting arithmetic computations in smali files'):
                self.logger.debug('Inserting arithmetic computations in file "{0}"'.format(smali_file))
                with util.inplace_edit_file(smali_file) as current_file:
                    editing_method = False
                    start_label = None
                    end_label = None
                    for line in current_file:
                        if line.startswith('.method ') and ' abstract ' not in line and \
                                ' native ' not in line and not editing_method:
                            # Entering method.
                            print(line, end='')
                            editing_method = True

                        elif line.startswith('.end method') and editing_method:
                            # Exiting method.
                            if start_label and end_label:
                                print('\t:{0}'.format(end_label))
                                print('\tgoto/32 :{0}'.format(start_label))
                                start_label = None
                                end_label = None
                            print(line, end='')
                            editing_method = False

                        elif editing_method:
                            # Inside method.
                            print(line, end='')
                            match = util.locals_pattern.match(line)
                            if match and int(match.group('local_count')) >= 2:
                                # If there are at least 2 registers available, add a fake branch at the beginning of
                                # the method: one branch will continue from here, the other branch will go to the end
                                # of the method and then will return here through a "goto" instruction.
                                v0, v1 = util.get_random_int(1, 32), util.get_random_int(1, 32)
                                start_label = util.get_random_string(16)
                                end_label = util.get_random_string(16)
                                tmp_label = util.get_random_string(16)
                                print('\n\tconst v0, {0}'.format(v0))
                                print('\tconst v1, {0}'.format(v1))
                                print('\tadd-int v0, v0, v1')
                                print('\trem-int v0, v0, v1')
                                print('\tif-gtz v0, :{0}'.format(tmp_label))
                                print('\tgoto/32 :{0}'.format(end_label))
                                print('\t:{0}'.format(tmp_label))
                                print('\t:{0}'.format(start_label))

                        else:
                            print(line, end='')

        except Exception as e:
            self.logger.error('Error during execution of "{0}" obfuscator: {1}'.format(self.__class__.__name__, e))
            raise

        finally:
            obfuscation_info.used_obfuscators.append(self.__class__.__name__)
Esempio n. 8
0
    def obfuscate(self, obfuscation_info: Obfuscation):
        self.logger.info('Running "{0}" obfuscator'.format(
            self.__class__.__name__))

        try:
            for smali_file in util.show_list_progress(
                    obfuscation_info.get_smali_files(),
                    interactive=obfuscation_info.interactive,
                    description='Inserting "goto" instructions in smali files',
            ):
                self.logger.debug(
                    'Inserting "goto" instructions in file "{0}"'.format(
                        smali_file))
                with util.inplace_edit_file(smali_file) as (in_file, out_file):
                    editing_method = False
                    for line in in_file:
                        if (line.startswith(".method ")
                                and " abstract " not in line
                                and " native " not in line
                                and not editing_method):
                            # If at the beginning of a non abstract/native method
                            # (after the .locals instruction), insert a "goto" to the
                            # label at the end of the method and a label to the first
                            # instruction of the method.
                            out_file.write(line)
                            editing_method = True

                        elif editing_method and util.locals_pattern.match(
                                line):
                            out_file.write(line)
                            out_file.write(
                                "\n\tgoto/32 :after_last_instruction\n\n")
                            out_file.write("\t:before_first_instruction\n")

                        elif line.startswith(".end method") and editing_method:
                            # If at the end of the method, insert a label after the
                            # last instruction of the method and a "goto" to the label
                            # at the beginning of the method. This will not cause an
                            # endless loop because the method will return at some point
                            # and the second "goto" won't be called again when the
                            # method finishes.
                            out_file.write("\n\t:after_last_instruction\n\n")
                            out_file.write(
                                "\tgoto/32 :before_first_instruction\n\n")
                            out_file.write(line)
                            editing_method = False

                        else:
                            out_file.write(line)

        except Exception as e:
            self.logger.error(
                'Error during execution of "{0}" obfuscator: {1}'.format(
                    self.__class__.__name__, e))
            raise

        finally:
            obfuscation_info.used_obfuscators.append(self.__class__.__name__)
Esempio n. 9
0
 def add_method(self, smali_file: str, new_method: StringIO):
     with util.inplace_edit_file(smali_file) as current_file:
         for line in current_file:
             if line.startswith('# direct methods'):
                 # Add the new indirection method(s) in the direct methods section.
                 print(line, end='')
                 print(new_method.getvalue(), end='')
             else:
                 print(line, end='')
Esempio n. 10
0
 def add_method(self, smali_file: str, new_method: StringIO):
     with util.inplace_edit_file(smali_file) as (in_file, out_file):
         for line in in_file:
             if line.startswith("# direct methods"):
                 # Add the new indirection method(s) in the direct methods section.
                 out_file.write(line)
                 out_file.write(new_method.getvalue())
             else:
                 out_file.write(line)
Esempio n. 11
0
    def rename_field_declarations(self,
                                  smali_files: List[str],
                                  interactive: bool = False) -> Set[str]:
        renamed_fields: Set[str] = set()

        # Search for field definitions that can be renamed.
        for smali_file in util.show_list_progress(
                smali_files,
                interactive=interactive,
                description="Renaming field declarations",
        ):
            with util.inplace_edit_file(smali_file) as (in_file, out_file):
                for line in in_file:
                    # Field declared in class.
                    field_match = util.field_pattern.match(line)

                    if field_match:
                        field_name = field_match.group("field_name")
                        # Avoid sub-fields.
                        if "$" not in field_name:
                            # Rename field declaration (usages of this field will be
                            # renamed later) and add some random fields.
                            line = line.replace(
                                "{0}:".format(field_name),
                                "{0}:".format(self.rename_field(field_name)),
                            )
                            out_file.write(line)

                            # Add random fields.
                            if self.added_fields < self.max_fields_to_add:
                                for _ in range(util.get_random_int(1, 4)):
                                    out_file.write("\n")
                                    out_file.write(
                                        line.replace(
                                            ":",
                                            "{0}:".format(
                                                util.get_random_string(8)),
                                        ))
                                    self.added_fields += 1

                            field = "{field_name}:{field_type}".format(
                                field_name=field_match.group("field_name"),
                                field_type=field_match.group("field_type"),
                            )
                            renamed_fields.add(field)
                        else:
                            out_file.write(line)
                    else:
                        out_file.write(line)

        return renamed_fields
Esempio n. 12
0
 def rename_method_invocations(
     self,
     smali_files: List[str],
     methods_to_rename: Set[str],
     interactive: bool = False,
 ):
     for smali_file in util.show_list_progress(
         smali_files,
         interactive=interactive,
         description="Renaming method invocations",
     ):
         with util.inplace_edit_file(smali_file) as (in_file, out_file):
             for line in in_file:
                 # Method invocation.
                 invoke_match = util.invoke_pattern.match(line)
                 if invoke_match:
                     method = (
                         "{class_name}->"
                         "{method_name}({method_param}){method_return}".format(
                             class_name=invoke_match.group("invoke_object"),
                             method_name=invoke_match.group("invoke_method"),
                             method_param=invoke_match.group("invoke_param"),
                             method_return=invoke_match.group("invoke_return"),
                         )
                     )
                     invoke_type = invoke_match.group("invoke_type")
                     # Rename the method invocation only if is direct or static (we
                     # are renaming only direct methods). The list of methods to
                     # rename already contains the class name of each method, since
                     # here we have a list of methods whose declarations were already
                     # renamed.
                     if (
                         "direct" in invoke_type or "static" in invoke_type
                     ) and method in methods_to_rename:
                         method_name = invoke_match.group("invoke_method")
                         out_file.write(
                             line.replace(
                                 "->{0}(".format(method_name),
                                 "->{0}(".format(self.rename_method(method_name)),
                             )
                         )
                     else:
                         out_file.write(line)
                 else:
                     out_file.write(line)
Esempio n. 13
0
 def rename_method_invocations(
     self,
     smali_files: List[str],
     methods_to_rename: Set[str],
     android_class_names: Set[str],
     interactive: bool = False,
 ):
     for smali_file in util.show_list_progress(
             smali_files,
             interactive=interactive,
             description="Renaming method invocations",
     ):
         with util.inplace_edit_file(smali_file) as (in_file, out_file):
             for line in in_file:
                 # Method invocation.
                 invoke_match = util.invoke_pattern.match(line)
                 if invoke_match:
                     method = "{method_name}({method_param}){method_return}".format(
                         method_name=invoke_match.group("invoke_method"),
                         method_param=invoke_match.group("invoke_param"),
                         method_return=invoke_match.group("invoke_return"),
                     )
                     invoke_type = invoke_match.group("invoke_type")
                     class_name = invoke_match.group("invoke_object")
                     # Rename the method invocation only if is direct or static (we
                     # are renaming only direct methods) and if is called from a
                     # class that is not an Android API class.
                     if (("direct" in invoke_type
                          or "static" in invoke_type)
                             and method in methods_to_rename
                             and class_name not in android_class_names):
                         method_name = invoke_match.group("invoke_method")
                         out_file.write(
                             line.replace(
                                 "{0}(".format(method_name),
                                 "{0}(".format(
                                     self.rename_method(method_name)),
                             ))
                     else:
                         out_file.write(line)
                 else:
                     out_file.write(line)
Esempio n. 14
0
 def rename_field_references(
     self,
     fields_to_rename: Set[str],
     smali_files: List[str],
     sdk_classes: Set[str],
     interactive: bool = False,
 ):
     for smali_file in util.show_list_progress(
             smali_files,
             interactive=interactive,
             description="Renaming field references",
     ):
         with util.inplace_edit_file(smali_file) as (in_file, out_file):
             for line in in_file:
                 # Field usage.
                 field_usage_match = util.field_usage_pattern.match(line)
                 if field_usage_match:
                     field = "{field_name}:{field_type}".format(
                         field_name=field_usage_match.group("field_name"),
                         field_type=field_usage_match.group("field_type"),
                     )
                     class_name = field_usage_match.group("field_object")
                     field_name = field_usage_match.group("field_name")
                     if field in fields_to_rename and (
                             not class_name.startswith(
                                 ("Landroid", "Ljava"))
                             or class_name in sdk_classes):
                         # Rename field usage.
                         out_file.write(
                             line.replace(
                                 "{0}:".format(field_name),
                                 "{0}:".format(
                                     self.rename_field(field_name)),
                             ))
                     else:
                         out_file.write(line)
                 else:
                     out_file.write(line)
Esempio n. 15
0
    def rename_field_declarations(self,
                                  smali_files: List[str],
                                  interactive: bool = False) -> Set[str]:
        renamed_fields: Set[str] = set()

        # Search for field definitions that can be renamed.
        for smali_file in util.show_list_progress(
                smali_files,
                interactive=interactive,
                description='Renaming field declarations'):
            with util.inplace_edit_file(smali_file) as current_smali_file:
                for line in current_smali_file:
                    # Field declared in class.
                    field_match = util.field_pattern.match(line)

                    if field_match:
                        field_name = field_match.group('field_name')
                        # Avoid sub-fields.
                        if '$' not in field_name:
                            # Rename field declaration (usages of this field will be renamed later) and add some
                            # random fields.
                            line = line.replace(
                                '{0}:'.format(field_name),
                                '{0}:'.format(self.rename_field(field_name)))
                            print(line, end='')
                            self.add_random_fields(line)

                            field = '{field_name}:{field_type}'.format(
                                field_name=field_match.group('field_name'),
                                field_type=field_match.group('field_type'))
                            renamed_fields.add(field)
                        else:
                            print(line, end='')
                    else:
                        print(line, end='')

        return renamed_fields
Esempio n. 16
0
    def rename_method_declarations(self, smali_files: List[str], methods_to_ignore: Set[str],
                                   interactive: bool = False) -> Set[str]:
        renamed_methods: Set[str] = set()

        # Search for method definitions that can be renamed.
        for smali_file in util.show_list_progress(smali_files,
                                                  interactive=interactive,
                                                  description='Renaming method declarations'):
            with util.inplace_edit_file(smali_file) as current_file:

                skip_remaining_lines = False
                class_name = None
                for line in current_file:

                    if skip_remaining_lines:
                        print(line, end='')
                        continue

                    if not class_name:
                        class_match = util.class_pattern.match(line)
                        # If this is an enum class, don't rename anything.
                        if ' enum ' in line:
                            skip_remaining_lines = True
                            print(line, end='')
                            continue
                        elif class_match:
                            class_name = class_match.group('class_name')
                            print(line, end='')
                            continue

                    # Skip virtual methods, consider only the direct methods defined earlier in the file.
                    if line.startswith('# virtual methods'):
                        skip_remaining_lines = True
                        print(line, end='')
                        continue

                    # Method declared in class.
                    method_match = util.method_pattern.match(line)

                    # Avoid constructors, native and abstract methods.
                    if method_match and '<init>' not in line and '<clinit>' not in line and \
                            ' native ' not in line and ' abstract ' not in line:
                        method = '{method_name}({method_param}){method_return}'.format(
                            method_name=method_match.group('method_name'),
                            method_param=method_match.group('method_param'),
                            method_return=method_match.group('method_return')
                        )
                        if method not in methods_to_ignore:
                            # Rename method declaration (invocations of this method will be renamed later).
                            method_name = method_match.group('method_name')
                            print(line.replace(
                                '{0}('.format(method_name),
                                '{0}('.format(self.rename_method(method_name))
                            ), end='')
                            renamed_methods.add(method)
                        else:
                            print(line, end='')
                    else:
                        print(line, end='')

        return renamed_methods
Esempio n. 17
0
    def obfuscate(self, obfuscation_info: Obfuscation):
        self.logger.info('Running "{0}" obfuscator'.format(
            self.__class__.__name__))

        try:
            op_codes = util.get_code_block_valid_op_codes()
            op_code_pattern = re.compile(r"\s+(?P<op_code>\S+)")
            if_pattern = re.compile(
                r"\s+(?P<if_op_code>\S+)"
                r"\s(?P<register>[vp0-9,\s]+?),\s:(?P<goto_label>\S+)")

            for smali_file in util.show_list_progress(
                    obfuscation_info.get_smali_files(),
                    interactive=obfuscation_info.interactive,
                    description="Code reordering",
            ):
                self.logger.debug(
                    'Reordering code in file "{0}"'.format(smali_file))
                with util.inplace_edit_file(smali_file) as (in_file, out_file):
                    editing_method = False
                    inside_try_catch = False
                    jump_count = 0
                    for line in in_file:
                        if (line.startswith(".method ")
                                and " abstract " not in line
                                and " native " not in line
                                and not editing_method):
                            # If at the beginning of a non abstract/native method
                            out_file.write(line)
                            editing_method = True
                            inside_try_catch = False
                            jump_count = 0

                        elif line.startswith(".end method") and editing_method:
                            # If a the end of the method.
                            out_file.write(line)
                            editing_method = False
                            inside_try_catch = False

                        elif editing_method:
                            # Inside method. Check if this line contains an op code at
                            # the beginning of the string.
                            match = op_code_pattern.match(line)
                            if match:
                                op_code = match.group("op_code")

                                # Check if we are entering or leaving a try-catch
                                # block of code.
                                if op_code.startswith(":try_start_"):
                                    out_file.write(line)
                                    inside_try_catch = True
                                elif op_code.startswith(":try_end_"):
                                    out_file.write(line)
                                    inside_try_catch = False

                                # If this is a valid op code, and we are not inside a
                                # try-catch block, mark this section with a special
                                # label that will be used later and invert the if
                                # conditions (if any).
                                elif op_code in op_codes and not inside_try_catch:
                                    jump_name = util.get_random_string(16)
                                    out_file.write(
                                        "\tgoto/32 :l_{label}_{count}\n\n".
                                        format(label=jump_name,
                                               count=jump_count))
                                    out_file.write("\tnop\n\n")
                                    out_file.write("#!code_block!#\n")
                                    out_file.write(
                                        "\t:l_{label}_{count}\n".format(
                                            label=jump_name, count=jump_count))
                                    jump_count += 1

                                    new_if = self.if_mapping.get(op_code, None)
                                    if new_if:
                                        if_match = if_pattern.match(line)
                                        random_label_name = util.get_random_string(
                                            16)
                                        out_file.write(
                                            "\t{if_cond} {register}, "
                                            ":gl_{new_label}\n\n".format(
                                                if_cond=new_if,
                                                register=if_match.group(
                                                    "register"),
                                                new_label=random_label_name,
                                            ))
                                        out_file.write(
                                            "\tgoto/32 :{0}\n\n".format(
                                                if_match.group("goto_label")))
                                        out_file.write("\t:gl_{0}".format(
                                            random_label_name))
                                    else:
                                        out_file.write(line)
                                else:
                                    out_file.write(line)
                            else:
                                out_file.write(line)

                        else:
                            out_file.write(line)

                # Reorder code blocks randomly.
                with util.inplace_edit_file(smali_file) as (in_file, out_file):
                    editing_method = False
                    block_count = 0
                    code_blocks: List[CodeBlock] = []
                    current_code_block = None
                    for line in in_file:
                        if (line.startswith(".method ")
                                and " abstract " not in line
                                and " native " not in line
                                and not editing_method):
                            # If at the beginning of a non abstract/native method
                            out_file.write(line)
                            editing_method = True
                            block_count = 0
                            code_blocks = []
                            current_code_block = None

                        elif line.startswith(".end method") and editing_method:
                            # If a the end of the method.
                            editing_method = False
                            random.shuffle(code_blocks)
                            for code_block in code_blocks:
                                out_file.write(code_block.smali_code)
                            out_file.write(line)

                        elif editing_method:
                            # Inside method. Check if this line is marked with
                            # a special label.
                            if line.startswith("#!code_block!#"):
                                block_count += 1
                                current_code_block = CodeBlock(block_count, "")
                                code_blocks.append(current_code_block)
                            else:
                                if block_count > 0 and current_code_block:
                                    current_code_block.add_smali_code_to_block(
                                        line)
                                else:
                                    out_file.write(line)

                        else:
                            out_file.write(line)

        except Exception as e:
            self.logger.error(
                'Error during execution of "{0}" obfuscator: {1}'.format(
                    self.__class__.__name__, e))
            raise

        finally:
            obfuscation_info.used_obfuscators.append(self.__class__.__name__)
Esempio n. 18
0
    def rename_class_declarations(self, smali_files: List[str], interactive: bool = False) -> dict:
        renamed_classes = {}

        # Search for class declarations that can be renamed.
        for smali_file in util.show_list_progress(smali_files,
                                                  interactive=interactive,
                                                  description='Renaming class declarations'):
            annotation_flag = False
            with util.inplace_edit_file(smali_file) as current_file:

                skip_remaining_lines = False
                class_name = None
                r_class = False
                for line in current_file:

                    if skip_remaining_lines:
                        print(line, end='')
                        continue

                    if not class_name:
                        class_match = util.class_pattern.match(line)
                        if class_match:
                            class_name = class_match.group('class_name')

                            # Split class name to its components and encrypt them.
                            class_tokens = self.split_class_pattern.split(class_name[1:-1])

                            encrypted_class_name = 'L'
                            separator_index = 1
                            for token in class_tokens:
                                separator_index += len(token)
                                if token == 'R':
                                    r_class = True
                                if token.isdigit():
                                    encrypted_class_name += token + class_name[separator_index]
                                elif not r_class:
                                    encrypted_class_name += self.encrypt_identifier(token) + \
                                                            class_name[separator_index]
                                else:
                                    encrypted_class_name += token + class_name[separator_index]
                                separator_index += 1

                            print(line.replace(class_name, encrypted_class_name), end='')

                            renamed_classes[class_name] = encrypted_class_name
                            continue

                    if line.strip() == '.annotation system Ldalvik/annotation/InnerClass;':
                        annotation_flag = True
                        print(line, end='')
                        continue

                    if annotation_flag and 'name = "' in line:
                        # Subclasses have to be renamed as well.
                        subclass_match = self.subclass_name_pattern.match(line)
                        if subclass_match and not r_class:
                            subclass_name = subclass_match.group('subclass_name')
                            print(line.replace(subclass_name, self.encrypt_identifier(subclass_name)), end='')
                        else:
                            print(line, end='')
                        continue

                    if line.strip() == '.end annotation':
                        annotation_flag = False
                        print(line, end='')
                        continue

                    # Method declaration reached, no more class definitions in this file.
                    if line.startswith('.method '):
                        skip_remaining_lines = True
                        print(line, end='')
                    else:
                        print(line, end='')

        return renamed_classes
Esempio n. 19
0
    def add_method_overloads_to_file(self, smali_file: str,
                                     overloaded_method_body: str,
                                     methods_to_ignore: Set[str]) -> int:
        new_methods_num: int = 0
        with util.inplace_edit_file(smali_file) as current_file:

            skip_remaining_lines = False
            class_name = None
            for line in current_file:

                if skip_remaining_lines:
                    print(line, end='')
                    continue

                if not class_name:
                    class_match = util.class_pattern.match(line)
                    # If this is an enum class, skip it.
                    if ' enum ' in line:
                        skip_remaining_lines = True
                        print(line, end='')
                        continue
                    elif class_match:
                        class_name = class_match.group('class_name')
                        print(line, end='')
                        continue

                # Skip virtual methods, consider only the direct methods defined earlier in the file.
                if line.startswith('# virtual methods'):
                    skip_remaining_lines = True
                    print(line, end='')
                    continue

                # Method declared in class.
                method_match = util.method_pattern.match(line)

                # Avoid constructors, native and abstract methods.
                if method_match and '<init>' not in line and '<clinit>' not in line and \
                        ' native ' not in line and ' abstract ' not in line:
                    method = '{method_name}({method_param}){method_return}'.format(
                        method_name=method_match.group('method_name'),
                        method_param=method_match.group('method_param'),
                        method_return=method_match.group('method_return'))
                    # Add method overload.
                    if method not in methods_to_ignore:
                        # Create random parameter lists to be added to the method signature.
                        # Add 3 overloads for each method and for each overload use 4 random params.
                        for params in util.get_random_list_permutations(
                                random.sample(self.param_types, 4))[:3]:
                            new_param = ''.join(params)
                            # Update parameter list and add void return type.
                            overloaded_signature = line.replace(
                                '({0}){1}'.format(
                                    method_match.group('method_param'),
                                    method_match.group('method_return')),
                                '({0}{1})V'.format(
                                    method_match.group('method_param'),
                                    new_param))
                            print(overloaded_signature, end='')
                            print(overloaded_method_body)
                            new_methods_num += 1

                        # Print original method.
                        print(line, end='')
                    else:
                        print(line, end='')
                else:
                    print(line, end='')

        return new_methods_num
Esempio n. 20
0
    def obfuscate(self, obfuscation_info: Obfuscation):
        self.logger.info('Running "{0}" obfuscator'.format(
            self.__class__.__name__))

        try:
            op_codes = util.get_code_block_valid_op_codes()
            op_code_pattern = re.compile(r'\s+(?P<op_code>\S+)')
            if_pattern = re.compile(
                r'\s+(?P<if_op_code>\S+)\s(?P<register>[vp0-9,\s]+?),\s:(?P<goto_label>\S+)'
            )

            for smali_file in util.show_list_progress(
                    obfuscation_info.get_smali_files(),
                    interactive=obfuscation_info.interactive,
                    description='Code reordering'):
                self.logger.debug(
                    'Reordering code in file "{0}"'.format(smali_file))
                with util.inplace_edit_file(smali_file) as current_file:
                    editing_method = False
                    inside_try_catch = False
                    jump_count = 0
                    for line in current_file:
                        if line.startswith('.method ') and ' abstract ' not in line and \
                                ' native ' not in line and not editing_method:
                            # If at the beginning of a non abstract/native method
                            print(line, end='')
                            editing_method = True
                            inside_try_catch = False
                            jump_count = 0

                        elif line.startswith('.end method') and editing_method:
                            # If a the end of the method.
                            print(line, end='')
                            editing_method = False
                            inside_try_catch = False

                        elif editing_method:
                            # Inside method. Check if this line contains an op code at the beginning of the string.
                            match = op_code_pattern.match(line)
                            if match:
                                op_code = match.group('op_code')

                                # Check if we are entering or leaving a try-catch block of code.
                                if op_code.startswith(':try_start_'):
                                    print(line, end='')
                                    inside_try_catch = True
                                elif op_code.startswith(':try_end_'):
                                    print(line, end='')
                                    inside_try_catch = False

                                # If this is a valid op code, and we are not inside a try-catch block, mark this
                                # section with a special label that will be used later and invert the if conditions
                                # (if any).
                                elif op_code in op_codes and not inside_try_catch:
                                    jump_name = util.get_random_string(16)
                                    print('\tgoto/32 :l_{label}_{count}\n'.
                                          format(label=jump_name,
                                                 count=jump_count))
                                    print('\tnop\n')
                                    print('#!code_block!#')
                                    print('\t:l_{label}_{count}'.format(
                                        label=jump_name, count=jump_count))
                                    jump_count += 1

                                    new_if = self.if_mapping.get(op_code, None)
                                    if new_if:
                                        if_match = if_pattern.match(line)
                                        random_label_name = util.get_random_string(
                                            16)
                                        print(
                                            '\t{if_cond} {register}, :gl_{new_label}\n'
                                            .format(
                                                if_cond=new_if,
                                                register=if_match.group(
                                                    'register'),
                                                new_label=random_label_name))
                                        print('\tgoto/32 :{0}\n'.format(
                                            if_match.group('goto_label')))
                                        print('\t:gl_{0}'.format(
                                            random_label_name),
                                              end='')
                                    else:
                                        print(line, end='')
                                else:
                                    print(line, end='')
                            else:
                                print(line, end='')

                        else:
                            print(line, end='')

                # Reorder code blocks randomly.
                with util.inplace_edit_file(smali_file) as current_file:
                    editing_method = False
                    block_count = 0
                    code_blocks: List[CodeBlock] = []
                    current_code_block = None
                    for line in current_file:
                        if line.startswith('.method ') and ' abstract ' not in line and \
                                ' native ' not in line and not editing_method:
                            # If at the beginning of a non abstract/native method
                            print(line, end='')
                            editing_method = True
                            block_count = 0
                            code_blocks = []
                            current_code_block = None

                        elif line.startswith('.end method') and editing_method:
                            # If a the end of the method.
                            editing_method = False
                            random.shuffle(code_blocks)
                            for code_block in code_blocks:
                                print(code_block.smali_code, end='')
                            print(line, end='')

                        elif editing_method:
                            # Inside method. Check if this line is marked with a special label.
                            if line.startswith('#!code_block!#'):
                                block_count += 1
                                current_code_block = CodeBlock(block_count, '')
                                code_blocks.append(current_code_block)
                            else:
                                if block_count > 0 and current_code_block:
                                    current_code_block.add_smali_code_to_block(
                                        line)
                                else:
                                    print(line, end='')

                        else:
                            print(line, end='')

        except Exception as e:
            self.logger.error(
                'Error during execution of "{0}" obfuscator: {1}'.format(
                    self.__class__.__name__, e))
            raise

        finally:
            obfuscation_info.used_obfuscators.append(self.__class__.__name__)
Esempio n. 21
0
    def rename_method_declarations(
        self,
        smali_files: List[str],
        methods_to_ignore: Set[str],
        interactive: bool = False,
    ) -> Set[str]:
        renamed_methods: Set[str] = set()

        # Search for method definitions that can be renamed.
        for smali_file in util.show_list_progress(
                smali_files,
                interactive=interactive,
                description="Renaming method declarations",
        ):
            with util.inplace_edit_file(smali_file) as (in_file, out_file):

                skip_remaining_lines = False
                class_name = None
                for line in in_file:

                    if skip_remaining_lines:
                        out_file.write(line)
                        continue

                    if not class_name:
                        class_match = util.class_pattern.match(line)
                        # If this is an enum class, don't rename anything.
                        if " enum " in line:
                            skip_remaining_lines = True
                            out_file.write(line)
                            continue
                        elif class_match:
                            class_name = class_match.group("class_name")
                            out_file.write(line)
                            continue

                    # Skip virtual methods, consider only the direct methods defined
                    # earlier in the file.
                    if line.startswith("# virtual methods"):
                        skip_remaining_lines = True
                        out_file.write(line)
                        continue

                    # Method declared in class.
                    method_match = util.method_pattern.match(line)

                    # Avoid constructors, native and abstract methods.
                    if (method_match and "<init>" not in line
                            and "<clinit>" not in line
                            and " native " not in line
                            and " abstract " not in line):
                        method = "{method_name}({method_param}){method_return}".format(
                            method_name=method_match.group("method_name"),
                            method_param=method_match.group("method_param"),
                            method_return=method_match.group("method_return"),
                        )
                        if method not in methods_to_ignore:
                            # Rename method declaration (invocations of this method
                            # will be renamed later).
                            method_name = method_match.group("method_name")
                            out_file.write(
                                line.replace(
                                    "{0}(".format(method_name),
                                    "{0}(".format(
                                        self.rename_method(method_name)),
                                ))
                            renamed_methods.add(method)
                        else:
                            out_file.write(line)
                    else:
                        out_file.write(line)

        return renamed_methods
Esempio n. 22
0
    def add_method_overloads_to_file(self, smali_file: str,
                                     overloaded_method_body: str,
                                     methods_to_ignore: Set[str]) -> int:
        new_methods_num: int = 0
        with util.inplace_edit_file(smali_file) as (in_file, out_file):

            skip_remaining_lines = False
            class_name = None
            for line in in_file:

                if skip_remaining_lines:
                    out_file.write(line)
                    continue

                if not class_name:
                    class_match = util.class_pattern.match(line)
                    # If this is an enum class, skip it.
                    if " enum " in line:
                        skip_remaining_lines = True
                        out_file.write(line)
                        continue
                    elif class_match:
                        class_name = class_match.group("class_name")
                        out_file.write(line)
                        continue

                # Skip virtual methods, consider only the direct methods defined
                # earlier in the file.
                if line.startswith("# virtual methods"):
                    skip_remaining_lines = True
                    out_file.write(line)
                    continue

                # Method declared in class.
                method_match = util.method_pattern.match(line)

                # Avoid constructors, native and abstract methods.
                if (method_match and "<init>" not in line
                        and "<clinit>" not in line and " native " not in line
                        and " abstract " not in line):
                    method = "{method_name}({method_param}){method_return}".format(
                        method_name=method_match.group("method_name"),
                        method_param=method_match.group("method_param"),
                        method_return=method_match.group("method_return"),
                    )
                    # Add method overload.
                    if method not in methods_to_ignore:
                        # Create random parameter lists to be added to the method
                        # signature. Add 3 overloads for each method and for each
                        # overload use 4 random params.
                        for params in util.get_random_list_permutations(
                                random.sample(self.param_types, 4))[:3]:
                            new_param = "".join(params)
                            # Update parameter list and add void return type.
                            overloaded_signature = line.replace(
                                "({0}){1}".format(
                                    method_match.group("method_param"),
                                    method_match.group("method_return"),
                                ),
                                "({0}{1})V".format(
                                    method_match.group("method_param"),
                                    new_param),
                            )
                            out_file.write(overloaded_signature)
                            out_file.write(overloaded_method_body)
                            new_methods_num += 1

                        # Print original method.
                        out_file.write(line)
                    else:
                        out_file.write(line)
                else:
                    out_file.write(line)

        return new_methods_num
Esempio n. 23
0
    def rename_class_declarations(
        self, smali_files: List[str], interactive: bool = False
    ) -> dict:
        renamed_classes = {}

        # Search for class declarations that can be renamed.
        for smali_file in util.show_list_progress(
            smali_files,
            interactive=interactive,
            description="Renaming class declarations",
        ):
            annotation_flag = False
            with util.inplace_edit_file(smali_file) as (in_file, out_file):

                skip_remaining_lines = False
                class_name = None
                r_class = False
                for line in in_file:

                    if skip_remaining_lines:
                        out_file.write(line)
                        continue

                    if not class_name:
                        class_match = util.class_pattern.match(line)
                        if class_match:
                            class_name = class_match.group("class_name")

                            ignore_class = class_name.startswith(
                                tuple(self.ignore_package_names)
                            )

                            # Split class name to its components and encrypt them.
                            class_tokens = self.split_class_pattern.split(
                                class_name[1:-1]
                            )

                            encrypted_class_name = "L"
                            separator_index = 1
                            for token in class_tokens:
                                separator_index += len(token)
                                if token == "R":
                                    r_class = True
                                if token.isdigit():
                                    encrypted_class_name += (
                                        token + class_name[separator_index]
                                    )
                                elif not r_class and not ignore_class:
                                    encrypted_class_name += (
                                        self.encrypt_identifier(token)
                                        + class_name[separator_index]
                                    )
                                else:
                                    encrypted_class_name += (
                                        token + class_name[separator_index]
                                    )
                                separator_index += 1

                            out_file.write(
                                line.replace(class_name, encrypted_class_name)
                            )

                            renamed_classes[class_name] = encrypted_class_name
                            continue

                    if (
                        line.strip()
                        == ".annotation system Ldalvik/annotation/InnerClass;"
                    ):
                        annotation_flag = True
                        out_file.write(line)
                        continue

                    if annotation_flag and 'name = "' in line:
                        # Subclasses have to be renamed as well.
                        subclass_match = self.subclass_name_pattern.match(line)
                        if subclass_match and not r_class:
                            subclass_name = subclass_match.group("subclass_name")
                            out_file.write(
                                line.replace(
                                    subclass_name,
                                    self.encrypt_identifier(subclass_name),
                                )
                            )
                        else:
                            out_file.write(line)
                        continue

                    if line.strip() == ".end annotation":
                        annotation_flag = False
                        out_file.write(line)
                        continue

                    # Method declaration reached, no more class definitions in
                    # this file.
                    if line.startswith(".method "):
                        skip_remaining_lines = True
                        out_file.write(line)
                    else:
                        out_file.write(line)

        return renamed_classes