Created
January 18, 2019 18:32
-
-
Save GarnetSunset/01c71be4a7047d4720f01c7ac2be3551 to your computer and use it in GitHub Desktop.
Powershell Deobfuscator in Python, mixing multiple different methods together.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#Created by Max 'Libra' Kersten (@LibraAnalysis) | |
#Additions from lasq88's research and code. | |
#QoL changes and combination by GarnetSunset | |
import re,sys,zlib,base64,argparse | |
obfuscation_methods = [ | |
('replace','Replace String','replace\s?\('), | |
('decompress','Compress string','::decompress'), | |
('split','ASCII table with split','-split'), | |
('ascii','ASCII table','\((?:\s?[0-9]{2,3}\s?,?){5,}\)') | |
] | |
if sys.version_info < (3, 0): | |
sys.stdout.write("Sorry, requires Python 3.x, not Python 2.x\n") | |
sys.exit(1) | |
def FindString(str,regx): | |
match = re.compile(regx,flags=re.I) | |
search = re.findall(match,str) | |
return search | |
def StrChar(matchobj): | |
return "'"+chr(int(matchobj.group(1)))+"'" | |
def FindReplaces(text,replacements): | |
match = re.compile("replace\s*(\(|\(\(|)\\\\?'([a-zA-Z0-9]{2,4})\\\\?'(\)|),\\\\?'(.{1})\\\\?'(\)|)",flags=re.I) | |
search = re.findall(match,text) | |
if (search == []): | |
return 0 | |
else: | |
for i in search: | |
replacements.add((i[1],i[3])) | |
return replacements | |
def DeobfReplace(text): | |
replacements = set() | |
while True: | |
text = re.sub("'\+'|''\+|\+''","",text) | |
text = re.sub('\[char\]([0-9]{2,3})',StrChar,text,flags=re.I) | |
text = re.sub("'\+'|''\+|\+''","",text) | |
text = re.sub('\[string\]','',text,flags=re.I) | |
if(FindReplaces(text, replacements) == 0): | |
return text | |
else: | |
replacements = FindReplaces(text, replacements) | |
for i in replacements: | |
text = re.sub("'\+'|''\+|\+''","",text) | |
text = text.replace(i[0],i[1]) | |
def Detect(text): | |
for (_, method, regx) in obfuscation_methods: | |
match = re.compile(regx,flags=re.I) | |
search = re.findall(match,text) | |
if (search != []): | |
return method | |
if (search == []): | |
return -1 | |
def Deobfuscate(text,method): | |
try: | |
if (method == "Replace String"): | |
return DeobfReplace(text) | |
elif (method == "Compress string"): | |
compressed = FindString(text,"frombase64string\(\s?'(\S*)'")[0] | |
return zlib.decompress(base64.b64decode(compressed),-zlib.MAX_WBITS) | |
elif (method == "ASCII table with split"): | |
split_by = '|'.join(FindString(text,"split\s?'(\S)'")) | |
ascii_table = re.split(split_by,FindString(text,"\('(\S*)'")[0]) | |
return ''.join(chr(int(i)) for i in ascii_table) | |
elif (method == "ASCII table"): | |
ascii_table = re.split(',',FindString(text,'\(((?:\s?[0-9]{2,3}\s?,?){5,})\)')[0]) | |
return ''.join(chr(int(i)) for i in ascii_table) | |
except: | |
print("Unfortunately deobfuscation failed. Maybe you provided wrong method or there is no method to deobfuscate this code.") | |
return -1 | |
#Define information regarding the original script's location | |
powershellPath = input('Powershell script file with extension: ') | |
powershellFile = open(powershellPath,'r') | |
#Read all lines of the original script | |
powershellContent = powershellFile.readlines() | |
#The variable which contains all deobfuscated lines | |
output = '' | |
#The variable which keeps track of the amount of string formats that have been replaced | |
formatCount = 0 | |
#The variable which keeps track of the amount of variables that have been replaced | |
variableCount = 0 | |
#The variable which keeps track of the amount of removed back ticks | |
backtickCount = 0 | |
#Loop through the file, line by line | |
for line in powershellContent: | |
detected = Detect(line) | |
if (detected != -1): | |
line = Deobfuscate(line,detected) | |
else: | |
backtickCount += line.count("`") | |
#Replace the back tick with nothing to remove the needless back ticks | |
line = line.replace("`", "") | |
#Match the string formatting | |
matchedLine = re.findall(""""((?:\{\d+\})+)"\s*-[fF]\s*((?:'.*?',?)+)""", line) | |
#If one or more matches have been found, continue. Otherwise skip the replacement part | |
if len(matchedLine) > 0: | |
#Each match in each line is broken down into two parts: the indices part ("{0}{2}{1}") and the strings ("var", "ble", "ia") | |
for match in matchedLine: | |
#Convert all indices to integers within a list | |
indices = list(map(int, re.findall("{(\d+)}", match[0]))) | |
#All strings are saved in an array | |
strings = re.findall("'([^']+?)'", match[1]) | |
#The result is the correctly formatted string | |
result = "".join([strings[i] for i in indices]) | |
#The current line is altered based on the found match, with which it is replaced | |
line = line.replace(match[0], result, 1) | |
line = line.replace(match[1], "", 1) | |
#Regex the "-f" and "-F" so that "-f[something]" is not replaced | |
formatFlag = re.findall("""(-[fF])(?=[^\w])""", line) | |
if len(formatFlag) > 0: | |
for formatFlagMatch in formatFlag: | |
line = line.replace(formatFlagMatch, "") | |
#Find all strings between quotation marks. | |
varDeclaration = re.findall("""(?<=\()\"(?=[^\)]+\+[^\)]+\))(?:[^\{\}\-\)])+\"(?=\))""", line) | |
#The concatenated variable | |
variable = '' | |
#For each string in the list, the items are concatenated | |
if len(varDeclaration) > 0: | |
for string in varDeclaration: | |
variable = string.replace("\"", "") | |
variable = variable.replace("+", "") | |
variable = variable.replace(" ", "") | |
variable = "\"" + variable + "\"" | |
variableCount += 1 | |
#Replace the variable with the concatenated one | |
line = line.replace(varDeclaration[0], variable) | |
formatCount += 1 | |
#When all matches are done, add the altered line to the output | |
output += line | |
urls = FindString(line,'https?://(?:[-\w.]|(?:%[\da-fA-F]{2}))+[/\-\w]+') | |
print('URLs Found:') | |
for url in urls: | |
print(url) | |
#When all lines are checked, write the output variable to a file | |
with open("deobfusc_"+powershellPath, 'w') as f: | |
f.write(output) | |
print("Amount of removed back ticks:") | |
print(backtickCount) | |
print("Amount of formatted strings that have been deobfuscated and concatenated:") | |
print(formatCount) | |
print("Amount of variables that have been concatenated:") | |
print(variableCount) | |
print("Total amount of modifications:") | |
print((backtickCount + formatCount + variableCount)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment