python-obfuscation-framework 1.9.3__py3-none-any.whl → 1.10.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.
- pof/cli_v2.py +0 -6
- pof/main.py +6 -59
- pof/obfuscator/__init__.py +2 -6
- pof/obfuscator/controlflow/control_flow_flatten.py +1 -1
- pof/obfuscator/junk/dead_code.py +358 -0
- pof/obfuscator/names.py +191 -280
- {python_obfuscation_framework-1.9.3.dist-info → python_obfuscation_framework-1.10.0.dist-info}/METADATA +39 -32
- {python_obfuscation_framework-1.9.3.dist-info → python_obfuscation_framework-1.10.0.dist-info}/RECORD +12 -14
- pof/obfuscator/definitions.py +0 -356
- pof/obfuscator/names_rope.py +0 -397
- pof/obfuscator/variables.py +0 -116
- {python_obfuscation_framework-1.9.3.dist-info → python_obfuscation_framework-1.10.0.dist-info}/WHEEL +0 -0
- {python_obfuscation_framework-1.9.3.dist-info → python_obfuscation_framework-1.10.0.dist-info}/entry_points.txt +0 -0
- {python_obfuscation_framework-1.9.3.dist-info → python_obfuscation_framework-1.10.0.dist-info}/licenses/LICENSE +0 -0
- {python_obfuscation_framework-1.9.3.dist-info → python_obfuscation_framework-1.10.0.dist-info}/top_level.txt +0 -0
pof/cli_v2.py
CHANGED
|
@@ -92,9 +92,6 @@ def add_obfuscation(tokens, args):
|
|
|
92
92
|
if args.obf_constants:
|
|
93
93
|
logger.debug("obfuscating constants")
|
|
94
94
|
tokens = ConstantsObfuscator().obfuscate_tokens(tokens)
|
|
95
|
-
if args.obf_definitions:
|
|
96
|
-
logger.debug("obfuscating definitions")
|
|
97
|
-
tokens = DefinitionsObfuscator().obfuscate_tokens(tokens)
|
|
98
95
|
if args.obf_a85:
|
|
99
96
|
logger.debug("obfuscating a85")
|
|
100
97
|
tokens = ASCII85Obfuscator().obfuscate_tokens(tokens)
|
|
@@ -146,9 +143,6 @@ def add_obfuscation(tokens, args):
|
|
|
146
143
|
if args.obf_names:
|
|
147
144
|
logger.debug("obfuscating names")
|
|
148
145
|
tokens = NamesObfuscator().obfuscate_tokens(tokens)
|
|
149
|
-
if args.obf_names_rope:
|
|
150
|
-
logger.debug("obfuscating names_rope")
|
|
151
|
-
tokens = NamesRopeObfuscator().obfuscate_tokens(tokens)
|
|
152
146
|
if args.obf_numbers:
|
|
153
147
|
logger.debug("obfuscating numbers")
|
|
154
148
|
tokens = NumberObfuscator().obfuscate_tokens(tokens)
|
pof/main.py
CHANGED
|
@@ -45,7 +45,6 @@ from pof.obfuscator import (
|
|
|
45
45
|
BuiltinsObfuscator,
|
|
46
46
|
CommentsObfuscator,
|
|
47
47
|
ConstantsObfuscator,
|
|
48
|
-
DefinitionsObfuscator,
|
|
49
48
|
DocstringObfuscator,
|
|
50
49
|
ExceptionObfuscator,
|
|
51
50
|
GlobalsObfuscator,
|
|
@@ -56,7 +55,6 @@ from pof.obfuscator import (
|
|
|
56
55
|
NumberObfuscator,
|
|
57
56
|
PrintObfuscator,
|
|
58
57
|
StringsObfuscator,
|
|
59
|
-
VariablesObfuscator,
|
|
60
58
|
XORObfuscator,
|
|
61
59
|
)
|
|
62
60
|
from pof.stager import ImageStager, RC4Stager
|
|
@@ -123,8 +121,6 @@ class Obfuscator(BaseObfuscator):
|
|
|
123
121
|
generator=ex_generator,
|
|
124
122
|
).obfuscate_tokens(tokens)
|
|
125
123
|
|
|
126
|
-
tokens = DefinitionsObfuscator().obfuscate_tokens(tokens)
|
|
127
|
-
|
|
128
124
|
# configure generator
|
|
129
125
|
# generator = alphabet_generator()
|
|
130
126
|
gen_dict = {
|
|
@@ -145,42 +141,7 @@ class Obfuscator(BaseObfuscator):
|
|
|
145
141
|
obf_builtins_rate=0.3,
|
|
146
142
|
).obfuscate_tokens(tokens)
|
|
147
143
|
|
|
148
|
-
|
|
149
|
-
# for detailed explanation just consider the following:
|
|
150
|
-
# ```
|
|
151
|
-
# import module
|
|
152
|
-
# class Bar:
|
|
153
|
-
# def __init__(self):
|
|
154
|
-
# self.foo = getattr(config, "FOO", True)
|
|
155
|
-
# module.imported.function(self.foo)
|
|
156
|
-
# ```
|
|
157
|
-
# in this case the first instance of `foo` will successfully be
|
|
158
|
-
# obfuscated (as it should) but then with the getattr it's marked has
|
|
159
|
-
# "imported" because it's the result of a builtin function, but notice
|
|
160
|
-
# that it's not a "simple" variable but rather it's a class attribute
|
|
161
|
-
# and has a 'self' behind it, so if the variable is marked has imported
|
|
162
|
-
# and a `.` is placed behind it, it won't be changed, this is the case
|
|
163
|
-
# in the function call, which is itself imported, and thus set the
|
|
164
|
-
# imported attribute for itself and all the parameters given to it
|
|
165
|
-
# this is because we don't keep track of the level of the imported
|
|
166
|
-
#
|
|
167
|
-
# FIXME (deoktr): breaks !
|
|
168
|
-
# Another problem is related to result of function, this is, ofc very
|
|
169
|
-
# hard to deal with, but if a function returns an object, such has for
|
|
170
|
-
# example an object of an imported class, which attribute are not
|
|
171
|
-
# obfuscatable, then it breaks.
|
|
172
|
-
# ```
|
|
173
|
-
# import foo
|
|
174
|
-
# def a():
|
|
175
|
-
# return foo.bar()
|
|
176
|
-
# x = a()
|
|
177
|
-
# x.baz()
|
|
178
|
-
# ```
|
|
179
|
-
# In this context, `baz` would be obfuscated, but it shouldn't because
|
|
180
|
-
# the function is part of the `foo` imported module
|
|
181
|
-
# tokens = NamesObfuscator(generator=generator).obfuscate_tokens(tokens)
|
|
182
|
-
# TODO (deoktr): use alternative variable obfuscator using the AST
|
|
183
|
-
# tokens = VariablesObfuscator().obfuscate_tokens(tokens)
|
|
144
|
+
tokens = NamesObfuscator(generator=generator).obfuscate_tokens(tokens)
|
|
184
145
|
|
|
185
146
|
tokens = GlobalsObfuscator().obfuscate_tokens(tokens)
|
|
186
147
|
tokens = BuiltinsObfuscator().obfuscate_tokens(tokens)
|
|
@@ -241,9 +202,7 @@ class Obfuscator(BaseObfuscator):
|
|
|
241
202
|
tokens = self._get_tokens(source)
|
|
242
203
|
tokens = CommentsObfuscator().obfuscate_tokens(tokens)
|
|
243
204
|
generator = BasicGenerator.alphabet_generator()
|
|
244
|
-
|
|
245
|
-
tokens = VariablesObfuscator(generator=generator).obfuscate_tokens(tokens)
|
|
246
|
-
tokens = DefinitionsObfuscator(generator=generator).obfuscate_tokens(tokens)
|
|
205
|
+
tokens = NamesObfuscator(generator=generator).obfuscate_tokens(tokens)
|
|
247
206
|
tokens = IndentsObfuscator().obfuscate_tokens(tokens)
|
|
248
207
|
tokens = NewlineObfuscator().obfuscate_tokens(tokens)
|
|
249
208
|
return self._untokenize(tokens)
|
|
@@ -309,18 +268,7 @@ class Obfuscator(BaseObfuscator):
|
|
|
309
268
|
tokens = CommentsObfuscator().obfuscate_tokens(tokens)
|
|
310
269
|
tokens = ExceptionObfuscator(generator=generator).obfuscate_tokens(tokens)
|
|
311
270
|
tokens = LoggingObfuscator(generator=generator).obfuscate_tokens(tokens)
|
|
312
|
-
|
|
313
|
-
# tokens = ConstantsObfuscator(
|
|
314
|
-
# generator=generator,
|
|
315
|
-
# obf_number_rate=1,
|
|
316
|
-
# # FIXME (deoktr): breaks if obf_string_rate=1 with NamesObfuscator
|
|
317
|
-
# obf_string_rate=0,
|
|
318
|
-
# # FIXME (deoktr): breaks if obf_builtins_rate=1 with NamesObfuscator
|
|
319
|
-
# obf_builtins_rate=0,
|
|
320
|
-
# ).obfuscate_tokens(tokens)
|
|
321
|
-
# tokens = NamesObfuscator(generator=generator).obfuscate_tokens(tokens)
|
|
322
|
-
tokens = VariablesObfuscator(generator=generator).obfuscate_tokens(tokens)
|
|
323
|
-
tokens = DefinitionsObfuscator(generator=generator).obfuscate_tokens(tokens)
|
|
271
|
+
tokens = NamesObfuscator(generator=generator).obfuscate_tokens(tokens)
|
|
324
272
|
tokens = ConstantsObfuscator(
|
|
325
273
|
generator=generator,
|
|
326
274
|
obf_number_rate=1,
|
|
@@ -384,10 +332,9 @@ class Obfuscator(BaseObfuscator):
|
|
|
384
332
|
# tokens = ConstantsObfuscator(generator=generator).obfuscate_tokens(tokens)
|
|
385
333
|
|
|
386
334
|
tokens = CommentsObfuscator().obfuscate_tokens(tokens)
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
# ).obfuscate_tokens(tokens)
|
|
335
|
+
tokens = NamesObfuscator(
|
|
336
|
+
generator=AdvancedGenerator.fixed_length_generator(),
|
|
337
|
+
).obfuscate_tokens(tokens)
|
|
391
338
|
tokens = BooleanObfuscator().obfuscate_tokens(tokens)
|
|
392
339
|
tokens = IndentsObfuscator().obfuscate_tokens(tokens)
|
|
393
340
|
tokens = NewlineObfuscator().obfuscate_tokens(tokens)
|
pof/obfuscator/__init__.py
CHANGED
|
@@ -26,7 +26,6 @@ from .compression.lzma import LzmaObfuscator
|
|
|
26
26
|
from .compression.zlib import ZlibObfuscator
|
|
27
27
|
from .constants import ConstantsObfuscator
|
|
28
28
|
from .controlflow.control_flow_flatten import ControlFlowFlattenObfuscator
|
|
29
|
-
from .definitions import DefinitionsObfuscator
|
|
30
29
|
from .encoding.a85 import ASCII85Obfuscator
|
|
31
30
|
from .encoding.b16 import Base16Obfuscator
|
|
32
31
|
from .encoding.b32 import Base32Obfuscator
|
|
@@ -43,8 +42,8 @@ from .esoteric.imports import ImportsObfuscator
|
|
|
43
42
|
from .extract_variables import ExtractVariablesObfuscator
|
|
44
43
|
from .junk.add_comments import AddCommentsObfuscator
|
|
45
44
|
from .junk.add_newlines import AddNewlinesObfuscator
|
|
45
|
+
from .junk.dead_code import DeadCodeObfuscator
|
|
46
46
|
from .names import NamesObfuscator
|
|
47
|
-
from .names_rope import NamesRopeObfuscator
|
|
48
47
|
from .numbers import NumberObfuscator
|
|
49
48
|
from .other.tokens import TokensObfuscator
|
|
50
49
|
from .remove.comments import CommentsObfuscator
|
|
@@ -58,7 +57,6 @@ from .stegano.ipv6encoding import IPv6Obfuscator
|
|
|
58
57
|
from .stegano.macencoding import MACObfuscator
|
|
59
58
|
from .stegano.uuidencoding import UUIDObfuscator
|
|
60
59
|
from .strings import StringsObfuscator
|
|
61
|
-
from .variables import VariablesObfuscator
|
|
62
60
|
|
|
63
61
|
__all__ = [
|
|
64
62
|
"ASCII85Obfuscator",
|
|
@@ -78,8 +76,8 @@ __all__ = [
|
|
|
78
76
|
"CommentsObfuscator",
|
|
79
77
|
"ConstantsObfuscator",
|
|
80
78
|
"ControlFlowFlattenObfuscator",
|
|
79
|
+
"DeadCodeObfuscator",
|
|
81
80
|
"DeepEncryptionObfuscator",
|
|
82
|
-
"DefinitionsObfuscator",
|
|
83
81
|
"DocstringObfuscator",
|
|
84
82
|
"ExceptionObfuscator",
|
|
85
83
|
"ExtractVariablesObfuscator",
|
|
@@ -93,7 +91,6 @@ __all__ = [
|
|
|
93
91
|
"LzmaObfuscator",
|
|
94
92
|
"MACObfuscator",
|
|
95
93
|
"NamesObfuscator",
|
|
96
|
-
"NamesRopeObfuscator",
|
|
97
94
|
"NewlineObfuscator",
|
|
98
95
|
"NumberObfuscator",
|
|
99
96
|
"PrintObfuscator",
|
|
@@ -103,7 +100,6 @@ __all__ = [
|
|
|
103
100
|
"StringsObfuscator",
|
|
104
101
|
"TokensObfuscator",
|
|
105
102
|
"UUIDObfuscator",
|
|
106
|
-
"VariablesObfuscator",
|
|
107
103
|
"WhitespaceObfuscator",
|
|
108
104
|
"XORObfuscator",
|
|
109
105
|
"ZlibObfuscator",
|
|
@@ -67,7 +67,7 @@ class ControlFlowFlattenObfuscator:
|
|
|
67
67
|
|
|
68
68
|
dispatcher_cases: list[ast.If | None] = []
|
|
69
69
|
|
|
70
|
-
for idx, (state_num, stmt) in enumerate(zip(block_states, body)):
|
|
70
|
+
for idx, (state_num, stmt) in enumerate(zip(block_states, body, strict=True)):
|
|
71
71
|
next_state = block_states[idx + 1] if idx + 1 < num_blocks else exit_state
|
|
72
72
|
|
|
73
73
|
case_body: list[ast.stmt] = []
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
# POF, a free and open source Python obfuscation framework.
|
|
2
|
+
# Copyright (C) 2022 - 2026 Deoktr
|
|
3
|
+
#
|
|
4
|
+
# This program is free software: you can redistribute it and/or modify
|
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
|
6
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
# (at your option) any later version.
|
|
8
|
+
#
|
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
# GNU General Public License for more details.
|
|
13
|
+
#
|
|
14
|
+
# You should have received a copy of the GNU General Public License
|
|
15
|
+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
16
|
+
|
|
17
|
+
import random
|
|
18
|
+
from collections.abc import Generator
|
|
19
|
+
from tokenize import (
|
|
20
|
+
COMMENT,
|
|
21
|
+
DEDENT,
|
|
22
|
+
INDENT,
|
|
23
|
+
NAME,
|
|
24
|
+
NEWLINE,
|
|
25
|
+
NL,
|
|
26
|
+
NUMBER,
|
|
27
|
+
OP,
|
|
28
|
+
STRING,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
from pof.utils.generator import BasicGenerator
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class DeadCodeObfuscator:
|
|
35
|
+
"""Insert dead (unreachable/unused) code blocks into the source."""
|
|
36
|
+
|
|
37
|
+
VALID_STATEMENT_TYPES = (
|
|
38
|
+
"function",
|
|
39
|
+
"if",
|
|
40
|
+
"for",
|
|
41
|
+
"while",
|
|
42
|
+
"assignment",
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
def __init__( # noqa: PLR0913
|
|
46
|
+
self,
|
|
47
|
+
frequency: float = 0.3,
|
|
48
|
+
max_function_depth: int = 2,
|
|
49
|
+
max_branches: int = 3,
|
|
50
|
+
statement_types: list[str] | None = None,
|
|
51
|
+
generate_classes: bool = False, # noqa: FBT001, FBT002
|
|
52
|
+
generator: Generator[str] | None = None,
|
|
53
|
+
) -> None:
|
|
54
|
+
self.frequency: float = frequency
|
|
55
|
+
self.max_function_depth: int = max_function_depth
|
|
56
|
+
self.max_branches: int = max_branches
|
|
57
|
+
self.generate_classes: bool = generate_classes
|
|
58
|
+
|
|
59
|
+
if statement_types is not None:
|
|
60
|
+
self.statement_types: list[str] = [
|
|
61
|
+
s for s in statement_types if s in self.VALID_STATEMENT_TYPES
|
|
62
|
+
]
|
|
63
|
+
else:
|
|
64
|
+
self.statement_types = list(self.VALID_STATEMENT_TYPES)
|
|
65
|
+
|
|
66
|
+
if generator is None:
|
|
67
|
+
generator = BasicGenerator.function_generator()
|
|
68
|
+
self.generator: Generator[str] = generator
|
|
69
|
+
self._var_generator: Generator[str] = BasicGenerator.alphabet_generator()
|
|
70
|
+
|
|
71
|
+
def _next_name(self) -> str:
|
|
72
|
+
return next(self.generator)
|
|
73
|
+
|
|
74
|
+
def _next_var(self) -> str:
|
|
75
|
+
return next(self._var_generator)
|
|
76
|
+
|
|
77
|
+
@staticmethod
|
|
78
|
+
def _collect_existing_names(tokens) -> set[str]:
|
|
79
|
+
names: set[str] = set()
|
|
80
|
+
for toknum, tokval, *_ in tokens:
|
|
81
|
+
if toknum == NAME:
|
|
82
|
+
names.add(tokval)
|
|
83
|
+
return names
|
|
84
|
+
|
|
85
|
+
def _random_expr_tokens(self):
|
|
86
|
+
choice = random.randint(0, 3)
|
|
87
|
+
if choice == 0:
|
|
88
|
+
a, b = random.randint(1, 999), random.randint(1, 999)
|
|
89
|
+
op = random.choice(["+", "-", "*", "%"])
|
|
90
|
+
return [(NUMBER, str(a)), (OP, op), (NUMBER, str(b))]
|
|
91
|
+
if choice == 1:
|
|
92
|
+
words = ["data", "info", "temp", "cache", "buf", "msg", "tag"]
|
|
93
|
+
return [(STRING, repr(random.choice(words)))]
|
|
94
|
+
if choice == 2: # noqa: PLR2004
|
|
95
|
+
items = [random.randint(0, 100) for _ in range(random.randint(1, 4))]
|
|
96
|
+
tokens = [(OP, "[")]
|
|
97
|
+
for i, item in enumerate(items):
|
|
98
|
+
if i > 0:
|
|
99
|
+
tokens.append((OP, ","))
|
|
100
|
+
tokens.append((NUMBER, str(item)))
|
|
101
|
+
tokens.append((OP, "]"))
|
|
102
|
+
return tokens
|
|
103
|
+
return [(NUMBER, str(random.randint(0, 9999)))]
|
|
104
|
+
|
|
105
|
+
def _body_tokens(self, count: int = 0):
|
|
106
|
+
"""Generate body tokens for a block (assignments)."""
|
|
107
|
+
if count == 0:
|
|
108
|
+
count = random.randint(1, 3)
|
|
109
|
+
tokens = []
|
|
110
|
+
for _ in range(count):
|
|
111
|
+
var = self._next_var()
|
|
112
|
+
tokens.append((NAME, var))
|
|
113
|
+
tokens.append((OP, "="))
|
|
114
|
+
tokens.extend(self._random_expr_tokens())
|
|
115
|
+
tokens.append((NEWLINE, "\n"))
|
|
116
|
+
return tokens
|
|
117
|
+
|
|
118
|
+
def _generate_dead_function_tokens(self, indent_level: int, depth: int):
|
|
119
|
+
fname = self._next_name()
|
|
120
|
+
params = [self._next_var() for _ in range(random.randint(0, 3))]
|
|
121
|
+
inner_indent = " " * (indent_level + 1)
|
|
122
|
+
|
|
123
|
+
tokens = [
|
|
124
|
+
(NAME, "def"),
|
|
125
|
+
(NAME, fname),
|
|
126
|
+
(OP, "("),
|
|
127
|
+
]
|
|
128
|
+
for i, p in enumerate(params):
|
|
129
|
+
if i > 0:
|
|
130
|
+
tokens.append((OP, ","))
|
|
131
|
+
tokens.append((NAME, p))
|
|
132
|
+
tokens.extend(
|
|
133
|
+
[
|
|
134
|
+
(OP, ")"),
|
|
135
|
+
(OP, ":"),
|
|
136
|
+
(NEWLINE, "\n"),
|
|
137
|
+
(INDENT, inner_indent),
|
|
138
|
+
],
|
|
139
|
+
)
|
|
140
|
+
tokens.extend(self._body_tokens())
|
|
141
|
+
|
|
142
|
+
if depth < self.max_function_depth and random.random() < 0.3: # noqa: PLR2004
|
|
143
|
+
tokens.extend(
|
|
144
|
+
self._generate_dead_function_tokens(indent_level + 1, depth + 1),
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
tokens.append((DEDENT, ""))
|
|
148
|
+
return tokens
|
|
149
|
+
|
|
150
|
+
@staticmethod
|
|
151
|
+
def _get_false_cond():
|
|
152
|
+
false_conds = [
|
|
153
|
+
[(NAME, "False")],
|
|
154
|
+
[(NUMBER, "0")],
|
|
155
|
+
[(STRING, '""')],
|
|
156
|
+
[(NAME, "None")],
|
|
157
|
+
[
|
|
158
|
+
(NUMBER, str(random.randint(1, 50))),
|
|
159
|
+
(OP, ">"),
|
|
160
|
+
(NUMBER, str(random.randint(51, 100))),
|
|
161
|
+
],
|
|
162
|
+
[
|
|
163
|
+
(NUMBER, str(random.randint(1, 50))),
|
|
164
|
+
(OP, "=="),
|
|
165
|
+
(NUMBER, str(random.randint(51, 100))),
|
|
166
|
+
],
|
|
167
|
+
[(NAME, "not"), (NAME, "True")],
|
|
168
|
+
[(NAME, "True"), (NAME, "and"), (NAME, "False")],
|
|
169
|
+
[(NUMBER, "0"), (OP, "*"), (NUMBER, str(random.randint(1, 999)))],
|
|
170
|
+
[(NAME, "len"), (OP, "("), (STRING, '""'), (OP, ")")],
|
|
171
|
+
[(NAME, "bool"), (OP, "("), (NUMBER, "0"), (OP, ")")],
|
|
172
|
+
[(OP, "("), (OP, ")"), (NAME, "and"), (NAME, "True")],
|
|
173
|
+
]
|
|
174
|
+
return random.choice(false_conds)
|
|
175
|
+
|
|
176
|
+
def _generate_dead_if_tokens(self, indent_level: int):
|
|
177
|
+
inner_indent = " " * (indent_level + 1)
|
|
178
|
+
|
|
179
|
+
tokens = [(NAME, "if")]
|
|
180
|
+
tokens.extend(self._get_false_cond())
|
|
181
|
+
tokens.extend(
|
|
182
|
+
[
|
|
183
|
+
(OP, ":"),
|
|
184
|
+
(NEWLINE, "\n"),
|
|
185
|
+
(INDENT, inner_indent),
|
|
186
|
+
],
|
|
187
|
+
)
|
|
188
|
+
tokens.extend(self._body_tokens())
|
|
189
|
+
tokens.append((DEDENT, ""))
|
|
190
|
+
|
|
191
|
+
num_elif = random.randint(0, max(0, self.max_branches - 1))
|
|
192
|
+
for _ in range(num_elif):
|
|
193
|
+
tokens.append((NAME, "elif"))
|
|
194
|
+
tokens.extend(self._get_false_cond())
|
|
195
|
+
tokens.extend(
|
|
196
|
+
[
|
|
197
|
+
(OP, ":"),
|
|
198
|
+
(NEWLINE, "\n"),
|
|
199
|
+
(INDENT, inner_indent),
|
|
200
|
+
],
|
|
201
|
+
)
|
|
202
|
+
tokens.extend(self._body_tokens())
|
|
203
|
+
tokens.append((DEDENT, ""))
|
|
204
|
+
|
|
205
|
+
return tokens
|
|
206
|
+
|
|
207
|
+
def _generate_dead_for_tokens(self, indent_level: int):
|
|
208
|
+
var = self._next_var()
|
|
209
|
+
inner_indent = " " * (indent_level + 1)
|
|
210
|
+
|
|
211
|
+
empty_choices = [
|
|
212
|
+
[(OP, "["), (OP, "]")],
|
|
213
|
+
[(NAME, "range"), (OP, "("), (NUMBER, "0"), (OP, ")")],
|
|
214
|
+
[(OP, "("), (OP, ")")],
|
|
215
|
+
[(OP, "{"), (OP, "}")],
|
|
216
|
+
[(STRING, '""')],
|
|
217
|
+
[(NAME, "set"), (OP, "("), (OP, ")")],
|
|
218
|
+
]
|
|
219
|
+
|
|
220
|
+
tokens = [
|
|
221
|
+
(NAME, "for"),
|
|
222
|
+
(NAME, var),
|
|
223
|
+
(NAME, "in"),
|
|
224
|
+
]
|
|
225
|
+
tokens.extend(random.choice(empty_choices))
|
|
226
|
+
tokens.extend(
|
|
227
|
+
[
|
|
228
|
+
(OP, ":"),
|
|
229
|
+
(NEWLINE, "\n"),
|
|
230
|
+
(INDENT, inner_indent),
|
|
231
|
+
],
|
|
232
|
+
)
|
|
233
|
+
tokens.extend(self._body_tokens())
|
|
234
|
+
tokens.append((DEDENT, ""))
|
|
235
|
+
return tokens
|
|
236
|
+
|
|
237
|
+
def _generate_dead_while_tokens(self, indent_level: int):
|
|
238
|
+
inner_indent = " " * (indent_level + 1)
|
|
239
|
+
tokens = [(NAME, "while")]
|
|
240
|
+
tokens.extend(self._get_false_cond())
|
|
241
|
+
tokens.extend(
|
|
242
|
+
[
|
|
243
|
+
(OP, ":"),
|
|
244
|
+
(NEWLINE, "\n"),
|
|
245
|
+
(INDENT, inner_indent),
|
|
246
|
+
],
|
|
247
|
+
)
|
|
248
|
+
tokens.extend(self._body_tokens())
|
|
249
|
+
tokens.append((DEDENT, ""))
|
|
250
|
+
return tokens
|
|
251
|
+
|
|
252
|
+
def _generate_dead_class_tokens(self, indent_level: int):
|
|
253
|
+
cname = self._next_name().capitalize()
|
|
254
|
+
inner_indent = " " * (indent_level + 1)
|
|
255
|
+
tokens = [
|
|
256
|
+
(NAME, "class"),
|
|
257
|
+
(NAME, cname),
|
|
258
|
+
(OP, ":"),
|
|
259
|
+
(NEWLINE, "\n"),
|
|
260
|
+
(INDENT, inner_indent),
|
|
261
|
+
]
|
|
262
|
+
tokens.extend(self._generate_dead_function_tokens(indent_level + 1, 0))
|
|
263
|
+
tokens.append((DEDENT, ""))
|
|
264
|
+
return tokens
|
|
265
|
+
|
|
266
|
+
def _generate_dead_assignment_tokens(self):
|
|
267
|
+
var = self._next_var()
|
|
268
|
+
tokens = [
|
|
269
|
+
(NAME, var),
|
|
270
|
+
(OP, "="),
|
|
271
|
+
]
|
|
272
|
+
tokens.extend(self._random_expr_tokens())
|
|
273
|
+
tokens.append((NEWLINE, "\n"))
|
|
274
|
+
return tokens
|
|
275
|
+
|
|
276
|
+
def _generate_dead_code(self, indent_level: int):
|
|
277
|
+
"""Generate a dead code block and return its tokens."""
|
|
278
|
+
available = list(self.statement_types)
|
|
279
|
+
if self.generate_classes:
|
|
280
|
+
available.append("class")
|
|
281
|
+
|
|
282
|
+
if indent_level > 0:
|
|
283
|
+
weights = []
|
|
284
|
+
for t in available:
|
|
285
|
+
if t in ("function", "class"):
|
|
286
|
+
weights.append(1)
|
|
287
|
+
else:
|
|
288
|
+
weights.append(3)
|
|
289
|
+
else:
|
|
290
|
+
weights = [1] * len(available)
|
|
291
|
+
|
|
292
|
+
choice = random.choices(available, weights=weights, k=1)[0]
|
|
293
|
+
|
|
294
|
+
match choice:
|
|
295
|
+
case "function":
|
|
296
|
+
return self._generate_dead_function_tokens(indent_level, 0)
|
|
297
|
+
case "if":
|
|
298
|
+
return self._generate_dead_if_tokens(indent_level)
|
|
299
|
+
case "for":
|
|
300
|
+
return self._generate_dead_for_tokens(indent_level)
|
|
301
|
+
case "while":
|
|
302
|
+
return self._generate_dead_while_tokens(indent_level)
|
|
303
|
+
case "class":
|
|
304
|
+
return self._generate_dead_class_tokens(indent_level)
|
|
305
|
+
case _:
|
|
306
|
+
return self._generate_dead_assignment_tokens()
|
|
307
|
+
|
|
308
|
+
def obfuscate_tokens(self, tokens): # noqa: C901
|
|
309
|
+
existing: set[str] = self._collect_existing_names(tokens)
|
|
310
|
+
BasicGenerator.extend_reserved(list(existing))
|
|
311
|
+
|
|
312
|
+
result = []
|
|
313
|
+
indent_level: int = 0
|
|
314
|
+
paren_depth: int = 0
|
|
315
|
+
in_decorator: bool = False
|
|
316
|
+
|
|
317
|
+
for idx, (toknum, tokval, *_) in enumerate(tokens):
|
|
318
|
+
if toknum == INDENT:
|
|
319
|
+
indent_level += 1
|
|
320
|
+
elif toknum == DEDENT:
|
|
321
|
+
indent_level = max(0, indent_level - 1)
|
|
322
|
+
|
|
323
|
+
if toknum == OP and tokval in ("(", "[", "{"):
|
|
324
|
+
paren_depth += 1
|
|
325
|
+
elif toknum == OP and tokval in (")", "]", "}"):
|
|
326
|
+
paren_depth = max(0, paren_depth - 1)
|
|
327
|
+
|
|
328
|
+
if toknum == OP and tokval == "@":
|
|
329
|
+
in_decorator = True
|
|
330
|
+
if in_decorator and toknum == NAME and tokval in ("def", "class"):
|
|
331
|
+
in_decorator = False
|
|
332
|
+
|
|
333
|
+
result.append((toknum, tokval))
|
|
334
|
+
|
|
335
|
+
# insert dead code after NEWLINE tokens
|
|
336
|
+
# do NOT insert if an INDENT is upcoming (possibly after NL/COMMENT
|
|
337
|
+
# tokens), that would break the block header (def/if/for/while/
|
|
338
|
+
# class/try) by placing code between the header's NEWLINE and the
|
|
339
|
+
# body's INDENT
|
|
340
|
+
if (
|
|
341
|
+
toknum == NEWLINE
|
|
342
|
+
and paren_depth == 0
|
|
343
|
+
and not in_decorator
|
|
344
|
+
and random.random() < self.frequency
|
|
345
|
+
):
|
|
346
|
+
has_upcoming_indent = False
|
|
347
|
+
for j in range(idx + 1, len(tokens)):
|
|
348
|
+
upcoming = tokens[j][0]
|
|
349
|
+
if upcoming in (NL, COMMENT):
|
|
350
|
+
continue
|
|
351
|
+
if upcoming == INDENT:
|
|
352
|
+
has_upcoming_indent = True
|
|
353
|
+
break
|
|
354
|
+
if not has_upcoming_indent:
|
|
355
|
+
dead_tokens = self._generate_dead_code(indent_level)
|
|
356
|
+
result.extend(dead_tokens)
|
|
357
|
+
|
|
358
|
+
return result
|