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 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
- # FIXME (deoktr): breaks !
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
- # tokens = NamesObfuscator(generator=generator).obfuscate_tokens(tokens)
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
- # FIXME (deoktr): when placed BEFORE NamesObfuscator it breaks the code
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
- # tokens = DeepEncryptionEvasion().add_evasion(tokens) # TODO (deoktr): fix
388
- # tokens = NamesObfuscator(
389
- # generator=AdvancedGenerator.fixed_length_generator(),
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)
@@ -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