pylockware 2.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.
- pylockware/__init__.py +79 -0
- pylockware/anti_debug/__init__.py +15 -0
- pylockware/anti_debug/antidebug_crossplatform.py +508 -0
- pylockware/anti_debug/antidebug_llvm.py +1001 -0
- pylockware/cli/__init__.py +3 -0
- pylockware/cli/build.py +173 -0
- pylockware/cli/main.py +129 -0
- pylockware/core/__init__.py +9 -0
- pylockware/core/module_base.py +69 -0
- pylockware/core/module_manager.py +98 -0
- pylockware/core/name_generator.py +72 -0
- pylockware/core/obfuscator.py +321 -0
- pylockware/decorators.py +101 -0
- pylockware/gui/__init__.py +3 -0
- pylockware/gui/main.py +28 -0
- pylockware/gui/obfuscator_gui.py +789 -0
- pylockware/modules/__init__.py +19 -0
- pylockware/modules/anti_debug_module.py +123 -0
- pylockware/modules/builtin_dispatcher_module.py +214 -0
- pylockware/modules/call_obf_module.py +298 -0
- pylockware/modules/decorator_obf_module.py +71 -0
- pylockware/modules/disable_traceback_module.py +97 -0
- pylockware/modules/import_obf_module.py +189 -0
- pylockware/modules/junk_code_module.py +82 -0
- pylockware/modules/nuitka_builder_module.py +502 -0
- pylockware/modules/number_obf_module.py +92 -0
- pylockware/modules/remap_module.py +328 -0
- pylockware/modules/remove_annotations_module.py +197 -0
- pylockware/modules/state_machine_module.py +100 -0
- pylockware/modules/string_protect_module.py +73 -0
- pylockware/modules/type_annotation_obf_module.py +71 -0
- pylockware/modules/virtualization_module.py +410 -0
- pylockware/sdk/__init__.py +19 -0
- pylockware/sdk/builder.py +228 -0
- pylockware/sdk/config.py +477 -0
- pylockware/transforms/__init__.py +10 -0
- pylockware/transforms/annotation_aware_transformer.py +123 -0
- pylockware/transforms/builtin_dispatcher.py +197 -0
- pylockware/transforms/decorator_obf.py +195 -0
- pylockware/transforms/junk_code_transformer.py +703 -0
- pylockware/transforms/num_obf.py +560 -0
- pylockware/transforms/remap_transformer.py +234 -0
- pylockware/transforms/state_machine_transformer.py +962 -0
- pylockware/transforms/str_prot.py +383 -0
- pylockware/transforms/type_annotation_obf.py +181 -0
- pylockware/vendor/__init__.py +3 -0
- pylockware/vendor/customvm/README.md +27 -0
- pylockware/vendor/customvm/__init__.py +8 -0
- pylockware/vendor/customvm/builder.py +293 -0
- pylockware/vendor/customvm/compiler.py +970 -0
- pylockware/vendor/customvm/crypto.py +84 -0
- pylockware/vendor/customvm/loader.py +167 -0
- pylockware/vendor/customvm/opcodes.py +97 -0
- pylockware/vendor/customvm/vm.py +554 -0
- pylockware-2.0.0.dist-info/METADATA +644 -0
- pylockware-2.0.0.dist-info/RECORD +59 -0
- pylockware-2.0.0.dist-info/WHEEL +5 -0
- pylockware-2.0.0.dist-info/entry_points.txt +6 -0
- pylockware-2.0.0.dist-info/top_level.txt +1 -0
pylockware/__init__.py
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PyLockWare - Python Code Protection SDK
|
|
3
|
+
A comprehensive SDK for protecting Python code with multiple obfuscation layers.
|
|
4
|
+
|
|
5
|
+
Usage:
|
|
6
|
+
# Using decorators
|
|
7
|
+
from pylockware import external, skip_obf, virtualize
|
|
8
|
+
|
|
9
|
+
@external
|
|
10
|
+
def public_api():
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
@skip_obf
|
|
14
|
+
def debug_function():
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
@virtualize
|
|
18
|
+
def secret_algorithm():
|
|
19
|
+
# Converted to CustomVM bytecode
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
# Using SDK
|
|
23
|
+
from pylockware.sdk import Builder, BuildConfig
|
|
24
|
+
|
|
25
|
+
config = BuildConfig(entry_point="main.py", virtualization=True)
|
|
26
|
+
builder = Builder(config)
|
|
27
|
+
builder.build()
|
|
28
|
+
|
|
29
|
+
# Using pyproject.toml
|
|
30
|
+
from pylockware.sdk import Builder
|
|
31
|
+
|
|
32
|
+
builder = Builder.from_pyproject()
|
|
33
|
+
builder.build()
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
# Core components
|
|
37
|
+
from .core import ModuleBase, PyObfuscator
|
|
38
|
+
|
|
39
|
+
# Modules
|
|
40
|
+
from .modules import (
|
|
41
|
+
RemapModule,
|
|
42
|
+
StringProtectModule,
|
|
43
|
+
NumberObfModule,
|
|
44
|
+
AntiDebugModule,
|
|
45
|
+
ImportObfuscateModule,
|
|
46
|
+
StateMachineModule
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
# SDK components
|
|
50
|
+
from .decorators import external, skip_obf, preserve_name, virtualize
|
|
51
|
+
from .sdk import Builder, BuildConfig, load_config, save_config, init_config
|
|
52
|
+
|
|
53
|
+
__version__ = "3.0.0"
|
|
54
|
+
__all__ = [
|
|
55
|
+
# Core
|
|
56
|
+
'ModuleBase',
|
|
57
|
+
'PyObfuscator',
|
|
58
|
+
|
|
59
|
+
# Modules
|
|
60
|
+
'RemapModule',
|
|
61
|
+
'StringProtectModule',
|
|
62
|
+
'NumberObfModule',
|
|
63
|
+
'AntiDebugModule',
|
|
64
|
+
'ImportObfuscateModule',
|
|
65
|
+
'StateMachineModule',
|
|
66
|
+
|
|
67
|
+
# SDK
|
|
68
|
+
'Builder',
|
|
69
|
+
'BuildConfig',
|
|
70
|
+
'load_config',
|
|
71
|
+
'save_config',
|
|
72
|
+
'init_config',
|
|
73
|
+
|
|
74
|
+
# Decorators
|
|
75
|
+
'external',
|
|
76
|
+
'skip_obf',
|
|
77
|
+
'preserve_name',
|
|
78
|
+
'virtualize',
|
|
79
|
+
]
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PyLockWare Anti-Debug Module
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from pylockware.anti_debug.antidebug_llvm import AntiDebugEngine, init, check, guard, monitor
|
|
6
|
+
from pylockware.anti_debug.antidebug_crossplatform import AntiDebugCrossPlatform
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
'AntiDebugEngine',
|
|
10
|
+
'AntiDebugCrossPlatform',
|
|
11
|
+
'init',
|
|
12
|
+
'check',
|
|
13
|
+
'guard',
|
|
14
|
+
'monitor',
|
|
15
|
+
]
|
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PyLockWare AntiDebug Engine (Cross-Platform)
|
|
3
|
+
=============================================
|
|
4
|
+
Кроссплатформенный антиотладочный модуль для Python.
|
|
5
|
+
Работает на Windows, Linux и macOS.
|
|
6
|
+
|
|
7
|
+
Проверки:
|
|
8
|
+
1. Python-отладчики — sys.gettrace, sys.monitoring, загруженные модули
|
|
9
|
+
2. Python-потоки — поиск потоков отладчиков (pydevd, debugpy)
|
|
10
|
+
3. Переменные окружения — PYTHONDEBUG, PYTHONTRACEMODULESHACK и т.д.
|
|
11
|
+
4. Файловые дескрипторы — обнаружение отладочных сокетов
|
|
12
|
+
5. procfs (Linux) — /proc/self/status, /proc/self/wchan
|
|
13
|
+
6. lsof/netstat — поиск отладочных портов
|
|
14
|
+
|
|
15
|
+
При срабатывании любой проверки — выводит причину в stderr и вызывает os._exit(1).
|
|
16
|
+
|
|
17
|
+
Зависимости: psutil (опционально для расширенных проверок)
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import ctypes
|
|
23
|
+
import os
|
|
24
|
+
import platform
|
|
25
|
+
import re
|
|
26
|
+
import socket
|
|
27
|
+
import sys
|
|
28
|
+
import threading
|
|
29
|
+
import time
|
|
30
|
+
from dataclasses import dataclass, field
|
|
31
|
+
from typing import List, Set, Optional
|
|
32
|
+
|
|
33
|
+
# ---------------------------------------------------------------------------
|
|
34
|
+
# Python C-API для callback (печать причины + выход)
|
|
35
|
+
# ---------------------------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
_PySys_WriteStderr = ctypes.pythonapi.PySys_WriteStderr
|
|
38
|
+
_PySys_WriteStderr.argtypes = [ctypes.c_char_p]
|
|
39
|
+
_PySys_WriteStderr.restype = None
|
|
40
|
+
|
|
41
|
+
_os_exit = os._exit
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _native_kill(reason: str) -> None:
|
|
45
|
+
"""Вызывается при обнаружении отладчика/инжекции."""
|
|
46
|
+
msg = (
|
|
47
|
+
f"\n[ANTIDEBUG] VIOLATION DETECTED\n"
|
|
48
|
+
f"[ANTIDEBUG] Reason: {reason}\n"
|
|
49
|
+
f"[ANTIDEBUG] Terminating process...\n"
|
|
50
|
+
)
|
|
51
|
+
_PySys_WriteStderr(msg.encode('utf-8'))
|
|
52
|
+
_os_exit(1)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# ---------------------------------------------------------------------------
|
|
56
|
+
# Dataclasses
|
|
57
|
+
# ---------------------------------------------------------------------------
|
|
58
|
+
|
|
59
|
+
@dataclass
|
|
60
|
+
class Violation:
|
|
61
|
+
check_name: str
|
|
62
|
+
reason: str
|
|
63
|
+
details: dict = field(default_factory=dict)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
# ---------------------------------------------------------------------------
|
|
67
|
+
# Cross-Platform AntiDebug Engine
|
|
68
|
+
# ---------------------------------------------------------------------------
|
|
69
|
+
|
|
70
|
+
class AntiDebugCrossPlatform:
|
|
71
|
+
"""
|
|
72
|
+
Кроссплатформенный движок антиотладки.
|
|
73
|
+
Работает на Windows, Linux, macOS.
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
# Чёрный список DLL/модулей — известные отладчики/инжекторы
|
|
77
|
+
BLACKLIST_MODULES: Set[str] = {
|
|
78
|
+
# Python debuggers
|
|
79
|
+
"pydevd",
|
|
80
|
+
"pydevd_cython",
|
|
81
|
+
"pydevd_pep_669_tracing_cython",
|
|
82
|
+
"debugpy",
|
|
83
|
+
"_pydevd_bundle",
|
|
84
|
+
"pydevd_plugins",
|
|
85
|
+
"pydevd_xml",
|
|
86
|
+
# Native debuggers / analysis tools
|
|
87
|
+
"frida",
|
|
88
|
+
"frida-gadget",
|
|
89
|
+
"libinjector",
|
|
90
|
+
"x64dbg",
|
|
91
|
+
"x32dbg",
|
|
92
|
+
"ollydbg",
|
|
93
|
+
"cheatengine",
|
|
94
|
+
# Injection frameworks
|
|
95
|
+
"pyshell",
|
|
96
|
+
"reflectivedll",
|
|
97
|
+
"sliver",
|
|
98
|
+
"meterpreter",
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
# Чёрный список имён потоков
|
|
102
|
+
BLACKLIST_THREAD_NAMES: Set[str] = {
|
|
103
|
+
"pydevd.Writer",
|
|
104
|
+
"pydevd.Reader",
|
|
105
|
+
"pydevd.CommandThread",
|
|
106
|
+
"pydevd.CheckAliveThread",
|
|
107
|
+
"pydevd.SuspendThread",
|
|
108
|
+
"pydevd.BreakpointWatchThread",
|
|
109
|
+
"debugpy",
|
|
110
|
+
"debugpy.server",
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
# Подозрительные переменные окружения
|
|
114
|
+
SUSPICIOUS_ENV_VARS: Set[str] = {
|
|
115
|
+
"PYTHONDEBUG",
|
|
116
|
+
"PYTHONTRACEMODULESHACK",
|
|
117
|
+
"PYTHONTRACEBACK",
|
|
118
|
+
"PYTHONVERBOSE",
|
|
119
|
+
"PYTHONDUMPREFS",
|
|
120
|
+
"PYTHONDUMPAST",
|
|
121
|
+
"PYTHONASYNCIODEBUG",
|
|
122
|
+
"PYTHONMALLOCSTATS",
|
|
123
|
+
"LD_PRELOAD",
|
|
124
|
+
"LD_DEBUG",
|
|
125
|
+
"DYLD_INSERT_LIBRARIES",
|
|
126
|
+
"DYLD_DEBUG",
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
def __init__(self, strict: bool = True):
|
|
130
|
+
self.strict = strict
|
|
131
|
+
self.violations: List[Violation] = []
|
|
132
|
+
self._baseline_threads: Set[int] = set()
|
|
133
|
+
self._lock = threading.Lock()
|
|
134
|
+
|
|
135
|
+
# Захватываем baseline при инициализации
|
|
136
|
+
self._capture_baseline()
|
|
137
|
+
|
|
138
|
+
def _capture_baseline(self):
|
|
139
|
+
"""Запоминаем начальный набор потоков как легитимный."""
|
|
140
|
+
self._baseline_threads = {t.ident for t in threading.enumerate() if t.ident}
|
|
141
|
+
|
|
142
|
+
# ------------------------------------------------------------------
|
|
143
|
+
# Python-level checks
|
|
144
|
+
# ------------------------------------------------------------------
|
|
145
|
+
|
|
146
|
+
def check_python_debugger(self) -> List[Violation]:
|
|
147
|
+
"""Эвристики Python-level отладчиков."""
|
|
148
|
+
results: List[Violation] = []
|
|
149
|
+
|
|
150
|
+
# 1. sys.gettrace() — классический способ отладки
|
|
151
|
+
if hasattr(sys, 'gettrace') and sys.gettrace() is not None:
|
|
152
|
+
results.append(Violation(
|
|
153
|
+
"PythonTrace",
|
|
154
|
+
"sys.gettrace() is active — Python-level debugger detected",
|
|
155
|
+
{"trace_function": str(sys.gettrace())}
|
|
156
|
+
))
|
|
157
|
+
|
|
158
|
+
# 2. sys.monitoring (Python 3.12+ PEP 669)
|
|
159
|
+
if hasattr(sys, 'monitoring') and sys.monitoring:
|
|
160
|
+
try:
|
|
161
|
+
if sys.monitoring.get_tool(sys.monitoring.DEBUGGER_ID) is not None:
|
|
162
|
+
results.append(Violation(
|
|
163
|
+
"PEP669Debugger",
|
|
164
|
+
"sys.monitoring DEBUGGER_ID tool is active"
|
|
165
|
+
))
|
|
166
|
+
except (AttributeError, ValueError):
|
|
167
|
+
pass
|
|
168
|
+
|
|
169
|
+
# 3. sys.settrace (альтернативный API)
|
|
170
|
+
if hasattr(sys, 'gettrace') and sys.gettrace() is not None:
|
|
171
|
+
# Уже проверено выше, но для полноты
|
|
172
|
+
pass
|
|
173
|
+
|
|
174
|
+
# 4. Проверяем загруженные модули
|
|
175
|
+
current_modules = set(sys.modules.keys())
|
|
176
|
+
for mod in current_modules:
|
|
177
|
+
mod_lower = mod.lower()
|
|
178
|
+
for bad in self.BLACKLIST_MODULES:
|
|
179
|
+
if bad in mod_lower:
|
|
180
|
+
results.append(Violation(
|
|
181
|
+
"BlacklistedModule",
|
|
182
|
+
f"Forbidden debugger/injector module loaded: {mod}",
|
|
183
|
+
{"module": mod, "pattern": bad}
|
|
184
|
+
))
|
|
185
|
+
|
|
186
|
+
# 5. Проверяем Python-потоки
|
|
187
|
+
for t in threading.enumerate():
|
|
188
|
+
if t.name in self.BLACKLIST_THREAD_NAMES:
|
|
189
|
+
results.append(Violation(
|
|
190
|
+
"BlacklistedThread",
|
|
191
|
+
f"Forbidden debugger thread detected: {t.name}",
|
|
192
|
+
{"thread_name": t.name, "tid": t.ident}
|
|
193
|
+
))
|
|
194
|
+
|
|
195
|
+
return results
|
|
196
|
+
|
|
197
|
+
def check_environment(self) -> List[Violation]:
|
|
198
|
+
"""Проверка подозрительных переменных окружения."""
|
|
199
|
+
results: List[Violation] = []
|
|
200
|
+
|
|
201
|
+
for var in self.SUSPICIOUS_ENV_VARS:
|
|
202
|
+
value = os.environ.get(var)
|
|
203
|
+
if value is not None:
|
|
204
|
+
# Для LD_PRELOAD/DYLD_INSERT_LIBRARIES — проверим конкретные значения
|
|
205
|
+
if var in ("LD_PRELOAD", "DYLD_INSERT_LIBRARIES"):
|
|
206
|
+
# Проверяем на известные инжекторы
|
|
207
|
+
injectors = ["libinjector", "frida", "preload"]
|
|
208
|
+
if any(inj in value.lower() for inj in injectors):
|
|
209
|
+
results.append(Violation(
|
|
210
|
+
"SuspiciousEnvVar",
|
|
211
|
+
f"Suspicious library preloaded: {var}={value}",
|
|
212
|
+
{"variable": var, "value": value}
|
|
213
|
+
))
|
|
214
|
+
else:
|
|
215
|
+
results.append(Violation(
|
|
216
|
+
"SuspiciousEnvVar",
|
|
217
|
+
f"Suspicious environment variable detected: {var}={value}",
|
|
218
|
+
{"variable": var, "value": value}
|
|
219
|
+
))
|
|
220
|
+
|
|
221
|
+
return results
|
|
222
|
+
|
|
223
|
+
def check_threads(self) -> List[Violation]:
|
|
224
|
+
"""Обнаружение новых подозрительных потоков."""
|
|
225
|
+
results: List[Violation] = []
|
|
226
|
+
current_threads = {t.ident for t in threading.enumerate() if t.ident}
|
|
227
|
+
new_threads = current_threads - self._baseline_threads
|
|
228
|
+
|
|
229
|
+
# Получаем имена известных потоков
|
|
230
|
+
known_thread_names = {t.name for t in threading.enumerate()}
|
|
231
|
+
|
|
232
|
+
for tid in new_threads:
|
|
233
|
+
# Проверяем, не является ли это потоком отладчика
|
|
234
|
+
for t in threading.enumerate():
|
|
235
|
+
if t.ident == tid:
|
|
236
|
+
if t.name in self.BLACKLIST_THREAD_NAMES:
|
|
237
|
+
results.append(Violation(
|
|
238
|
+
"NewDebuggerThread",
|
|
239
|
+
f"New debugger thread detected: {t.name}",
|
|
240
|
+
{"thread_name": t.name, "tid": tid}
|
|
241
|
+
))
|
|
242
|
+
break
|
|
243
|
+
|
|
244
|
+
# Обновляем baseline
|
|
245
|
+
self._baseline_threads = current_threads
|
|
246
|
+
return results
|
|
247
|
+
|
|
248
|
+
def check_network(self) -> List[Violation]:
|
|
249
|
+
"""Проверка открытых сокетов на отладочные порты."""
|
|
250
|
+
results: List[Violation] = []
|
|
251
|
+
|
|
252
|
+
# Известные отладочные порты
|
|
253
|
+
DEBUGGER_PORTS = {
|
|
254
|
+
5678, # debugpy default
|
|
255
|
+
5679,
|
|
256
|
+
5680,
|
|
257
|
+
5681,
|
|
258
|
+
12345, # pydevd default
|
|
259
|
+
12346,
|
|
260
|
+
4444, # common debug port
|
|
261
|
+
5005, # Java debugger
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
try:
|
|
265
|
+
# Получаем все открытые сокеты
|
|
266
|
+
import psutil
|
|
267
|
+
for conn in psutil.net_connections(kind='inet'):
|
|
268
|
+
if conn.status == 'LISTEN' and conn.laddr.port in DEBUGGER_PORTS:
|
|
269
|
+
results.append(Violation(
|
|
270
|
+
"DebuggerPort",
|
|
271
|
+
f"Debugger listening port detected: {conn.laddr.port}",
|
|
272
|
+
{"port": conn.laddr.port, "address": conn.laddr.ip}
|
|
273
|
+
))
|
|
274
|
+
except (ImportError, Exception):
|
|
275
|
+
# psutil недоступен — пропускаем
|
|
276
|
+
pass
|
|
277
|
+
|
|
278
|
+
return results
|
|
279
|
+
|
|
280
|
+
def check_procfs_linux(self) -> List[Violation]:
|
|
281
|
+
"""Проверка /proc файловой системы (Linux only)."""
|
|
282
|
+
results: List[Violation] = []
|
|
283
|
+
|
|
284
|
+
if platform.system() != 'Linux':
|
|
285
|
+
return results
|
|
286
|
+
|
|
287
|
+
try:
|
|
288
|
+
# Проверяем /proc/self/status на флаги отладки
|
|
289
|
+
with open('/proc/self/status', 'r') as f:
|
|
290
|
+
content = f.read()
|
|
291
|
+
|
|
292
|
+
# TracerPid — если не 0, значит нас отслеживают
|
|
293
|
+
match = re.search(r'TracerPid:\s*(\d+)', content)
|
|
294
|
+
if match and int(match.group(1)) != 0:
|
|
295
|
+
results.append(Violation(
|
|
296
|
+
"LinuxTracerPid",
|
|
297
|
+
f"TracerPid is non-zero: {match.group(1)} — process is being traced",
|
|
298
|
+
{"tracer_pid": match.group(1)}
|
|
299
|
+
))
|
|
300
|
+
|
|
301
|
+
# Seccomp — если 1 или 2, может указывать на sandbox
|
|
302
|
+
match = re.search(r'Seccomp:\s*(\d+)', content)
|
|
303
|
+
if match and int(match.group(1)) in (1, 2):
|
|
304
|
+
results.append(Violation(
|
|
305
|
+
"LinuxSeccomp",
|
|
306
|
+
f"Seccomp mode detected: {match.group(1)}",
|
|
307
|
+
{"seccomp_mode": match.group(1)}
|
|
308
|
+
))
|
|
309
|
+
|
|
310
|
+
# Проверяем /proc/self/wchan — если не 0, поток может быть в отладчике
|
|
311
|
+
try:
|
|
312
|
+
with open('/proc/self/wchan', 'r') as f:
|
|
313
|
+
wchan = f.read().strip()
|
|
314
|
+
# 0 означает, что процесс остановлен отладчиком
|
|
315
|
+
if wchan == '0':
|
|
316
|
+
results.append(Violation(
|
|
317
|
+
"LinuxWchan",
|
|
318
|
+
"Process is stopped (ptraced)"
|
|
319
|
+
))
|
|
320
|
+
except (FileNotFoundError, PermissionError):
|
|
321
|
+
pass
|
|
322
|
+
|
|
323
|
+
except (FileNotFoundError, PermissionError, IOError):
|
|
324
|
+
pass
|
|
325
|
+
|
|
326
|
+
return results
|
|
327
|
+
|
|
328
|
+
def check_virtualization(self) -> List[Violation]:
|
|
329
|
+
"""Проверка на виртуальные машины и контейнеры."""
|
|
330
|
+
results: List[Violation] = []
|
|
331
|
+
|
|
332
|
+
# Проверяем VM/контейнерные индикаторы
|
|
333
|
+
indicators = []
|
|
334
|
+
|
|
335
|
+
if platform.system() == 'Linux':
|
|
336
|
+
# /proc/1/cgroup — если содержит docker/lxc, то в контейнере
|
|
337
|
+
try:
|
|
338
|
+
with open('/proc/1/cgroup', 'r') as f:
|
|
339
|
+
cgroup = f.read()
|
|
340
|
+
if 'docker' in cgroup or 'lxc' in cgroup or 'containerd' in cgroup:
|
|
341
|
+
indicators.append(f"Container: {cgroup[:100]}")
|
|
342
|
+
except:
|
|
343
|
+
pass
|
|
344
|
+
|
|
345
|
+
# /proc/self/root — если отличается от /, то chroot
|
|
346
|
+
try:
|
|
347
|
+
import os
|
|
348
|
+
if os.readlink('/proc/self/root') != '/':
|
|
349
|
+
indicators.append("Chroot detected")
|
|
350
|
+
except:
|
|
351
|
+
pass
|
|
352
|
+
|
|
353
|
+
elif platform.system() == 'Windows':
|
|
354
|
+
# Проверяем реестр на VM
|
|
355
|
+
try:
|
|
356
|
+
import winreg
|
|
357
|
+
key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE,
|
|
358
|
+
r"SYSTEM\CurrentControlSet\Services\VBoxGuest")
|
|
359
|
+
indicators.append("VirtualBox detected")
|
|
360
|
+
winreg.CloseKey(key)
|
|
361
|
+
except FileNotFoundError:
|
|
362
|
+
pass
|
|
363
|
+
|
|
364
|
+
try:
|
|
365
|
+
key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE,
|
|
366
|
+
r"SYSTEM\CurrentControlSet\Services\VBoxMouse")
|
|
367
|
+
indicators.append("VirtualBox detected")
|
|
368
|
+
winreg.CloseKey(key)
|
|
369
|
+
except FileNotFoundError:
|
|
370
|
+
pass
|
|
371
|
+
|
|
372
|
+
if indicators:
|
|
373
|
+
results.append(Violation(
|
|
374
|
+
"Virtualization",
|
|
375
|
+
f"Virtualization or container detected: {', '.join(indicators)}",
|
|
376
|
+
{"indicators": indicators}
|
|
377
|
+
))
|
|
378
|
+
|
|
379
|
+
return results
|
|
380
|
+
|
|
381
|
+
# ------------------------------------------------------------------
|
|
382
|
+
# Orchestration
|
|
383
|
+
# ------------------------------------------------------------------
|
|
384
|
+
|
|
385
|
+
def run_all_checks(self) -> List[Violation]:
|
|
386
|
+
"""Выполняет все проверки и возвращает список нарушений."""
|
|
387
|
+
all_violations: List[Violation] = []
|
|
388
|
+
|
|
389
|
+
checks = [
|
|
390
|
+
("PythonDebugger", self.check_python_debugger),
|
|
391
|
+
("Environment", self.check_environment),
|
|
392
|
+
("Threads", self.check_threads),
|
|
393
|
+
("Network", self.check_network),
|
|
394
|
+
("ProcFS", self.check_procfs_linux),
|
|
395
|
+
("Virtualization", self.check_virtualization),
|
|
396
|
+
]
|
|
397
|
+
|
|
398
|
+
for check_name, check_func in checks:
|
|
399
|
+
try:
|
|
400
|
+
violations = check_func()
|
|
401
|
+
all_violations.extend(violations)
|
|
402
|
+
except Exception as e:
|
|
403
|
+
if self.strict:
|
|
404
|
+
all_violations.append(Violation(
|
|
405
|
+
f"{check_name}CheckError",
|
|
406
|
+
f"{check_name} check failed: {e}"
|
|
407
|
+
))
|
|
408
|
+
|
|
409
|
+
return all_violations
|
|
410
|
+
|
|
411
|
+
def kill_if_violations(self, violations: Optional[List[Violation]] = None):
|
|
412
|
+
"""Если есть нарушения — печатает причину и убивает процесс."""
|
|
413
|
+
if violations is None:
|
|
414
|
+
violations = self.run_all_checks()
|
|
415
|
+
|
|
416
|
+
if not violations:
|
|
417
|
+
return
|
|
418
|
+
|
|
419
|
+
with self._lock:
|
|
420
|
+
lines = [
|
|
421
|
+
"",
|
|
422
|
+
"=" * 70,
|
|
423
|
+
" ANTIDEBUG VIOLATION — PROCESS WILL BE TERMINATED",
|
|
424
|
+
"=" * 70,
|
|
425
|
+
f" Total violations: {len(violations)}",
|
|
426
|
+
"",
|
|
427
|
+
]
|
|
428
|
+
|
|
429
|
+
for i, v in enumerate(violations, 1):
|
|
430
|
+
lines.append(f" [{i}] {v.check_name}")
|
|
431
|
+
lines.append(f" Reason: {v.reason}")
|
|
432
|
+
if v.details:
|
|
433
|
+
for k, val in v.details.items():
|
|
434
|
+
lines.append(f" {k}: {val}")
|
|
435
|
+
lines.append("")
|
|
436
|
+
|
|
437
|
+
lines.append("=" * 70)
|
|
438
|
+
lines.append(" Terminating with os._exit(1)")
|
|
439
|
+
lines.append("=" * 70)
|
|
440
|
+
|
|
441
|
+
report = "\n".join(lines) + "\n"
|
|
442
|
+
_PySys_WriteStderr(report.encode('utf-8'))
|
|
443
|
+
time.sleep(0.1)
|
|
444
|
+
os._exit(1)
|
|
445
|
+
|
|
446
|
+
def start_monitoring(self, interval_ms: float = 500):
|
|
447
|
+
"""Запускает фоновый мониторинг в отдельном потоке."""
|
|
448
|
+
def _monitor():
|
|
449
|
+
while True:
|
|
450
|
+
self.kill_if_violations()
|
|
451
|
+
time.sleep(interval_ms / 1000.0)
|
|
452
|
+
|
|
453
|
+
t = threading.Thread(target=_monitor, name="AntiDebugMonitor", daemon=True)
|
|
454
|
+
t.start()
|
|
455
|
+
return t
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
# ---------------------------------------------------------------------------
|
|
459
|
+
# Convenience API
|
|
460
|
+
# ---------------------------------------------------------------------------
|
|
461
|
+
|
|
462
|
+
_engine: Optional[AntiDebugCrossPlatform] = None
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
def init(strict: bool = True) -> AntiDebugCrossPlatform:
|
|
466
|
+
"""Инициализирует движок (один на процесс)."""
|
|
467
|
+
global _engine
|
|
468
|
+
if _engine is None:
|
|
469
|
+
_engine = AntiDebugCrossPlatform(strict=strict)
|
|
470
|
+
return _engine
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
def check() -> List[Violation]:
|
|
474
|
+
"""Однократная проверка."""
|
|
475
|
+
return init().run_all_checks()
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
def guard():
|
|
479
|
+
"""Проверить и убить процесс при нарушениях."""
|
|
480
|
+
init().kill_if_violations()
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
def monitor(interval_ms: float = 500):
|
|
484
|
+
"""Запустить фоновый мониторинг."""
|
|
485
|
+
return init().start_monitoring(interval_ms)
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
# ---------------------------------------------------------------------------
|
|
489
|
+
# CLI / standalone
|
|
490
|
+
# ---------------------------------------------------------------------------
|
|
491
|
+
|
|
492
|
+
if __name__ == "__main__":
|
|
493
|
+
print(f"[PyLockWare AntiDebug] Running self-test on {platform.system()}...")
|
|
494
|
+
print(f"[PyLockWare AntiDebug] PID: {os.getpid()}")
|
|
495
|
+
engine = init(strict=True)
|
|
496
|
+
|
|
497
|
+
violations = engine.run_all_checks()
|
|
498
|
+
if violations:
|
|
499
|
+
print(f"[PyLockWare AntiDebug] Found {len(violations)} violations!")
|
|
500
|
+
for v in violations:
|
|
501
|
+
print(f" - {v.check_name}: {v.reason}")
|
|
502
|
+
else:
|
|
503
|
+
print("[PyLockWare AntiDebug] No violations detected")
|
|
504
|
+
|
|
505
|
+
print("[PyLockWare AntiDebug] Starting monitoring...")
|
|
506
|
+
monitor(interval_ms=500)
|
|
507
|
+
print("[PyLockWare AntiDebug] Monitoring started. Press Enter to exit...")
|
|
508
|
+
input()
|