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.
@@ -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
+ ]
@@ -0,0 +1,4 @@
1
+ from .cli import main
2
+
3
+ if __name__ == "__main__":
4
+ main()
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,5 @@
1
+ from .encode_strings import encode_strings
2
+ from .bytecode_obfuscation import bytecode_obfuscation
3
+ from .simple_marshal import simple_marshal
4
+
5
+ __all__ = ["encode_strings", "bytecode_obfuscation", "simple_marshal", "cpython_obfuscation"]
@@ -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))