PyProtect-v3 3.0.0__tar.gz → 3.1.0__tar.gz

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.
Files changed (21) hide show
  1. {pyprotect_v3-3.0.0 → pyprotect_v3-3.1.0}/PKG-INFO +1 -1
  2. {pyprotect_v3-3.0.0 → pyprotect_v3-3.1.0}/PyProtect_v3.egg-info/PKG-INFO +1 -1
  3. {pyprotect_v3-3.0.0 → pyprotect_v3-3.1.0}/pyproject.toml +1 -1
  4. {pyprotect_v3-3.0.0 → pyprotect_v3-3.1.0}/pyprotect/__main__.py +0 -2
  5. {pyprotect_v3-3.0.0 → pyprotect_v3-3.1.0}/pyprotect/engine.c +172 -6
  6. {pyprotect_v3-3.0.0 → pyprotect_v3-3.1.0}/pyprotect/obfuscator.py +8 -67
  7. {pyprotect_v3-3.0.0 → pyprotect_v3-3.1.0}/MANIFEST.in +0 -0
  8. {pyprotect_v3-3.0.0 → pyprotect_v3-3.1.0}/PyProtect_v3.egg-info/SOURCES.txt +0 -0
  9. {pyprotect_v3-3.0.0 → pyprotect_v3-3.1.0}/PyProtect_v3.egg-info/dependency_links.txt +0 -0
  10. {pyprotect_v3-3.0.0 → pyprotect_v3-3.1.0}/PyProtect_v3.egg-info/entry_points.txt +0 -0
  11. {pyprotect_v3-3.0.0 → pyprotect_v3-3.1.0}/PyProtect_v3.egg-info/requires.txt +0 -0
  12. {pyprotect_v3-3.0.0 → pyprotect_v3-3.1.0}/PyProtect_v3.egg-info/top_level.txt +0 -0
  13. {pyprotect_v3-3.0.0 → pyprotect_v3-3.1.0}/README.md +0 -0
  14. {pyprotect_v3-3.0.0 → pyprotect_v3-3.1.0}/pyprotect/__init__.py +0 -0
  15. {pyprotect_v3-3.0.0 → pyprotect_v3-3.1.0}/pyprotect/_key.py +0 -0
  16. {pyprotect_v3-3.0.0 → pyprotect_v3-3.1.0}/pyprotect/anti_debug.py +0 -0
  17. {pyprotect_v3-3.0.0 → pyprotect_v3-3.1.0}/pyprotect/crypto.py +0 -0
  18. {pyprotect_v3-3.0.0 → pyprotect_v3-3.1.0}/pyprotect/engine.py +0 -0
  19. {pyprotect_v3-3.0.0 → pyprotect_v3-3.1.0}/pyprotect/hooks.py +0 -0
  20. {pyprotect_v3-3.0.0 → pyprotect_v3-3.1.0}/setup.cfg +0 -0
  21. {pyprotect_v3-3.0.0 → pyprotect_v3-3.1.0}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyProtect-v3
3
- Version: 3.0.0
3
+ Version: 3.1.0
4
4
  Summary: Python script obfuscation library with AES-256-GCM encryption and C extension acceleration
5
5
  Author-email: NguyenNgocNam <c.nam020213@gmail.com>
6
6
  License-Expression: MIT
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyProtect-v3
3
- Version: 3.0.0
3
+ Version: 3.1.0
4
4
  Summary: Python script obfuscation library with AES-256-GCM encryption and C extension acceleration
5
5
  Author-email: NguyenNgocNam <c.nam020213@gmail.com>
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "PyProtect-v3"
7
- version = "3.0.0"
7
+ version = "3.1.0"
8
8
  description = "Python script obfuscation library with AES-256-GCM encryption and C extension acceleration"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.8"
@@ -15,7 +15,6 @@ def cmd_encrypt(args):
15
15
  )
16
16
  out_path = obf.encrypt_file(args.input)
17
17
  print(f"[+] Encrypted: {args.input} -> {out_path}")
18
- _copy_engine(args.output_dir)
19
18
 
20
19
 
21
20
  def cmd_encrypt_dir(args):
@@ -29,7 +28,6 @@ def cmd_encrypt_dir(args):
29
28
  print(f"[+] Encrypted {len(out_files)} files from {args.input}")
30
29
  for f in out_files:
31
30
  print(f" -> {f}")
