first commit

This commit is contained in:
2025-08-07 20:40:38 +07:00
commit 8a4fae89df
29 changed files with 94019 additions and 0 deletions

View File

@@ -0,0 +1,136 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# JetBrain
.idea/
*.ps1
*.json
*.7z
!utils/AMSITrigger.exe
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
.python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 klezVirus
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,175 @@
:triangular_flag_on_post: This is the public repository of chameleon, for latest version and updates please consider supporting us through https://porchetta.industries/
# Chameleon
Chameleon is yet another PowerShell obfuscation tool designed to bypass AMSI and commercial antivirus solutions.
## :triangular_flag_on_post: Sponsors
If you want to sponsors this project and have the latest updates on chameleon, latest issues fixed, latest features, please support us on https://porchetta.industries/
## Official Discord Channel
Come hang out on Discord!
[![Porchetta Industries](https://discordapp.com/api/guilds/736724457258745996/widget.png?style=banner3)](https://discord.gg/ycGXUxy)
## Overview
The tool has been developed as a Python port of the [Chimera][1] project, by [tokioneon_][2]. As such, it uses
mostly the same techniques to evade common detection signatures, such as:
* comment deletion/substitution
* string substitution (variables, functions, data-types)
* variable concatenation
* indentation randomization
* semi-random backticks insertion
* case randomization
* encoding
## Why porting it
Chimera was indeed a shiny project, so why did I decided to port it to Python and why you should use chameleon?
Well, there are several reasons why I decided to build Chameleon. I wrote a more detailed post about them [here][7].
I've also listed below the most important ones.
##### Reliability
As the author of Chimera states in the readme, the chimera script can successfully obfuscate scripts that the author
tested personally, which are contained in the [shells][3] directory. However, the tool is not very reliable with other,
untested, scripts. Quoting the author:
> there's no telling how untested scripts will reproduce with Chimera...
This alone was a good reason to attempt to make the tool a bit more reliable, and also capable to obfuscate
more complex scripts.
##### Speed
Chimera attempts several obfuscation steps, which usually requires the input to be read from a file, and stored back
in a file again. While this is a safe approach, because each step is saved to disk (let's say there is an error at step
n, we would still have the result of the obfuscation till n - 1), this is not really efficient. The overhead of writing
and reading from a file at each time make the tool really slow when operating on large scripts (up to several minutes
with the -a option).
Chameleon, instead, performs all obfuscation steps in memory, meaning it is extremely faster.
##### Portability
Chimera has been developed as a Bash Script, and heavily relies on common Linux utilities to accomplish the obfuscation.
Chameleon, on the other hand, is built with Python, meaning that you can use it wherever Python is installed.
##### Smart evasion checking
Chimera offers a function to submit scripts to VirusTotal directly. While this might be considered a useful utility,
it will expose the obfuscated script to third party threat-intelligence, weakening the obfuscation engine.
To address this issue, Chameleon uses the utility [AMSITrigger][4] by [RhytmStick][5], to check if the obfuscated result will indeed
bypass AMSI.
### Improvements
So far, we've talked about the efficiency and reliability issues of chimera, but what are the real improvements
from an obfuscation standpoint? The techniques used by Chameleon are for the most the same as Chimera, with some improvements:
* "Smart" variable scope identification (function local variables will be replaced "carefully" or left untouched)
* Random backticks insertion (not just limited to a set of strings)
* Random case switch (not just limited to a set of strings)
* Supports an external obfuscation mapping for functions ~~and parameters~~ (TODO)
* Additional Base64 Encoding wrapping
Chameleon manages to handle function and local parameters by implementing a very minimalist PowerShell "reader", which is
capable of distinguish three contexts:
* Global/Main Scope
* In-Function Scope
* Param() Blocks
The reader is still not a real parser, and relies on Dick Language to find relevant areas limits.
### Usage
Using the tool is pretty straightforward, as observable from the help:
```
usage: chameleon.py [-h] [-l {0,1,2,3,4,5}] -o OUTPUT [-v] [-s] [-d] [-n] [-c] [-f] [-b] [--random-backticks] [-r] [-i] [-x] [-j] [-a] [--decimal] [--base64] [-z] [-F FUNCTION_MAPPING] [-K KEYWORDS] [-B BACKTICKS] [-t {r,d,h}] [--safe] [--verbose] [--about]
target
Chameleon - PowerShell script obfuscator (Improved Python port of Chimera)
positional arguments:
target Script to obfuscate
optional arguments:
-h, --help show this help message and exit
-l {0,1,2,3,4,5}, --level {0,1,2,3,4,5}
String manipulation Level (1: MIN, 5: MAX, 0: RANDOM)
-o OUTPUT, --output OUTPUT
Store the payload in a file
-v, --variables Enable variable obfuscation
-s, --strings Enable string obfuscation
-d, --data-types Enable data types obfuscation
-n, --nishang Enable Nishang scripts obfuscation
-c, --comments Enable comments obfuscation
-f, --functions Enable functions obfuscation
-b, --use-backticks Enable use of backticks with generated strings
--random-backticks Enable use of backticks randomization
-r, --random-cases Enable upper/lower randomization
-i, --random-spaces Enable indentation randomization
-x, --hex-ip Enable indentation randomization
-j, --true-false-null
Try and obfuscate $true, $false and $null (experimental)
-a, --enable-all Enable all obfuscation types
--decimal Convert obfuscated payload to decimal format
--base64 Convert obfuscated payload to base64 format
-z, --check Check the script against AMSI Trigger (@RythmStick, @rasta-mouse)
-F FUNCTION_MAPPING, --function-mapping FUNCTION_MAPPING
Add custom keywords to obfuscate
-K KEYWORDS, --keywords KEYWORDS
Add custom keywords to obfuscate
-B BACKTICKS, --backticks BACKTICKS
Add a list of words to backtick
-t {r,d,h}, --randomization-type {r,d,h}
Type of randomization (r: Random, d: Dictionary, h: Hybrid)
--safe Reduce obfuscation of certain variables
--verbose Enable verbose output
--about Shows additional information about the tool
```
### Notes
Worth saying that, even if now Chameleon is capable of obfuscate also complex scripts, it's still not comparable with
Invoke-Obfuscation, which actually is way more mature and is also backed-up by a fully fledged parser `Management.Automation.Language.Parser`.
### Next steps
Moreover, Chameleon is still not perfect and still needs further development to increase both its accuracy and improve
its obfuscation techniques. A non-exhaustive list of planned improvements are below:
* Upgrade the PowerShell reader
* Include other encoding schemes
* Add more obfuscation methods
## Contribute
If you want to contribute, just fork the repository. Any PR is well accepted.
## Credits
Worth saying that Chameleon would not be a thing without the work of [tokioneon_][2] on [Chimera][1], as the most of the
obfuscation process was ported from Bash to Python (of course with some mods).
## References
* [Chimera][1]
* [Invoke-Obfuscation][6]
* [AMSITrigger][4]
[1]: https://github.com/tokyoneon/Chimera.git
[2]: https://twitter.com/tokyoneon_
[3]: https://github.com/tokyoneon/Chimera/tree/master/shells
[4]: https://github.com/RythmStick/AMSITrigger
[5]: https://github.com/RythmStick
[6]: https://github.com/danielbohannon/Invoke-Obfuscation
[7]: https://klezvirus.github.io/RedTeaming/AV_Evasion/BornFromAChimera/

View File

@@ -0,0 +1 @@
# Created to allow using Chameleon as Library

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,17 @@
system.net.sockets.tcpclient
system.io.streamwriter
system.byte
system.text.asciiencoding
system.diagnostics.processstartinfo
system.diagnostics.process
system.text.asciiencoding
net.sockets.tcpclient
system.io.streamwriter
system.net.networkinformation.ping
psobject
net.webclient
system.net.httplistener
security.principal.windowsprincipal
system.net.ipendpoint
text.asciiencoding
io.streamwriter

View File

@@ -0,0 +1,8 @@
Write-Verbose[^\n\}\)\(\{\;]+
Write-Output[^\n\}\)\(\{\;]+
Write-Warning[^\n\}\)\(\{\;]+
,\s*Mandatory\s*=\s*\$true
,\s*Mandatory\s*=\s*\$false
Mandatory\s*=\s*\$true,
Mandatory\s*=\s*\$false,
validatepattern

View File

@@ -0,0 +1,32 @@
obfuscat
nishang
payload
virus
malware
hack
reverse
powershell
icmp
shell
backdoor
evil
elevate
privil
regsitry
script
SanityCheck
WindowsSanity
execut
scriptengine
hidden
bypass
regedit.exe
cmd.exe
powershell.exe
encode
iex
invoke-
getstream
new-object
getstring
getbytes

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,2 @@
colorama
numpy

View File

@@ -0,0 +1,245 @@
import json
import os
import secrets
import string
import sys
import re
from enum import Enum
import argparse
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()
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):
self.ctx.pop()
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)
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))])
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 replace_comments(text):
# Get rid of <# ... #> comments
_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(), "")
text = _content.split("\n")
rows = []
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 = "\n".join(rows)
# Single Line Comments
slc_pattern = re.compile(r"#.+")
matches = slc_pattern.finditer(_content)
for _, match in enumerate(matches, start=1):
_content = _content.replace(match.group(), "")
return _content
if __name__ == '__main__':
parser = argparse.ArgumentParser(
description='Chameleon PSMapper - Helper to create obfuscated function mappings'
)
parser.add_argument('-o', '--outfile', required=True, type=str, default=None, help='Output file')
parser.add_argument('target', help='Target PS1 script')
args = parser.parse_args()
if not os.path.isfile(args.target):
print(f"[-] File {args.target} not found")
sys.exit(1)
with open(args.target, "r") as ps:
content = ps.read()
content = replace_comments(content)
content = content.split("\n")
tree = PSTree(
psctx=PSContext(name="main", ctx_type=PSContextType.MAIN)
)
mapping = {}
function = ""
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:
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:
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:
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:
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()
print(f" > Found parameters: {params}")
mapping[function] = params
# Close parameters context
print(f"Param() close at line {n}")
tree.close()
elif tree.current_ctx_type == PSContextType.NESTED_FUNCTION:
# Close function context
print(f"Nested function closes at line {n}")
tree.close()
elif tree.current_ctx_type == PSContextType.FUNCTION:
# Close function context
print(f"Function closes at line {n}")
tree.close()
new_mapping = {}
for k, v in mapping.items():
new_params = {}
new_params["original"] = v
new_params["repl"] = [scramble(param) for param in v]
new_mapping[k] = {
"repl": scramble(k),
"params": new_params
}
with open(args.outfile, 'w') as outfile:
json.dump(new_mapping, outfile)
json_formatted = json.dumps(new_mapping, indent=2)
# print(json_formatted)

