Created
August 27, 2022 21:07
-
-
Save oddmario/f8f313b01634c906f579c3df132767e2 to your computer and use it in GitHub Desktop.
https://github.com/0sir1ss/Carbon + string obfuscation
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
""" | |
https://github.com/0sir1ss/Carbon | |
""" | |
import ast | |
import re | |
import random | |
import io | |
import tokenize | |
import os | |
import zlib, base64 | |
import sys | |
import string as string2 | |
from charset_normalizer import from_path | |
def remove_docs(source): | |
io_obj = io.StringIO(source) | |
out = "" | |
prev_toktype = tokenize.INDENT | |
last_lineno = -1 | |
last_col = 0 | |
for tok in tokenize.generate_tokens(io_obj.readline): | |
token_type = tok[0] | |
token_string = tok[1] | |
start_line, start_col = tok[2] | |
end_line, end_col = tok[3] | |
if start_line > last_lineno: | |
last_col = 0 | |
if start_col > last_col: | |
out += (" " * (start_col - last_col)) | |
if token_type == tokenize.COMMENT: | |
pass | |
elif token_type == tokenize.STRING: | |
if prev_toktype != tokenize.INDENT: | |
if prev_toktype != tokenize.NEWLINE: | |
if start_col > 0: | |
out += token_string | |
else: | |
out += token_string | |
prev_toktype = token_type | |
last_col = end_col | |
last_lineno = end_line | |
out = '\n'.join(l for l in out.splitlines() if l.strip()) | |
return out | |
def do_rename(pairs, code): | |
for key in pairs: | |
code = re.sub(fr"\b({key})\b", pairs[key], code, re.MULTILINE) | |
return code | |
def rename(code): | |
code = remove_docs(code) | |
parsed = ast.parse(code) | |
funcs = { | |
node for node in ast.walk(parsed) if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) | |
} | |
classes = { | |
node for node in ast.walk(parsed) if isinstance(node, ast.ClassDef) | |
} | |
args = { | |
node.id for node in ast.walk(parsed) if isinstance(node, ast.Name) and not isinstance(node.ctx, ast.Load) | |
} | |
attrs = { | |
node.attr for node in ast.walk(parsed) if isinstance(node, ast.Attribute) and not isinstance(node.ctx, ast.Load) | |
} | |
for func in funcs: | |
if func.args.args: | |
for arg in func.args.args: | |
args.add(arg.arg) | |
if func.args.kwonlyargs: | |
for arg in func.args.kwonlyargs: | |
args.add(arg.arg) | |
if func.args.vararg: | |
args.add(func.args.vararg.arg) | |
if func.args.kwarg: | |
args.add(func.args.kwarg.arg) | |
pairs = {} | |
used = set() | |
for func in funcs: | |
if func.name == "__init__": | |
continue | |
newname = "".join(random.choice(["I", "l"]) for i in range(random.randint(8, 20))) | |
while newname in used: | |
newname = "".join(random.choice(["I", "l"]) for i in range(random.randint(8, 20))) | |
used.add(newname) | |
pairs[func.name] = newname | |
for _class in classes: | |
newname = "".join(random.choice(["I", "l"]) for i in range(random.randint(8, 20))) | |
while newname in used: | |
newname = "".join(random.choice(["I", "l"]) for i in range(random.randint(8, 20))) | |
used.add(newname) | |
pairs[_class.name] = newname | |
for arg in args: | |
if arg != "args" and arg != "kwargs": | |
newname = "".join(random.choice(["I", "l"]) for i in range(random.randint(8, 20))) | |
while newname in used: | |
newname = "".join(random.choice(["I", "l"]) for i in range(random.randint(8, 20))) | |
used.add(newname) | |
pairs[arg] = newname | |
for attr in attrs: | |
newname = "".join(random.choice(["I", "l"]) for i in range(random.randint(8, 20))) | |
while newname in used: | |
newname = "".join(random.choice(["I", "l"]) for i in range(random.randint(8, 20))) | |
used.add(newname) | |
pairs[attr] = newname | |
string_regex = r"('|\")[\x1f-\x7e]{1,}?('|\")" | |
original_strings = re.finditer(string_regex, code, re.MULTILINE) | |
originals = [] | |
for matchNum, match in enumerate(original_strings, start=1): | |
originals.append(match.group().replace("\\", "\\\\")) | |
placeholder = os.urandom(16).hex() | |
code = re.sub(string_regex, f"'{placeholder}'", code, 0, re.MULTILINE) | |
for i in range(len(originals)): | |
for key in pairs: | |
originals[i] = re.sub(r"({.*)(" + key + r")(.*})", "\\1" + pairs[key] + "\\3", originals[i], re.MULTILINE) | |
while True: | |
found = False | |
code = do_rename(pairs, code) | |
for key in pairs: | |
if re.findall(fr"\b({key})\b", code): | |
found = True | |
if found == False: | |
break | |
replace_placeholder = r"('|\")" + placeholder + r"('|\")" | |
for original in originals: | |
code = re.sub(replace_placeholder, original, code, 1, re.MULTILINE) | |
return code | |
def str_obfuscation(string): | |
inside_quotes = False | |
inside_quotes_string = False | |
string_quotes_mark = "" | |
is_multiline = False | |
string = str(string) + ' ' | |
result = "" | |
skipped_indices = [] | |
string_decryptor_name = ''.join(random.choices(string2.ascii_uppercase + string2.ascii_lowercase, k=1)) + ''.join(random.choices(string2.ascii_uppercase + string2.ascii_lowercase + string2.digits, k=4)) | |
string = 'def ' + string_decryptor_name + '(s): return zlib.decompress(base64.b64decode(s)).decode()\n' + string | |
if not 'import base64' in string: | |
string = "import base64\n" + string | |
if not 'import zlib' in string: | |
string = "import zlib\n" + string | |
for index, c in enumerate(string): | |
if index in skipped_indices: | |
continue | |
c = str(c) | |
if not inside_quotes and inside_quotes_string != False: | |
if not "[@ignore_str_obf]" in inside_quotes_string: | |
obfuscation = base64.b64encode(zlib.compress(inside_quotes_string.encode())).decode() | |
result += string_decryptor_name + "('" + obfuscation + "')" | |
else: | |
if is_multiline: | |
string_quotes_mark = string_quotes_mark + string_quotes_mark + string_quotes_mark | |
result += string_quotes_mark + inside_quotes_string + string_quotes_mark | |
inside_quotes_string = False | |
string_quotes_mark = "" | |
is_multiline = False | |
if not inside_quotes: | |
if c == "'" or c == '"': | |
# String started | |
try: | |
if string[index+1] == c and string[index+2] == c: | |
try: | |
if string[index+3] == c and string[index+4] == c and string[index+5] == c: | |
# Empty quotation marks | |
result += c + c + c + c + c + c | |
skipped_indices.append(index+1) | |
skipped_indices.append(index+2) | |
skipped_indices.append(index+3) | |
skipped_indices.append(index+4) | |
skipped_indices.append(index+5) | |
else: | |
is_multiline = True | |
string_quotes_mark = c | |
inside_quotes = True | |
skipped_indices.append(index+1) | |
skipped_indices.append(index+2) | |
except IndexError: | |
pass | |
else: | |
try: | |
if string[index+1] != c: | |
string_quotes_mark = c | |
inside_quotes = True | |
else: | |
# Empty quotation marks | |
result += c + c | |
skipped_indices.append(index+1) | |
except IndexError: | |
pass | |
except IndexError: | |
pass | |
else: | |
result += c | |
else: | |
try: | |
if c == "\\" and string[index+1] == string_quotes_mark: | |
c = '' | |
except IndexError: | |
pass | |
if c == string_quotes_mark and string[index-1] != "\\": | |
# String ended | |
inside_quotes = False | |
if is_multiline: | |
try: | |
if string[index+1] != string_quotes_mark and string[index+2] != string_quotes_mark: | |
# Nevermind, multiline string is still going | |
inside_quotes = True | |
if inside_quotes_string == False: | |
inside_quotes_string = "" | |
inside_quotes_string += c | |
else: | |
skipped_indices.append(index+1) | |
skipped_indices.append(index+2) | |
except IndexError: | |
pass | |
else: | |
if inside_quotes_string == False: | |
inside_quotes_string = "" | |
inside_quotes_string += c | |
return result[:-1].replace("[@ignore_str_obf]", "") | |
def main(): | |
fname = os.path.basename(__file__) | |
usage = f"python {fname} [input] [output] [ignore warnings -> 0/1]" | |
args = sys.argv[1:] | |
ignore_warnings = False | |
if not args[0]: | |
print(f"[ERROR] Usage: {usage}") | |
sys.exit() | |
else: | |
input_ = str(args[0]) | |
if not args[1]: | |
print(f"[ERROR] Usage: {usage}") | |
sys.exit() | |
else: | |
output = str(args[1]) | |
if not args[2]: | |
print(f"[ERROR] Usage: {usage}") | |
sys.exit() | |
else: | |
if int(args[2]) == 0: | |
ignore_warnings = False | |
else: | |
ignore_warnings = True | |
if not ignore_warnings: | |
input("- Make sure you are not using any f-strings (replace them with concatenation instead)\n\n- Make sure you are not setting any variable names (including the variables of your function arguments) as the same name of any of your imports (or their properties)\n\nPress any key to continue with the obfuscation execution") | |
fEncoding = str(from_path(input_).best().encoding) | |
with open(input_, 'r', encoding=fEncoding) as inputFile: | |
obf = str_obfuscation(rename(inputFile.read())) | |
with open(output, 'w', encoding=fEncoding) as outputFile: | |
outputFile.write(obf) | |
print("[+] Done!") | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment