Ejemplo n.º 1
0
 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')]
Ejemplo n.º 2
0
class Obfuscator:
    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')]

    # str.format with int deobfuscator name
    string_deobfuscator = "lambda s: ''.join(chr(int({}(ord(c)))) for c in s)"

    # Obfuscator methods
    def obfuscation1(self, n):
        return (n + self.ints[0]) + (self.ints[2] % self.ints[1])

    def obfuscation2(self, n):
        return (n * self.ints[1]) ^ self.ints[0]

    def obfuscation3(self, n):
        return n + sum(self.ints)

    def obfuscation4(self, n):
        return (n * self.ints[0]) - (self.ints[2] + self.ints[1])

    def obfuscation5(self, n):
        return (n * (self.ints[0] + self.ints[1])) - self.ints[2]

    # Escape string
    def _escape(self, s):
        s = s.replace('"', r'\"').replace("'", r"\'")
        for escape in self.escapes:
            s = s.replace(escape[0], escape[1])
        return s

    # Escape file
    # (eg.: Replace '\' with placeholder)
    def _escape_file(self, file_content):
        # Double quote
        file_content = file_content.replace("\\\"", "\\d")
        # Single quote
        file_content = file_content.replace("\\\'", "\\s")
        file_content = file_content.replace("\\", self.escape_placeholder)
        self.logger.log('Escaped source file.')
        return file_content

    # Unescape string using placeholder
    def _unescape_str(self, s):
        for escape in self.escapes:
            s = s.replace(
                self.escape_placeholder + escape[1].replace("\\", ""),
                escape[0])
        s = s.replace(self.escape_placeholder, '\\')
        # Single quote
        s = s.replace('\\s', '\'')
        # Double quote
        s = s.replace('\\d', '\"')
        return s

    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 _obfuscate_vars(self, obfuscator, token_type):
        # Inner function for type casting
        # [can be simplified]
        def cast(token):
            if token_type == Token.Literal.Number.Integer:
                return int(token)
            elif token_type == Token.Literal.Number.Float:
                return float(token)
            elif token_type == Token.Keyword.Constant:
                return bool(token)

        # Iterate through the tokens and check if
        # token type equals token_type
        self.logger.log("Obfuscating " + str(token_type).split('.')[-1] +
                        "s...")
        try:
            for token in self.tokenizer.TOKENS:
                if token[1][0] == token_type and token[
                        0] not in self.has_handled:
                    # Cast and obfuscate the value
                    obf_val = obfuscator(cast(token[2]))
                    # Find usages for current token
                    token_index = self.tokenizer.find_index_by_id(token[0])
                    # Update old values with new obfuscated values
                    # If obfuscation type is a boolean obfuscation
                    # don't forget to add casting to new string
                    self.has_handled.add(token[0])
                    for index in token_index:
                        # Get token
                        current_token = self.tokenizer.TOKENS[index]
                        # Check boolean obfuscation
                        if token_type == Token.Keyword.Constant and \
                        current_token[2] in self.boolean_val:
                            # Add boolean casting [bool(...)]
                            # Update TOKENS
                            self.tokenizer.TOKENS[index] = (
                                current_token[0], current_token[1],
                                f'bool(int({self.deobfuscator_name}({obf_val})))'
                            )
                        else:
                            # Update TOKENS
                            if token_type == Token.Literal.Number.Float:
                                self.tokenizer.TOKENS[index] = (
                                    current_token[0], current_token[1],
                                    f'{self.deobfuscator_name}({obf_val})')
                            elif token_type == Token.Literal.Number.Integer:
                                self.tokenizer.TOKENS[index] = (
                                    current_token[0], current_token[1],
                                    f'int({self.deobfuscator_name}({obf_val}))'
                                )

        except Exception as ex:
            self.logger.log(
                f'{type(ex).__name__} has occured while obfuscating variables \n[{ex}]',
                state='error')
        else:
            self.logger.log(
                str(token_type).split('.')[-1] + " obfuscation done.")

    def _obfuscate_strings(self, obfuscator):
        # Iterate through the tokens and make sure
        # current token is string and not an unnecessary
        # char (quote)
        self.logger.log('Obfuscating strings...')
        try:
            for key, token in enumerate(self.tokenizer.TOKENS):
                if token[1][0] == Token.Literal.String.Double and not token[2] in self.quotes or \
                token[1][0] == Token.Literal.String.Single and not token[2] in self.quotes \
                        and token[0] not in self.has_handled:
                    # Don't touch multi line strings
                    try:
                        if self.tokenizer.TOKENS[key-1][2] == self.quotes[2] or \
                        self.tokenizer.TOKENS[key+1][2] == self.quotes[2]:
                            continue
                    except:
                        pass
                    # only handle one string ,such as "123" or '123'
                    # other not handle
                    try:
                        if token[1][0] != self.tokenizer.TOKENS[key - 1][1][0]:
                            continue
                        if token[1][0] != self.tokenizer.TOKENS[key + 1][1][0]:
                            continue
                        if token[1][0] == self.tokenizer.TOKENS[key - 2][1][0]:
                            continue
                        if token[1][0] == self.tokenizer.TOKENS[key + 2][1][0]:
                            continue
                        if self.tokenizer.TOKENS[
                                key -
                                2][1][0] == Token.Literal.String.Interpol:
                            continue
                        if self.tokenizer.TOKENS[
                                key +
                                2][1][0] == Token.Literal.String.Interpol:
                            continue
                        if self.tokenizer.TOKENS[
                                key -
                                3][1][0] == Token.Literal.String.Interpol:
                            continue
                        if self.tokenizer.TOKENS[
                                key +
                                3][1][0] == Token.Literal.String.Interpol:
                            continue
                    except:
                        pass
                    # String value
                    string_value = self._unescape_str(token[2])
                    # String obfuscation procedure
                    obfuscated = ''
                    # Obfuscate chars in string
                    for c in string_value:
                        obfuscated += ''.join(chr(obfuscator(ord(c))))
                    # Create obfuscated string
                    # (eg.: <deobfuscator_name>('<obfuscated_string>'))
                    obf_string = self.str_deobfuscator_name + "(\"" + self._escape(
                        obfuscated) + "\")"
                    # Find usages for current token
                    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:
                        current_token = self.tokenizer.TOKENS[index]
                        self.tokenizer.TOKENS[index] = (current_token[0],
                                                        (Token.Name,
                                                         obf_string),
                                                        obf_string)
                        # Pop unnecessary escape characters
                        # (eg.: print("deobf("test")") -> Quote is unnecessary)
                        for n_index in range(self.quote_dist_constant):
                            if self.tokenizer.TOKENS[
                                    index - n_index][2] in self.quotes:
                                self.tokenizer.TOKENS.pop(index - n_index)
                                self.tokenizer.TOKENS.pop(index)
        except Exception as ex:
            self.logger.log(
                f'{type(ex).__name__} has occured while obfuscating strings \n[{ex}]',
                state='error')
        else:
            self.logger.log("String obfuscation done.")

    def obfuscate(self, obfuscation=random.randint(1, 5)):
        self.logger.log('Obfuscation started')
        # Declare obfuscator
        obfuscators = {
            1: self.obfuscation1,
            2: self.obfuscation2,
            3: self.obfuscation3,
            4: self.obfuscation4,
            5: self.obfuscation5
        }
        # Select obfuscator
        obfuscator = obfuscators[obfuscation]
        self.deobfuscator = self.deobfuscators[obfuscation]
        # Variable Name Obfuscation
        try:
            self._obfuscate_names(Token.Name)
        except Exception as ex:
            self.logger.log(
                f'{type(ex).__name__} has occured while obfuscating variable names \n[{ex}]',
                'error')
        else:
            self.logger.log(f'Obfuscated variable names')
        # Function Name Obfuscation
        try:
            self._obfuscate_names(Token.Name.Function)
            pass
        except Exception as ex:
            self.logger.log(
                f'{type(ex).__name__} has occured while obfuscating function names \n[{ex}]',
                'error')
        else:
            self.logger.log(f'Obfuscated function names')
        try:
            # Integer Obfuscation
            self._obfuscate_vars(obfuscator, Token.Literal.Number.Integer)
            # Float Obfuscation
            if obfuscation != 2:  # sadly, ^ operator doesn't work on floats
                self._obfuscate_vars(obfuscator, Token.Literal.Number.Float)
            # # Boolean Obfuscation
            self._obfuscate_vars(obfuscator, Token.Keyword.Constant)
            # String Obfuscation
            self._obfuscate_strings(obfuscator)
            pass
        except Exception as ex:
            self.logger.log(
                f'{type(ex).__name__} has occured while obfuscating values \n[{ex}]',
                'error')
        else:
            self.logger.log(f'Obfuscated values')
        # Save file
        self._save_obfuscated_file()

    def _pack(self, file_content):
        try:
            # Packer functions
            packer_dispatcher = {
                'bz2': bz2_pack(file_content),
                'gz': gz_pack(file_content),
                'lzma': lzma_pack(file_content)
            }
            # Pack file if -p argument is provided and packer type is valid
            try:
                file_content = self.obfx_header + '\n' + packer_dispatcher[
                    self.args['pack']]
                self.logger.log('File packed. (' + self.args['pack'] + ')')
            except KeyError:
                pass
        except Exception as ex:
            self.logger.log(
                f'{type(ex).__name__} has occured while packing the obfuscated file \n[{ex}]',
                state='error')
        return file_content

    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)

    def _save_obfuscated_file(self):
        try:
            path_for_output = "./" if not self.args[
                "out"] else self.args["out"] + "/"
            new_file_content = ''
            # Shebang check & fix
            for index, token in enumerate(self.tokenizer.TOKENS[:4]):
                if token[2].startswith('#') or token[2] == '\n':
                    new_file_content += token[2]
                    self.tokenizer.TOKENS.pop(0)
            # New file name
            new_file_name = self.file_name.replace(
                "." +
                self.file_name.split('.')[len(self.file_name.split('.')) - 1],
                self.obfx_ext)
            if "\\" in new_file_name:
                new_file_name = new_file_name.split('\\')[-1]
            if "/" in new_file_name:
                new_file_name = new_file_name.split('/')[-1]
            # Add header
            new_file_content += self.obfx_header + '\n'
            new_file_content += self.import_content + '\n'
            # Write deobfuscator functions
            new_file_content += f'{self.deobfuscator_name} = {self.deobfuscator}\n{self.str_deobfuscator_name} = {self.string_deobfuscator.format(self.deobfuscator_name)}\n'
            # Create new file content from tokens
            for token in self.tokenizer.TOKENS:
                new_file_content += token[2]
            # Pack
            new_file_content = self._pack(new_file_content)
            # Write file
            write_file(path_for_output + new_file_name, new_file_content)
        except Exception as ex:
            self.logger.log(
                f'{type(ex).__name__} has occured while saving the obfuscated file \n[{ex}]',
                state='error')
        else:
            self.logger.log("Successfully obfuscated.")
            self.logger.log("Saved to \"" + new_file_name + "\"")