def __init__(self, **args): # Logger self.logger = Log( log_name=f"pyobfx_log-{time.strftime('%X')}.txt", active=args['silent']) if not args['no_log'] else Log( active=args['silent']) self.logger.log('Starting obfuscator') # Strgen type self.strgen_type = { 'jp': 3, 'ch': 4, 'in': 5 }[args['str_gen']] if args['str_gen'] and args['str_gen'] in [ 'jp', 'ch', 'in' ] else 1 # Header for obfuscated file self.obfx_header = "# Obfuscated with PyObfx #" # Escape placeholder self.escape_placeholder = "#escaped_char#" # Obfx arguments self.args = args # File name and content self.file_name = self.args['file'] self.file_content = read_file(self.file_name) # Length constant for random string self.obf_len_constant = 2 # Quote character distance from string (max) self.quote_dist_constant = 5 # Imports obfuscation self.import_dict, self.import_content = self._prepare_imports( ) # warn: change self.file_content variable # Escape chars in file self.escaped_file = self._escape_file(self.file_content) # Tokenize the source and retrieve tokenizer object self.tokenizer = Tokenizer(self.escaped_file) self.has_handled = set() self.build_in_func = ['exit', ''] #过滤python内建函数 # Random integers for obfuscation self.ints = [random.randint(1, 5) for _ in range(3)] # Deobfuscator declarations self.deobfuscators = { 1: "lambda n: (n - ({} % {})) - {}".format(*self.ints[::-1]), 2: "lambda n: (n ^ {}) / {}".format(*self.ints[:2]), 3: "lambda n: n - sum({})".format(str(self.ints)), 4: "lambda n: (n + ({} + {})) / {}".format(*self.ints[::-1]), 5: "lambda n: (n + {}) / ({} + {})".format(*self.ints) } # New file extension for obfuscated file self.obfx_ext = "_obfx.py" # Randomized deobfuscator function names self.deobfuscator_name = generate_rand_str(self.strgen_type, 10) self.str_deobfuscator_name = generate_rand_str(self.strgen_type, 10) # Quote list self.quotes = ["'", '"', '"""'] # Boolean value list self.boolean_val = ['True', 'False'] # Escape Sequences self.escapes = [('\a', '\\a'), ('\b', '\\b'), \ ('\f', '\\f'), ('\n', '\\n'), ('\r', '\\r'), \ ('\t', '\\t'), ('\v', '\\v')]
def _obfuscate_names(self, token_type): self.logger.log("Obfuscating " + str(token_type).split('.')[-1] + "s...") # Iterate through the tokens and check if # token type is Token.Name try: for t_index, token in enumerate(self.tokenizer.TOKENS): # Get the name value name_value = token[2] if token[1][0] == token_type and token[0] not in self.has_handled \ and name_value not in self.build_in_func: if name_value.startswith('__') and name_value.endswith( '__'): continue # Obfuscate the name string obf_var_name = generate_rand_str( self.strgen_type, len(name_value) * self.obf_len_constant) # Fix imports if name_value in list(self.import_dict.keys()): obf_var_name = self.import_dict[name_value] else: # Continue if current token is part of a function # (eg.: random.randint) if self.tokenizer.TOKENS[t_index+1][1][0] == Token.Operator or \ self.tokenizer.TOKENS[t_index-1][1][0] == Token.Operator: continue # Find usages for current name with find_index_by_id method token_index = self.tokenizer.find_index_by_id(token[0]) # Iterate through the indexes and change current value with # new obfuscated value self.has_handled.add(token[0]) for index in token_index: # Check if token is a function # https://github.com/PyObfx/PyObfx/issues/61 if self.tokenizer.TOKENS[index - 1][1][0] == Token.Operator: continue current_token = self.tokenizer.TOKENS[index] # Change list element self.tokenizer.TOKENS[index] = (current_token[0], (Token.Name, obf_var_name), obf_var_name) except Exception as ex: self.logger.log( f'{type(ex).__name__} has occured while obfuscating names \n[{ex}]', state='error') else: self.logger.log( str(token_type).split('.')[-1] + " obfuscation done.")
def _prepare_imports(self): self.logger.log('Extracting imports...') # for the content to be obfuscated replaced = "" # for the remaining content except import parts other_content = "" obf_dict = {} enter = False file_content_ln = self.file_content.split('\n') try: # Drafts draft1 = '^from\s+(.+)\s+import\s+(.*)' draft2 = '^import\s+(.+)' for num, line in enumerate(file_content_ln): #-------------------------------# que1 = re.search('as\s+([^:]+)$', line) # import .. as .. if que1: # same for the next 4 steps # Get random variable name obf_name = generate_rand_str( self.strgen_type, len(line.split(' as ')[1]) * self.obf_len_constant) real_namespace = line.split(' as ')[1] obf_dict[real_namespace] = obf_name replaced += line.split( ' as ')[0] + ' as ' + obf_name + '\n' continue #-------------------------------# que2 = re.search(draft1, line) if que2: if que2.group(2).strip() == '*': obf_dict[que2.group(2).strip()] = que2.group(2).strip() replaced += line + '\n' continue # from x import (y, z, t) re_imp = re.search('\((.+)\)', line) if re_imp: #re_imp: x,y,z for namespace in re_imp.group(1).split(','): # routine obf_name = generate_rand_str( self.strgen_type, len(namespace.strip()) * self.obf_len_constant) real_namespace = namespace.strip() obf_dict[real_namespace] = obf_name replaced += f"from {que2.group(1)} import {namespace} as {obf_name}\n" continue #----------------------------# if '(' in que2.group(2) and not ')' in que2.group(2): # from x import ( # y,z,a, # b,c,d # ) # this code block is for catching the # namespaces between '(' and ')' enter = True index = 1 tmp = "" while True: new_ln = file_content_ln[num + index] tmp += new_ln index += 1 if ')' in new_ln: break tmp = tmp.replace(')', '') namesp_list = [i.strip() for i in tmp.split(',')] for namesp in namesp_list: # routine obf_name = generate_rand_str( self.strgen_type, len(namesp.strip()) * self.obf_len_constant) real_namespace = namesp.strip() obf_dict[real_namespace] = obf_name replaced += f"from {que2.group(1)} import {namesp} as {obf_name}\n" continue # ------------------------------ # if ',' in que2.group(2): for namespace in que2.group(2).split( ','): # from x import y,z,t obf_name = generate_rand_str( self.strgen_type, len(namespace.strip()) * self.obf_len_constant) real_namespace = namespace.strip() obf_dict[real_namespace] = obf_name replaced += f"from {que2.group(1)} import {namespace} as {obf_name}\n" continue # ---------------------------- # if not ',' in que2.group(2) and not '(' in que2.group( 2): # from x import y (single) obf_name = generate_rand_str( self.strgen_type, len(que2.group(2)) * self.obf_len_constant) real_namespace = que2.group(2) obf_dict[real_namespace] = obf_name replaced += re.sub(draft1, line + f' as {obf_name}\n', line) continue # ------------------------- # que3 = re.search(draft2, line) if que3: if ',' in que3.group(1): for namespace in que3.group(1).split( ','): # import x,y,z obf_name = generate_rand_str( self.strgen_type, len(namespace.strip()) * self.obf_len_constant) real_namespace = namespace.strip() obf_dict[real_namespace] = obf_name replaced += f'import {namespace} as {obf_name}\n' continue # -------------------- # else: obf_name = generate_rand_str( self.strgen_type, len(que3.group(1)) * self.obf_len_constant) real_namespace = que3.group(1) obf_dict[real_namespace] = obf_name replaced += f"import {real_namespace} as {obf_name}\n" continue # --------------------- # # Escape other import things if enter: if '(' in line: enter = True continue # all contents except import other_content += line + '\n' # eleminate the class variable from import parts self.file_content = other_content except Exception as ex: self.logger.log( f'{type(ex).__name__} has occured while extracting the imports \n[{ex}]', state='critical') else: self.logger.log('Imports extracted from source.') return (obf_dict, replaced)