32
- _copy_engine(args.output_dir)
33
31
 
34
32
 
35
33
  def cmd_build_engine(args):
@@ -298,6 +298,86 @@ static int deep_anti_debug(void) {
298
298
  return detected;
299
299
  }
300
300
 
301
+ /* ------------------------------------------------------------------ */
302
+ /* Anti-hook — detect debugger trace, profile, suspicious import hooks */
303
+ /* ------------------------------------------------------------------ */
304
+
305
+ static int deep_anti_hook(void) {
306
+ int detected = 0;
307
+ PyObject *sys_mod = PyImport_AddModule("sys");
308
+ if (!sys_mod) return 0;
309
+ PyObject *f = PyObject_GetAttrString(sys_mod, "gettrace");
310
+ if (f) {
311
+ PyObject *v = PyObject_CallNoArgs(f);
312
+ if (v && v != Py_None) detected = 1;
313
+ Py_XDECREF(v); Py_DECREF(f);
314
+ }
315
+ f = PyObject_GetAttrString(sys_mod, "getprofile");
316
+ if (f) {
317
+ PyObject *v = PyObject_CallNoArgs(f);
318
+ if (v && v != Py_None) detected = 1;
319
+ Py_XDECREF(v); Py_DECREF(f);
320
+ }
321
+ /* Check meta_path for known hostile importers */
322
+ PyObject *meta_path = PySys_GetObject("meta_path");
323
+ if (meta_path && PyList_Check(meta_path)) {
324
+ Py_ssize_t n = PyList_Size(meta_path);
325
+ for (Py_ssize_t i = 0; i < n; i++) {
326
+ PyObject *item = PyList_GetItem(meta_path, i);
327
+ if (!item) continue;
328
+ PyObject *name = PyObject_GetAttrString(item, "__class__");
329
+ if (!name) { PyErr_Clear(); continue; }
330
+ PyObject *cname = PyObject_GetAttrString(name, "__name__");
331
+ Py_DECREF(name);
332
+ if (!cname) { PyErr_Clear(); continue; }
333
+ const char *s = PyUnicode_AsUTF8(cname);
334
+ if (s) {
335
+ if (strstr(s, "Hook") || strstr(s, "Redirect") ||
336
+ strstr(s, "Monitor") || strstr(s, "Interceptor") ||
337
+ strstr(s, "Tracer") || strstr(s, "Proxy") ||
338
+ strstr(s, "Debug") || strstr(s, "Inject")) {
339
+ detected = 1;
340
+ Py_DECREF(cname);
341
+ break;
342
+ }
343
+ }
344
+ Py_DECREF(cname);
345
+ }
346
+ }
347
+ return detected;
348
+ }
349
+
350
+ /* ------------------------------------------------------------------ */
351
+ /* Anti-decode — block decompilers, profilers, debugger modules */
352
+ /* ------------------------------------------------------------------ */
353
+
354
+ static int deep_anti_decode(void) {
355
+ int detected = 0;
356
+ PyObject *sys_mod = PyImport_AddModule("sys");
357
+ if (sys_mod) {
358
+ PyObject *modules = PyObject_GetAttrString(sys_mod, "modules");
359
+ if (modules && PyDict_Check(modules)) {
360
+ const char *sus[] = {
361
+ "decompyle3","uncompyle6","decompyle","frida",
362
+ "pyrasite","pydevd","pdb","ipdb",
363
+ "pyinstrument","cProfile","line_profiler",NULL
364
+ };
365
+ for (int i = 0; sus[i]; i++) {
366
+ PyObject *k = PyUnicode_FromString(sus[i]);
367
+ if (k) {
368
+ if (PyDict_GetItem(modules, k)) { detected = 1; Py_DECREF(k); break; }
369
+ Py_DECREF(k);
370
+ }
371
+ }
372
+ }
373
+ Py_XDECREF(modules);
374
+ }
375
+ if (getenv("PYTHONDEVMODE")) detected = 1;
376
+ if (getenv("PYTHONINSPECT")) detected = 1;
377
+ if (getenv("PYSEC")) detected = 1;
378
+ return detected;
379
+ }
380
+
301
381
  /* ------------------------------------------------------------------ */
302
382
  /* Memory protection: re-encrypt buffer after use */
303
383
  /* ------------------------------------------------------------------ */
