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='')
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__)
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='')
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='')
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='')
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)
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__)
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__)
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='')
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)
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
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)
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)
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)
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
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
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__)
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
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
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__)
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
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
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