# This is a sample Python script. import argparse import base64 import ipaddress import json import os import random import re import secrets import string import subprocess import sys import time from datetime import datetime from enum import Enum from pathlib import Path import numpy as np from colorama import Fore class AMSITrigger: def __init__(self): self.path = os.path.join(Utils.get_project_root(), "utils", "AMSITrigger.exe") self.args = "-f1" def check(self, filename): if not os.path.isfile(filename): Console.auto_line(f"[-] AMSITrigger: File {filename} not found") sys.exit(1) try: cmd = f"\"{self.path}\" {self.args} -i {filename}" # print(cmd) output = subprocess.check_output(cmd).decode().rstrip() if output.find("AMSI_RESULT_NOT_DETECTED") >= 0: Console.auto_line(" [+] SUCCESS: AMSI Bypassed!") elif output.find("Check Real Time protection is enabled") >= 0: Console.auto_line(" [#] UNKNOWN: Real-Time Protection Disabled") else: Console.auto_line(" [-] FAILED: AMSI Triggered!") except subprocess.CalledProcessError as e: for line in e.output.decode().split("\n"): if re.search(r"error", line): print(f" [-] Error: {line}") sys.exit(1) class Utils: @staticmethod def get_project_root(): return str(Path(__file__).parent.absolute()) class PSContextType(Enum): MAIN = 0 FUNCTION = 1 NESTED_FUNCTION = 2 PARAMS = 3 COMMENTS = 4 class PSContext: def __init__(self, name: str, ctx_type: PSContextType): self.last_line = 0 self.ctx_type = ctx_type self.name = name self.content = "" self.brackets = 0 self.first_line = 0 def open_brackets(self, nb=1): self.brackets += nb def close_brackets(self, nb=1): self.brackets -= nb def change_context(self, new_context): self.ctx_type = new_context class PSTree: def __init__(self, psctx: PSContext): self.ctx = [psctx] @property def current(self) -> PSContext: if not self.is_empty(): return self.ctx[-1] @property def current_ctx_type(self) -> PSContextType: if not self.is_empty(): return self.ctx[-1].ctx_type @property def previous(self) -> PSContext: self.ctx.pop(-1) return self.current @property def balanced(self) -> bool: if not self.is_empty(): return self.ctx[-1].brackets == 0 def is_empty(self): return len(self.ctx) == 0 def close(self): return self.ctx.pop(-1) def add_content(self, value): self.current.content += f"\n{value}" def open_brackets(self, nb=1): self.current.brackets += nb def close_brackets(self, nb=1): self.current.brackets -= nb def change_context(self, ctx_name: str, ctx_type: PSContextType): self.ctx.append(PSContext(ctx_name, ctx_type)) def extract_data(self): var_pattern = re.compile(r'\$[\w|_]+') matches = [match.group() for match in var_pattern.finditer(self.current.content)] _matches = list(set(matches)) matches = [] for e in _matches: if e.lower() in ["$true", "$false", "$null", "$_"]: continue matches.append(e.strip().lower()) matches.sort(key=len) return matches[::-1] def to_string(self): return "->".join([self.ctx[i].ctx_type.name for i in range(len(self.ctx))]) class ObfuscationLevel: def __init__(self, lvl_id=0): # Random string min and max size self.random_min = 25 self.random_max = 1000 # Junk size - Comments self.junk_min = 125 self.junk_max = 2000 # Function names self.function_min = 40 self.function_max = 41 # Token min and max size (min is 1) # Max=0: Unlimited self.token_min = 1 self.token_max = 0 # Case switcher iterations self.iterations = 6 if lvl_id == 0 else lvl_id if lvl_id == 1: self.random_min = 20 self.random_max = 45 self.junk_min = 25 self.junk_max = 125 self.function_min = 12 self.function_max = 13 self.token_min = 1 self.token_max = 4 elif lvl_id == 2: self.random_min = 75 self.random_max = 125 self.junk_min = 125 self.junk_max = 250 self.function_min = 40 self.function_max = 41 self.token_min = 1 self.token_max = 3 elif lvl_id == 3: self.random_min = 125 self.random_max = 225 self.junk_min = 125 self.junk_max = 500 self.function_min = 40 self.function_max = 41 self.token_min = 1 self.token_max = 2 elif lvl_id == 4: self.random_min = 225 self.random_max = 325 self.junk_min = 500 self.junk_max = 1000 self.function_min = 40 self.function_max = 60 self.token_min = 1 self.token_max = 2 elif lvl_id == 5: self.random_min = 500 self.random_max = 750 self.junk_min = 1000 self.junk_max = 2000 self.function_min = 50 self.function_max = 60 self.token_min = 1 self.token_max = 1 class Chameleon: def __init__(self, filename, outfile, config: dict = None, lvl_id: int = 0, fmap: str = None, quiet: bool = False): self.content = None self.outfile = outfile self.eol = os.linesep self.load_from_file(filename=filename) self.quiet = quiet # Use case randomization self.case_randomization = config["cases"] self.level = ObfuscationLevel(lvl_id=lvl_id) # Use dictionary instead of random strings self.dictionary = None # This should be a mapping self.scoped_variables = [] self.debug = False self.use_dictionary = config["random-type"] != "r" if self.use_dictionary: self.dictionary = open(os.path.join(Utils.get_project_root(), "dictionary", "food.txt"), "r").readlines() self.config = config self.function_mapping_file = fmap self.function_mapping = {} self.load_mapping(filename=fmap) self.placeholder = "####CHIMERA_COMMENT####" # Probabilities self.probabilities = { "backticker": 0.75, "case_randomize": 0.5 } # Patterns self.nishang_patterns = open( os.path.join( Utils.get_project_root(), "config", "nishang.txt") ).readlines() # AMSI triggering strings self.default_patterns = open( os.path.join( Utils.get_project_root(), "config", "strings.txt") ).readlines() self.default_type_patterns = open( os.path.join( Utils.get_project_root(), "config", "data_types.txt") ).readlines() self.dont_backtick = [ "kernel32", "ntdll" ] @staticmethod def scramble(text): new_text = "" for char in text: if char.islower(): new_text += secrets.choice(string.ascii_lowercase) else: new_text += char return new_text def load_from_file(self, filename): if not os.path.isfile(filename): Console.auto_line("[-] File not found") sys.exit(1) with open(filename, 'rb') as in_file: raw = in_file.read() if len(raw) <= 3: Console.auto_line("[-] This file doesn't seem a valid PowerShell script") bom = 0 while chr(raw[bom]) not in string.printable: bom += 1 self.content = raw[bom:].decode(encoding="windows-1252", errors="replace") if not len(self.content.split(self.eol)) > 1: self.eol = "\n" def load_mapping(self, filename): if not filename: return if filename and not os.path.isfile(filename): Console.auto_line("[-] Mapping file not found") try: with open(filename, 'r') as in_file: self.function_mapping = json.load(in_file) except: Console.warn_line(" [-] Wrong mapping format. Skipping") def save_mapping(self): try: with open(self.function_mapping_file, 'w') as out_file: json.dump(self.function_mapping, out_file) except: Console.auto_line(" [-] Error saving mapping") def random_ascii_string(self, min_size=None, max_size=None): if not min_size: min_size = self.level.random_min if not max_size: max_size = self.level.random_max if self.config["random-type"] == "d": return self.create_random_word(min_size=min_size, max_size=max_size) return ''.join(secrets.choice(string.ascii_letters) for _ in range(random.randint(min_size, max_size))) def random_alpha_string(self, min_size=None, max_size=None): if not min_size: min_size = self.level.random_min if not max_size: max_size = self.level.random_max if self.config["random-type"] == "d": return self.create_random_word(min_size=min_size, max_size=max_size) return ''.join( secrets.choice(string.ascii_letters + string.digits) for _ in range(random.randint(min_size, max_size))) def random_variable(self, min_size=None, max_size=None): return f"${self.random_alpha_string(min_size=min_size, max_size=max_size)}" def tokenize(self, input_string, min_size=None, max_size=None): if not min_size: min_size = self.level.token_min if not max_size: max_size = self.level.token_max ret = [] i = 0 while i < len(input_string): j = max(random.SystemRandom().randint(i, i + max_size if max_size else len(input_string)), min_size) if i != j: ret.append(input_string[i:j]) i = j return ret def randomize_cases(self): var_pattern = re.compile(r'\$[\w|_]+', re.IGNORECASE) data_type_pattern1 = re.compile(r"(?<=New-Object ).+?(?=[\(\-\@])", re.IGNORECASE) data_type_pattern2 = re.compile(r"(?<=\[)([\w]+\.)+[\w]+?(?=\])", re.IGNORECASE) matches = [match.group().strip() for match in var_pattern.finditer(self.content)] dt_new_obj_matches = [match.group().strip() for match in data_type_pattern1.finditer(self.content)] dt_brackets_matches = [match.group().strip() for match in data_type_pattern2.finditer(self.content)] for match in dt_new_obj_matches: extract = None if match.lower().find("typename") > -1: regex = r"(?<=-TypeName).+?(?=\s)" pattern = re.compile(regex, re.IGNORECASE) raw = pattern.search(match + " ") if raw: extract = raw.group().strip() else: if self.debug: Console.fail_line(match) else: extract = match if extract: dt_brackets_matches.append(extract) # Console.warn_line(extract) matches += dt_brackets_matches matches = list(set(matches)) matches.sort(key=len) matches = matches[::-1] for match in matches: self.content = self.content.replace( match, self.case_randomize(match) ) def case_randomize(self, input_string: str, probability: float = None): if not probability: probability = self.probabilities["case_randomize"] ret = "" for s in input_string: if np.random.binomial(1, probability): ret += s.upper() else: ret += s.lower() return ret def convert_decimal(self): wrapper = ["iex", "exit"] w = [] if self.config["backticks"]: for wrap in wrapper: w.append(self.backticker(wrap)) else: w = wrapper self.content = f"{w[0]}(-join(({','.join([str(int(b)) for b in self.content.encode()])})|%{{[char]$_}}));{w[1]}" def convert_base64(self): wrapper = ["iex", "exit"] w = [] enc = "[System.Text.Encoding]::UTF8.GetString" conv = "[System.Convert]::FromBase64String" if self.config["cases"]: enc = self.case_randomize(enc) conv = self.case_randomize(conv) payload = base64.b64encode(self.content.encode()).decode() if self.config["backticks"]: for wrap in wrapper: w.append(self.backticker(wrap)) payload = self.backticker(payload) else: w = wrapper self.content = f"{w[0]}(" \ f"{enc}(" \ f"{conv}('{payload}')));{w[1]}" def replace_comments(self): # Get rid of <# ... #> comments text = self.content rows = [] _content = text.encode().decode(encoding="windows-1252", errors="replace") start = False matches = re.finditer(r"<#[^#]+#>", _content, re.MULTILINE) for _, match in enumerate(matches, start=1): _content = _content.replace(match.group(), self.placeholder) text = _content.split(self.eol) for nr, row in enumerate(text, start=1): if row.find("<#") > -1: start = True if row.find("#>") > -1: start = False if not start: rows.append(row) _content = self.eol.join(rows) # Single Line Comments slc_pattern = re.compile(r"#.+") s1_pattern = re.compile(r"\"([^\"]*)\"") s2_pattern = re.compile(r"'([^']*)'") for line in rows: match = slc_pattern.search(line) if match: # Single string, we don't do anything = won't break the script res = [s1_pattern.search(line), s2_pattern.search(line)] if any(res): res = [r.group() for r in res if r and r.group().find("#") > -1] else: res = [] if len(res) > 0: continue _content = _content.replace(match.group(), self.placeholder) self.content = _content def remove_comment_placeholders(self): while self.content.find(self.placeholder) >= 0: # Replace each occurrence with a new random string self.content = self.content.replace(self.placeholder, "") def insert_comments(self): while self.content.find(self.placeholder) >= 0: # Replace each occurrence with a new random string self.content = self.content.replace(self.placeholder, self.create_junk(prefix="#"), 1) def random_backtick(self): # Pattern 1 and 2 are still unsafe to use string_pattern1 = re.compile(r'"([^\"]+)"') string_pattern2 = re.compile(r"'([^\']+)'") function_pattern = re.compile(r'function\s+([\w\-\:]+)') _content = self.content.split(self.eol) for n, line in enumerate(_content, start=0): f = function_pattern.search(line) s1 = string_pattern1.search(line) s2 = string_pattern2.search(line) _t = 0 if f: _t = 1 repl = [p.groups()[0] for p in function_pattern.finditer(line)] elif s1 and False: repl = [p.groups()[0] for p in string_pattern1.finditer(line)] elif s2: repl = [p.groups()[0] for p in string_pattern2.finditer(line)] else: continue for match in repl: if _t == 1 and match.find(":") > -1: match = match.split(":")[1] if _t == 1 and match.find("(") > -1: match = match.split("(")[0] if match in self.dont_backtick: continue if _t < 1 or match.find("$") + line.find("[") + line.find("]") > -3 or line.count( match) > 1 or re.search(r"[\w]+", match) is None: continue _content[n] = line.replace(match, self.backticker(match)) self.content = self.eol.join(_content) def backticker(self, input_string: str, probability: float = None): if not probability: probability = self.probabilities["backticker"] ret = "" for char in input_string: backtick = '`' # % chance an input character will be backticked (default 75%) if np.random.binomial(1, probability): backtick = '' if char in "a0befnrtuxv": backtick = '' ret += f"{backtick}{char}" return ret def custom_backticker(self, strings: list): for s in strings: rs = s # If case randomization is enabled, perform it before the backtick is applied if self.case_randomization: rs = self.case_randomize(s) self.content = self.content.replace(s, self.backticker(rs)) def nishang_script(self): for pattern in self.nishang_patterns: pattern = re.compile(pattern, re.IGNORECASE) self.content = pattern.sub("", self.content) def indentation_randomization(self): lines = self.content.split(self.eol) space_pattern = re.compile(r"^\s+") for n, line in enumerate(lines, start=0): new_line = space_pattern.sub(" " * np.random.randint(0, 10), line) lines[n] = new_line self.content = self.eol.join(lines) def safety_check(self, target): clean = True matches = [] for regex in [rf"[\w\.]{target}", rf"{target}[\w\.]"]: pattern = re.compile(regex, re.IGNORECASE) matches = [match.group().strip() for match in pattern.finditer(self.content)] if len(matches) > 0: clean = False return clean def transformer(self, target_patterns=None, regex=None, strict=True): if self.debug: print() if not target_patterns: target_patterns = self.default_patterns mapping = {} pattern = re.compile(regex, re.IGNORECASE) matches = [match.group().strip() for match in pattern.finditer(self.content)] matches = list(set(matches)) for match in matches: red_flag = "" if strict and match.lower() not in target_patterns: if self.debug: Console.warn_line(f"[D] {match} is not a red flag") continue elif not strict: for rf in target_patterns: if not re.search(rf"[\s]+{rf}", match, re.IGNORECASE): continue if self.debug: Console.warn_line(f"[+] {rf} found in {match}") red_flag = rf break else: red_flag = match if red_flag == "": if self.debug: Console.warn_line(f"[D] {match} is not a red flag") continue if not self.safety_check(red_flag): continue if self.debug: Console.warn_line(f"[+] Red flag {red_flag} found") green_flag = self.random_variable() tokens = self.tokenize(red_flag) for token in tokens: if self.config["backticks"]: token = self.backticker(token) mapping[self.random_variable()] = f'"{token}"' mapping[green_flag] = f"({' + '.join(mapping.keys())})" self.content = self.content.replace(red_flag, green_flag) raw = self.eol.join([f"{k} = {v}{self.eol}" for k, v in mapping.items()]) self.content = raw + self.content def replace_strings(self, targets_strings=None): if not targets_strings: targets_strings = self.default_patterns regex = r"(?<=').+?(?=')" self.transformer(target_patterns=targets_strings, regex=regex, strict=False) regex = r'(?<=").+?(?=")' self.transformer(target_patterns=targets_strings, regex=regex, strict=False) regex = r'(?<=\.)[\w]+?(?=\()' self.transformer(target_patterns=targets_strings, regex=regex, strict=True) def replace_functions(self): function_pattern = re.compile(r'function\s+([\w|\_|\-]+)') matches = [match.groups()[0] for match in function_pattern.finditer(self.content)] matches.sort(key=len) matches = matches[::-1] for match in matches: if match in self.function_mapping.keys(): repl = self.function_mapping[match]['repl'] else: continue if match in "".join(["function", "filter"]): if self.function_mapping: self.function_mapping.pop(match) continue if not self.safety_check(match): if self.function_mapping: self.function_mapping.pop(match) continue self.content = self.content.replace( match, repl ) def replace_variables(self): special_vars = { "$null": self.random_variable(), "$true": self.random_variable(), "$false": self.random_variable(), "$args": self.random_variable(), "$_": self.random_variable() } if self.config["tfn-values"]: # Without AST parsing, it's kinda difficult to fix variable scoping issues # with TFN, the values $true, $false and $null are replaced at global and "maybe" # in-function scope # Issue 1: $true, $false and $null should be declared with global scope for k, v in special_vars.items(): # Self is untouchable if k == "$_": continue # Args is untouchable if k == "$args": continue self.content = self.content.replace(k, v) self.content = f"{v} = {k}\n{self.content}" # Issue 2: $true, $false and $null should be declared at function scope function_pattern = re.compile(r'function\s+[\w|\_|\-]+\s*\{', re.MULTILINE) for match in function_pattern.finditer(self.content): self.content = self.content.replace( match.group(), match.group() + "\n" + "\n".join([f"{v} = {k}" for k, v in special_vars.items()]) ) var_pattern = re.compile(r'\$[\w|_]+') matches = [match.group() for match in var_pattern.finditer(self.content)] matches = list(set(matches)) matches.sort(key=len) matches = matches[::-1] for match in matches: # if not self.safety_check(matches): # continue if match.strip().lower() in self.scoped_variables or match.strip().lower() in "".join( self.scoped_variables): continue elif match.strip().lower() in special_vars.keys() or re.search(r"^\$env", match, re.IGNORECASE): continue else: self.content = self.content.replace(match, self.random_variable()) def replace_data_types(self, target_data_types=None): if not target_data_types: target_data_types = self.default_type_patterns # regex = r"(?<=\[).+?(?=\])" # self.transformer(target_patterns=target_data_types, regex=regex) regex = r"(?<=New-Object )[^\-]+?(?=[\(\-\@\)])" self.transformer(target_patterns=target_data_types, regex=regex) regex = r"(?<=-TypeName).+?(?=\s)" self.transformer(target_patterns=target_data_types, regex=regex) def create_word(self): ret = "" if self.config["random-type"] != "r": ret += str(secrets.choice(self.dictionary)).strip(" " + self.eol).capitalize() else: ret = self.random_ascii_string(3, 15) return ret def create_random_word(self, min_size=None, max_size=None): ret = "" size = np.random.randint(min_size, max_size) while len(ret) < size: ret += str(secrets.choice(self.dictionary)).capitalize().strip() return ret def create_junk(self, prefix="#"): junk_text = f"{prefix}" junk_size = np.random.randint(self.level.junk_min, self.level.junk_max) current_line_length = 0 while len(junk_text) <= junk_size: next_word = " " + self.create_word() if current_line_length <= 80: junk_text += next_word current_line_length += len(next_word) else: junk_text += f"{self.eol}{prefix}{' ' * np.random.randint(0, 5)}{next_word}" current_line_length = 0 return junk_text def hex_address(self): ip_regex = r"(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])" ip_pattern = re.compile(ip_regex) matches = ip_pattern.finditer(self.content) for _, match in enumerate(matches, start=1): # convert `192.168.56.101` to `0xC0A83865` hexified = hex(int(ipaddress.IPv4Address(match.group()))) self.content = self.content.replace(match.group(), hexified) def identify_reflective_constructors(self): content = self.content.split(self.eol) var_pattern = re.compile(r'\$[\w|_]+') all_vars = [] for n, line in enumerate(content, start=0): if line.lower().find("customattribute") > -1: ob = line[line.lower().find("customattribute"):].count("(") offset = 1 search_area = line while ob != 0 or offset >= len(content): search_area += f" {content[n + offset]}" offset += 1 ob = search_area.count("(") - search_area.count(")") _vars = [var.group().strip().lower() for var in var_pattern.finditer(search_area)] all_vars += _vars all_vars = list(set(all_vars)) if self.debug: print(f"[+] Adding {all_vars}") self.scoped_variables += all_vars def identify_scoped_variables(self): tree = PSTree( psctx=PSContext(name="main", ctx_type=PSContextType.MAIN) ) mapping = {} function = "" content = self.content.split(self.eol) for n, line in enumerate(content, start=1): state_changed = False see_next = False tree.current.current_line = n function_match = None param_match = None ob = 0 cb = 0 tree.add_content(line) if tree.current_ctx_type in [PSContextType.MAIN, PSContextType.FUNCTION, PSContextType.NESTED_FUNCTION]: function_match = re.search(r"(filter|function)\s+([\w][^\s]+)[\{\s]?", line, re.IGNORECASE) param_match = re.search(r"param[\s|\n|\r|\(]*\(?", line, re.IGNORECASE) if function_match: function = function_match.groups()[1].split("(")[0] if function.find(":") > -1: function = function.split(":")[1] if tree.current_ctx_type == PSContextType.MAIN: if self.debug: print(f"Found new function {function} at line: {n}") tree.change_context(ctx_name=function, ctx_type=PSContextType.FUNCTION) see_next = True elif tree.current_ctx_type == PSContextType.FUNCTION: if self.debug: print(f"Found new nested function {function} at line: {n}") tree.change_context(ctx_name=function, ctx_type=PSContextType.NESTED_FUNCTION) see_next = True ob = line.count("{") if ob == 0: see_next = True else: if self.debug: print(f"Function starts at line: {n}") cb = line.count("}") elif param_match: tree.change_context(ctx_name=tree.current.name + "-param", ctx_type=PSContextType.PARAMS) ob = line.count("(") cb = line.count(")") if ob == 0: see_next = True else: if self.debug: print(f"Param() start at line: {n}") else: ob = line.count("{") cb = line.count("}") elif tree.current_ctx_type == PSContextType.PARAMS: ob = line.count("(") cb = line.count(")") if ob == 0 and cb == 0: see_next = True else: continue tree.open_brackets(nb=ob) tree.close_brackets(nb=cb) if tree.balanced and not see_next: if tree.current_ctx_type == PSContextType.PARAMS: params = tree.extract_data() if self.debug: print(f" > Found parameters: {params}") mapping[function] = params self.scoped_variables += params # Close parameters context if self.debug: print(f"Param() close at line {n}") tree.close() elif tree.current_ctx_type == PSContextType.NESTED_FUNCTION: # Close function context if self.debug: print(f"Nested function closes at line {n}") tree.close() elif tree.current_ctx_type == PSContextType.FUNCTION: # Close function context if self.debug: print(f"Function closes at line {n}") tree.close() if not self.function_mapping: Console.auto(" [>] Generating function mapping... ", quiet=self.quiet) self.generate_mapping(mapping) self.save_mapping() Console.auto_line("Success", quiet=self.quiet) self.clean_scoped_variables() def generate_mapping(self, mapping, scope="function"): new_mapping = {} for k, v in mapping.items(): new_params = {} new_params["original"] = v new_params["repl"] = [Chameleon.scramble(param) if scope != "function" else param for param in v] new_mapping[k] = { "repl": self.random_ascii_string( min_size=self.level.function_min, max_size=self.level.function_max ) if scope else k, "params": new_params } # Updating the global mapping self.function_mapping = new_mapping # Forcing a default file name self.function_mapping_file = "function_mapping.json" def clean_scoped_variables(self): self.scoped_variables = list(set(self.scoped_variables)) def obfuscate(self): Console.auto(" [*] Zeroing out comments... ", quiet=self.quiet) self.replace_comments() Console.auto_line("Done", quiet=self.quiet) Console.auto_line("[+] Chameleon: standard obfuscation", quiet=self.quiet) Console.auto_line(" [*] Identifying scoped variables and reflective constructors", quiet=self.quiet) if self.config["safe"]: self.identify_reflective_constructors() self.identify_scoped_variables() if len(self.scoped_variables) > 0: if self.config["verbose"]: Console.auto_line(" [>] These variables will not be obfuscated", quiet=self.quiet) Console.auto_line(f" [>] {', '.join(self.scoped_variables)}", quiet=self.quiet) else: Console.auto_line(f" [>] Identified {len(self.scoped_variables)} scoped variables " f"which will not be obfuscated", quiet=self.quiet) else: Console.auto_line(" [-] No variables found", quiet=self.quiet) if self.config["variables"]: Console.auto(" [*] Variables Obfuscation... ", quiet=self.quiet) self.replace_variables() Console.auto_line("Done", quiet=self.quiet) if self.config["data-types"]: Console.auto(" [*] Data Types Obfuscation... ", quiet=self.quiet) self.replace_data_types() Console.auto_line("Done", quiet=self.quiet) if self.config["functions"]: Console.auto(" [*] Function Obfuscation... ", quiet=self.quiet) self.replace_functions() Console.auto_line("Done", quiet=self.quiet) if self.config["nishang"]: Console.auto(" [*] Nishang Obfuscation... ", quiet=self.quiet) self.nishang_script() Console.auto_line("Done", quiet=self.quiet) if self.config["cases"]: Console.auto(" [*] Cases randomization... ", quiet=self.quiet) self.randomize_cases() Console.auto_line("Done", quiet=self.quiet) if self.config["hex-ip"]: Console.auto(" [*] IP Address to Hex... ", quiet=self.quiet) self.hex_address() Console.auto_line("Done", quiet=self.quiet) if self.config["comments"]: Console.auto(" [*] Comments Obfuscation... ", quiet=self.quiet) self.insert_comments() Console.auto_line("Done", quiet=self.quiet) else: Console.auto(" [*] Removing comment placeholders... ", quiet=self.quiet) self.remove_comment_placeholders() Console.auto_line("Done", quiet=self.quiet) if self.config["spaces"]: Console.auto(" [*] Indentation Randomization... ", quiet=self.quiet) self.indentation_randomization() Console.auto_line("Done", quiet=self.quiet) if self.config["strings"]: Console.auto(" [*] Strings Obfuscation... ", quiet=self.quiet) self.replace_strings() Console.auto_line("Done", quiet=self.quiet) if self.config["random-backticks"]: Console.auto(" [*] Random Backticking... ", quiet=self.quiet) self.random_backtick() Console.auto_line("Done", quiet=self.quiet) Console.auto_line("[+] Chameleon: obfuscation via encoding", quiet=self.quiet) if self.config["decimal"]: Console.auto(" [*] Converting to decimal... ", quiet=self.quiet) self.convert_decimal() Console.auto_line("Done", quiet=self.quiet) if self.config["base64"]: Console.auto(" [*] Converting to base64... ", quiet=self.quiet) self.convert_base64() Console.auto_line("Done", quiet=self.quiet) def write_file(self): Console.auto(f" [*] Writing obfuscated payload to {self.outfile}... ", quiet=self.quiet) with open(self.outfile, "w") as out: out.write(self.content) Console.auto_line("Done", quiet=self.quiet) # print(self.content) class Console: @staticmethod def write(what, color=Fore.WHITE): index = what.find("]") if index > -1: what = f"{color}{what[:index + 1]}{Fore.WHITE}{what[index + 1:]}" else: what = f"{color}{what}{Fore.WHITE}" print(what, end='') @staticmethod def write_line(what, color=Fore.WHITE): index = what.find("]") if index > -1: what = f"{color}{what[:index + 1]}{Fore.WHITE}{what[index + 1:]}{Fore.WHITE}" else: what = f"{color}{what}{Fore.WHITE}" print(what) @staticmethod def success(what): Console.write(what=what, color=Fore.GREEN) @staticmethod def success_line(what): Console.write_line(what=what, color=Fore.GREEN) @staticmethod def fail(what): Console.write(what=what, color=Fore.RED) @staticmethod def fail_line(what): Console.write_line(what=what, color=Fore.RED) @staticmethod def info(what): Console.write(what=what, color=Fore.BLUE) @staticmethod def info_line(what): Console.write_line(what=what, color=Fore.BLUE) @staticmethod def progress(what): Console.write(what=what, color=Fore.CYAN) @staticmethod def progress_line(what): Console.write_line(what=what, color=Fore.CYAN) @staticmethod def warn(what): Console.write(what=what, color=Fore.YELLOW) @staticmethod def warn_line(what): Console.write_line(what=what, color=Fore.YELLOW) @staticmethod def auto(what, quiet=False): if quiet: return if what.find("[+]") > -1: Console.success(what=what) elif what.find("[*]") > -1: Console.info(what=what) elif what.find("[>]") > -1: Console.progress(what=what) elif what.find("[#]") > -1: Console.warn(what=what) elif what.find("[-]") > -1: Console.fail(what=what) elif what == "Success" or what == "Done": Console.success(what=what) elif what == "Fail": Console.fail(what=what) else: Console.write(what=what) @staticmethod def auto_line(what, quiet=False): if quiet: return if what.find("[+]") > -1: Console.success_line(what=what) elif what.find("[*]") > -1: Console.info_line(what=what) elif what.find("[>]") > -1: Console.progress_line(what=what) elif what.find("[#]") > -1: Console.warn_line(what=what) elif what.find("[-]") > -1: Console.fail_line(what=what) elif what == "Success" or what == "Done": Console.write_line(what=what, color=Fore.LIGHTWHITE_EX) elif what == "Fail": Console.fail_line(what=what) else: Console.write_line(what=what) def welcome(): banner = rf"""{Fore.RED}__________________________________________________________________________________ {Fore.LIGHTRED_EX}▒▒▒▒▒▒ {Fore.RED}▒▒ ▒▒ {Fore.LIGHTBLUE_EX} ▒▒▒▒▒ {Fore.BLUE}▒▒▒ ▒▒▒ {Fore.LIGHTYELLOW_EX}▒▒▒▒▒▒▒ {Fore.YELLOW}▒▒ {Fore.LIGHTGREEN_EX}▒▒▒▒▒▒▒ {Fore.GREEN} ▒▒▒▒▒ {Fore.LIGHTCYAN_EX}▒▒▒ ▒▒ {Fore.CYAN} ▒▒▒ {Fore.LIGHTRED_EX}▒▒ {Fore.RED}▒▒ ▒▒ {Fore.LIGHTBLUE_EX}▒▒ ▒▒ {Fore.BLUE}▒▒▒▒ ▒▒▒▒ {Fore.LIGHTYELLOW_EX}▒▒ {Fore.YELLOW}▒▒ {Fore.LIGHTGREEN_EX}▒▒ {Fore.GREEN}▒▒ ▒▒ {Fore.LIGHTCYAN_EX}▒▒▒▒ ▒▒ {Fore.CYAN}▒▒▒▒ {Fore.LIGHTRED_EX}▓▓ {Fore.RED}▓▓▓▓▓▓▓ {Fore.LIGHTBLUE_EX}▓▓▓▓▓▓▓ {Fore.BLUE}▓▓ ▓▓▓▓ ▓▓ {Fore.LIGHTYELLOW_EX}▓▓▓▓▓ {Fore.YELLOW}▓▓ {Fore.LIGHTGREEN_EX}▓▓▓▓▓ {Fore.GREEN}▓▓ ▓▓ {Fore.LIGHTCYAN_EX}▓▓ ▓▓▓▓ {Fore.CYAN} ▓▓ {Fore.LIGHTRED_EX}██ {Fore.RED}██ ██ {Fore.LIGHTBLUE_EX}██ ██ {Fore.BLUE}██ ██ ██ {Fore.LIGHTYELLOW_EX}██ {Fore.YELLOW}██ {Fore.LIGHTGREEN_EX}██ {Fore.GREEN}██ ██ {Fore.LIGHTCYAN_EX}██ ███ {Fore.CYAN} ██ {Fore.LIGHTRED_EX}██████ {Fore.RED}██ ██ {Fore.LIGHTBLUE_EX}██ ██ {Fore.BLUE}██ ██ {Fore.LIGHTYELLOW_EX}███████ {Fore.YELLOW}██████ {Fore.LIGHTGREEN_EX}███████ {Fore.GREEN} █████ {Fore.LIGHTCYAN_EX}██ ██ {Fore.CYAN} ██ {Fore.RED}----------------------------------------------------------------------------------{Fore.LIGHTWHITE_EX} ▒ by d3adc0de (@klezVirus) {Fore.RED}__________________________________________________________________________________{Fore.WHITE} """ # mind-blowing banner rendering # os.system('color') for n, line in enumerate(banner.split("\n"), start=0): for char in line: print(char, end='', flush=True) # time.sleep(0.0001) if n < len(banner.split("\n")) - 1: print() time.sleep(0.1) def author(): info = { "whoami": "d3adc0de (@klezVirus)", "groups d3adc0de": "d3adc0de : authors", "echo $HOME": "https://github.com/klezVirus", "credits": "@tokyoneon_" } # mind-blowing banner rendering os.system('color') print(Fore.LIGHTBLACK_EX + "▩" * 82) for cmd, out in info.items(): print(f"{Fore.WHITE}▒ {Fore.LIGHTGREEN_EX}$ ", end='', flush=True) time.sleep(0.5) for char in cmd: print(char, end='', flush=True) time.sleep(0.1) print() print(f"{Fore.WHITE}▒ ", end='', flush=True) time.sleep(0.5) print(f"{Fore.LIGHTCYAN_EX}{out}{Fore.WHITE}") time.sleep(0.3) print(Fore.LIGHTBLACK_EX + "▩" * 82 + Fore.WHITE) time.sleep(1) if __name__ == '__main__': parser = argparse.ArgumentParser( description='Chameleon - PowerShell script obfuscator (Improved Python port of Chimera)' ) parser.add_argument( '-l', '--level', required=False, type=int, choices=list(range(6)), default=0, help='String manipulation Level (1: MIN, 5: MAX, 0: RANDOM)') parser.add_argument( '-o', '--output', required=True, type=str, default=None, help='Store the payload in a file') parser.add_argument( '-v', '--variables', required=False, action="store_true", help='Enable variable obfuscation') parser.add_argument( '-s', '--strings', required=False, action="store_true", help='Enable string obfuscation') parser.add_argument( '-d', '--data-types', required=False, action="store_true", help='Enable data types obfuscation') parser.add_argument( '-n', '--nishang', required=False, action="store_true", help='Enable Nishang scripts obfuscation') parser.add_argument( '-c', '--comments', required=False, action="store_true", help='Enable comments obfuscation') parser.add_argument( '-f', '--functions', required=False, action="store_true", help='Enable functions obfuscation') parser.add_argument( '-b', '--use-backticks', required=False, action="store_true", help='Enable use of backticks with generated strings') parser.add_argument( '--random-backticks', required=False, action="store_true", help='Enable use of backticks randomization') parser.add_argument( '-r', '--random-cases', required=False, action="store_true", help='Enable upper/lower randomization') parser.add_argument( '-i', '--random-spaces', required=False, action="store_true", help='Enable indentation randomization') parser.add_argument( '-x', '--hex-ip', required=False, action="store_true", help='Enable indentation randomization') parser.add_argument( '-j', '--true-false-null', required=False, action="store_true", help='Try and obfuscate $true, $false and $null (experimental)') parser.add_argument( '-a', '--enable-all', required=False, action="store_true", help='Enable all obfuscation types') parser.add_argument( '--decimal', required=False, action="store_true", help='Convert obfuscated payload to decimal format') parser.add_argument( '--base64', required=False, action="store_true", help='Convert obfuscated payload to base64 format') parser.add_argument( '-z', '--check', required=False, action="store_true", help='Check the script against AMSI Trigger (@RythmStick, @rasta-mouse)') parser.add_argument( '-F', '--function-mapping', required=False, type=str, help='Add custom keywords to obfuscate') parser.add_argument( '-K', '--keywords', required=False, action="append", help='Add custom keywords to obfuscate') parser.add_argument( '-B', '--backticks', required=False, action="append", help='Add a list of words to backtick') parser.add_argument( '-t', '--randomization-type', required=False, type=str, choices=['r', 'd', 'h'], default='r', help='Type of randomization (r: Random, d: Dictionary, h: Hybrid)') parser.add_argument( '--safe', required=False, action="store_true", help='Reduce obfuscation of certain variables') parser.add_argument( '--verbose', required=False, action="store_true", help='Enable verbose output') parser.add_argument( '--about', required=False, action="store_true", help='Shows additional information about the tool') parser.add_argument( 'target', default=None, help='Script to obfuscate') try: sys.argv.index("--about") welcome() author() if len(sys.argv) == 2: sys.exit(0) except ValueError: pass args = parser.parse_args() welcome() level = args.level config = { "strings": args.strings or args.enable_all, "variables": args.variables or args.enable_all, "data-types": args.data_types or args.enable_all, "functions": args.functions or args.enable_all, "comments": args.comments or args.enable_all, "spaces": args.random_spaces or args.enable_all, "cases": args.random_cases or args.enable_all, "nishang": args.nishang or args.enable_all, "backticks": args.use_backticks or args.enable_all, "random-backticks": args.random_backticks, "backticks-list": args.backticks, "hex-ip": args.hex_ip or args.enable_all, "random-type": args.randomization_type.lower(), "decimal": args.decimal, "base64": args.base64, "tfn-values": args.true_false_null, "safe": args.safe, "verbose": args.verbose } chameleon = Chameleon(filename=args.target, outfile=args.output, config=config, fmap=args.function_mapping) Console.auto_line(f"[+] Starting obfuscation at {datetime.utcnow()}") chameleon.obfuscate() chameleon.write_file() if args.check: Console.auto_line(" [#] Checking file against AMSI Trigger...") amsi = AMSITrigger() amsi.check(args.output) Console.auto_line(f"[+] Ended obfuscation at {datetime.utcnow()}\n")