@@ -382,6 +462,77 @@ static int _decrypt_payload(const uint8_t *data, size_t data_len,
382
462
  return 0;
383
463
  }
384
464
 
465
+ /* ------------------------------------------------------------------ */
466
+ /* Frame evaluation hook — anti-tamper check on every function call */
467
+ /* We use the CPython 3.11+ internal API to intercept frame evaluation */
468
+ /* ------------------------------------------------------------------ */
469
+
470
+ /* These are declared in cpython/pystate.h and cpython/ceval.h
471
+ which are included by Python.h */
472
+
473
+ static _PyFrameEvalFunction original_eval_frame = NULL;
474
+ static PyInterpreterState *hooked_interp = NULL;
475
+
476
+ static PyObject* protected_eval_frame(PyThreadState *tstate, struct _PyInterpreterFrame *frame, int throwflag) {
477
+ if (deep_anti_debug() || deep_anti_hook() || deep_anti_decode()) {
478
+ PyErr_SetString(PyExc_RuntimeError, "Tamper detected");
479
+ return NULL;
480
+ }
481
+ return _PyEval_EvalFrameDefault(tstate, frame, throwflag);
482
+ }
483
+
484
+ static void install_frame_hook(void) {
485
+ if (original_eval_frame) return;
486
+ PyInterpreterState *interp = PyInterpreterState_Get();
487
+ if (!interp) return;
488
+ _PyFrameEvalFunction cur = _PyInterpreterState_GetEvalFrameFunc(interp);
489
+ if (cur == protected_eval_frame) return;
490
+ original_eval_frame = cur;
491
+ hooked_interp = interp;
492
+ _PyInterpreterState_SetEvalFrameFunc(interp, protected_eval_frame);
493
+ }
494
+
495
+ static void restore_frame_hook(void) {
496
+ if (!original_eval_frame || !hooked_interp) return;
497
+ _PyInterpreterState_SetEvalFrameFunc(hooked_interp, original_eval_frame);
498
+ original_eval_frame = NULL;
499
+ hooked_interp = NULL;
500
+ }
501
+
502
+ /* ------------------------------------------------------------------ */
503
+ /* Wipe code tree — recursively clear all code objects' bytecodes */
504
+ /* ------------------------------------------------------------------ */
505
+
506
+ static void wipe_code_tree(PyObject *co) {
507
+ if (!co || !PyCode_Check(co)) return;
508
+ PyCodeObject *code = (PyCodeObject*)co;
509
+
510
+ PyObject *bc = PyCode_GetCode(code);
511
+ if (bc && PyBytes_Check(bc)) {
512
+ Py_ssize_t sz;
513
+ char *buf;
514
+ if (PyBytes_AsStringAndSize(bc, &buf, &sz) == 0 && buf && sz > 0) {
515
+ memset(buf, 0, (size_t)sz);
516
+ }
517
+ }
518
+ Py_XDECREF(bc);
519
+
520
+ PyObject *consts = code->co_consts;
521
+ if (consts && PyTuple_Check(consts)) {
522
+ Py_ssize_t n = PyTuple_Size(consts);
523
+ for (Py_ssize_t i = 0; i < n; i++) {
524
+ PyObject *item = PyTuple_GetItem(consts, i);
525
+ if (item && PyCode_Check(item)) {
526
+ wipe_code_tree(item);
527
+ }
528
+ }
529
+ }
530
+ }
531
+
532
+ /* ------------------------------------------------------------------ */
533
+ /* Decrypt and exec */
534
+ /* ------------------------------------------------------------------ */
535
+
385
536
  static PyObject* engine_decrypt_and_exec(PyObject *self, PyObject *args) {
386
537
  const uint8_t *data;
387
538
  Py_ssize_t data_len;
@@ -389,8 +540,8 @@ static PyObject* engine_decrypt_and_exec(PyObject *self, PyObject *args) {
389
540
  if (!PyArg_ParseTuple(args, "y#", &data, &data_len))
390
541
  return NULL;
391
542
 
392
- if (deep_anti_debug()) {
393
- PyErr_SetString(PyExc_RuntimeError, "Debugger detected");
543
+ if (deep_anti_debug() || deep_anti_hook() || deep_anti_decode()) {
544
+ PyErr_SetString(PyExc_RuntimeError, "Tamper detected");
394
545
  return NULL;
395
546
  }
396
547
 
@@ -462,15 +613,25 @@ static PyObject* engine_decrypt_and_exec(PyObject *self, PyObject *args) {
462
613
  return NULL;
463
614
  }
464
615
 
616
+ /* Install frame eval hook */
617
+ install_frame_hook();
618
+
465
619
  PyObject *builtins = PyEval_GetBuiltins();
466
- if (!builtins) { Py_DECREF(code_obj); return NULL; }
620
+ if (!builtins) { restore_frame_hook(); Py_DECREF(code_obj); return NULL; }
467
621
 
468
622
  PyObject *globals = PyDict_New();
469
- if (!globals) { Py_DECREF(code_obj); return NULL; }
623
+ if (!globals) { restore_frame_hook(); Py_DECREF(code_obj); return NULL; }
470
624
 
471
625
  PyDict_SetItemString(globals, "__builtins__", builtins);
472
626
 
473
627
  PyObject *result = PyEval_EvalCode((PyObject*)code_obj, globals, globals);
628
+
629
+ /* Restore frame hook */
630
+ restore_frame_hook();
631
+
632
+ /* Wipe all decrypted bytecodes from memory */
633
+ wipe_code_tree(code_obj);
634
+
474
635
  Py_DECREF(code_obj);
475
636
  Py_DECREF(globals);
476
637
 
@@ -489,6 +650,11 @@ static PyObject* engine_decrypt_and_get_code(PyObject *self, PyObject *args) {
489
650
  if (!PyArg_ParseTuple(args, "y#", &data, &data_len))
490
651
  return NULL;
491
652
 
653
+ if (deep_anti_debug() || deep_anti_hook() || deep_anti_decode()) {
654
+ PyErr_SetString(PyExc_RuntimeError, "Tamper detected");
655
+ return NULL;
656
+ }
657
+
492
658
  uint8_t iv[12];
493
659
  uint8_t *plain = NULL;
494
660
  size_t plain_len = 0;
@@ -554,14 +720,14 @@ static PyObject* engine_decrypt_and_get_code(PyObject *self, PyObject *args) {
554
720
  /* ------------------------------------------------------------------ */
555
721
 
556
722
  static PyMethodDef EngineMethods[] = {
557
- {"decrypt_and_exec", engine_decrypt_and_exec, METH_VARARGS, "Decrypt and execute protected payload."},
723
+ {"decrypt_and_exec", engine_decrypt_and_exec, METH_VARARGS, "Decrypt+execute with frame hook + anti-tamper + memory wipe."},
558
724
  {"decrypt_and_get_code", engine_decrypt_and_get_code, METH_VARARGS, "Decrypt and return code object."},
559
725
  {NULL, NULL, 0, NULL}
560
726
  };
561
727
 
562
728
  static struct PyModuleDef enginemodule = {
563
729
  PyModuleDef_HEAD_INIT, "engine_c",
564
- "pyprotect-v4 engine - embedded key, anti-debug, anti-dump",
730
+ "pyprotect-v4 engine - frame hook, anti-tamper, anti-dump",
565
731
  -1, EngineMethods
566
732
  };
567
733
 
@@ -21,36 +21,13 @@ except ImportError:
21
21
  EMBEDDED_KEY = b'\xa1\xb2\xc3\xd4\xe5\xf6\x07\x18\x29\x3a\x4b\x5c\x6d\x7e\x8f\x90\x01\x23\x45\x67\x89\xab\xcd\xef\xfe\xdc\xba\x98\x76\x54\x32\x10'
22
22
 
23
23
 
24
- BOOTSTRAP_TEMPLATE = '''"""Protected script - generated by pyprotect-v3 v{version}."""
25
- import sys
26
- import os
27
-
28
- {payload_var} = {payload!r}
29
-
30
- def _exec_protected():
31
- import importlib.util
32
- _d = os.path.dirname(os.path.abspath(__file__))
33
- for _f in os.listdir(_d):
34
- if _f.startswith('engine_c') and any(_f.endswith(e) for e in ('.so', '.pyd', '.dll')):
35
- _spec = importlib.util.spec_from_file_location('engine_c', os.path.join(_d, _f))
36
- if _spec:
37
- _mod = importlib.util.module_from_spec(_spec)
38
- _spec.loader.exec_module(_mod)
39
- return _mod.decrypt_and_exec({payload_var})
40
- from pyprotect.engine_c import decrypt_and_exec as _e
41
- return _e({payload_var})
42
-
43
- if __name__ == "__main__":
44
- try:
45
- _exec_protected()
46
- except BaseException:
47
- sys.stderr.write("[-] C engine not available. Install pyprotect-v3 with C extension.\\n")
48
- sys.exit(1)
49
- '''
24
+ BOOTSTRAP_TEMPLATE = '''# PyProtect-v3 | github.com/PyProtect/PyProtect
25
+ from pyprotect.engine_c import decrypt_and_exec; decrypt_and_exec({payload!r})'''
50
26
 
51
27
  MULTI_BOOTSTRAP_TEMPLATE = '''"""Protected package - generated by pyprotect-v3 v{version}."""
52
28
  import sys
53
29
  import os
30
+
54
31
  import importlib.abc
55
32
  import importlib.machinery
56
33
  import types
@@ -288,41 +265,9 @@ def _add_junk_imports(tree):
288
265
  # ---------------------------------------------------------------------------
289
266
 
290
267
  def _generate_polymorphic_stub(payload_expr: str) -> str:
291
- """Generate a polymorphic stub. No key in stub — key is in .so binary."""
292
- used = set(_BAD_NAMES)
293
- pname = _random_name(used)
294
-
295
- stub = f'''"""Protected - {base64.b64encode(os.urandom(8)).decode()}"""
296
- import sys
297
- import os
298
-
299
- {pname} = {payload_expr}
300
-
301
- if __name__ == "__main__":
302
- try:
303
- import importlib.util
304
- _d = os.path.dirname(os.path.abspath(__file__))
305
- _ok = False
306
- for _f in os.listdir(_d):
307
- if _f.startswith('engine_c') and any(_f.endswith(e) for e in ('.so', '.pyd', '.dll')):
308
- _spec = importlib.util.spec_from_file_location('engine_c', os.path.join(_d, _f))
309
- if _spec:
310
- _mod = importlib.util.module_from_spec(_spec)
311
- _spec.loader.exec_module(_mod)
312
- _mod.decrypt_and_exec({pname})
313
- _ok = True
314
- break
315
- if not _ok:
316
- from pyprotect.engine_c import decrypt_and_exec as _e
317
- _e({pname})
318
- _ok = True
319
- except SystemExit:
320
- raise
321
- except BaseException:
322
- if not _ok:
323
- sys.stderr.write("[-] C engine not available. Install pyprotect-v3 with C extension.\\n")
324
- sys.exit(1)
325
- '''
268
+ """Generate a compact stub. No key in stub — key is in .so binary."""
269
+ stub = f'''# PyProtect-v3 | github.com/PyProtect/PyProtect
270
+ from pyprotect.engine_c import decrypt_and_exec; decrypt_and_exec({payload_expr})'''
326
271
  return stub
327
272
 
328
273
  # ---------------------------------------------------------------------------
@@ -434,7 +379,7 @@ class Obfuscator:
434
379
 
435
380
  # Generate multi bootstrap
436
381
  bootstrap_source = MULTI_BOOTSTRAP_TEMPLATE.format(
437
- version="3.0.0",
382
+ version="3.1.0",
438
383
  modules=modules,
439
384
  )
440
385
 
@@ -467,14 +412,12 @@ class Obfuscator:
467
412
  )
468
413
  os.makedirs(output_dir, exist_ok=True)
469
414
 
470
- payload_var = random.choice(string.ascii_letters) + "_" + base64.b16encode(os.urandom(6)).decode()
471
415
  if self.polymorphic:
472
416
  stub_source = _generate_polymorphic_stub(repr(encrypted))
473
417
  else:
474
418
  stub_source = BOOTSTRAP_TEMPLATE.format(
475
- version="3.0.0",
419
+ version="3.1.0",
476
420
  payload=encrypted,
477
- payload_var=f"_{payload_var}",
478
421
  )
479
422
 
480
423
  stub_name = original_filename.replace(".py", "_protected.py")
@@ -485,8 +428,6 @@ class Obfuscator:
485
428
 
486
429
  os.chmod(stub_path, 0o755)
487
430
 
488
- self._copy_engine(output_dir)
489
-
490
431
  return stub_path
491
432
 
492
433
  def _copy_engine(self, output_dir: str):
File without changes
File without changes
File without changes
File without changes