Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit a8aaa1b

Browse files
Add files via upload
1 parent 8e7645a commit a8aaa1b

File tree

1 file changed

+266
-0
lines changed

1 file changed

+266
-0
lines changed

‎obfuscate.py

Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
4+
import random
5+
import string
6+
import re
7+
from itertools import product
8+
9+
10+
END_SYMBOLS = [' ', '\\', '\n', '.', ',', ';', '=', '`', '"', "'", '(', ')', ':', '[', ']', '{', '}', '+'] # no ++
11+
END_SYMBOLS_BY_2 = list(product(END_SYMBOLS, END_SYMBOLS))
12+
13+
14+
def random_string():
15+
"""Generate a random string of fixed length """
16+
length = random.randint(5, 30)
17+
letters = string.ascii_lowercase + string.ascii_uppercase
18+
return ''.join(random.choice(letters) for i in range(length))
19+
20+
21+
def random_bits(length):
22+
"""Generate a random 1/0 string of fixed length """
23+
chars = '01'
24+
return ''.join(random.choice(chars) for i in range(length))
25+
26+
27+
def randomize_case(input_string):
28+
"""Randomly change the case of each letter of the input_string"""
29+
rand_bits = random_bits(len(input_string))
30+
return ''.join(input_string[i].lower() if rand_bits[i] == '0' else
31+
input_string[i].upper()
32+
for i in range(len(input_string)))
33+
34+
35+
def delete_comments(input_text):
36+
"""
37+
Completely delete all the comments in the input_text.
38+
Type 1 comments: "<# ... #>".
39+
Type 2 comments: "# ... \n", except for cases when # is surrounded with " or '.
40+
:param input_text: a string representing a script to work on.
41+
:return: an input_text freed from any comments.
42+
"""
43+
output = ''
44+
start_symbols = ['<#', '#']
45+
end_symbols = ['#>', '\n']
46+
assert len(start_symbols) == len(end_symbols)
47+
for i in range(len(start_symbols)):
48+
output = ''
49+
# 1. initial search
50+
start_index = input_text.find(start_symbols[i])
51+
while start_index >= 0:
52+
if input_text[:start_index].split('\n')[-1].replace(" ", "") == "": # handling spaces before the comment
53+
if len(input_text[:start_index].split('\n')[-1]) > 0:
54+
start_index = start_index - len(input_text[:start_index].split('\n')[-1])
55+
# 2. append everything up to start_index to the output
56+
output = output + input_text[:start_index]
57+
# 3. then, either:
58+
if i == 0 or (i == 1 and input_text[start_index - 1] != "`" and
59+
((input_text[:start_index].split('\n')[-1].find("'") == -1 or
60+
input_text[start_index:].split('\n')[0].find("'") == -1) and
61+
(input_text[:start_index].split('\n')[-1].find('"') == -1 or
62+
input_text[start_index:].split('\n')[0].find('"') == -1))):
63+
# 3.1. skip the comment
64+
end_index = start_index + input_text[start_index:].find(end_symbols[i]) + len(end_symbols[i])
65+
else:
66+
# 3.2. or add the "false" positive '#' to the output
67+
end_index = start_index + 1
68+
output = output + '#' # we need '#' this time
69+
# 4. cut input_text from the end position
70+
input_text = input_text[end_index:]
71+
# 5. loop
72+
start_index = input_text.find(start_symbols[i])
73+
output = output + input_text
74+
input_text = output
75+
while output.find('\n\n\n') != -1:
76+
output = output.replace('\n\n\n', '\n\n')
77+
return output
78+
79+
80+
def rename_variables(input_text):
81+
"""
82+
Randomly rename variables in input_text and return the result with a mapping table.
83+
:param input_text: a string representing a script to work on.
84+
:return: (string, dict): an input_text with renamed variables, a mapping table
85+
"""
86+
start_symbol = '$'
87+
powershell_auto_vars = ['$NestedPromptLevel', '$PSBoundParameters', '$ExecutionContext', '$ConsoleFileName',
88+
'$EventSubscriber', '$SourceEventArgs', '$PSDebugContext', '$PsVersionTable',
89+
'$PSCommandPath', '$LastExitCode', '$MyInvocation', '$PSScriptRoot', '$PSSenderInfo',
90+
'$PsUICulture', '$SourceArgs', '$StackTrace', '$EventArgs', '$PsCulture', '$Allnodes',
91+
'$PsCmdlet', '$ForEach', '$Matches', '$Profile', '$ShellID', '$PsHome', '$PSitem',
92+
'$Sender', '$Error', '$Event', '$False', '$Input', '$Args', '$Home', '$Host', '$NULL',
93+
'$This', '$True', '$OFS', '$PID', '$Pwd', '$$', '$?', '$^', '$_', '$(']
94+
95+
powershell_system_vars = []
96+
powershell_system_vars = ['Advapi32', 'AccessMask', 'AppDomain', 'Architecture', 'AssemblyName', 'Assembly',
97+
'BatPath', 'B64Binary', 'BackupPath', 'BindingFlags', 'binPath', 'Bitfield',
98+
'CallingConvention', 'Charset', 'CheckAllPermissionsInSet', 'Class', 'Command',
99+
'Credential', 'CurrentUser', 'CustomAttributeBuilder', 'DllBytes', 'DllImportAttribute',
100+
'DllName', 'DllPath', 'Emit', 'EntryPoint', 'EnumElements', 'ExcludeProgramFiles', 'Env',
101+
'ExcludeWindows', 'ExcludeOwned', 'FieldInfo', 'FieldName', 'FieldProp', 'Field', 'File',
102+
'Filter', 'Force', 'FunctionDefinitions', 'FunctionName', 'GetServiceHandle',
103+
'InteropServices', 'Kernel32', 'Keys', 'KeyName', 'KnownDLLs', 'LiteralPaths',
104+
'LocalGroup', 'MarshalAsAttribute', 'MarshalAs', 'Marshal', 'ModuleBuilder', 'ModuleName',
105+
'Module', 'Namespace', 'Name', 'NativeCallingConvention', 'NewField', 'Offset',
106+
'OpCodes', 'Out', 'Owners', 'PackingSize', 'ParameterTypes', 'PasswordToAdd', 'Password',
107+
'Path', 'PermissionSet', 'Permissions', 'Position', 'ProcessName', 'PropertyInfo',
108+
'Properties', 'ReadControl', 'ReplaceString', 'Runtime', 'ReturnType', 'SearchString',
109+
'ServiceAccessRights', 'ServiceCommand', 'ServiceCommands', 'ServiceDetails',
110+
'ServiceName', 'Service', 'SetLastError', 'SID_AND_ATTRIBUTES', 'SidAttributes',
111+
'SizeConst', 'StructBuilder', 'System', 'TargetPermissions', 'TargetService',
112+
'TOKEN_GROUPS', 'TokenGroups', 'TypeAttributes', 'TypeHash', 'Types', 'Type',
113+
'UnmanagedType', 'UserNameToAdd']
114+
powershell_auto_system_vars = powershell_auto_vars + ['$' + i for i in powershell_system_vars]
115+
# before obfuscation
116+
not_to_replace_dict = {}
117+
for system_var in powershell_system_vars:
118+
# rand_str = random_string()
119+
if '$' + system_var.lower() in input_text.lower():
120+
for es in END_SYMBOLS:
121+
if '$' + system_var.lower() + es in input_text.lower():
122+
# extract children if any
123+
# ensure there are no variables to obfuscate that will coincide with powershell_system_vars children.
124+
local_found = input_text.lower().find('$' + system_var.lower() + '.')
125+
if local_found > -1:
126+
local_found += len('$' + system_var.lower() + '.')
127+
child = input_text[local_found:
128+
local_found + min([input_text[local_found:].find(end_symb)
129+
for end_symb in END_SYMBOLS
130+
if input_text[local_found:].find(end_symb) != -1])]
131+
if '$' + child.lower() not in not_to_replace_dict.keys():
132+
for end_symbol in END_SYMBOLS_BY_2:
133+
if '.' + child.lower() + end_symbol[0] in input_text.lower() and \
134+
'$' + child.lower() + end_symbol[1] in input_text.lower():
135+
if '$' + child.lower() not in not_to_replace_dict.keys():
136+
not_to_replace_dict['$' + child.lower()] = None
137+
break
138+
# rename
139+
# re_sv = re.compile(re.escape('$' + system_var.lower() + es), re.IGNORECASE)
140+
# input_text = re_sv.sub('$' + system_var + '_' + rand_str + es, input_text)
141+
# OBFUSCATION STARTS
142+
input_text_raw = input_text
143+
vars_dict = {}
144+
output = ''
145+
# 1. initial search
146+
start_index = input_text.find(start_symbol)
147+
start_index_raw = start_index
148+
end_index = start_index
149+
while start_index >= 0:
150+
# 1. append everything up to start_index to the output
151+
output = output + input_text[:start_index]
152+
# 2. then, either 2.1, 2.2 or 2.3:
153+
# " does not cancel $ symbol, ' does.
154+
# if we're in "here", ' inside "" does not cancel $.
155+
assert input_text_raw[:start_index_raw][-start_index:] == input_text[:start_index][-start_index:]
156+
if (input_text_raw[:start_index_raw].count("'") -
157+
input_text_raw[:start_index_raw].count("`'") -
158+
input_text_raw[:start_index_raw].count('"\'"')) % 2 != 0 and not\
159+
(input_text_raw[:start_index_raw].count('"') -
160+
input_text_raw[:start_index_raw].count('`"') -
161+
input_text_raw[:start_index_raw].count("'\"'")) % 2 != 0:
162+
# we're in 'here'
163+
# 2.1.1. add for the "false" positive '$'
164+
end_index = start_index + 1
165+
output = output + "$"
166+
elif input_text[start_index - 1] == "`" or input_text[start_index + 1] in END_SYMBOLS:
167+
# 2.1.2. add for the "false" positive '$'
168+
end_index = start_index + 1
169+
output = output + "$"
170+
elif any([(input_text[start_index: start_index + len(exc_var)].lower() == exc_var.lower() and
171+
input_text[start_index + len(exc_var)] in END_SYMBOLS) for exc_var in powershell_auto_system_vars]):
172+
for exc_var in powershell_auto_system_vars:
173+
if input_text[start_index: start_index + len(exc_var)].lower() == exc_var.lower() and \
174+
input_text[start_index + len(exc_var)] in END_SYMBOLS:
175+
# 2.2. add for the "false" positive '$'
176+
end_index = start_index + len(exc_var)
177+
output = output + exc_var # randomize_case(exc_var)
178+
break
179+
# value's guaranteed by elif condition.
180+
else:
181+
# 2.3. or find the ending
182+
end_index = start_index
183+
end_index = end_index + min([input_text[end_index:].find(end_symbol)
184+
for end_symbol in END_SYMBOLS
185+
if input_text[end_index:].find(end_symbol) != -1])
186+
# check if the ending was a false positive due to escape symbols:
187+
# assert input_text_raw[:start_index_raw][-8:] == input_text[:start_index][-8:]
188+
# while input_text[end_index] == "`" or \
189+
# (input_text_raw[:start_index_raw].count("'") -
190+
# input_text_raw[:start_index_raw].count("`'") -
191+
# input_text_raw[:start_index_raw].count('"\'"')) % 2 != 0 and not \
192+
#(input_text_raw[:start_index_raw].count('"') -
193+
#input_text_raw[:start_index_raw].count('`"') -
194+
#input_text_raw[:start_index_raw].count("'\"'")) % 2 != 0:
195+
#end_index = end_index + min([input_text[end_index:].find(end_symbol)
196+
#for end_symbol in END_SYMBOLS
197+
#if input_text[end_index:].find(end_symbol) != -1])
198+
# 3. generate and append a new variable name
199+
source_var_name = input_text[start_index: end_index]
200+
201+
res_1 = any([(" -" + source_var_name[1:].lower() + es) in input_text_raw.lower() for es in END_SYMBOLS])
202+
res_2 = any([("." + source_var_name[1:].lower() + es) in input_text_raw.lower() for es in END_SYMBOLS])
203+
res_3 = ("['" + source_var_name[1:].lower() + "']") in input_text_raw.lower()
204+
res_4 = ('["' + source_var_name[1:].lower() + '"]') in input_text_raw.lower()
205+
res_5 = ('$' + source_var_name[1:].lower() + ':') in input_text_raw.lower()
206+
if source_var_name.lower() not in not_to_replace_dict and not (res_1 or res_2 or res_3 or res_4 or res_5):
207+
if source_var_name.lower() not in vars_dict:
208+
vars_dict[source_var_name.lower()] = random_string()
209+
output = output + "$" + vars_dict[source_var_name.lower()]
210+
else:
211+
output = output + source_var_name
212+
# 4. cut input_text from the end position
213+
input_text = input_text[end_index:]
214+
offset = end_index - start_index # if var this is len(source_var_name)
215+
input_text_raw_cut = input_text_raw[start_index_raw + offset:]
216+
assert input_text_raw_cut[:-3] == input_text[:-3]
217+
# 5. loop
218+
start_index = input_text.find(start_symbol)
219+
start_index_raw = start_index_raw + offset + start_index
220+
output = output + input_text
221+
# 6. additional replacement 1 - function parameter names (FUNCTION_NAME -PARAMETER):
222+
for end_symbol in END_SYMBOLS:
223+
if end_symbol != '\\':
224+
for k, v in vars_dict.items():
225+
k_ = k[1:]
226+
re_k = re.compile(re.escape(" -" + k_ + end_symbol), re.IGNORECASE)
227+
output = re_k.sub(" -" + v + end_symbol, output)
228+
# 7. additional replacement 2 - attributes (object.attribute):
229+
for end_symbol in END_SYMBOLS:
230+
if end_symbol != '\\':
231+
for k, v in vars_dict.items():
232+
k_ = k[1:]
233+
re_k = re.compile(re.escape("." + k_ + end_symbol), re.IGNORECASE)
234+
output = re_k.sub("." + v + end_symbol, output)
235+
# 8. additional replacement 3 - parameter names in quotes (GetField('PARAMETER')):
236+
for k, v in vars_dict.items():
237+
k_ = k[1:]
238+
re_k = re.compile(re.escape("'" + k_ + "'"), re.IGNORECASE)
239+
output = re_k.sub("'" + v + "'", output)
240+
return output, vars_dict
241+
242+
243+
def main(input_text):
244+
v_dict, f_dict = {}, {}
245+
output1 = delete_comments(input_text)
246+
output2, v_dict = rename_variables(output1)
247+
return output1, output2, v_dict, f_dict
248+
249+
250+
if __name__ == '__main__':
251+
old_file = 'PowerUp.ps1 - Source.txt'
252+
old_file_split = old_file.split('.')
253+
new_semi_obfs_file = ''.join(old_file_split[:-1]) + ' - semi-obfuscated.' + old_file_split[-1]
254+
new_obfs_file = ''.join(old_file_split[:-1]) + ' - obfuscated.' + old_file_split[-1]
255+
with open(old_file, 'r') as fr:
256+
input_data = fr.read()
257+
semi_obfs_data, obfs_data, vars_dict, funcs_dict = main(input_data)
258+
with open(new_semi_obfs_file, 'w') as f:
259+
f.write(semi_obfs_data)
260+
with open(new_obfs_file, 'w') as f:
261+
f.write(obfs_data)
262+
vd = sorted([' - '.join(i) for i in vars_dict.items()])
263+
fd = sorted([' - '.join(i) for i in funcs_dict.items()])
264+
mapping = 'Functions: \n' + '\n'.join(fd) + '\n\n\nVariables: \n' + '\n'.join(vd)
265+
with open(new_obfs_file + '- name mapping.txt', 'w') as f:
266+
f.write(str(mapping))

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /