Skip to content

Instantly share code, notes, and snippets.

@oddmario
Created August 27, 2022 21:07
Show Gist options
  • Save oddmario/f8f313b01634c906f579c3df132767e2 to your computer and use it in GitHub Desktop.
Save oddmario/f8f313b01634c906f579c3df132767e2 to your computer and use it in GitHub Desktop.
https://github.com/0sir1ss/Carbon + string obfuscation
"""
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