pyprivate-obf 1.0.0__py3-none-any.whl
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.
- pyprivate_obf/__init__.py +18 -0
- pyprivate_obf/__main__.py +4 -0
- pyprivate_obf/cli.py +476 -0
- pyprivate_obf/encoding_methods/__init__.py +5 -0
- pyprivate_obf/encoding_methods/bytecode_obfuscation.py +83 -0
- pyprivate_obf/encoding_methods/cpython_obfuscation.py +111 -0
- pyprivate_obf/encoding_methods/encode_strings.py +129 -0
- pyprivate_obf/encoding_methods/simple_marshal.py +12 -0
- pyprivate_obf/encryption/__init__.py +0 -0
- pyprivate_obf/encryption/script.py +52 -0
- pyprivate_obf/layers/__init__.py +7 -0
- pyprivate_obf/layers/base.py +18 -0
- pyprivate_obf/layers/check_version.py +31 -0
- pyprivate_obf/layers/enter_channel.py +78 -0
- pyprivate_obf/layers/expiry_time.py +27 -0
- pyprivate_obf/layers/libraries_installer.py +34 -0
- pyprivate_obf-1.0.0.dist-info/METADATA +347 -0
- pyprivate_obf-1.0.0.dist-info/RECORD +22 -0
- pyprivate_obf-1.0.0.dist-info/WHEEL +5 -0
- pyprivate_obf-1.0.0.dist-info/entry_points.txt +2 -0
- pyprivate_obf-1.0.0.dist-info/licenses/LICENSE +28 -0
- pyprivate_obf-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from .encoding_methods.encode_strings import encode_strings
|
|
2
|
+
from .encoding_methods.bytecode_obfuscation import bytecode_obfuscation
|
|
3
|
+
from .encoding_methods.simple_marshal import simple_marshal
|
|
4
|
+
from .layers.expiry_time import ExpiryTime
|
|
5
|
+
from .layers.check_version import CheckVersion
|
|
6
|
+
from .layers.libraries_installer import LibrariesInstaller
|
|
7
|
+
from .layers.enter_channel import EnterChannel
|
|
8
|
+
|
|
9
|
+
__version__ = "1.0.0"
|
|
10
|
+
__all__ = [
|
|
11
|
+
"encode_strings",
|
|
12
|
+
"bytecode_obfuscation",
|
|
13
|
+
"simple_marshal",
|
|
14
|
+
"ExpiryTime",
|
|
15
|
+
"CheckVersion",
|
|
16
|
+
"LibrariesInstaller",
|
|
17
|
+
"EnterChannel",
|
|
18
|
+
]
|
pyprivate_obf/cli.py
ADDED
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
from rich.panel import Panel
|
|
8
|
+
from rich.table import Table
|
|
9
|
+
from rich.text import Text
|
|
10
|
+
from rich.rule import Rule
|
|
11
|
+
from rich.align import Align
|
|
12
|
+
from rich.prompt import Prompt, Confirm
|
|
13
|
+
from rich.padding import Padding
|
|
14
|
+
from rich import box
|
|
15
|
+
|
|
16
|
+
from .encoding_methods.encode_strings import encode_strings
|
|
17
|
+
from .encoding_methods.bytecode_obfuscation import bytecode_obfuscation
|
|
18
|
+
from .encoding_methods.simple_marshal import simple_marshal
|
|
19
|
+
from .layers.expiry_time import ExpiryTime
|
|
20
|
+
from .layers.libraries_installer import LibrariesInstaller
|
|
21
|
+
from .layers.check_version import CheckVersion
|
|
22
|
+
from .layers.enter_channel import EnterChannel
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
console = Console(highlight=False)
|
|
26
|
+
|
|
27
|
+
BANNER_ART = r"""
|
|
28
|
+
██████╗ ██╗ ██╗ ██████╗ ██████╗ ██╗██╗ ██╗ █████╗ ████████╗███████╗
|
|
29
|
+
██╔══██╗╚██╗ ██╔╝ ██╔══██╗██╔══██╗██║██║ ██║██╔══██╗╚══██╔══╝██╔════╝
|
|
30
|
+
██████╔╝ ╚████╔╝ ██████╔╝██████╔╝██║██║ ██║███████║ ██║ █████╗
|
|
31
|
+
██╔═══╝ ╚██╔╝ ██╔═══╝ ██╔══██╗██║╚██╗ ██╔╝██╔══██║ ██║ ██╔══╝
|
|
32
|
+
██║ ██║ ██║ ██║ ██║██║ ╚████╔╝ ██║ ██║ ██║ ███████╗
|
|
33
|
+
╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═╝ ╚═╝ ╚═╝ ╚══════╝
|
|
34
|
+
""".strip("\n")
|
|
35
|
+
|
|
36
|
+
VERSION = "4.1.2"
|
|
37
|
+
CHANNEL = "t.me/psh_team"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def build(
|
|
41
|
+
source,
|
|
42
|
+
*,
|
|
43
|
+
extras=None,
|
|
44
|
+
encode_strings_opt=False,
|
|
45
|
+
cpython=False,
|
|
46
|
+
bytecode=False,
|
|
47
|
+
layers=1,
|
|
48
|
+
re_check_version=False,
|
|
49
|
+
):
|
|
50
|
+
extras = extras or []
|
|
51
|
+
|
|
52
|
+
if extras:
|
|
53
|
+
injected_modules = []
|
|
54
|
+
injected_code = ""
|
|
55
|
+
for extra in extras:
|
|
56
|
+
extra.source = source
|
|
57
|
+
injected_code += extra.injected_code() + "\n"
|
|
58
|
+
injected_modules.extend(extra.injected_modules())
|
|
59
|
+
injected_modules = list(set(injected_modules))
|
|
60
|
+
source = "\n".join(injected_modules) + "\n\n\n" + injected_code + source
|
|
61
|
+
|
|
62
|
+
if encode_strings_opt:
|
|
63
|
+
source = encode_strings(source)
|
|
64
|
+
|
|
65
|
+
if cpython:
|
|
66
|
+
from .encoding_methods.cpython_obfuscation import cpython_obfuscation
|
|
67
|
+
source = cpython_obfuscation(source)
|
|
68
|
+
if encode_strings_opt:
|
|
69
|
+
source = encode_strings(source)
|
|
70
|
+
|
|
71
|
+
no_algorithm_selected = not (cpython or bytecode)
|
|
72
|
+
for _ in range(layers):
|
|
73
|
+
if bytecode:
|
|
74
|
+
source = bytecode_obfuscation(source)
|
|
75
|
+
if no_algorithm_selected:
|
|
76
|
+
source = simple_marshal(source)
|
|
77
|
+
if re_check_version:
|
|
78
|
+
source = CheckVersion().get_new_source(source)
|
|
79
|
+
if encode_strings_opt:
|
|
80
|
+
source = encode_strings(source)
|
|
81
|
+
|
|
82
|
+
return source
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def print_banner():
|
|
86
|
+
console.print()
|
|
87
|
+
console.print(Align.center(Text(BANNER_ART, style="bold cyan")))
|
|
88
|
+
console.print()
|
|
89
|
+
meta = Table.grid(padding=(0, 3))
|
|
90
|
+
meta.add_column(justify="center")
|
|
91
|
+
meta.add_column(justify="center")
|
|
92
|
+
meta.add_column(justify="center")
|
|
93
|
+
meta.add_row(
|
|
94
|
+
Text(f"v{VERSION}", style="bold bright_green"),
|
|
95
|
+
Text("Python Source Obfuscator", style="bold white"),
|
|
96
|
+
Text(CHANNEL, style="bold bright_blue underline"),
|
|
97
|
+
)
|
|
98
|
+
console.print(Align.center(meta))
|
|
99
|
+
console.print()
|
|
100
|
+
console.print(Rule(style="bright_black"))
|
|
101
|
+
console.print()
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def print_encoders_table():
|
|
105
|
+
t = Table(
|
|
106
|
+
title="[bold white]Encoding Methods[/bold white]",
|
|
107
|
+
box=box.ROUNDED,
|
|
108
|
+
border_style="cyan",
|
|
109
|
+
header_style="bold cyan",
|
|
110
|
+
title_style="bold white",
|
|
111
|
+
show_lines=True,
|
|
112
|
+
expand=True,
|
|
113
|
+
)
|
|
114
|
+
t.add_column("Flag", style="bold green", justify="center", width=16)
|
|
115
|
+
t.add_column("Method", style="white")
|
|
116
|
+
t.add_column("Description", style="dim white")
|
|
117
|
+
t.add_row("--bytecode", "BytecodeObfuscation",
|
|
118
|
+
"Compile to .pyc and marshal into exec loader")
|
|
119
|
+
t.add_row("--cpython", "CPythonObfuscation",
|
|
120
|
+
"Transpile via Cython to native C binary launcher")
|
|
121
|
+
t.add_row("--strings", "EncodeStrings",
|
|
122
|
+
"Replace string literals with bytes([...]).decode()")
|
|
123
|
+
t.add_row("--marshal", "SimpleMarshal",
|
|
124
|
+
"Marshal code object into base64-wrapped exec loader")
|
|
125
|
+
console.print(t)
|
|
126
|
+
console.print()
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def print_layers_table():
|
|
130
|
+
t = Table(
|
|
131
|
+
title="[bold white]Protection Layers[/bold white]",
|
|
132
|
+
box=box.ROUNDED,
|
|
133
|
+
border_style="magenta",
|
|
134
|
+
header_style="bold magenta",
|
|
135
|
+
title_style="bold white",
|
|
136
|
+
show_lines=True,
|
|
137
|
+
expand=True,
|
|
138
|
+
)
|
|
139
|
+
t.add_column("Flag", style="bold yellow", justify="center", width=22)
|
|
140
|
+
t.add_column("Layer", style="white")
|
|
141
|
+
t.add_column("Description", style="dim white")
|
|
142
|
+
t.add_row("--expiry DATE", "ExpiryTime",
|
|
143
|
+
"Script self-destructs after the given date")
|
|
144
|
+
t.add_row("--channel URL", "EnterChannel",
|
|
145
|
+
"Requires user to join a Telegram channel to run")
|
|
146
|
+
t.add_row("--check-version", "CheckVersion",
|
|
147
|
+
"Locks execution to the current Python version")
|
|
148
|
+
t.add_row("--pip PKG [PKG ...]", "LibsInstaller",
|
|
149
|
+
"Auto-installs required pip packages on first run")
|
|
150
|
+
console.print(t)
|
|
151
|
+
console.print()
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def print_help():
|
|
155
|
+
print_banner()
|
|
156
|
+
|
|
157
|
+
usage = Panel(
|
|
158
|
+
"[bold white]pyprivate-obf[/bold white] [green]<input.py>[/green] "
|
|
159
|
+
"[cyan]\\[--bytecode|--cpython|--strings|--marshal][/cyan] "
|
|
160
|
+
"[yellow]\\[--expiry DATE][/yellow] [yellow]\\[--channel URL][/yellow] "
|
|
161
|
+
"[yellow]\\[--pip PKG ...][/yellow] [dim]\\[-o output.py][/dim]",
|
|
162
|
+
title="[bold white]Usage[/bold white]",
|
|
163
|
+
border_style="bright_black",
|
|
164
|
+
padding=(0, 2),
|
|
165
|
+
)
|
|
166
|
+
console.print(usage)
|
|
167
|
+
console.print()
|
|
168
|
+
print_encoders_table()
|
|
169
|
+
print_layers_table()
|
|
170
|
+
|
|
171
|
+
eg = Table.grid(padding=(0, 2))
|
|
172
|
+
eg.add_column(style="bold dim white", justify="right")
|
|
173
|
+
eg.add_column()
|
|
174
|
+
eg.add_row(
|
|
175
|
+
"Bytecode",
|
|
176
|
+
"[green]pyprivate-obf[/green] script.py [cyan]--bytecode[/cyan]",
|
|
177
|
+
)
|
|
178
|
+
eg.add_row(
|
|
179
|
+
"CPython",
|
|
180
|
+
"[green]pyprivate-obf[/green] script.py [cyan]--cpython[/cyan] [dim]-o out.py[/dim]",
|
|
181
|
+
)
|
|
182
|
+
eg.add_row(
|
|
183
|
+
"Strings",
|
|
184
|
+
"[green]pyprivate-obf[/green] script.py [cyan]--strings[/cyan]",
|
|
185
|
+
)
|
|
186
|
+
eg.add_row(
|
|
187
|
+
"Layered",
|
|
188
|
+
"[green]pyprivate-obf[/green] script.py [cyan]--marshal[/cyan] "
|
|
189
|
+
"[yellow]--expiry 2025-12-31 --channel https://t.me/psh_team[/yellow]",
|
|
190
|
+
)
|
|
191
|
+
eg.add_row(
|
|
192
|
+
"Interactive",
|
|
193
|
+
"[green]pyprivate-obf[/green] [cyan]--interactive[/cyan]",
|
|
194
|
+
)
|
|
195
|
+
console.print(Panel(eg, title="[bold white]Examples[/bold white]",
|
|
196
|
+
border_style="bright_black", padding=(1, 2)))
|
|
197
|
+
console.print()
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def ask(prompt, default=None):
|
|
201
|
+
suffix = f" [dim]\\[{default}][/dim]" if default is not None else ""
|
|
202
|
+
while True:
|
|
203
|
+
try:
|
|
204
|
+
val = Prompt.ask(f"[bold yellow]?[/bold yellow] {prompt}{suffix}")
|
|
205
|
+
except (EOFError, KeyboardInterrupt):
|
|
206
|
+
console.print()
|
|
207
|
+
sys.exit(1)
|
|
208
|
+
val = val.strip()
|
|
209
|
+
if val == "" and default is not None:
|
|
210
|
+
return default
|
|
211
|
+
if val:
|
|
212
|
+
return val
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def ask_yn(prompt, default=False):
|
|
216
|
+
label = "[bold green]Y[/bold green]/n" if default else "y/[bold red]N[/bold red]"
|
|
217
|
+
while True:
|
|
218
|
+
try:
|
|
219
|
+
ans = Prompt.ask(
|
|
220
|
+
f"[bold yellow]?[/bold yellow] {prompt} ([{label}])",
|
|
221
|
+
default="y" if default else "n",
|
|
222
|
+
).strip().lower()
|
|
223
|
+
except (EOFError, KeyboardInterrupt):
|
|
224
|
+
console.print()
|
|
225
|
+
sys.exit(1)
|
|
226
|
+
if ans in ("y", "yes"):
|
|
227
|
+
return True
|
|
228
|
+
if ans in ("n", "no"):
|
|
229
|
+
return False
|
|
230
|
+
console.print("[red] Please answer y or n.[/red]")
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def ask_int(prompt, default=1, lo=1, hi=20):
|
|
234
|
+
while True:
|
|
235
|
+
raw = ask(prompt, str(default))
|
|
236
|
+
try:
|
|
237
|
+
v = int(raw)
|
|
238
|
+
if lo <= v <= hi:
|
|
239
|
+
return v
|
|
240
|
+
except ValueError:
|
|
241
|
+
pass
|
|
242
|
+
console.print(f"[red] Enter a number between {lo} and {hi}.[/red]")
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def ask_path(prompt):
|
|
246
|
+
while True:
|
|
247
|
+
p = ask(prompt)
|
|
248
|
+
p = os.path.expanduser(p.strip("'\" "))
|
|
249
|
+
if os.path.isfile(p):
|
|
250
|
+
return p
|
|
251
|
+
console.print(f"[red] Not a file:[/red] [white]{p}[/white]")
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def parse_args():
|
|
255
|
+
p = argparse.ArgumentParser(
|
|
256
|
+
prog="pyprivate-obf",
|
|
257
|
+
description="Py Private v4.1.2 — Python source obfuscator.",
|
|
258
|
+
)
|
|
259
|
+
p.add_argument("input", nargs="?", help="path to the .py file to obfuscate")
|
|
260
|
+
p.add_argument("-o", "--output", help="output file (default: <input>.enc.py)")
|
|
261
|
+
p.add_argument("--encode-strings", action="store_true")
|
|
262
|
+
p.add_argument("--cpython", action="store_true")
|
|
263
|
+
p.add_argument("--bytecode", action="store_true")
|
|
264
|
+
p.add_argument("--marshal", action="store_true")
|
|
265
|
+
p.add_argument("--layers", type=int, default=1)
|
|
266
|
+
p.add_argument("--re-check-version", action="store_true")
|
|
267
|
+
p.add_argument("--expiry")
|
|
268
|
+
p.add_argument("--expiry-message", default="This file has expired.")
|
|
269
|
+
p.add_argument("--pip", nargs="+", metavar="PKG")
|
|
270
|
+
p.add_argument("--check-version", action="store_true")
|
|
271
|
+
p.add_argument("--telegram-bot-token")
|
|
272
|
+
p.add_argument("--telegram-channel-id")
|
|
273
|
+
p.add_argument("--channel", help="Telegram channel URL for EnterChannel guard")
|
|
274
|
+
p.add_argument("--interactive", action="store_true")
|
|
275
|
+
p.add_argument("--list", action="store_true", help="list all methods and layers")
|
|
276
|
+
return p.parse_args()
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def interactive_main():
|
|
280
|
+
print_banner()
|
|
281
|
+
|
|
282
|
+
console.print(Panel(
|
|
283
|
+
"[dim]Answer the prompts to configure your obfuscation job.[/dim]\n"
|
|
284
|
+
"[dim]Press Ctrl-C at any time to quit.[/dim]",
|
|
285
|
+
border_style="bright_black",
|
|
286
|
+
padding=(0, 2),
|
|
287
|
+
))
|
|
288
|
+
console.print()
|
|
289
|
+
|
|
290
|
+
in_path = ask_path("Input [bold].py[/bold] file path")
|
|
291
|
+
size = os.path.getsize(in_path)
|
|
292
|
+
console.print(Panel(
|
|
293
|
+
f"[bold green]{in_path}[/bold green] [dim]{size:,} bytes[/dim]",
|
|
294
|
+
title="[bold white]Loaded[/bold white]",
|
|
295
|
+
border_style="green",
|
|
296
|
+
padding=(0, 2),
|
|
297
|
+
))
|
|
298
|
+
console.print()
|
|
299
|
+
|
|
300
|
+
console.print(Rule("[bold cyan]Encoding Method[/bold cyan]", style="cyan"))
|
|
301
|
+
console.print()
|
|
302
|
+
encode_strings_opt = ask_yn("Encode strings [dim](bytes-decode trick)[/dim]", default=True)
|
|
303
|
+
bytecode_opt = ask_yn("Bytecode obfuscation [dim](marshal + opcode swap)[/dim]", default=True)
|
|
304
|
+
cpython_opt = ask_yn("CPython obfuscation [dim](Cython → C → gcc launcher)[/dim]", default=False)
|
|
305
|
+
marshal_opt = ask_yn("Simple marshal [dim](base64 exec loader)[/dim]", default=False)
|
|
306
|
+
layers = ask_int("Marshal layers [dim](1-20)[/dim]", default=1, lo=1, hi=20)
|
|
307
|
+
re_check_version = ask_yn("Re-prepend check_version inside each layer", default=False)
|
|
308
|
+
console.print()
|
|
309
|
+
|
|
310
|
+
console.print(Rule("[bold magenta]Protection Layers[/bold magenta]", style="magenta"))
|
|
311
|
+
console.print()
|
|
312
|
+
extras = []
|
|
313
|
+
|
|
314
|
+
if ask_yn("Add [bold]EXPIRY[/bold] guard", default=False):
|
|
315
|
+
ts = ask(" Expiry date [dim](YYYY-MM-DD HH:MM:SS)[/dim]",
|
|
316
|
+
datetime.now().replace(microsecond=0).isoformat(" "))
|
|
317
|
+
msg = ask(" Expiry message", "This file has expired.")
|
|
318
|
+
extras.append(ExpiryTime(time=ts + ".000000", message=msg))
|
|
319
|
+
|
|
320
|
+
if ask_yn("Add [bold]PIP INSTALLER[/bold] guard", default=False):
|
|
321
|
+
pkgs = ask(" Packages [dim](space-separated)[/dim]").split()
|
|
322
|
+
if pkgs:
|
|
323
|
+
extras.append(LibrariesInstaller(package_names=pkgs))
|
|
324
|
+
|
|
325
|
+
if ask_yn("Add [bold]CHECK VERSION[/bold] guard", default=False):
|
|
326
|
+
extras.append(CheckVersion())
|
|
327
|
+
|
|
328
|
+
if ask_yn("Add [bold]TELEGRAM CHANNEL[/bold] guard", default=False):
|
|
329
|
+
token = ask(" Bot token")
|
|
330
|
+
chan = ask(" Channel id [dim](e.g. @my_channel)[/dim]")
|
|
331
|
+
extras.append(EnterChannel(bot_token=token, channel_id=chan))
|
|
332
|
+
|
|
333
|
+
console.print()
|
|
334
|
+
|
|
335
|
+
default_out = os.path.splitext(in_path)[0] + ".enc.py"
|
|
336
|
+
out_path = os.path.expanduser(ask("Output file path", default_out))
|
|
337
|
+
|
|
338
|
+
console.print()
|
|
339
|
+
console.print(Rule("[bold white]Building[/bold white]", style="bright_black"))
|
|
340
|
+
console.print()
|
|
341
|
+
|
|
342
|
+
with open(in_path, encoding="utf-8") as f:
|
|
343
|
+
source = f.read()
|
|
344
|
+
|
|
345
|
+
try:
|
|
346
|
+
new_source = build(
|
|
347
|
+
source,
|
|
348
|
+
extras=extras,
|
|
349
|
+
encode_strings_opt=encode_strings_opt,
|
|
350
|
+
cpython=cpython_opt,
|
|
351
|
+
bytecode=bytecode_opt or marshal_opt,
|
|
352
|
+
layers=layers,
|
|
353
|
+
re_check_version=re_check_version,
|
|
354
|
+
)
|
|
355
|
+
except ImportError as e:
|
|
356
|
+
console.print(Panel(
|
|
357
|
+
f"[red]{e}[/red]\n"
|
|
358
|
+
+ ("[yellow]Install:[/yellow] pip install cython (and ensure gcc is in PATH)" if "Cython" in str(e) else ""),
|
|
359
|
+
title="[red]Import Error[/red]",
|
|
360
|
+
border_style="red",
|
|
361
|
+
))
|
|
362
|
+
sys.exit(1)
|
|
363
|
+
|
|
364
|
+
with open(out_path, "w", encoding="utf-8") as f:
|
|
365
|
+
f.write(new_source)
|
|
366
|
+
|
|
367
|
+
result = Table.grid(padding=(0, 2))
|
|
368
|
+
result.add_column(style="bold dim white", justify="right")
|
|
369
|
+
result.add_column(style="bold white")
|
|
370
|
+
result.add_row("Output", f"[bold green]{out_path}[/bold green]")
|
|
371
|
+
result.add_row("Size", f"[white]{len(new_source):,} bytes[/white]")
|
|
372
|
+
if extras:
|
|
373
|
+
result.add_row("Layers", f"[magenta]{len(extras)} guard(s) applied[/magenta]")
|
|
374
|
+
console.print(Panel(result, title="[bold green]Done[/bold green]",
|
|
375
|
+
border_style="green", padding=(1, 2)))
|
|
376
|
+
console.print()
|
|
377
|
+
console.print(f" [dim]Run:[/dim] [bold]python {out_path}[/bold]")
|
|
378
|
+
console.print()
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
def cli_main():
|
|
382
|
+
args = parse_args()
|
|
383
|
+
|
|
384
|
+
if args.list:
|
|
385
|
+
print_banner()
|
|
386
|
+
print_encoders_table()
|
|
387
|
+
print_layers_table()
|
|
388
|
+
return
|
|
389
|
+
|
|
390
|
+
if args.interactive or args.input is None:
|
|
391
|
+
interactive_main()
|
|
392
|
+
return
|
|
393
|
+
|
|
394
|
+
print_banner()
|
|
395
|
+
|
|
396
|
+
if not os.path.isfile(args.input):
|
|
397
|
+
console.print(Panel(
|
|
398
|
+
f"[red]File not found:[/red] [white]{args.input}[/white]",
|
|
399
|
+
title="[red]Error[/red]",
|
|
400
|
+
border_style="red",
|
|
401
|
+
))
|
|
402
|
+
sys.exit(1)
|
|
403
|
+
|
|
404
|
+
with open(args.input, encoding="utf-8") as f:
|
|
405
|
+
source = f.read()
|
|
406
|
+
|
|
407
|
+
extras = []
|
|
408
|
+
if args.expiry:
|
|
409
|
+
extras.append(ExpiryTime(time=args.expiry, message=args.expiry_message))
|
|
410
|
+
if args.pip:
|
|
411
|
+
extras.append(LibrariesInstaller(package_names=args.pip))
|
|
412
|
+
if args.check_version:
|
|
413
|
+
extras.append(CheckVersion())
|
|
414
|
+
if args.telegram_bot_token and args.telegram_channel_id:
|
|
415
|
+
extras.append(EnterChannel(
|
|
416
|
+
bot_token=args.telegram_bot_token,
|
|
417
|
+
channel_id=args.telegram_channel_id,
|
|
418
|
+
))
|
|
419
|
+
if args.channel:
|
|
420
|
+
extras.append(EnterChannel(channel_url=args.channel))
|
|
421
|
+
|
|
422
|
+
use_marshal = args.marshal and not (args.cpython or args.bytecode)
|
|
423
|
+
|
|
424
|
+
info = Table.grid(padding=(0, 2))
|
|
425
|
+
info.add_column(style="bold dim white", justify="right")
|
|
426
|
+
info.add_column(style="bold white")
|
|
427
|
+
info.add_row("Input", f"[green]{args.input}[/green]")
|
|
428
|
+
info.add_row("Method",
|
|
429
|
+
"[cyan]cpython[/cyan]" if args.cpython else
|
|
430
|
+
"[cyan]bytecode[/cyan]" if args.bytecode else
|
|
431
|
+
"[cyan]strings[/cyan]" if args.encode_strings else
|
|
432
|
+
"[cyan]marshal[/cyan]" if use_marshal else "[dim]default (marshal)[/dim]"
|
|
433
|
+
)
|
|
434
|
+
if extras:
|
|
435
|
+
info.add_row("Guards", f"[magenta]{', '.join(type(e).__name__ for e in extras)}[/magenta]")
|
|
436
|
+
console.print(Panel(info, title="[bold white]Job[/bold white]",
|
|
437
|
+
border_style="cyan", padding=(1, 2)))
|
|
438
|
+
console.print()
|
|
439
|
+
|
|
440
|
+
console.print("[dim] Encoding...[/dim]")
|
|
441
|
+
|
|
442
|
+
output_source = build(
|
|
443
|
+
source,
|
|
444
|
+
extras=extras,
|
|
445
|
+
encode_strings_opt=args.encode_strings,
|
|
446
|
+
cpython=args.cpython,
|
|
447
|
+
bytecode=args.bytecode,
|
|
448
|
+
layers=args.layers,
|
|
449
|
+
re_check_version=args.re_check_version,
|
|
450
|
+
)
|
|
451
|
+
|
|
452
|
+
out = args.output or (os.path.splitext(args.input)[0] + ".enc.py")
|
|
453
|
+
with open(out, "w", encoding="utf-8") as f:
|
|
454
|
+
f.write(output_source)
|
|
455
|
+
|
|
456
|
+
done = Table.grid(padding=(0, 2))
|
|
457
|
+
done.add_column(style="bold dim white", justify="right")
|
|
458
|
+
done.add_column(style="bold white")
|
|
459
|
+
done.add_row("Written", f"[bold green]{out}[/bold green]")
|
|
460
|
+
done.add_row("Size", f"[white]{len(output_source):,} bytes[/white]")
|
|
461
|
+
console.print(Panel(done, title="[bold green]Done[/bold green]",
|
|
462
|
+
border_style="green", padding=(1, 2)))
|
|
463
|
+
console.print()
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
def main():
|
|
467
|
+
try:
|
|
468
|
+
cli_main()
|
|
469
|
+
except KeyboardInterrupt:
|
|
470
|
+
console.print()
|
|
471
|
+
console.print("[dim]Aborted.[/dim]")
|
|
472
|
+
sys.exit(130)
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
if __name__ == "__main__":
|
|
476
|
+
main()
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import dis
|
|
2
|
+
import marshal
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
FAKE_CODE: str = """
|
|
6
|
+
foo = False
|
|
7
|
+
if foo:
|
|
8
|
+
bar = 1/0
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
HEX_CODE: str = "6401" "6402" "1b00" "5a01"
|
|
13
|
+
NEW_HEX_CODE: str = "6401" "6402" "8421" "5a01"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _swap_pattern():
|
|
17
|
+
LOAD_CONST = dis.opmap.get("LOAD_CONST")
|
|
18
|
+
STORE_NAME = dis.opmap.get("STORE_NAME")
|
|
19
|
+
MAKE_FUNCTION = dis.opmap.get("MAKE_FUNCTION")
|
|
20
|
+
if None in (LOAD_CONST, STORE_NAME, MAKE_FUNCTION):
|
|
21
|
+
return None, None
|
|
22
|
+
|
|
23
|
+
code = compile(FAKE_CODE, "<f>", "exec")
|
|
24
|
+
bc = code.co_code
|
|
25
|
+
consts = code.co_consts
|
|
26
|
+
names = code.co_names
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _find_int(seq, n):
|
|
30
|
+
for i, v in enumerate(seq):
|
|
31
|
+
if type(v) is int and v == n:
|
|
32
|
+
return i
|
|
33
|
+
return -1
|
|
34
|
+
|
|
35
|
+
try:
|
|
36
|
+
i_one = _find_int(consts, 1)
|
|
37
|
+
i_zero = _find_int(consts, 0)
|
|
38
|
+
i_bar = names.index("bar")
|
|
39
|
+
if i_one < 0 or i_zero < 0:
|
|
40
|
+
return None, None
|
|
41
|
+
except ValueError:
|
|
42
|
+
return None, None
|
|
43
|
+
|
|
44
|
+
needle = bytes([LOAD_CONST, i_one, LOAD_CONST, i_zero])
|
|
45
|
+
store_name = bytes([STORE_NAME, i_bar])
|
|
46
|
+
|
|
47
|
+
start = bc.find(needle)
|
|
48
|
+
if start < 0:
|
|
49
|
+
return None, None
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
end = bc.find(store_name, start + len(needle))
|
|
53
|
+
if end < 0:
|
|
54
|
+
return None, None
|
|
55
|
+
|
|
56
|
+
old = bytes(bc[start: end + len(store_name)])
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
middle_len = end - (start + len(needle))
|
|
60
|
+
if middle_len < 2:
|
|
61
|
+
return None, None
|
|
62
|
+
new_middle = bytes([MAKE_FUNCTION, 0x21]) + b"\x00" * (middle_len - 2)
|
|
63
|
+
new = needle + new_middle + store_name
|
|
64
|
+
|
|
65
|
+
if len(old) != len(new):
|
|
66
|
+
return None, None
|
|
67
|
+
return old, new
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def bytecode_obfuscation(source: str) -> str:
|
|
71
|
+
bytecode = marshal.dumps(compile(FAKE_CODE + source, "string", "exec"))
|
|
72
|
+
|
|
73
|
+
old, new = _swap_pattern()
|
|
74
|
+
if old is not None and old in bytecode:
|
|
75
|
+
bytecode = bytecode.replace(old, new)
|
|
76
|
+
|
|
77
|
+
return f"import marshal\nexec(marshal.loads({bytecode}))"
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
if __name__ == "__main__":
|
|
81
|
+
import sys
|
|
82
|
+
src = open(sys.argv[1]).read()
|
|
83
|
+
sys.stdout.write(bytecode_obfuscation(src))
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import io
|
|
2
|
+
import os
|
|
3
|
+
import re
|
|
4
|
+
from contextlib import redirect_stderr
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
|
|
7
|
+
from Cython.Compiler.CmdLine import parse_command_line
|
|
8
|
+
from Cython.Compiler import Main
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def C_SOURCE(c_source: str) -> str:
|
|
12
|
+
return r"""
|
|
13
|
+
import os
|
|
14
|
+
import sys
|
|
15
|
+
|
|
16
|
+
PSH_TEAM_KEY = "بخ 👀"
|
|
17
|
+
|
|
18
|
+
EXECUTE_FILE = ".PY_PRIVATE/%s"
|
|
19
|
+
PREFIX = sys.prefix
|
|
20
|
+
EXPORT_PYTHONHOME = 'export PYTHONHOME='+PREFIX
|
|
21
|
+
EXPORT_PYTHON_EXECUTABLE = 'export PYTHON_EXECUTABLE='+sys.executable
|
|
22
|
+
|
|
23
|
+
RUN = "./"+EXECUTE_FILE
|
|
24
|
+
|
|
25
|
+
if os.path.isfile(EXECUTE_FILE):
|
|
26
|
+
os.system(EXPORT_PYTHONHOME+" && "+EXPORT_PYTHON_EXECUTABLE+" && "+RUN)
|
|
27
|
+
exit(0)
|
|
28
|
+
|
|
29
|
+
C_SOURCE = r'''%s'''
|
|
30
|
+
C_FILE = ".py_private.c"
|
|
31
|
+
PYTHON_VERSION = ".".join(sys.version.split(" ")[0].split(".")[:-1])
|
|
32
|
+
COMPILE_FILE = (
|
|
33
|
+
'gcc -I' +
|
|
34
|
+
PREFIX +
|
|
35
|
+
'/include/python' +
|
|
36
|
+
PYTHON_VERSION +
|
|
37
|
+
' -o ' +
|
|
38
|
+
EXECUTE_FILE +
|
|
39
|
+
' ' +
|
|
40
|
+
C_FILE +
|
|
41
|
+
' -L' +
|
|
42
|
+
PREFIX +
|
|
43
|
+
'/lib -lpython' +
|
|
44
|
+
PYTHON_VERSION
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
with open(C_FILE, "w") as f:
|
|
49
|
+
f.write(C_SOURCE)
|
|
50
|
+
|
|
51
|
+
os.makedirs(os.path.dirname(EXECUTE_FILE), exist_ok=True)
|
|
52
|
+
os.system(EXPORT_PYTHONHOME+" && "+EXPORT_PYTHON_EXECUTABLE+" && "+COMPILE_FILE+" && "+RUN)
|
|
53
|
+
|
|
54
|
+
os.remove(C_FILE)
|
|
55
|
+
""" % (datetime.now().strftime("%Y%m%d%H%M%S%f")[:-3], c_source)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _compile_single(input_file: str, option) -> None:
|
|
59
|
+
try:
|
|
60
|
+
Main.compile_single(input_file, option)
|
|
61
|
+
except TypeError:
|
|
62
|
+
|
|
63
|
+
Main.compile_single(input_file, option, full_module_name=None)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def cpython_obfuscation(
|
|
67
|
+
source: str,
|
|
68
|
+
input_file: str = "input.py",
|
|
69
|
+
output_file: str = "input.c",
|
|
70
|
+
) -> str:
|
|
71
|
+
|
|
72
|
+
with open(input_file, "w") as f:
|
|
73
|
+
f.write(source)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
option, _ = parse_command_line(
|
|
77
|
+
["-o", output_file, "-3", "--embed", input_file, "-f"]
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
f = io.StringIO()
|
|
82
|
+
with redirect_stderr(f):
|
|
83
|
+
_compile_single(input_file, option)
|
|
84
|
+
errors = f.getvalue()
|
|
85
|
+
if errors.strip() != "":
|
|
86
|
+
print(errors)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
comment_pattern = re.compile(
|
|
90
|
+
r"/\*\s*" + f'"{input_file}"' + r"\s*.*?\*/", re.DOTALL
|
|
91
|
+
)
|
|
92
|
+
cython_header_pattern = re.compile(
|
|
93
|
+
r"/\*\s*Generated by Cython\s+[\d.A-Za-z+-]+\s*\*/"
|
|
94
|
+
)
|
|
95
|
+
with open(output_file, "r") as f:
|
|
96
|
+
c_source = f.read()
|
|
97
|
+
c_source = re.sub(comment_pattern, "", c_source)
|
|
98
|
+
c_source = re.sub(cython_header_pattern, "", c_source).strip()
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
os.remove(input_file)
|
|
102
|
+
os.remove(output_file)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
return C_SOURCE(c_source)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
if __name__ == "__main__":
|
|
109
|
+
import sys
|
|
110
|
+
src = open(sys.argv[1]).read()
|
|
111
|
+
sys.stdout.write(cpython_obfuscation(src))
|