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,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)