283 lines
9.0 KiB
Python
283 lines
9.0 KiB
Python
#!/usr/bin/env python3
|
|
|
|
'''
|
|
|
|
PyFuscation.py
|
|
This python3 script obfuscates powershell function, variable and parameters in an attempt to bypass AV blacklists
|
|
|
|
'''
|
|
|
|
import re
|
|
import os
|
|
import sys
|
|
import ast
|
|
import time
|
|
import shutil
|
|
import random
|
|
import subprocess
|
|
import shlex
|
|
import string
|
|
from argparse import ArgumentParser
|
|
import configparser
|
|
import fileinput
|
|
|
|
def printR(out): print("\033[91m{}\033[00m" .format("[!] " + out))
|
|
def printG(out): print("\033[92m{}\033[00m" .format("[*] " + out))
|
|
def printY(out): print("\033[93m{}\033[00m" .format("[+] " + out))
|
|
def printP(out): print("\033[95m{}\033[00m" .format("[-] " + out))
|
|
|
|
def realTimeMuxER(command):
|
|
p = subprocess.Popen(shlex.split(command), stdout=subprocess.PIPE)
|
|
while True:
|
|
output = p.stdout.readline().decode()
|
|
if output == '' and p.poll() is not None:
|
|
break
|
|
if output:
|
|
print(output.strip())
|
|
rc = p.poll()
|
|
|
|
def removeJunk(oF):
|
|
# general cleanup
|
|
cmd = "sed -i -e \'/<#/,/#>/c\\\\\' " + "'" + oF + "'"
|
|
realTimeMuxER(cmd)
|
|
cmd = "sed -i -e \'s/^[[:space:]]*#.*$//g\' " + "'" + oF + "'"
|
|
realTimeMuxER(cmd)
|
|
cmd = "sed -i \'/^$/d\' " + "'" + oF + "'"
|
|
realTimeMuxER(cmd)
|
|
|
|
def useSED(DICT, oF):
|
|
for var in DICT:
|
|
new = str(DICT.get(var))
|
|
cmd = "sed -i -e \'s/" + var +'\\b' + "/" + new + "/g\' " + "'" + oF + "'"
|
|
realTimeMuxER(cmd)
|
|
|
|
def THEreplacER(DICT, iF, oF):
|
|
iFHandle = open(iF, 'r')
|
|
ofHandle = open(oF, 'w')
|
|
regex = r'(\$\w{3,})'
|
|
lower_DICT = list(map(lambda x:x.lower(),DICT))
|
|
# For var replace with Dictionary value
|
|
for line in iFHandle:
|
|
v = re.findall(regex,line)
|
|
if not v:
|
|
#print("Not: " + line)
|
|
ofHandle.write(line + "\n")
|
|
ofHandle.flush()
|
|
else:
|
|
for var in v:
|
|
if var.lower() in lower_DICT:
|
|
new = str(DICT.get(var))
|
|
#print("Replacing " + var + " with " + new)
|
|
ofHandle.write(line.replace(var, new) + "\n")
|
|
ofHandle.flush()
|
|
else:
|
|
#print(var)
|
|
ofHandle.write(line + "\n")
|
|
ofHandle.flush()
|
|
|
|
iFHandle.close()
|
|
ofHandle.close()
|
|
|
|
def findCustomParams(iFile,oFile,VARs):
|
|
PARAMs = {}
|
|
READ = False
|
|
start = 0
|
|
end = 0
|
|
regex = r'([\$-]\w{4,})'
|
|
ofHandle = open(oFile, 'w')
|
|
|
|
with open(iFile, "r") as f:
|
|
for line in f:
|
|
line = line.strip()
|
|
|
|
if re.search(r'\bparam\b', line, re.I):
|
|
# Ok we are at the begining of a custum parameter
|
|
READ = True
|
|
|
|
# The open paren is on another line so move until we find it
|
|
start = start + line.count('(')
|
|
if start == 0:
|
|
continue
|
|
|
|
end = end + line.count(')')
|
|
|
|
v = re.findall(regex,line)
|
|
for i in v:
|
|
if i.lower() not in lower_Reserverd and i not in PARAMs:
|
|
# Lets check to see if this has been replaced already
|
|
new = VARs.get(i)
|
|
if not new:
|
|
continue
|
|
new = " -" + new[1:]
|
|
old = " -" + i[1:]
|
|
PARAMs[old] = new
|
|
ofHandle.write("Replacing: " + old + " with: " + new + "\n")
|
|
|
|
# If the params are all on one line were done here
|
|
if start != 0 and start == end:
|
|
start = 0
|
|
end = 0
|
|
READ = False
|
|
continue
|
|
|
|
# These are the custom parameters
|
|
elif READ:
|
|
v = re.findall(regex,line)
|
|
for i in v:
|
|
if i.lower() not in lower_Reserverd and i not in PARAMs:
|
|
new = VARs.get(i)
|
|
if not new:
|
|
continue
|
|
new = " -" + new[1:]
|
|
old = " -" + i[1:]
|
|
PARAMs[old] = new
|
|
ofHandle.write("Replacing: " + old + " with: " + new + "\n")
|
|
|
|
start = start + line.count('(')
|
|
end = end + line.count(')')
|
|
if start != 0 and start == end:
|
|
start = 0
|
|
end = 0
|
|
READ = False
|
|
|
|
# Keep moving until we have work
|
|
else:
|
|
continue
|
|
|
|
printY("Parameters Replaced : " + str(len(PARAMs)))
|
|
|
|
return PARAMs
|
|
|
|
def findVARs(iFile,lFile):
|
|
VARs = {}
|
|
vNum = 9999
|
|
regex = r'(\$\w{6,})'
|
|
ofHandle = open(lFile, 'w')
|
|
|
|
with open(iFile, "r") as f:
|
|
for line in f:
|
|
v = re.findall(regex,line)
|
|
for i in v:
|
|
if i in VARs:
|
|
continue
|
|
elif i.lower() not in lower_Reserverd:
|
|
# Powershell vars are case insensitive
|
|
lowerVARS = {k.lower(): v for k, v in VARs.items()}
|
|
if i.lower() in lowerVARS:
|
|
new = lowerVARS.get(i.lower())
|
|
ofHandle.write("Replacing: " + i + " with: " + new + "\n")
|
|
VARs[i] = new
|
|
else:
|
|
vNum = 99
|
|
new = "$" + ''.join([random.choice(string.ascii_letters) for n in range(8)])
|
|
VARs[i] = new + str(vNum)
|
|
ofHandle.write("Replacing: " + i + " with: " + new + "\n")
|
|
vNum += 1
|
|
|
|
# return dict of variable and their replacements
|
|
printY("Variables Replaced : " + str(len(VARs)))
|
|
return VARs
|
|
|
|
def findFUNCs(iFile,lFile):
|
|
|
|
FUNCs = {}
|
|
ofHandle = open(lFile, 'w')
|
|
with open(iFile, "r") as f:
|
|
for line in f:
|
|
funcMatch = re.search(r'^\s*Function ([a-zA-Z0-9_-]{6,})[\s\{]+$',line, re.IGNORECASE)
|
|
if funcMatch and funcMatch.group(1) not in FUNCs:
|
|
if funcMatch.group(1) == "main":
|
|
continue
|
|
vNum = 9999
|
|
new = randomString(wordList)
|
|
FUNCs[funcMatch.group(1)] = new
|
|
ofHandle.write("Replacing: " + funcMatch.group(1) + " with: " + str(new) + "\n")
|
|
vNum += 1
|
|
# return dict of variable and their replacements
|
|
printY("Functions Replaced : " + str(len(FUNCs)))
|
|
return FUNCs
|
|
|
|
def randomString(iFile):
|
|
with open(iFile, "r") as f:
|
|
line = next(f)
|
|
for num, aline in enumerate(f, 2):
|
|
if random.randrange(num): continue
|
|
line = aline
|
|
string = ''.join(e for e in line if e.isalnum())
|
|
return string
|
|
|
|
def main():
|
|
iFile = args.script
|
|
|
|
printR("Obfuscating: " + iFile)
|
|
oDir = os.getcwd() + "/Resources/PyFuscation/tmp"
|
|
os.mkdir( oDir );
|
|
oFile = oDir + "/" + "script.ps1"
|
|
vFile = oDir + "/" + "vFile.ps1"
|
|
fFile = oDir + "/" + "fFile.ps1"
|
|
pFile = oDir + "/" + "pFile.ps1"
|
|
shutil.copy(args.script, oFile)
|
|
|
|
obfuVAR = dict()
|
|
obfuPARMS = dict()
|
|
obfuFUNCs = dict()
|
|
|
|
# Remove White space and comments
|
|
removeJunk(oFile)
|
|
|
|
# Obfuscate Variables
|
|
if (args.var):
|
|
obfuVAR = findVARs(iFile,vFile)
|
|
useSED(obfuVAR, oFile)
|
|
|
|
# Obfuscate custom parameters
|
|
if (args.par):
|
|
obfuPARMS = findCustomParams(iFile, pFile, obfuVAR)
|
|
useSED(obfuPARMS, oFile)
|
|
|
|
# Obfuscate Functions
|
|
if (args.func):
|
|
obfuFUNCs = findFUNCs(iFile, fFile)
|
|
useSED(obfuFUNCs, oFile)
|
|
|
|
# Print the Functions
|
|
print("")
|
|
printP("Obfuscated Functions located : " + fFile)
|
|
|
|
if __name__ == "__main__":
|
|
|
|
if sys.version_info <= (3, 0):
|
|
sys.stdout.write("This script requires Python 3.x\n")
|
|
sys.exit(1)
|
|
|
|
config = configparser.ConfigParser()
|
|
parser = ArgumentParser()
|
|
parser.add_argument("-f", dest="func", help="Obfuscate functions", action="store_true")
|
|
parser.add_argument("-v", dest="var", help="Obfuscate variables", action="store_true")
|
|
parser.add_argument("-p", dest="par", help="Obfuscate parameters", action="store_true")
|
|
parser.add_argument("--ps", dest="script", help="Obfuscate powershell")
|
|
|
|
args = parser.parse_args()
|
|
|
|
# Powershell script
|
|
if (args.script is None):
|
|
parser.print_help()
|
|
exit()
|
|
else:
|
|
# Check if the input file is valid:
|
|
if not os.path.isfile(args.script):
|
|
printR("Check File: " + args.script)
|
|
exit()
|
|
else:
|
|
PSconfigFile = os.path.abspath(os.path.dirname(__file__)) + "/PSconfig.ini"
|
|
config.read(PSconfigFile)
|
|
Reseverd = ast.literal_eval(config.get("PS_Reserverd", "f"))
|
|
lower_Reserverd = list(map(lambda x:x.lower(),Reseverd))
|
|
|
|
wordList = os.path.abspath(os.path.dirname(__file__)) + "/Wordlist.txt"
|
|
if not os.path.isfile(wordList):
|
|
printR("Check wordList: " + wordList)
|
|
exit()
|
|
|
|
main()
|