View File

@@ -0,0 +1,49 @@
import json
import os
import sys
import re
import argparse
from colorama import Fore
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description='Chameleon PSTranslate - Helper to search obfuscated functions'
)
parser.add_argument('-f', '--function', required=True, type=str, default=None, help='Function to search')
parser.add_argument('mapping', help='Mapping file')
args = parser.parse_args()
os.system('color')
if not os.path.isfile(args.mapping):
print("[-] Mapping file not found")
sys.exit(1)
with open(args.mapping, "r") as ps:
mapping = json.load(ps)
function = args.function
for k in mapping.keys():
if re.search(function, k, re.IGNORECASE):
rainbow = [Fore.LIGHTBLUE_EX,
Fore.LIGHTGREEN_EX,
Fore.LIGHTRED_EX,
Fore.LIGHTCYAN_EX,
Fore.LIGHTMAGENTA_EX,
Fore.LIGHTYELLOW_EX
]
op = mapping[k]['params']['original']
np = mapping[k]['params']['repl']
orig_params = ", ".join([f"{rainbow[j%5]}{op[j]}{Fore.WHITE}" for j in range(len(op))])
new_params = ", ".join([f"{rainbow[j%5]}{np[j]}{Fore.WHITE}"for j in range(len(np))])
print(f"[+] Found func : {rainbow[5]}{k}{Fore.WHITE}")
print(f" [>] Replaced by: {rainbow[5]}{mapping[k]['repl']}{Fore.WHITE}")
print(f" [>] Original params: {orig_params}")
print(f" [>] Obfuscated with: {new_params}")
print()