forked from wufhex/PyDelta-PythonObfuscator
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathstring_encryption.py
153 lines (126 loc) · 5.32 KB
/
string_encryption.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
import ast
import os
import base64
import random
from .runtime_code import RuntimeCode
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives import padding
from cryptography.hazmat.backends import default_backend
# Cryptography was used because it seems to work faster with pyscript and left here
# pycryptodome seems to be faster and safer locally
class StringEncryptor:
def __init__(self):
self.encrypted_strings = []
# string key iv
self.obfstr_var_id = ['_________lithium_________', '_________carbon_________', '_________hydrogen_________']
def encrypt_string(self, s):
key = os.urandom(32)
iv = os.urandom(16)
# Encrypting string
padder = padding.PKCS7(algorithms.AES.block_size).padder()
padded_data = padder.update(s.encode('utf-8')) + padder.finalize()
cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())
encryptor = cipher.encryptor()
encrypted_data = encryptor.update(padded_data) + encryptor.finalize()
# Encoding encrypted data key and iv
encoded_string = base64.b64encode(encrypted_data).decode('utf-8')
key = base64.b64encode(key).decode('utf-8')
iv = base64.b64encode(iv).decode('utf-8')
return encoded_string, key, iv
# This function adds a lambda function called __bb01 to the code which is used to decrypt
# the encrypted strings, its intentionally obfuscated but it
# basically reverses what the encrypt_string function does
def find_and_encrypt_strings(self, code):
tree = ast.parse(code)
transformer = StringTransformer(self.encrypt_string, self.encrypted_strings, self.obfstr_var_id)
new_tree = transformer.visit(tree)
# Arrays to contains assignments
encstr_assignment_arr = []
key_assignment_arr = []
iv_assignment_arr = []
# For every encrypted string key and iv enumerate them and add a new variable to the code
for i, (encrypted_string, key, iv) in enumerate(transformer.encrypted_strings, start=1):
encstr_assignment = ast.Assign(
targets=[ast.Name(id=self.obfstr_var_id[0] + str(i), ctx=ast.Store())],
value=ast.Constant(encrypted_string)
)
key_assignment = ast.Assign(
targets=[ast.Name(id=self.obfstr_var_id[1] + str(i), ctx=ast.Store())],
value=ast.Constant(key)
)
iv_assignment = ast.Assign(
targets=[ast.Name(id=self.obfstr_var_id[2] + str(i), ctx=ast.Store())],
value=ast.Constant(iv)
)
# Appeand them to the assignments array
encstr_assignment_arr.append(encstr_assignment)
key_assignment_arr.append(key_assignment)
iv_assignment_arr.append(iv_assignment)
# Shuffle the variable order in the code
random.shuffle(encstr_assignment_arr)
random.shuffle(key_assignment_arr)
random.shuffle(iv_assignment_arr)
# Insert the shuffled variables to the code
for encrypted_string, key, iv in zip(encstr_assignment_arr, key_assignment_arr, iv_assignment_arr):
new_tree.body.insert(0, encrypted_string)
new_tree.body.insert(0, key)
new_tree.body.insert(0, iv)
ast.fix_missing_locations(new_tree)
# Add decryption and settostring code
new_code = ast.unparse(new_tree)
new_code = f"{RuntimeCode.set_to_string}\n{RuntimeCode.dec_code}\n{new_code}"
return new_code
class StringTransformer(ast.NodeTransformer):
def __init__(self, encrypt_string, encrypted_strings, obf_var_id):
self.obfstr_var_id = obf_var_id
self.encrypt_string = encrypt_string
self.encrypted_strings = encrypted_strings
self.decrypt_func_name = "__bb01"
self.set_unpacker_func_name = "__rfs"
self.str_len_cap = 2048
self.in_del_method = False
def visit_FunctionDef(self, node):
if node.name == "__del__":
self.in_del_method = True
self.generic_visit(node)
self.in_del_method = False
else:
self.generic_visit(node)
return node
# Adds a call to the decryptor functiona and references the encrypted strings keys and iv variables
def visit_Constant(self, node):
if isinstance(node.value, str) and not self.in_del_method:
original_string = node.value
# String length check
if len(original_string) >= self.str_len_cap:
return node
encrypted_string, key, iv = self.encrypt_string(original_string)
self.encrypted_strings.append((encrypted_string, key, iv))
enc_var = ast.Name(id=self.obfstr_var_id[0] + str(len(self.encrypted_strings)), ctx=ast.Load())
key_var = ast.Name(id=self.obfstr_var_id[1] + str(len(self.encrypted_strings)), ctx=ast.Load())
iv_var = ast.Name(id=self.obfstr_var_id[2] + str(len(self.encrypted_strings)), ctx=ast.Load())
return ast.Call(
func=ast.Name(id=self.decrypt_func_name, ctx=ast.Load()),
args=[enc_var, key_var, iv_var],
keywords=[]
)
return node
# Convert every f-string into a plus concatenated string, this also transforms
# variables become sets so thats why we need the settostr function
def visit_JoinedStr(self, node):
if self.in_del_method:
return self.generic_visit(node)
new_values = []
for value in node.values:
if isinstance(value, ast.Constant):
new_values.append(ast.Constant(value=value.value))
else:
new_values.append(ast.Call(
func=ast.Name(id=self.set_unpacker_func_name, ctx=ast.Load()),
args=[value],
keywords=[]
))
concat_expr = new_values[0]
for val in new_values[1:]:
concat_expr = ast.BinOp(left=concat_expr, op=ast.Add(), right=val)
return self.visit(concat_expr)