├── .gitignore
├── Makefile.template
├── README.md
├── app
├── __init__.py
├── app.py
├── manage.py
├── models
│ └── __init__.py
├── requirements.txt
├── routes
│ ├── __init__.py
│ ├── main.py
│ └── utils.py
├── static
│ └── js
│ │ └── calc.js
└── templates
│ ├── base.html
│ └── main.html
├── ast
├── hiding_strings
│ ├── Makefile
│ ├── README.md
│ ├── encode.py
│ ├── tree_parser.py
│ └── unparse.py
└── scrambling_code
│ ├── Makefile
│ ├── README.md
│ ├── ihook.py
│ ├── manage.py
│ ├── scramble.py
│ ├── scrambler.py
│ ├── unparse.py
│ └── unscramble.py
├── bytecode
├── pyc
│ ├── Makefile
│ └── README.md
└── pyo
│ ├── Makefile
│ └── README.md
├── custom_interpreter
└── opcodes
│ ├── .python-version
│ ├── 2.7.9-custom
│ ├── Makefile
│ ├── README.md
│ ├── patches
│ ├── .empty
│ └── 001_scrambled-opcodes.diff
│ └── scramble-opcodes.py
├── cython
├── Makefile
└── README.md
└── import_hook
├── compiled
├── Makefile
├── README.md
├── ihook.py
├── m.py
└── requirements.txt
└── python
├── Makefile
├── README.md
├── ihook.py
├── m.py
└── requirements.txt
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 |
5 | # C extensions
6 | *.so
7 |
8 | # Distribution / packaging
9 | .Python
10 | env/
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | lib/
17 | lib64/
18 | parts/
19 | sdist/
20 | var/
21 | *.egg-info/
22 | .installed.cfg
23 | *.egg
24 |
25 | # PyInstaller
26 | # Usually these files are written by a python script from a template
27 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
28 | *.manifest
29 | *.spec
30 |
31 | # Installer logs
32 | pip-log.txt
33 | pip-delete-this-directory.txt
34 |
35 | # Unit test / coverage reports
36 | htmlcov/
37 | .tox/
38 | .coverage
39 | .cache
40 | nosetests.xml
41 | coverage.xml
42 |
43 | # Translations
44 | *.mo
45 | *.pot
46 |
47 | # Django stuff:
48 | *.log
49 |
50 | # Sphinx documentation
51 | docs/_build/
52 |
53 | # PyBuilder
54 | target/
55 |
56 | */app
57 | */*/app
58 | custom_interpreter/opcodes/Python-2.7.9*
59 | custom_interpreter/opcodes/scrambled-opcodes.diff
60 | # WIP, temp ignore
61 | custom_interpreter/hardened/
62 |
--------------------------------------------------------------------------------
/Makefile.template:
--------------------------------------------------------------------------------
1 | PYTHON_FILES := $(shell find . -name "*.py" ! -name "__init__.py" ! -name "manage.py")
2 |
3 | all: $(PYTHON_FILES)
4 |
5 | clean-build:
6 | @ echo "Clean build files here"
7 |
8 | clean:
9 | @ rm -rf ./app
10 |
11 | run: clean
12 | @ cp -R ../app .
13 | @ find . -iname "*.py[co]" -delete
14 | @ $(MAKE) && $(MAKE) clean-build
15 |
16 | .DEFAULT: all
17 | .PHONY: all clean clean-build run
18 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Python code Protection Mechanisms
2 |
3 | Playground for python based project obfuscation techniques.
4 |
5 | ## Description
6 |
7 | In this repository I'll demo and try the different options available at the
8 | moment to protect a codebase based on python in order to avoid snooping from
9 | third-parties, options like source obfuscation, byte-code distribution, etc
10 | will be implemented and reviewed, later combinations of several techniques will
11 | be tried too.
12 |
13 | ## Organization
14 |
15 | The target is the project at the [app](app) directory, then each method will
16 | reside in a directory with a ``Makefile`` that will setup and run the
17 | particular technique. Also a ``README.md`` should be available in each
18 | directory explaining the current option, highlighting its pro and cons.
19 |
--------------------------------------------------------------------------------
/app/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FormulaMonks/python-obfuscation/8fb9897702dc51af7fb164f3e6906dd3d656ec68/app/__init__.py
--------------------------------------------------------------------------------
/app/app.py:
--------------------------------------------------------------------------------
1 | from flask import Flask
2 |
3 | from routes.main import main
4 |
5 |
6 | app = Flask(__name__)
7 | app.debug = True
8 | app.register_blueprint(main)
9 |
10 |
11 | if __name__ == '__main__':
12 | app.run()
13 |
--------------------------------------------------------------------------------
/app/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | from flask_script import Server, Manager, Shell
4 |
5 | from app import app
6 |
7 |
8 | manager = Manager(app)
9 | manager.add_command('runserver', Server())
10 | manager.add_command('shell', Shell(make_context=lambda: {
11 | 'app': app,
12 | }))
13 |
14 |
15 | if __name__ == '__main__':
16 | manager.run()
17 |
--------------------------------------------------------------------------------
/app/models/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FormulaMonks/python-obfuscation/8fb9897702dc51af7fb164f3e6906dd3d656ec68/app/models/__init__.py
--------------------------------------------------------------------------------
/app/requirements.txt:
--------------------------------------------------------------------------------
1 | flask
2 | flask-script
3 | sqlalchemy
4 | cython
5 | numpy
6 |
--------------------------------------------------------------------------------
/app/routes/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FormulaMonks/python-obfuscation/8fb9897702dc51af7fb164f3e6906dd3d656ec68/app/routes/__init__.py
--------------------------------------------------------------------------------
/app/routes/main.py:
--------------------------------------------------------------------------------
1 | from numpy import matrix
2 | from flask import Blueprint, jsonify, request
3 |
4 | from utils import render_to
5 |
6 |
7 | main = Blueprint('calc', __name__)
8 |
9 |
10 | @main.route('/')
11 | @render_to('main.html')
12 | def calc_form():
13 | pass
14 |
15 |
16 | @main.route('/calc')
17 | def calc():
18 | matrix1 = matrix(request.args['m1'].encode('utf8'))
19 | matrix2 = matrix(request.args['m2'].encode('utf8'))
20 | out = {'result': str(matrix1 * matrix2)}
21 | return jsonify(out)
22 |
--------------------------------------------------------------------------------
/app/routes/utils.py:
--------------------------------------------------------------------------------
1 | from functools import wraps
2 |
3 | from flask import render_template
4 |
5 |
6 | def render_to(tpl):
7 | def decorator(func):
8 | @wraps(func)
9 | def wrapper(*args, **kwargs):
10 | out = func(*args, **kwargs) or {}
11 | if isinstance(out, dict):
12 | out = render_template(tpl, **out)
13 | return out
14 | return wrapper
15 | return decorator
16 |
--------------------------------------------------------------------------------
/app/static/js/calc.js:
--------------------------------------------------------------------------------
1 | $(function () {
2 | $(document).on('submit', '#mult', function (event) {
3 | event.preventDefault();
4 |
5 | var $form = $(this);
6 |
7 | $.get('/calc', {
8 | m1: $form.find('#m1').val(),
9 | m2: $form.find('#m2').val()
10 | }, 'json').done(function (data, response, xhr) {
11 | console.log('data: ', data);
12 | $form.find('#result').html("
" + data.result + "
");
13 | });
14 | });
15 | });
16 |
--------------------------------------------------------------------------------
/app/templates/base.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Matrix multiplication
5 |
6 |
7 |
8 |
9 | {% block content %}
10 | {% endblock %}
11 |
12 |
13 | {% block scripts %}
14 | {% endblock %}
15 |
16 |
17 |
--------------------------------------------------------------------------------
/app/templates/main.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 |
3 | {% block content %}
4 |
18 | {% endblock %}
19 |
20 | {% block scripts %}
21 |
22 | {% endblock %}
23 |
--------------------------------------------------------------------------------
/ast/hiding_strings/Makefile:
--------------------------------------------------------------------------------
1 | INPUT_DIR := ../app
2 | OUTPUT_DIR := ./app
3 |
4 | all: encode
5 |
6 | encode: copy
7 | @ python encode.py $(OUTPUT_DIR)
8 |
9 | copy: clean
10 | @ cp -R $(INPUT_DIR) $(OUTPUT_DIR)
11 |
12 | clean:
13 | @ rm -rf $(OUTPUT_DIR)
14 |
15 | .DEFAULT: all
16 | .PHONY: encode copy clean
17 |
--------------------------------------------------------------------------------
/ast/hiding_strings/README.md:
--------------------------------------------------------------------------------
1 | # Python code Protection Mechanisms - Hidding Strings
2 |
3 | Process an python application and encode strings by processing the walking
4 | through the Abstract Syntax Trees.
5 |
6 |
7 | ## Description
8 |
9 | In this example, we will be customizing the processing a python application by
10 | walking the [Abstract Syntax Trees](https://docs.python.org/2.7/library/ast.html)
11 | and replacing strings occurrences with encoded versions that automatically
12 | decode on runtime. This comes handy when using [Cython](http://cython.org/) to
13 | compile the application code to Python extensions, that way a simple
14 | ``strings`` command won't be very useful to extract strings in the lib.
15 |
16 | This example uses [unparse.py](http://svn.python.org/view/python/trunk/Demo/parser/unparse.py?view=markup).
17 |
18 | ## Pros
19 |
20 | 1. Walking the [AST](https://docs.python.org/2.7/library/ast.html) is a very
21 | powerful transformation tool, this example just do string replacement, but
22 | more advanced transformations are possible.
23 |
24 | ## Cons
25 |
26 | 1. This just encodes strings (the example uses ``rot13``, but a custom encoder
27 | or translation table can be implemented). This is just a hiding technique.
28 |
--------------------------------------------------------------------------------
/ast/hiding_strings/encode.py:
--------------------------------------------------------------------------------
1 | import os
2 | import sys
3 | import ast
4 |
5 | from tree_parser import EncodeStrings
6 | from unparse import Unparser
7 |
8 |
9 | for root, dirs, files in os.walk(sys.argv[1]):
10 | for file in files:
11 | if file.endswith('.py'):
12 | with open('{0}/{1}'.format(root, file), 'r') as f:
13 | code = ''.join(f.readlines())
14 | with open('{0}/{1}'.format(root, file), 'w') as f:
15 | node = ast.parse(code)
16 | encoded = EncodeStrings().visit(node)
17 | Unparser(encoded, f)
18 | f.write('\n')
19 |
--------------------------------------------------------------------------------
/ast/hiding_strings/tree_parser.py:
--------------------------------------------------------------------------------
1 | from ast import NodeTransformer, copy_location, Call, Str, Attribute
2 |
3 |
4 | class EncodeStrings(NodeTransformer):
5 | def visit_Str(self, node):
6 | return copy_location(
7 | Call(
8 | func=Attribute(attr='decode',
9 | value=Str(s=node.s.encode('rot13'))),
10 | args=[Str(s='rot13')],
11 | keywords=(),
12 | starargs=None,
13 | kwargs=None
14 | ),
15 | node
16 | )
17 |
--------------------------------------------------------------------------------
/ast/hiding_strings/unparse.py:
--------------------------------------------------------------------------------
1 | "Usage: unparse.py "
2 | import sys
3 | import ast
4 | import cStringIO
5 | import os
6 |
7 | # Large float and imaginary literals get turned into infinities in the AST.
8 | # We unparse those infinities to INFSTR.
9 | INFSTR = "1e" + repr(sys.float_info.max_10_exp + 1)
10 |
11 | def interleave(inter, f, seq):
12 | """Call f on each item in seq, calling inter() in between.
13 | """
14 | seq = iter(seq)
15 | try:
16 | f(next(seq))
17 | except StopIteration:
18 | pass
19 | else:
20 | for x in seq:
21 | inter()
22 | f(x)
23 |
24 | class Unparser:
25 | """Methods in this class recursively traverse an AST and
26 | output source code for the abstract syntax; original formatting
27 | is disregarded. """
28 |
29 | def __init__(self, tree, file = sys.stdout):
30 | """Unparser(tree, file=sys.stdout) -> None.
31 | Print the source for tree to file."""
32 | self.f = file
33 | self.future_imports = []
34 | self._indent = 0
35 | self.dispatch(tree)
36 | self.f.write("")
37 | self.f.flush()
38 |
39 | def fill(self, text = ""):
40 | "Indent a piece of text, according to the current indentation level"
41 | self.f.write("\n"+" "*self._indent + text)
42 |
43 | def write(self, text):
44 | "Append a piece of text to the current line."
45 | self.f.write(text)
46 |
47 | def enter(self):
48 | "Print ':', and increase the indentation."
49 | self.write(":")
50 | self._indent += 1
51 |
52 | def leave(self):
53 | "Decrease the indentation level."
54 | self._indent -= 1
55 |
56 | def dispatch(self, tree):
57 | "Dispatcher function, dispatching tree type T to method _T."
58 | if isinstance(tree, list):
59 | for t in tree:
60 | self.dispatch(t)
61 | return
62 | meth = getattr(self, "_"+tree.__class__.__name__)
63 | meth(tree)
64 |
65 |
66 | ############### Unparsing methods ######################
67 | # There should be one method per concrete grammar type #
68 | # Constructors should be grouped by sum type. Ideally, #
69 | # this would follow the order in the grammar, but #
70 | # currently doesn't. #
71 | ########################################################
72 |
73 | def _Module(self, tree):
74 | for stmt in tree.body:
75 | self.dispatch(stmt)
76 |
77 | # stmt
78 | def _Expr(self, tree):
79 | self.fill()
80 | self.dispatch(tree.value)
81 |
82 | def _Import(self, t):
83 | self.fill("import ")
84 | interleave(lambda: self.write(", "), self.dispatch, t.names)
85 |
86 | def _ImportFrom(self, t):
87 | # A from __future__ import may affect unparsing, so record it.
88 | if t.module and t.module == '__future__':
89 | self.future_imports.extend(n.name for n in t.names)
90 |
91 | self.fill("from ")
92 | self.write("." * t.level)
93 | if t.module:
94 | self.write(t.module)
95 | self.write(" import ")
96 | interleave(lambda: self.write(", "), self.dispatch, t.names)
97 |
98 | def _Assign(self, t):
99 | self.fill()
100 | for target in t.targets:
101 | self.dispatch(target)
102 | self.write(" = ")
103 | self.dispatch(t.value)
104 |
105 | def _AugAssign(self, t):
106 | self.fill()
107 | self.dispatch(t.target)
108 | self.write(" "+self.binop[t.op.__class__.__name__]+"= ")
109 | self.dispatch(t.value)
110 |
111 | def _Return(self, t):
112 | self.fill("return")
113 | if t.value:
114 | self.write(" ")
115 | self.dispatch(t.value)
116 |
117 | def _Pass(self, t):
118 | self.fill("pass")
119 |
120 | def _Break(self, t):
121 | self.fill("break")
122 |
123 | def _Continue(self, t):
124 | self.fill("continue")
125 |
126 | def _Delete(self, t):
127 | self.fill("del ")
128 | interleave(lambda: self.write(", "), self.dispatch, t.targets)
129 |
130 | def _Assert(self, t):
131 | self.fill("assert ")
132 | self.dispatch(t.test)
133 | if t.msg:
134 | self.write(", ")
135 | self.dispatch(t.msg)
136 |
137 | def _Exec(self, t):
138 | self.fill("exec ")
139 | self.dispatch(t.body)
140 | if t.globals:
141 | self.write(" in ")
142 | self.dispatch(t.globals)
143 | if t.locals:
144 | self.write(", ")
145 | self.dispatch(t.locals)
146 |
147 | def _Print(self, t):
148 | self.fill("print ")
149 | do_comma = False
150 | if t.dest:
151 | self.write(">>")
152 | self.dispatch(t.dest)
153 | do_comma = True
154 | for e in t.values:
155 | if do_comma:self.write(", ")
156 | else:do_comma=True
157 | self.dispatch(e)
158 | if not t.nl:
159 | self.write(",")
160 |
161 | def _Global(self, t):
162 | self.fill("global ")
163 | interleave(lambda: self.write(", "), self.write, t.names)
164 |
165 | def _Yield(self, t):
166 | self.write("(")
167 | self.write("yield")
168 | if t.value:
169 | self.write(" ")
170 | self.dispatch(t.value)
171 | self.write(")")
172 |
173 | def _Raise(self, t):
174 | self.fill('raise ')
175 | if t.type:
176 | self.dispatch(t.type)
177 | if t.inst:
178 | self.write(", ")
179 | self.dispatch(t.inst)
180 | if t.tback:
181 | self.write(", ")
182 | self.dispatch(t.tback)
183 |
184 | def _TryExcept(self, t):
185 | self.fill("try")
186 | self.enter()
187 | self.dispatch(t.body)
188 | self.leave()
189 |
190 | for ex in t.handlers:
191 | self.dispatch(ex)
192 | if t.orelse:
193 | self.fill("else")
194 | self.enter()
195 | self.dispatch(t.orelse)
196 | self.leave()
197 |
198 | def _TryFinally(self, t):
199 | if len(t.body) == 1 and isinstance(t.body[0], ast.TryExcept):
200 | # try-except-finally
201 | self.dispatch(t.body)
202 | else:
203 | self.fill("try")
204 | self.enter()
205 | self.dispatch(t.body)
206 | self.leave()
207 |
208 | self.fill("finally")
209 | self.enter()
210 | self.dispatch(t.finalbody)
211 | self.leave()
212 |
213 | def _ExceptHandler(self, t):
214 | self.fill("except")
215 | if t.type:
216 | self.write(" ")
217 | self.dispatch(t.type)
218 | if t.name:
219 | self.write(" as ")
220 | self.dispatch(t.name)
221 | self.enter()
222 | self.dispatch(t.body)
223 | self.leave()
224 |
225 | def _ClassDef(self, t):
226 | self.write("\n")
227 | for deco in t.decorator_list:
228 | self.fill("@")
229 | self.dispatch(deco)
230 | self.fill("class "+t.name)
231 | if t.bases:
232 | self.write("(")
233 | for a in t.bases:
234 | self.dispatch(a)
235 | self.write(", ")
236 | self.write(")")
237 | self.enter()
238 | self.dispatch(t.body)
239 | self.leave()
240 |
241 | def _FunctionDef(self, t):
242 | self.write("\n")
243 | for deco in t.decorator_list:
244 | self.fill("@")
245 | self.dispatch(deco)
246 | self.fill("def "+t.name + "(")
247 | self.dispatch(t.args)
248 | self.write(")")
249 | self.enter()
250 | self.dispatch(t.body)
251 | self.leave()
252 |
253 | def _For(self, t):
254 | self.fill("for ")
255 | self.dispatch(t.target)
256 | self.write(" in ")
257 | self.dispatch(t.iter)
258 | self.enter()
259 | self.dispatch(t.body)
260 | self.leave()
261 | if t.orelse:
262 | self.fill("else")
263 | self.enter()
264 | self.dispatch(t.orelse)
265 | self.leave()
266 |
267 | def _If(self, t):
268 | self.fill("if ")
269 | self.dispatch(t.test)
270 | self.enter()
271 | self.dispatch(t.body)
272 | self.leave()
273 | # collapse nested ifs into equivalent elifs.
274 | while (t.orelse and len(t.orelse) == 1 and
275 | isinstance(t.orelse[0], ast.If)):
276 | t = t.orelse[0]
277 | self.fill("elif ")
278 | self.dispatch(t.test)
279 | self.enter()
280 | self.dispatch(t.body)
281 | self.leave()
282 | # final else
283 | if t.orelse:
284 | self.fill("else")
285 | self.enter()
286 | self.dispatch(t.orelse)
287 | self.leave()
288 |
289 | def _While(self, t):
290 | self.fill("while ")
291 | self.dispatch(t.test)
292 | self.enter()
293 | self.dispatch(t.body)
294 | self.leave()
295 | if t.orelse:
296 | self.fill("else")
297 | self.enter()
298 | self.dispatch(t.orelse)
299 | self.leave()
300 |
301 | def _With(self, t):
302 | self.fill("with ")
303 | self.dispatch(t.context_expr)
304 | if t.optional_vars:
305 | self.write(" as ")
306 | self.dispatch(t.optional_vars)
307 | self.enter()
308 | self.dispatch(t.body)
309 | self.leave()
310 |
311 | # expr
312 | def _Str(self, tree):
313 | # if from __future__ import unicode_literals is in effect,
314 | # then we want to output string literals using a 'b' prefix
315 | # and unicode literals with no prefix.
316 | if "unicode_literals" not in self.future_imports:
317 | self.write(repr(tree.s))
318 | elif isinstance(tree.s, str):
319 | self.write("b" + repr(tree.s))
320 | elif isinstance(tree.s, unicode):
321 | self.write(repr(tree.s).lstrip("u"))
322 | else:
323 | assert False, "shouldn't get here"
324 |
325 | def _Name(self, t):
326 | self.write(t.id)
327 |
328 | def _Repr(self, t):
329 | self.write("`")
330 | self.dispatch(t.value)
331 | self.write("`")
332 |
333 | def _Num(self, t):
334 | repr_n = repr(t.n)
335 | # Parenthesize negative numbers, to avoid turning (-1)**2 into -1**2.
336 | if repr_n.startswith("-"):
337 | self.write("(")
338 | # Substitute overflowing decimal literal for AST infinities.
339 | self.write(repr_n.replace("inf", INFSTR))
340 | if repr_n.startswith("-"):
341 | self.write(")")
342 |
343 | def _List(self, t):
344 | self.write("[")
345 | interleave(lambda: self.write(", "), self.dispatch, t.elts)
346 | self.write("]")
347 |
348 | def _ListComp(self, t):
349 | self.write("[")
350 | self.dispatch(t.elt)
351 | for gen in t.generators:
352 | self.dispatch(gen)
353 | self.write("]")
354 |
355 | def _GeneratorExp(self, t):
356 | self.write("(")
357 | self.dispatch(t.elt)
358 | for gen in t.generators:
359 | self.dispatch(gen)
360 | self.write(")")
361 |
362 | def _SetComp(self, t):
363 | self.write("{")
364 | self.dispatch(t.elt)
365 | for gen in t.generators:
366 | self.dispatch(gen)
367 | self.write("}")
368 |
369 | def _DictComp(self, t):
370 | self.write("{")
371 | self.dispatch(t.key)
372 | self.write(": ")
373 | self.dispatch(t.value)
374 | for gen in t.generators:
375 | self.dispatch(gen)
376 | self.write("}")
377 |
378 | def _comprehension(self, t):
379 | self.write(" for ")
380 | self.dispatch(t.target)
381 | self.write(" in ")
382 | self.dispatch(t.iter)
383 | for if_clause in t.ifs:
384 | self.write(" if ")
385 | self.dispatch(if_clause)
386 |
387 | def _IfExp(self, t):
388 | self.write("(")
389 | self.dispatch(t.body)
390 | self.write(" if ")
391 | self.dispatch(t.test)
392 | self.write(" else ")
393 | self.dispatch(t.orelse)
394 | self.write(")")
395 |
396 | def _Set(self, t):
397 | assert(t.elts) # should be at least one element
398 | self.write("{")
399 | interleave(lambda: self.write(", "), self.dispatch, t.elts)
400 | self.write("}")
401 |
402 | def _Dict(self, t):
403 | self.write("{")
404 | def write_pair(pair):
405 | (k, v) = pair
406 | self.dispatch(k)
407 | self.write(": ")
408 | self.dispatch(v)
409 | interleave(lambda: self.write(", "), write_pair, zip(t.keys, t.values))
410 | self.write("}")
411 |
412 | def _Tuple(self, t):
413 | self.write("(")
414 | if len(t.elts) == 1:
415 | (elt,) = t.elts
416 | self.dispatch(elt)
417 | self.write(",")
418 | else:
419 | interleave(lambda: self.write(", "), self.dispatch, t.elts)
420 | self.write(")")
421 |
422 | unop = {"Invert":"~", "Not": "not", "UAdd":"+", "USub":"-"}
423 | def _UnaryOp(self, t):
424 | self.write("(")
425 | self.write(self.unop[t.op.__class__.__name__])
426 | self.write(" ")
427 | # If we're applying unary minus to a number, parenthesize the number.
428 | # This is necessary: -2147483648 is different from -(2147483648) on
429 | # a 32-bit machine (the first is an int, the second a long), and
430 | # -7j is different from -(7j). (The first has real part 0.0, the second
431 | # has real part -0.0.)
432 | if isinstance(t.op, ast.USub) and isinstance(t.operand, ast.Num):
433 | self.write("(")
434 | self.dispatch(t.operand)
435 | self.write(")")
436 | else:
437 | self.dispatch(t.operand)
438 | self.write(")")
439 |
440 | binop = { "Add":"+", "Sub":"-", "Mult":"*", "Div":"/", "Mod":"%",
441 | "LShift":"<<", "RShift":">>", "BitOr":"|", "BitXor":"^", "BitAnd":"&",
442 | "FloorDiv":"//", "Pow": "**"}
443 | def _BinOp(self, t):
444 | self.write("(")
445 | self.dispatch(t.left)
446 | self.write(" " + self.binop[t.op.__class__.__name__] + " ")
447 | self.dispatch(t.right)
448 | self.write(")")
449 |
450 | cmpops = {"Eq":"==", "NotEq":"!=", "Lt":"<", "LtE":"<=", "Gt":">", "GtE":">=",
451 | "Is":"is", "IsNot":"is not", "In":"in", "NotIn":"not in"}
452 | def _Compare(self, t):
453 | self.write("(")
454 | self.dispatch(t.left)
455 | for o, e in zip(t.ops, t.comparators):
456 | self.write(" " + self.cmpops[o.__class__.__name__] + " ")
457 | self.dispatch(e)
458 | self.write(")")
459 |
460 | boolops = {ast.And: 'and', ast.Or: 'or'}
461 | def _BoolOp(self, t):
462 | self.write("(")
463 | s = " %s " % self.boolops[t.op.__class__]
464 | interleave(lambda: self.write(s), self.dispatch, t.values)
465 | self.write(")")
466 |
467 | def _Attribute(self,t):
468 | self.dispatch(t.value)
469 | # Special case: 3.__abs__() is a syntax error, so if t.value
470 | # is an integer literal then we need to either parenthesize
471 | # it or add an extra space to get 3 .__abs__().
472 | if isinstance(t.value, ast.Num) and isinstance(t.value.n, int):
473 | self.write(" ")
474 | self.write(".")
475 | self.write(t.attr)
476 |
477 | def _Call(self, t):
478 | self.dispatch(t.func)
479 | self.write("(")
480 | comma = False
481 | for e in t.args:
482 | if comma: self.write(", ")
483 | else: comma = True
484 | self.dispatch(e)
485 | for e in t.keywords:
486 | if comma: self.write(", ")
487 | else: comma = True
488 | self.dispatch(e)
489 | if t.starargs:
490 | if comma: self.write(", ")
491 | else: comma = True
492 | self.write("*")
493 | self.dispatch(t.starargs)
494 | if t.kwargs:
495 | if comma: self.write(", ")
496 | else: comma = True
497 | self.write("**")
498 | self.dispatch(t.kwargs)
499 | self.write(")")
500 |
501 | def _Subscript(self, t):
502 | self.dispatch(t.value)
503 | self.write("[")
504 | self.dispatch(t.slice)
505 | self.write("]")
506 |
507 | # slice
508 | def _Ellipsis(self, t):
509 | self.write("...")
510 |
511 | def _Index(self, t):
512 | self.dispatch(t.value)
513 |
514 | def _Slice(self, t):
515 | if t.lower:
516 | self.dispatch(t.lower)
517 | self.write(":")
518 | if t.upper:
519 | self.dispatch(t.upper)
520 | if t.step:
521 | self.write(":")
522 | self.dispatch(t.step)
523 |
524 | def _ExtSlice(self, t):
525 | interleave(lambda: self.write(', '), self.dispatch, t.dims)
526 |
527 | # others
528 | def _arguments(self, t):
529 | first = True
530 | # normal arguments
531 | defaults = [None] * (len(t.args) - len(t.defaults)) + t.defaults
532 | for a,d in zip(t.args, defaults):
533 | if first:first = False
534 | else: self.write(", ")
535 | self.dispatch(a),
536 | if d:
537 | self.write("=")
538 | self.dispatch(d)
539 |
540 | # varargs
541 | if t.vararg:
542 | if first:first = False
543 | else: self.write(", ")
544 | self.write("*")
545 | self.write(t.vararg)
546 |
547 | # kwargs
548 | if t.kwarg:
549 | if first:first = False
550 | else: self.write(", ")
551 | self.write("**"+t.kwarg)
552 |
553 | def _keyword(self, t):
554 | self.write(t.arg)
555 | self.write("=")
556 | self.dispatch(t.value)
557 |
558 | def _Lambda(self, t):
559 | self.write("(")
560 | self.write("lambda ")
561 | self.dispatch(t.args)
562 | self.write(": ")
563 | self.dispatch(t.body)
564 | self.write(")")
565 |
566 | def _alias(self, t):
567 | self.write(t.name)
568 | if t.asname:
569 | self.write(" as "+t.asname)
570 |
571 | def roundtrip(filename, output=sys.stdout):
572 | with open(filename, "r") as pyfile:
573 | source = pyfile.read()
574 | tree = compile(source, filename, "exec", ast.PyCF_ONLY_AST)
575 | Unparser(tree, output)
576 |
577 |
578 |
579 | def testdir(a):
580 | try:
581 | names = [n for n in os.listdir(a) if n.endswith('.py')]
582 | except OSError:
583 | sys.stderr.write("Directory not readable: %s" % a)
584 | else:
585 | for n in names:
586 | fullname = os.path.join(a, n)
587 | if os.path.isfile(fullname):
588 | output = cStringIO.StringIO()
589 | print 'Testing %s' % fullname
590 | try:
591 | roundtrip(fullname, output)
592 | except Exception as e:
593 | print ' Failed to compile, exception is %s' % repr(e)
594 | elif os.path.isdir(fullname):
595 | testdir(fullname)
596 |
597 | def main(args):
598 | if args[0] == '--testdir':
599 | for a in args[1:]:
600 | testdir(a)
601 | else:
602 | for a in args:
603 | roundtrip(a)
604 |
605 | if __name__=='__main__':
606 | main(sys.argv[1:])
607 |
--------------------------------------------------------------------------------
/ast/scrambling_code/Makefile:
--------------------------------------------------------------------------------
1 | INPUT_DIR := ../app
2 | OUTPUT_DIR := ./app
3 |
4 | all: scramble
5 |
6 | scramble: copy
7 | @ python scramble.py $(OUTPUT_DIR)
8 | @ cp ihook.py scrambler.py manage.py $(OUTPUT_DIR)
9 |
10 | unscramble:
11 | @ python unscramble.py $(OUTPUT_DIR)
12 |
13 | copy: clean
14 | @ cp -R $(INPUT_DIR) $(OUTPUT_DIR)
15 |
16 | clean:
17 | @ rm -rf $(OUTPUT_DIR)
18 |
19 | .DEFAULT: all
20 | .PHONY: unscramble scramble copy clean
21 |
--------------------------------------------------------------------------------
/ast/scrambling_code/README.md:
--------------------------------------------------------------------------------
1 | # Python code Protection Mechanisms - Code Scrambling
2 |
3 | This mechanism will make use of Python [Abstract Syntax Trees](https://docs.python.org/2/library/ast.html)
4 | and an [import hook](https://www.python.org/dev/peps/pep-0302/) in order to
5 | scramble code and unscramble it on import time.
6 |
7 |
8 | ## Description
9 |
10 | Python [Abstract Syntax Trees](https://docs.python.org/2/library/ast.html) is
11 | a tool to process of Python abstract syntax grammar, which can be modified on
12 | any way before the code is evaluated by the interpreter.
13 |
14 | In this case the code will be scrambled and written back to the file system.
15 | Later the application will configure an import hook to detect scrambled code
16 | and unscramble it on import time.
17 |
18 | The scrambling algorithm is fair simple, but good enough for this example.
19 |
20 | ## Pros
21 |
22 | 1. Simple to implement, scrambling algorithms can vary to make them more
23 | complicated to crack.
24 |
25 | ## Cons
26 |
27 | 1. The unscrambling code must be available in the server in order to run the
28 | application (the module can be compiled with Cython or implemented as python
29 | extension).
30 |
31 | 2. The decrypted code in memory can be inspected and dumped and decompiled from
32 | byte-code back to python.
33 |
--------------------------------------------------------------------------------
/ast/scrambling_code/ihook.py:
--------------------------------------------------------------------------------
1 | import os
2 | import sys
3 | import imp
4 | import ast
5 |
6 | from scrambler import Scrambler
7 |
8 |
9 | class BaseLoader(object):
10 | def modinfo(self, name, path):
11 | try:
12 | modinfo = imp.find_module(name.rsplit('.', 1)[-1], path)
13 | except ImportError:
14 | if '.' not in name:
15 | raise
16 | clean_path, clean_name = name.rsplit('.', 1)
17 | clean_path = [clean_path.replace('.', '/')]
18 | modinfo = imp.find_module(clean_name, clean_path)
19 |
20 | file, pathname, (suffix, mode, type_) = modinfo
21 | if type_ == imp.PY_SOURCE:
22 | filename = pathname
23 | elif type_ == imp.PY_COMPILED:
24 | filename = pathname[:-1]
25 | elif type_ == imp.PKG_DIRECTORY:
26 | filename = os.path.join(pathname, '__init__.py')
27 | else:
28 | return (None, None)
29 | return (filename, modinfo)
30 |
31 |
32 | class Loader(BaseLoader):
33 | def __init__(self, name, path):
34 | self.name = name
35 | self.path = path
36 |
37 | def is_package(self, val):
38 | """Flask requirement"""
39 | return None
40 |
41 | def load_module(self, name):
42 | if name in sys.modules:
43 | return sys.modules[name]
44 |
45 | filename, modinfo = self.modinfo(self.name, self.path)
46 | if filename is None and modinfo is None:
47 | return None
48 |
49 | file = modinfo[0] or open(filename, 'r')
50 | src = ''.join(file.readlines())
51 | src = self.unscramble(src)
52 | src = ast.fix_missing_locations(src)
53 |
54 | module = imp.new_module(name)
55 | module.__file__ = filename
56 | module.__path__ = [os.path.dirname(os.path.abspath(file.name))]
57 | module.__loader__ = self
58 | sys.modules[name] = module
59 | codeobj = compile(src, name, 'exec')
60 | eval(codeobj, module.__dict__, module.__dict__)
61 | print 'scrambled module loaded: {0}'.format(name)
62 | return module
63 |
64 | def unscramble(self, code):
65 | node = ast.parse(code)
66 | code = Scrambler(scramble=False).visit(node)
67 | return code
68 |
69 |
70 | class Finder(BaseLoader):
71 | def find_module(self, name, path=None):
72 | filename, modinfo = self.modinfo(name, path)
73 | if filename is None and modinfo is None:
74 | return None
75 |
76 | file = modinfo[0] or open(filename, 'r')
77 | if file.read(len(Scrambler.HEADER)) == Scrambler.HEADER:
78 | print 'scrambled module found: {0} (at {1})'.format(name, path)
79 | file.seek(0)
80 | return Loader(name, path)
81 |
82 |
83 | def install_hook():
84 | sys.meta_path.insert(0, Finder())
85 |
--------------------------------------------------------------------------------
/ast/scrambling_code/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | # Install import hook
4 | import ihook
5 | ihook.install_hook()
6 |
7 | from flask_script import Server, Manager, Shell
8 |
9 | from app import app
10 |
11 |
12 | manager = Manager(app)
13 | manager.add_command('runserver', Server())
14 | manager.add_command('shell', Shell(make_context=lambda: {
15 | 'app': app,
16 | }))
17 |
18 |
19 | if __name__ == '__main__':
20 | manager.run()
21 |
--------------------------------------------------------------------------------
/ast/scrambling_code/scramble.py:
--------------------------------------------------------------------------------
1 | import os
2 | import sys
3 | import ast
4 |
5 | from scrambler import Scrambler
6 | from unparse import Unparser
7 |
8 |
9 | for root, dirs, files in os.walk(sys.argv[1]):
10 | for file in files:
11 | if file.endswith('.py'):
12 | with open('{0}/{1}'.format(root, file), 'r') as f:
13 | code = ''.join(f.readlines())
14 | if code:
15 | with open('{0}/{1}'.format(root, file), 'w') as f:
16 | node = ast.parse(code)
17 | encoded = Scrambler(scramble=True).visit(node)
18 | f.write('{0}\n'.format(Scrambler.HEADER))
19 | Unparser(encoded, f)
20 | f.write('\n')
21 |
--------------------------------------------------------------------------------
/ast/scrambling_code/scrambler.py:
--------------------------------------------------------------------------------
1 | from ast import NodeTransformer
2 |
3 |
4 | class Scrambler(NodeTransformer):
5 | HEADER = '# scrambled'
6 |
7 | def __init__(self, scramble=True):
8 | self.do_scramble = scramble
9 |
10 | def visit(self, node):
11 | node_out = super(Scrambler, self).visit(node)
12 | if hasattr(node_out, 'body') and isinstance(node_out.body, list):
13 | if self.do_scramble:
14 | node_out.body = self.scramble(node_out.body)
15 | else:
16 | node_out.body = self.unscramble(node_out.body)
17 | return node_out
18 |
19 | def scramble(self, items):
20 | return self._step2(self._step1(items[:]))
21 |
22 | def unscramble(self, items):
23 | return self._step1(self._step2(items[:]))
24 |
25 | def _step1(self, items):
26 | i = 0
27 | length = len(items)
28 | while (i + 1) < length:
29 | items[i], items[i + 1] = items[i + 1], items[i]
30 | i += 2
31 | return items
32 |
33 | def _step2(self, items):
34 | length = len(items)
35 | if length % 2 == 0:
36 | items[:length / 2], items[length / 2:] = \
37 | items[length / 2:], items[:length / 2]
38 | else:
39 | items[:(length - 1) / 2], items[(length + 1) / 2:] = \
40 | items[(length + 1) / 2:], items[:(length - 1) / 2]
41 | return items
42 |
--------------------------------------------------------------------------------
/ast/scrambling_code/unparse.py:
--------------------------------------------------------------------------------
1 | "Usage: unparse.py "
2 | import sys
3 | import ast
4 | import cStringIO
5 | import os
6 |
7 | # Large float and imaginary literals get turned into infinities in the AST.
8 | # We unparse those infinities to INFSTR.
9 | INFSTR = "1e" + repr(sys.float_info.max_10_exp + 1)
10 |
11 | def interleave(inter, f, seq):
12 | """Call f on each item in seq, calling inter() in between.
13 | """
14 | seq = iter(seq)
15 | try:
16 | f(next(seq))
17 | except StopIteration:
18 | pass
19 | else:
20 | for x in seq:
21 | inter()
22 | f(x)
23 |
24 | class Unparser:
25 | """Methods in this class recursively traverse an AST and
26 | output source code for the abstract syntax; original formatting
27 | is disregarded. """
28 |
29 | def __init__(self, tree, file = sys.stdout):
30 | """Unparser(tree, file=sys.stdout) -> None.
31 | Print the source for tree to file."""
32 | self.f = file
33 | self.future_imports = []
34 | self._indent = 0
35 | self.dispatch(tree)
36 | self.f.write("")
37 | self.f.flush()
38 |
39 | def fill(self, text = ""):
40 | "Indent a piece of text, according to the current indentation level"
41 | self.f.write("\n"+" "*self._indent + text)
42 |
43 | def write(self, text):
44 | "Append a piece of text to the current line."
45 | self.f.write(text)
46 |
47 | def enter(self):
48 | "Print ':', and increase the indentation."
49 | self.write(":")
50 | self._indent += 1
51 |
52 | def leave(self):
53 | "Decrease the indentation level."
54 | self._indent -= 1
55 |
56 | def dispatch(self, tree):
57 | "Dispatcher function, dispatching tree type T to method _T."
58 | if isinstance(tree, list):
59 | for t in tree:
60 | self.dispatch(t)
61 | return
62 | meth = getattr(self, "_"+tree.__class__.__name__)
63 | meth(tree)
64 |
65 |
66 | ############### Unparsing methods ######################
67 | # There should be one method per concrete grammar type #
68 | # Constructors should be grouped by sum type. Ideally, #
69 | # this would follow the order in the grammar, but #
70 | # currently doesn't. #
71 | ########################################################
72 |
73 | def _Module(self, tree):
74 | for stmt in tree.body:
75 | self.dispatch(stmt)
76 |
77 | # stmt
78 | def _Expr(self, tree):
79 | self.fill()
80 | self.dispatch(tree.value)
81 |
82 | def _Import(self, t):
83 | self.fill("import ")
84 | interleave(lambda: self.write(", "), self.dispatch, t.names)
85 |
86 | def _ImportFrom(self, t):
87 | # A from __future__ import may affect unparsing, so record it.
88 | if t.module and t.module == '__future__':
89 | self.future_imports.extend(n.name for n in t.names)
90 |
91 | self.fill("from ")
92 | self.write("." * t.level)
93 | if t.module:
94 | self.write(t.module)
95 | self.write(" import ")
96 | interleave(lambda: self.write(", "), self.dispatch, t.names)
97 |
98 | def _Assign(self, t):
99 | self.fill()
100 | for target in t.targets:
101 | self.dispatch(target)
102 | self.write(" = ")
103 | self.dispatch(t.value)
104 |
105 | def _AugAssign(self, t):
106 | self.fill()
107 | self.dispatch(t.target)
108 | self.write(" "+self.binop[t.op.__class__.__name__]+"= ")
109 | self.dispatch(t.value)
110 |
111 | def _Return(self, t):
112 | self.fill("return")
113 | if t.value:
114 | self.write(" ")
115 | self.dispatch(t.value)
116 |
117 | def _Pass(self, t):
118 | self.fill("pass")
119 |
120 | def _Break(self, t):
121 | self.fill("break")
122 |
123 | def _Continue(self, t):
124 | self.fill("continue")
125 |
126 | def _Delete(self, t):
127 | self.fill("del ")
128 | interleave(lambda: self.write(", "), self.dispatch, t.targets)
129 |
130 | def _Assert(self, t):
131 | self.fill("assert ")
132 | self.dispatch(t.test)
133 | if t.msg:
134 | self.write(", ")
135 | self.dispatch(t.msg)
136 |
137 | def _Exec(self, t):
138 | self.fill("exec ")
139 | self.dispatch(t.body)
140 | if t.globals:
141 | self.write(" in ")
142 | self.dispatch(t.globals)
143 | if t.locals:
144 | self.write(", ")
145 | self.dispatch(t.locals)
146 |
147 | def _Print(self, t):
148 | self.fill("print ")
149 | do_comma = False
150 | if t.dest:
151 | self.write(">>")
152 | self.dispatch(t.dest)
153 | do_comma = True
154 | for e in t.values:
155 | if do_comma:self.write(", ")
156 | else:do_comma=True
157 | self.dispatch(e)
158 | if not t.nl:
159 | self.write(",")
160 |
161 | def _Global(self, t):
162 | self.fill("global ")
163 | interleave(lambda: self.write(", "), self.write, t.names)
164 |
165 | def _Yield(self, t):
166 | self.write("(")
167 | self.write("yield")
168 | if t.value:
169 | self.write(" ")
170 | self.dispatch(t.value)
171 | self.write(")")
172 |
173 | def _Raise(self, t):
174 | self.fill('raise ')
175 | if t.type:
176 | self.dispatch(t.type)
177 | if t.inst:
178 | self.write(", ")
179 | self.dispatch(t.inst)
180 | if t.tback:
181 | self.write(", ")
182 | self.dispatch(t.tback)
183 |
184 | def _TryExcept(self, t):
185 | self.fill("try")
186 | self.enter()
187 | self.dispatch(t.body)
188 | self.leave()
189 |
190 | for ex in t.handlers:
191 | self.dispatch(ex)
192 | if t.orelse:
193 | self.fill("else")
194 | self.enter()
195 | self.dispatch(t.orelse)
196 | self.leave()
197 |
198 | def _TryFinally(self, t):
199 | if len(t.body) == 1 and isinstance(t.body[0], ast.TryExcept):
200 | # try-except-finally
201 | self.dispatch(t.body)
202 | else:
203 | self.fill("try")
204 | self.enter()
205 | self.dispatch(t.body)
206 | self.leave()
207 |
208 | self.fill("finally")
209 | self.enter()
210 | self.dispatch(t.finalbody)
211 | self.leave()
212 |
213 | def _ExceptHandler(self, t):
214 | self.fill("except")
215 | if t.type:
216 | self.write(" ")
217 | self.dispatch(t.type)
218 | if t.name:
219 | self.write(" as ")
220 | self.dispatch(t.name)
221 | self.enter()
222 | self.dispatch(t.body)
223 | self.leave()
224 |
225 | def _ClassDef(self, t):
226 | self.write("\n")
227 | for deco in t.decorator_list:
228 | self.fill("@")
229 | self.dispatch(deco)
230 | self.fill("class "+t.name)
231 | if t.bases:
232 | self.write("(")
233 | for a in t.bases:
234 | self.dispatch(a)
235 | self.write(", ")
236 | self.write(")")
237 | self.enter()
238 | self.dispatch(t.body)
239 | self.leave()
240 |
241 | def _FunctionDef(self, t):
242 | self.write("\n")
243 | for deco in t.decorator_list:
244 | self.fill("@")
245 | self.dispatch(deco)
246 | self.fill("def "+t.name + "(")
247 | self.dispatch(t.args)
248 | self.write(")")
249 | self.enter()
250 | self.dispatch(t.body)
251 | self.leave()
252 |
253 | def _For(self, t):
254 | self.fill("for ")
255 | self.dispatch(t.target)
256 | self.write(" in ")
257 | self.dispatch(t.iter)
258 | self.enter()
259 | self.dispatch(t.body)
260 | self.leave()
261 | if t.orelse:
262 | self.fill("else")
263 | self.enter()
264 | self.dispatch(t.orelse)
265 | self.leave()
266 |
267 | def _If(self, t):
268 | self.fill("if ")
269 | self.dispatch(t.test)
270 | self.enter()
271 | self.dispatch(t.body)
272 | self.leave()
273 | # collapse nested ifs into equivalent elifs.
274 | while (t.orelse and len(t.orelse) == 1 and
275 | isinstance(t.orelse[0], ast.If)):
276 | t = t.orelse[0]
277 | self.fill("elif ")
278 | self.dispatch(t.test)
279 | self.enter()
280 | self.dispatch(t.body)
281 | self.leave()
282 | # final else
283 | if t.orelse:
284 | self.fill("else")
285 | self.enter()
286 | self.dispatch(t.orelse)
287 | self.leave()
288 |
289 | def _While(self, t):
290 | self.fill("while ")
291 | self.dispatch(t.test)
292 | self.enter()
293 | self.dispatch(t.body)
294 | self.leave()
295 | if t.orelse:
296 | self.fill("else")
297 | self.enter()
298 | self.dispatch(t.orelse)
299 | self.leave()
300 |
301 | def _With(self, t):
302 | self.fill("with ")
303 | self.dispatch(t.context_expr)
304 | if t.optional_vars:
305 | self.write(" as ")
306 | self.dispatch(t.optional_vars)
307 | self.enter()
308 | self.dispatch(t.body)
309 | self.leave()
310 |
311 | # expr
312 | def _Str(self, tree):
313 | # if from __future__ import unicode_literals is in effect,
314 | # then we want to output string literals using a 'b' prefix
315 | # and unicode literals with no prefix.
316 | if "unicode_literals" not in self.future_imports:
317 | self.write(repr(tree.s))
318 | elif isinstance(tree.s, str):
319 | self.write("b" + repr(tree.s))
320 | elif isinstance(tree.s, unicode):
321 | self.write(repr(tree.s).lstrip("u"))
322 | else:
323 | assert False, "shouldn't get here"
324 |
325 | def _Name(self, t):
326 | self.write(t.id)
327 |
328 | def _Repr(self, t):
329 | self.write("`")
330 | self.dispatch(t.value)
331 | self.write("`")
332 |
333 | def _Num(self, t):
334 | repr_n = repr(t.n)
335 | # Parenthesize negative numbers, to avoid turning (-1)**2 into -1**2.
336 | if repr_n.startswith("-"):
337 | self.write("(")
338 | # Substitute overflowing decimal literal for AST infinities.
339 | self.write(repr_n.replace("inf", INFSTR))
340 | if repr_n.startswith("-"):
341 | self.write(")")
342 |
343 | def _List(self, t):
344 | self.write("[")
345 | interleave(lambda: self.write(", "), self.dispatch, t.elts)
346 | self.write("]")
347 |
348 | def _ListComp(self, t):
349 | self.write("[")
350 | self.dispatch(t.elt)
351 | for gen in t.generators:
352 | self.dispatch(gen)
353 | self.write("]")
354 |
355 | def _GeneratorExp(self, t):
356 | self.write("(")
357 | self.dispatch(t.elt)
358 | for gen in t.generators:
359 | self.dispatch(gen)
360 | self.write(")")
361 |
362 | def _SetComp(self, t):
363 | self.write("{")
364 | self.dispatch(t.elt)
365 | for gen in t.generators:
366 | self.dispatch(gen)
367 | self.write("}")
368 |
369 | def _DictComp(self, t):
370 | self.write("{")
371 | self.dispatch(t.key)
372 | self.write(": ")
373 | self.dispatch(t.value)
374 | for gen in t.generators:
375 | self.dispatch(gen)
376 | self.write("}")
377 |
378 | def _comprehension(self, t):
379 | self.write(" for ")
380 | self.dispatch(t.target)
381 | self.write(" in ")
382 | self.dispatch(t.iter)
383 | for if_clause in t.ifs:
384 | self.write(" if ")
385 | self.dispatch(if_clause)
386 |
387 | def _IfExp(self, t):
388 | self.write("(")
389 | self.dispatch(t.body)
390 | self.write(" if ")
391 | self.dispatch(t.test)
392 | self.write(" else ")
393 | self.dispatch(t.orelse)
394 | self.write(")")
395 |
396 | def _Set(self, t):
397 | assert(t.elts) # should be at least one element
398 | self.write("{")
399 | interleave(lambda: self.write(", "), self.dispatch, t.elts)
400 | self.write("}")
401 |
402 | def _Dict(self, t):
403 | self.write("{")
404 | def write_pair(pair):
405 | (k, v) = pair
406 | self.dispatch(k)
407 | self.write(": ")
408 | self.dispatch(v)
409 | interleave(lambda: self.write(", "), write_pair, zip(t.keys, t.values))
410 | self.write("}")
411 |
412 | def _Tuple(self, t):
413 | self.write("(")
414 | if len(t.elts) == 1:
415 | (elt,) = t.elts
416 | self.dispatch(elt)
417 | self.write(",")
418 | else:
419 | interleave(lambda: self.write(", "), self.dispatch, t.elts)
420 | self.write(")")
421 |
422 | unop = {"Invert":"~", "Not": "not", "UAdd":"+", "USub":"-"}
423 | def _UnaryOp(self, t):
424 | self.write("(")
425 | self.write(self.unop[t.op.__class__.__name__])
426 | self.write(" ")
427 | # If we're applying unary minus to a number, parenthesize the number.
428 | # This is necessary: -2147483648 is different from -(2147483648) on
429 | # a 32-bit machine (the first is an int, the second a long), and
430 | # -7j is different from -(7j). (The first has real part 0.0, the second
431 | # has real part -0.0.)
432 | if isinstance(t.op, ast.USub) and isinstance(t.operand, ast.Num):
433 | self.write("(")
434 | self.dispatch(t.operand)
435 | self.write(")")
436 | else:
437 | self.dispatch(t.operand)
438 | self.write(")")
439 |
440 | binop = { "Add":"+", "Sub":"-", "Mult":"*", "Div":"/", "Mod":"%",
441 | "LShift":"<<", "RShift":">>", "BitOr":"|", "BitXor":"^", "BitAnd":"&",
442 | "FloorDiv":"//", "Pow": "**"}
443 | def _BinOp(self, t):
444 | self.write("(")
445 | self.dispatch(t.left)
446 | self.write(" " + self.binop[t.op.__class__.__name__] + " ")
447 | self.dispatch(t.right)
448 | self.write(")")
449 |
450 | cmpops = {"Eq":"==", "NotEq":"!=", "Lt":"<", "LtE":"<=", "Gt":">", "GtE":">=",
451 | "Is":"is", "IsNot":"is not", "In":"in", "NotIn":"not in"}
452 | def _Compare(self, t):
453 | self.write("(")
454 | self.dispatch(t.left)
455 | for o, e in zip(t.ops, t.comparators):
456 | self.write(" " + self.cmpops[o.__class__.__name__] + " ")
457 | self.dispatch(e)
458 | self.write(")")
459 |
460 | boolops = {ast.And: 'and', ast.Or: 'or'}
461 | def _BoolOp(self, t):
462 | self.write("(")
463 | s = " %s " % self.boolops[t.op.__class__]
464 | interleave(lambda: self.write(s), self.dispatch, t.values)
465 | self.write(")")
466 |
467 | def _Attribute(self,t):
468 | self.dispatch(t.value)
469 | # Special case: 3.__abs__() is a syntax error, so if t.value
470 | # is an integer literal then we need to either parenthesize
471 | # it or add an extra space to get 3 .__abs__().
472 | if isinstance(t.value, ast.Num) and isinstance(t.value.n, int):
473 | self.write(" ")
474 | self.write(".")
475 | self.write(t.attr)
476 |
477 | def _Call(self, t):
478 | self.dispatch(t.func)
479 | self.write("(")
480 | comma = False
481 | for e in t.args:
482 | if comma: self.write(", ")
483 | else: comma = True
484 | self.dispatch(e)
485 | for e in t.keywords:
486 | if comma: self.write(", ")
487 | else: comma = True
488 | self.dispatch(e)
489 | if t.starargs:
490 | if comma: self.write(", ")
491 | else: comma = True
492 | self.write("*")
493 | self.dispatch(t.starargs)
494 | if t.kwargs:
495 | if comma: self.write(", ")
496 | else: comma = True
497 | self.write("**")
498 | self.dispatch(t.kwargs)
499 | self.write(")")
500 |
501 | def _Subscript(self, t):
502 | self.dispatch(t.value)
503 | self.write("[")
504 | self.dispatch(t.slice)
505 | self.write("]")
506 |
507 | # slice
508 | def _Ellipsis(self, t):
509 | self.write("...")
510 |
511 | def _Index(self, t):
512 | self.dispatch(t.value)
513 |
514 | def _Slice(self, t):
515 | if t.lower:
516 | self.dispatch(t.lower)
517 | self.write(":")
518 | if t.upper:
519 | self.dispatch(t.upper)
520 | if t.step:
521 | self.write(":")
522 | self.dispatch(t.step)
523 |
524 | def _ExtSlice(self, t):
525 | interleave(lambda: self.write(', '), self.dispatch, t.dims)
526 |
527 | # others
528 | def _arguments(self, t):
529 | first = True
530 | # normal arguments
531 | defaults = [None] * (len(t.args) - len(t.defaults)) + t.defaults
532 | for a,d in zip(t.args, defaults):
533 | if first:first = False
534 | else: self.write(", ")
535 | self.dispatch(a),
536 | if d:
537 | self.write("=")
538 | self.dispatch(d)
539 |
540 | # varargs
541 | if t.vararg:
542 | if first:first = False
543 | else: self.write(", ")
544 | self.write("*")
545 | self.write(t.vararg)
546 |
547 | # kwargs
548 | if t.kwarg:
549 | if first:first = False
550 | else: self.write(", ")
551 | self.write("**"+t.kwarg)
552 |
553 | def _keyword(self, t):
554 | self.write(t.arg)
555 | self.write("=")
556 | self.dispatch(t.value)
557 |
558 | def _Lambda(self, t):
559 | self.write("(")
560 | self.write("lambda ")
561 | self.dispatch(t.args)
562 | self.write(": ")
563 | self.dispatch(t.body)
564 | self.write(")")
565 |
566 | def _alias(self, t):
567 | self.write(t.name)
568 | if t.asname:
569 | self.write(" as "+t.asname)
570 |
571 | def roundtrip(filename, output=sys.stdout):
572 | with open(filename, "r") as pyfile:
573 | source = pyfile.read()
574 | tree = compile(source, filename, "exec", ast.PyCF_ONLY_AST)
575 | Unparser(tree, output)
576 |
577 |
578 |
579 | def testdir(a):
580 | try:
581 | names = [n for n in os.listdir(a) if n.endswith('.py')]
582 | except OSError:
583 | sys.stderr.write("Directory not readable: %s" % a)
584 | else:
585 | for n in names:
586 | fullname = os.path.join(a, n)
587 | if os.path.isfile(fullname):
588 | output = cStringIO.StringIO()
589 | print 'Testing %s' % fullname
590 | try:
591 | roundtrip(fullname, output)
592 | except Exception as e:
593 | print ' Failed to compile, exception is %s' % repr(e)
594 | elif os.path.isdir(fullname):
595 | testdir(fullname)
596 |
597 | def main(args):
598 | if args[0] == '--testdir':
599 | for a in args[1:]:
600 | testdir(a)
601 | else:
602 | for a in args:
603 | roundtrip(a)
604 |
605 | if __name__=='__main__':
606 | main(sys.argv[1:])
607 |
--------------------------------------------------------------------------------
/ast/scrambling_code/unscramble.py:
--------------------------------------------------------------------------------
1 | import os
2 | import sys
3 | import ast
4 |
5 | from scrambler import Scrambler
6 | from unparse import Unparser
7 |
8 |
9 | for root, dirs, files in os.walk(sys.argv[1]):
10 | for file in files:
11 | if file.endswith('.py'):
12 | with open('{0}/{1}'.format(root, file), 'r') as f:
13 | lines = f.readlines()
14 | if lines[0].strip() != Scrambler.HEADER:
15 | continue
16 | code = ''.join(lines[1:])
17 | with open('{0}/{1}'.format(root, file), 'w') as f:
18 | node = ast.parse(code)
19 | decoded = Scrambler(scramble=False).visit(node)
20 | Unparser(decoded, f)
21 | f.write('\n')
22 |
--------------------------------------------------------------------------------
/bytecode/pyc/Makefile:
--------------------------------------------------------------------------------
1 | all:
2 | @ python -m compileall app/
3 |
4 | clean-build:
5 | @ find . ! -name "manage.py" -name "*.py" -delete
6 |
7 | clean:
8 | @ rm -rf ./app
9 |
10 | run: clean
11 | @ cp -R ../../app .
12 | @ find . -iname "*.py[co]" -delete
13 | @ $(MAKE) && $(MAKE) clean-build
14 |
15 | .DEFAULT: all
16 | .PHONY: all clean clean-build
17 |
--------------------------------------------------------------------------------
/bytecode/pyc/README.md:
--------------------------------------------------------------------------------
1 | # Python code Protection Mechanisms - Byte-code distribution
2 |
3 | Compile ``.py`` into ``.pyc`` to distribute.
4 |
5 | ## Description
6 |
7 | In this example, we will be using [compileall](https://docs.python.org/2/library/compileall.html)
8 | to compile the different ``.py`` files into ``.pyc``, then remove the ``.py``
9 | keeping the application functionality.
10 |
11 | ## Pros
12 |
13 | 1. Simple to implement (just a command call is needed)
14 |
15 | ## Cons
16 |
17 | 1. Byte-code is easily converted back to python code, [uncompyle2](https://pypi.python.org/pypi/uncompyle2)
18 | does the work to build back the ``.py`` files very well.
19 |
--------------------------------------------------------------------------------
/bytecode/pyo/Makefile:
--------------------------------------------------------------------------------
1 | all:
2 | @ python -OO -m compileall app/
3 |
4 | clean-build:
5 | @ find . ! -name "__init__.py" ! -name "manage.py" -name "*.py" -delete
6 |
7 | clean:
8 | @ rm -rf ./app
9 |
10 | run: clean
11 | @ cp -R ../../app .
12 | @ find . -iname "*.py[co]" -delete
13 | @ $(MAKE) && $(MAKE) clean-build
14 |
15 | .DEFAULT: all
16 | .PHONY: all clean clean-build run
17 |
--------------------------------------------------------------------------------
/bytecode/pyo/README.md:
--------------------------------------------------------------------------------
1 | # Python code Protection Mechanisms - Optimized Byte-code distribution
2 |
3 | Compile ``.py`` into ``.pyo`` to distribute, there's not much gain compared
4 | with the usual ``.pyc`` files since optimized byte-code mostly disables
5 | ``assert`` calls and removes docstrings.
6 |
7 | ## Description
8 |
9 | In this example, we will be using [compileall](https://docs.python.org/2/library/compileall.html)
10 | to compile the different ``.py`` files into ``.pyo``, then remove the ``.py``
11 | keeping the application functionality.
12 |
13 | ## Pros
14 |
15 | 1. Simple to implement (just a command call is needed).
16 |
17 | ## Cons
18 |
19 | 1. Optimized byte-code is easily converted back to python code,
20 | [uncompyle2](https://pypi.python.org/pypi/uncompyle2) does the work to build
21 | back the ``.py`` files very well.
22 |
--------------------------------------------------------------------------------
/custom_interpreter/opcodes/.python-version:
--------------------------------------------------------------------------------
1 | 2.7.9-custom
2 |
--------------------------------------------------------------------------------
/custom_interpreter/opcodes/2.7.9-custom:
--------------------------------------------------------------------------------
1 | #require_gcc
2 | install_package "readline-6.3" "http://ftpmirror.gnu.org/readline/readline-6.3.tar.gz#56ba6071b9462f980c5a72ab0023893b65ba6debb4eeb475d7a563dc65cafd43" standard --if has_broken_mac_readline
3 | install_package "Python-2.7.9" "https://www.python.org/ftp/python/2.7.9/Python-2.7.9.tgz#c8bba33e66ac3201dabdc556f0ea7cfe6ac11946ec32d357c4c6f9b018c12c5b" ldflags_dirs standard verify_py27 ensurepip
4 |
--------------------------------------------------------------------------------
/custom_interpreter/opcodes/Makefile:
--------------------------------------------------------------------------------
1 | PYTHON_DIR := Python-2.7.9
2 | PYTHON_CUSTOM_DIR := $(PYTHON_DIR)-customized
3 | PYTHON_XZ := Python-2.7.9.tar.xz
4 |
5 | all: pyenv
6 | @ cp -R ../../app .
7 | @ pip install -r app/requirements.txt
8 |
9 | clean:
10 | @ rm -rf app $(PYTHON_DIR) $(PYTHON_CUSTOM_DIR) *.diff
11 |
12 | $(PYTHON_XZ):
13 | @ wget -c https://www.python.org/ftp/python/2.7.9/Python-2.7.9.tar.xz
14 |
15 | $(PYTHON_DIR): $(PYTHON_XZ)
16 | @ tar xJf Python-2.7.9.tar.xz
17 |
18 | $(PYTHON_CUSTOM_DIR): $(PYTHON_DIR)
19 | @ cp -R $(PYTHON_DIR) $(PYTHON_CUSTOM_DIR)
20 |
21 | # Do "diff .. || true" because diff exits status is based on if there
22 | # are differences between the files or not (like cmp)
23 | scramble: $(PYTHON_CUSTOM_DIR)
24 | @ python scramble-opcodes.py --python-source=$(PYTHON_CUSTOM_DIR)
25 | @ diff -u $(PYTHON_DIR)/Include/opcode.h $(PYTHON_CUSTOM_DIR)/Include/opcode.h \
26 | > patches/001_scrambled-opcodes.diff || true
27 |
28 | patches: scramble
29 |
30 | pyenv: patches
31 | @ cat patches/*.diff | filterdiff --strip=1 | pyenv install -sp 2.7.9-custom
32 | @ pyenv local 2.7.9-custom
33 |
34 | run: clean $(PYTHON_CUSTOM_DIR) patches
35 |
36 | .DEFAULT: all
37 | .PHONY: all clean clean-build run
38 |
--------------------------------------------------------------------------------
/custom_interpreter/opcodes/README.md:
--------------------------------------------------------------------------------
1 | # Python code Protection Mechanisms - Custom Interpreter
2 |
3 | Customize python interpreter to protect a python codebase.
4 |
5 | ## Description
6 |
7 | In this example, we will be customizing the CPython interpreter in order to
8 | introduce protection measures to avoid the reverse engineering of the codebase.
9 |
10 | So far the following protections were introduced:
11 |
12 | 1. Opcode scrambling to protect against tools like [uncompyle2](https://pypi.python.org/pypi/uncompyle2)
13 |
14 | The following protections will be investigated too:
15 |
16 | 1. Dynamic ``opcode`` tables (use a different ``opcode`` table for each
17 | ``.py`` file to make decompiling technics even harder)
18 | 2. Removal of properties that leak code (``co_code``, etc)
19 | 3. Removal of functions that allow code injection (``PyRun_SimpleString``,
20 | ``compile``, etc)
21 | 4. Built-in encryption support
22 |
23 | ## Pros
24 |
25 | 1. Customizing the python interpreter opens the door to several protection
26 | mechanisms available for C applications (like anti-debugging techniques)
27 |
28 | 2. Conventional tools to reverse-engineer python application won't work,
29 | [pyREtic](https://github.com/MyNameIsMeerkat/pyREtic) might still work for
30 | if scrambled-opcodes is the only technique used.
31 |
32 | ## Cons
33 |
34 | 1. The whole python platform needs to be distributed when deployed to the
35 | servers (cross-compilation is needed for this)
36 |
37 | 2. Only ``.pyc`` files must be deployed (check [bytecode](../bytecode) example)
38 |
39 | 3. The application **must** be run with this interpreter, ``pyenv`` comes handy
40 | when deploying the application.
41 |
42 | 4. Demands a very good knowledge of the python interpreter internals
43 |
--------------------------------------------------------------------------------
/custom_interpreter/opcodes/patches/.empty:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FormulaMonks/python-obfuscation/8fb9897702dc51af7fb164f3e6906dd3d656ec68/custom_interpreter/opcodes/patches/.empty
--------------------------------------------------------------------------------
/custom_interpreter/opcodes/patches/001_scrambled-opcodes.diff:
--------------------------------------------------------------------------------
1 | --- Python-2.7.9/Include/opcode.h 2014-12-10 13:59:32.000000000 -0200
2 | +++ Python-2.7.9-customized/Include/opcode.h 2015-02-09 16:36:50.748148442 -0200
3 | @@ -7,148 +7,148 @@
4 |
5 | /* Instruction opcodes for compiled code */
6 |
7 | -#define STOP_CODE 0
8 | -#define POP_TOP 1
9 | -#define ROT_TWO 2
10 | -#define ROT_THREE 3
11 | -#define DUP_TOP 4
12 | -#define ROT_FOUR 5
13 | -#define NOP 9
14 | -
15 | -#define UNARY_POSITIVE 10
16 | -#define UNARY_NEGATIVE 11
17 | -#define UNARY_NOT 12
18 | -#define UNARY_CONVERT 13
19 | -
20 | -#define UNARY_INVERT 15
21 | -
22 | -#define BINARY_POWER 19
23 | -
24 | -#define BINARY_MULTIPLY 20
25 | -#define BINARY_DIVIDE 21
26 | -#define BINARY_MODULO 22
27 | -#define BINARY_ADD 23
28 | -#define BINARY_SUBTRACT 24
29 | -#define BINARY_SUBSCR 25
30 | -#define BINARY_FLOOR_DIVIDE 26
31 | -#define BINARY_TRUE_DIVIDE 27
32 | -#define INPLACE_FLOOR_DIVIDE 28
33 | -#define INPLACE_TRUE_DIVIDE 29
34 | +#define STOP_CODE 91
35 | +#define POP_TOP 38
36 | +#define ROT_TWO 100
37 | +#define ROT_THREE 1
38 | +#define DUP_TOP 19
39 | +#define ROT_FOUR 75
40 | +#define NOP 9
41 | +
42 | +#define UNARY_POSITIVE 73
43 | +#define UNARY_NEGATIVE 12
44 | +#define UNARY_NOT 11
45 | +#define UNARY_CONVERT 98
46 | +
47 | +#define UNARY_INVERT 5
48 | +
49 | +#define BINARY_POWER 66
50 | +
51 | +#define BINARY_MULTIPLY 74
52 | +#define BINARY_DIVIDE 82
53 | +#define BINARY_MODULO 25
54 | +#define BINARY_ADD 86
55 | +#define BINARY_SUBTRACT 80
56 | +#define BINARY_SUBSCR 81
57 | +#define BINARY_FLOOR_DIVIDE 62
58 | +#define BINARY_TRUE_DIVIDE 87
59 | +#define INPLACE_FLOOR_DIVIDE 0
60 | +#define INPLACE_TRUE_DIVIDE 101
61 |
62 | -#define SLICE 30
63 | +#define SLICE 33
64 | /* Also uses 31-33 */
65 |
66 | -#define STORE_SLICE 40
67 | +#define STORE_SLICE 93
68 | /* Also uses 41-43 */
69 |
70 | -#define DELETE_SLICE 50
71 | +#define DELETE_SLICE 27
72 | /* Also uses 51-53 */
73 |
74 | -#define STORE_MAP 54
75 | -#define INPLACE_ADD 55
76 | -#define INPLACE_SUBTRACT 56
77 | -#define INPLACE_MULTIPLY 57
78 | -#define INPLACE_DIVIDE 58
79 | -#define INPLACE_MODULO 59
80 | -#define STORE_SUBSCR 60
81 | -#define DELETE_SUBSCR 61
82 | -
83 | -#define BINARY_LSHIFT 62
84 | -#define BINARY_RSHIFT 63
85 | -#define BINARY_AND 64
86 | -#define BINARY_XOR 65
87 | -#define BINARY_OR 66
88 | -#define INPLACE_POWER 67
89 | -#define GET_ITER 68
90 | -
91 | -#define PRINT_EXPR 70
92 | -#define PRINT_ITEM 71
93 | -#define PRINT_NEWLINE 72
94 | -#define PRINT_ITEM_TO 73
95 | -#define PRINT_NEWLINE_TO 74
96 | -#define INPLACE_LSHIFT 75
97 | -#define INPLACE_RSHIFT 76
98 | -#define INPLACE_AND 77
99 | -#define INPLACE_XOR 78
100 | -#define INPLACE_OR 79
101 | -#define BREAK_LOOP 80
102 | -#define WITH_CLEANUP 81
103 | -#define LOAD_LOCALS 82
104 | -#define RETURN_VALUE 83
105 | -#define IMPORT_STAR 84
106 | -#define EXEC_STMT 85
107 | -#define YIELD_VALUE 86
108 | -#define POP_BLOCK 87
109 | -#define END_FINALLY 88
110 | -#define BUILD_CLASS 89
111 | -
112 | -#define HAVE_ARGUMENT 90 /* Opcodes from here have an argument: */
113 | -
114 | -#define STORE_NAME 90 /* Index in name list */
115 | -#define DELETE_NAME 91 /* "" */
116 | -#define UNPACK_SEQUENCE 92 /* Number of sequence items */
117 | -#define FOR_ITER 93
118 | -#define LIST_APPEND 94
119 | -
120 | -#define STORE_ATTR 95 /* Index in name list */
121 | -#define DELETE_ATTR 96 /* "" */
122 | -#define STORE_GLOBAL 97 /* "" */
123 | -#define DELETE_GLOBAL 98 /* "" */
124 | -#define DUP_TOPX 99 /* number of items to duplicate */
125 | -#define LOAD_CONST 100 /* Index in const list */
126 | -#define LOAD_NAME 101 /* Index in name list */
127 | -#define BUILD_TUPLE 102 /* Number of tuple items */
128 | -#define BUILD_LIST 103 /* Number of list items */
129 | -#define BUILD_SET 104 /* Number of set items */
130 | -#define BUILD_MAP 105 /* Always zero for now */
131 | -#define LOAD_ATTR 106 /* Index in name list */
132 | -#define COMPARE_OP 107 /* Comparison operator */
133 | -#define IMPORT_NAME 108 /* Index in name list */
134 | -#define IMPORT_FROM 109 /* Index in name list */
135 | -#define JUMP_FORWARD 110 /* Number of bytes to skip */
136 | +#define STORE_MAP 26
137 | +#define INPLACE_ADD 24
138 | +#define INPLACE_SUBTRACT 63
139 | +#define INPLACE_MULTIPLY 89
140 | +#define INPLACE_DIVIDE 90
141 | +#define INPLACE_MODULO 2
142 | +#define STORE_SUBSCR 71
143 | +#define DELETE_SUBSCR 92
144 | +
145 | +#define BINARY_LSHIFT 76
146 | +#define BINARY_RSHIFT 83
147 | +#define BINARY_AND 13
148 | +#define BINARY_XOR 78
149 | +#define BINARY_OR 3
150 | +#define INPLACE_POWER 79
151 | +#define GET_ITER 58
152 | +
153 | +#define PRINT_EXPR 68
154 | +#define PRINT_ITEM 99
155 | +#define PRINT_NEWLINE 4
156 | +#define PRINT_ITEM_TO 88
157 | +#define PRINT_NEWLINE_TO 69
158 | +#define INPLACE_LSHIFT 21
159 | +#define INPLACE_RSHIFT 67
160 | +#define INPLACE_AND 64
161 | +#define INPLACE_XOR 20
162 | +#define INPLACE_OR 23
163 | +#define BREAK_LOOP 48
164 | +#define WITH_CLEANUP 22
165 | +#define LOAD_LOCALS 72
166 | +#define RETURN_VALUE 32
167 | +#define IMPORT_STAR 10
168 | +#define EXEC_STMT 65
169 | +#define YIELD_VALUE 85
170 | +#define POP_BLOCK 84
171 | +#define END_FINALLY 15
172 | +#define BUILD_CLASS 70
173 | +
174 | +#define HAVE_ARGUMENT 102 /* Opcodes from here have an argument: */
175 | +
176 | +#define STORE_NAME 107 /* Index in name list */
177 | +#define DELETE_NAME 119 /* "" */
178 | +#define UNPACK_SEQUENCE 144 /* Number of sequence items */
179 | +#define FOR_ITER 127
180 | +#define LIST_APPEND 126
181 | +
182 | +#define STORE_ATTR 122 /* Index in name list */
183 | +#define DELETE_ATTR 103 /* "" */
184 | +#define STORE_GLOBAL 142 /* "" */
185 | +#define DELETE_GLOBAL 105 /* "" */
186 | +#define DUP_TOPX 108 /* number of items to duplicate */
187 | +#define LOAD_CONST 159 /* Index in const list */
188 | +#define LOAD_NAME 125 /* Index in name list */
189 | +#define BUILD_TUPLE 153 /* Number of tuple items */
190 | +#define BUILD_LIST 102 /* Number of list items */
191 | +#define BUILD_SET 133 /* Number of set items */
192 | +#define BUILD_MAP 123 /* Always zero for now */
193 | +#define LOAD_ATTR 128 /* Index in name list */
194 | +#define COMPARE_OP 120 /* Comparison operator */
195 | +#define IMPORT_NAME 148 /* Index in name list */
196 | +#define IMPORT_FROM 115 /* Index in name list */
197 | +#define JUMP_FORWARD 117 /* Number of bytes to skip */
198 |
199 | -#define JUMP_IF_FALSE_OR_POP 111 /* Target byte offset from beginning
200 | +#define JUMP_IF_FALSE_OR_POP 106 /* Target byte offset from beginning
201 | of code */
202 | -#define JUMP_IF_TRUE_OR_POP 112 /* "" */
203 | -#define JUMP_ABSOLUTE 113 /* "" */
204 | -#define POP_JUMP_IF_FALSE 114 /* "" */
205 | -#define POP_JUMP_IF_TRUE 115 /* "" */
206 | -
207 | -#define LOAD_GLOBAL 116 /* Index in name list */
208 | -
209 | -#define CONTINUE_LOOP 119 /* Start of loop (absolute) */
210 | -#define SETUP_LOOP 120 /* Target address (relative) */
211 | -#define SETUP_EXCEPT 121 /* "" */
212 | -#define SETUP_FINALLY 122 /* "" */
213 | -
214 | -#define LOAD_FAST 124 /* Local variable number */
215 | -#define STORE_FAST 125 /* Local variable number */
216 | -#define DELETE_FAST 126 /* Local variable number */
217 | +#define JUMP_IF_TRUE_OR_POP 152 /* "" */
218 | +#define JUMP_ABSOLUTE 137 /* "" */
219 | +#define POP_JUMP_IF_FALSE 147 /* "" */
220 | +#define POP_JUMP_IF_TRUE 136 /* "" */
221 | +
222 | +#define LOAD_GLOBAL 146 /* Index in name list */
223 | +
224 | +#define CONTINUE_LOOP 116 /* Start of loop (absolute) */
225 | +#define SETUP_LOOP 157 /* Target address (relative) */
226 | +#define SETUP_EXCEPT 118 /* "" */
227 | +#define SETUP_FINALLY 109 /* "" */
228 | +
229 | +#define LOAD_FAST 124 /* Local variable number */
230 | +#define STORE_FAST 155 /* Local variable number */
231 | +#define DELETE_FAST 145 /* Local variable number */
232 |
233 | -#define RAISE_VARARGS 130 /* Number of raise arguments (1, 2 or 3) */
234 | +#define RAISE_VARARGS 131 /* Number of raise arguments (1, 2 or 3) */
235 | /* CALL_FUNCTION_XXX opcodes defined below depend on this definition */
236 | -#define CALL_FUNCTION 131 /* #args + (#kwargs<<8) */
237 | -#define MAKE_FUNCTION 132 /* #defaults */
238 | -#define BUILD_SLICE 133 /* Number of items */
239 | -
240 | -#define MAKE_CLOSURE 134 /* #free vars */
241 | -#define LOAD_CLOSURE 135 /* Load free variable from closure */
242 | -#define LOAD_DEREF 136 /* Load and dereference from closure cell */
243 | -#define STORE_DEREF 137 /* Store into cell */
244 | +#define CALL_FUNCTION 154 /* #args + (#kwargs<<8) */
245 | +#define MAKE_FUNCTION 149 /* #defaults */
246 | +#define BUILD_SLICE 114 /* Number of items */
247 | +
248 | +#define MAKE_CLOSURE 113 /* #free vars */
249 | +#define LOAD_CLOSURE 132 /* Load free variable from closure */
250 | +#define LOAD_DEREF 138 /* Load and dereference from closure cell */
251 | +#define STORE_DEREF 112 /* Store into cell */
252 |
253 | /* The next 3 opcodes must be contiguous and satisfy
254 | (CALL_FUNCTION_VAR - CALL_FUNCTION) & 3 == 1 */
255 | -#define CALL_FUNCTION_VAR 140 /* #args + (#kwargs<<8) */
256 | -#define CALL_FUNCTION_KW 141 /* #args + (#kwargs<<8) */
257 | -#define CALL_FUNCTION_VAR_KW 142 /* #args + (#kwargs<<8) */
258 | +#define CALL_FUNCTION_VAR 163 /* #args + (#kwargs<<8) */
259 | +#define CALL_FUNCTION_KW 164 /* #args + (#kwargs<<8) */
260 | +#define CALL_FUNCTION_VAR_KW 165 /* #args + (#kwargs<<8) */
261 |
262 | -#define SETUP_WITH 143
263 | +#define SETUP_WITH 111
264 |
265 | /* Support for opargs more than 16 bits long */
266 | -#define EXTENDED_ARG 145
267 | +#define EXTENDED_ARG 143
268 |
269 | -#define SET_ADD 146
270 | -#define MAP_ADD 147
271 | +#define SET_ADD 110
272 | +#define MAP_ADD 104
273 |
274 |
275 | enum cmp_op {PyCmp_LT=Py_LT, PyCmp_LE=Py_LE, PyCmp_EQ=Py_EQ, PyCmp_NE=Py_NE, PyCmp_GT=Py_GT, PyCmp_GE=Py_GE,
276 |
--------------------------------------------------------------------------------
/custom_interpreter/opcodes/scramble-opcodes.py:
--------------------------------------------------------------------------------
1 | # Scramble python opcodes table to avoid byte-code to python decompilation
2 | import os
3 | import re
4 | import random
5 | import argparse
6 |
7 |
8 | OPCODE_H = 'Include/opcode.h'
9 | OPCODE_RE = re.compile(
10 | r'^#define\s+(?P[A-Z_]+)\s+(?P\d+)(?P.*)'
11 | )
12 |
13 |
14 | def fix_offests(opcodes, ignore_names, value, offs):
15 | if not isinstance(ignore_names, (list, tuple)):
16 | ignore_names = [ignore_names]
17 |
18 | new_opcodes = {}
19 | for key, val in opcodes.items():
20 | if key not in ignore_names and val >= value:
21 | val += offs
22 | new_opcodes[key] = val
23 | return new_opcodes
24 |
25 |
26 | def slice_opcodes(opcodes):
27 | for name in ['SLICE', 'STORE_SLICE', 'DELETE_SLICE']:
28 | if name in opcodes:
29 | opcodes = fix_offests(opcodes, name, opcodes[name], 4)
30 | return opcodes
31 |
32 |
33 | def call_function_opcodes(opcodes):
34 | if 'CALL_FUNCTION' in opcodes:
35 | opcodes['CALL_FUNCTION_VAR'] = opcodes['CALL_FUNCTION'] + 9
36 | opcodes['CALL_FUNCTION_KW'] = opcodes['CALL_FUNCTION_VAR'] + 1
37 | opcodes['CALL_FUNCTION_VAR_KW'] = opcodes['CALL_FUNCTION_VAR'] + 2
38 | opcodes = fix_offests(opcodes, [
39 | 'CALL_FUNCTION_VAR',
40 | 'CALL_FUNCTION_KW',
41 | 'CALL_FUNCTION_VAR_KW'
42 | ], opcodes['CALL_FUNCTION_VAR'], 3)
43 | return opcodes
44 |
45 |
46 | def is_opcode(line):
47 | return OPCODE_RE.match(line)
48 |
49 |
50 | def fix_opcodes_offsets(opcodes):
51 | opcodes = slice_opcodes(opcodes)
52 | opcodes = call_function_opcodes(opcodes)
53 | return opcodes
54 |
55 |
56 | def opcode(line):
57 | match = is_opcode(line)
58 | if match:
59 | return (match.group('name'),
60 | int(match.group('code')),
61 | match.group('extra'))
62 |
63 |
64 | def scramble_subset(opcodes):
65 | names = [name for name, code, extra in opcodes]
66 | opcodes = [code for name, code, extra in opcodes]
67 | random.shuffle(opcodes)
68 | return dict(zip(names, opcodes))
69 |
70 |
71 | def scramble_opcodes(src):
72 | path = os.path.join(src, OPCODE_H)
73 | lines = []
74 | dont_have_arg = []
75 | have_arg = []
76 |
77 | with open(path, 'r') as file:
78 | file_lines = file.readlines()
79 | opcodes = filter(None, map(opcode, file_lines))
80 |
81 | have = False
82 | for name, code, extra in opcodes:
83 | if name == 'HAVE_ARGUMENT':
84 | have = True
85 | continue
86 | opcodes_set = have_arg if have else dont_have_arg
87 | opcodes_set.append((name, code, extra))
88 |
89 | dont_have_arg = scramble_subset(dont_have_arg)
90 | have_arg = scramble_subset(have_arg)
91 | have_arg['HAVE_ARGUMENT'] = min(have_arg.values())
92 | opcodes = dict(dont_have_arg)
93 | opcodes.update(have_arg)
94 | opcodes = fix_opcodes_offsets(opcodes)
95 |
96 | for line in file_lines:
97 | match = is_opcode(line)
98 | if match:
99 | name = match.group('name')
100 | line = '#define {0} {1}{2}\n'.format(name, opcodes[name],
101 | match.group('extra'))
102 | lines.append(line)
103 |
104 | with open(path, 'w') as file:
105 | file.write(''.join(lines))
106 |
107 |
108 | parser = argparse.ArgumentParser(description='Scramble python opcodes table')
109 | parser.add_argument('--python-source', dest='src', type=str,
110 | help='Python source code', required=True)
111 |
112 |
113 | if __name__ == '__main__':
114 | args = parser.parse_args()
115 | scramble_opcodes(args.src)
116 |
--------------------------------------------------------------------------------
/cython/Makefile:
--------------------------------------------------------------------------------
1 | PYTHONLIB := /usr/include/python2.7
2 | CFLAGS := -shared -pthread -fPIC -fwrapv -O2 -Wall -fno-strict-aliasing -I${PYTHONLIB}
3 | PYTHON_FILES := $(shell find . -name "*.py" ! -name "__init__.py" ! -name "manage.py")
4 | OBJECTS := $(patsubst %.py,%.so,$(PYTHON_FILES))
5 |
6 | all: $(OBJECTS)
7 |
8 | %.c: %.py
9 | @ echo "Compiling $<"
10 | @ cython --no-docstrings $< -o $(patsubst %.py,%.c,$<)
11 |
12 | %.so: %.c
13 | @ $(CC) $(CFLAGS) -o $@ $<
14 | @ strip --strip-all $@
15 |
16 | compress:
17 | @ for f in `find . -name "*.so"`; do \
18 | upx -9 $$f; \
19 | done
20 |
21 | clean-build:
22 | @ find . ! -name "__init__.py" ! -name "manage.py" -name "*.py" -delete
23 | @ find . -name "__init__.so" -o -name "manage.so" -delete
24 |
25 | clean:
26 | @ rm -rf ./app
27 |
28 | run: clean
29 | @ cp -R ../../app .
30 | @ find . -iname "*.py[co]" -delete
31 | @ $(MAKE) && $(MAKE) clean-build
32 |
33 | .DEFAULT: all
34 | .PHONY: all %.c %.so clean clean-build compress run
35 |
--------------------------------------------------------------------------------
/cython/README.md:
--------------------------------------------------------------------------------
1 | # Python code Protection Mechanisms - Cython
2 |
3 | Cython mechanism to protect a python codebase.
4 |
5 | ## Description
6 |
7 | In this example, we will be using [Cython](http://cython.org/) to
8 | compile the different ``.py`` files into ``.so`` libs without loosing
9 | import capabilities or code functionality.
10 |
11 | Cython will compile the given python module into a ``.c`` source code,
12 | which is then compiled by ``gcc`` as a shared library (``.so``). Finally,
13 | the ``.py`` files can be removed, leaving just the ``.so``.
14 |
15 | **Note**: The ``__init__.py`` files **must** be kept in the tree, otherwise
16 | ``import`` won't work.
17 |
18 | ## Pros
19 |
20 | 1. Code speedup: since the code is translated to C, it's going to run
21 | faster compared to the python version.
22 |
23 | 2. Assembler ananlysis is needed in order to rebuild the code, which
24 | is compiled with ``O2`` flag (optimization) making the task even
25 | harder to achieve.
26 |
27 | 3. Code compilation can be applied just to key components of the
28 | application if necessary.
29 |
30 | 4. [UPX](http://upx.sourceforge.net/) compressor can applied in the
31 | generated libraries (avoids ``objdump`` assembler dumping, but it's
32 | possible to decompress it)
33 |
34 | ## Cons
35 |
36 | 1. Cython is not 100% python compatible [yet](http://docs.cython.org/src/userguide/limitations.html)
37 |
38 | 2. Cross-compilation is needed to properly build the binaries for the
39 | supported environments (docker can be used to achieve this)
40 |
--------------------------------------------------------------------------------
/import_hook/compiled/Makefile:
--------------------------------------------------------------------------------
1 | PYTHONLIB := /usr/include/python2.7
2 | CFLAGS := -shared -pthread -fPIC -fwrapv -O2 -Wall -fno-strict-aliasing -I${PYTHONLIB}
3 |
4 | all: ihook.so
5 | @ python ihook.py app/
6 | @ cp ihook.so m.py app/
7 |
8 | %.c: %.py
9 | @ echo "Compiling $<"
10 | @ cython --no-docstrings $< -o $(patsubst %.py,%.c,$<)
11 |
12 | %.so: %.c
13 | @ $(CC) $(CFLAGS) -o $@ $<
14 | @ strip --strip-all $@
15 |
16 | clean-build:
17 | @ find . -iname "*.py[co]" -delete
18 |
19 | clean:
20 | @ rm -rf ./app
21 |
22 | run: clean
23 | @ cp -R ../../app .
24 | @ find . -name "*.py[co]" -delete
25 | @ $(MAKE) && $(MAKE) clean-build
26 |
27 | .DEFAULT: all
28 | .PHONY: all clean clean-build run
29 |
--------------------------------------------------------------------------------
/import_hook/compiled/README.md:
--------------------------------------------------------------------------------
1 | # Python code Protection Mechanisms - Compiled Import Hook
2 |
3 | This mechanism will use a compiled [import hook](https://www.python.org/dev/peps/pep-0302/)
4 | in order to detect encrypted modules on import time.
5 |
6 |
7 | ## Description
8 |
9 | In this example we will be using a compiled [import hook](https://www.python.org/dev/peps/pep-0302/)
10 | to detect encrypted modules when they are being imported, the detection is done
11 | by verifying a signature in the encrypted files (in this case the signature is
12 | a simple string ``AESENC:``.
13 |
14 | The encrypted modules follow a simple format: ``AESENC:``,
15 | the signature is stripped, the code is decrypted and "evaluated" in the context
16 | of a in-memory created module.
17 |
18 | The import hook is compiled with Cython, this provides a better protection to
19 | the hook code to avoid accessing the encryption key.
20 |
21 | ## Pros
22 |
23 | 1. Simple to implement (just a command call is needed).
24 | 2. Strong encryption algorithms supported.
25 |
26 | ## Cons
27 |
28 | 1. The encryption key *must* be available in the server in order to run the
29 | application (unless it's possible to run it on demand by accessing the
30 | server over ssh and input the password in the command line, but still it's
31 | going to live in a in-memory object which can be inspected).
32 |
33 | 2. The decrypted code in memory can be inspected and dumped and decompiled from
34 | byte-code back to python.
35 |
36 | 3. Cross-compilation is requried to build the binary for the different
37 | environments.
38 |
--------------------------------------------------------------------------------
/import_hook/compiled/ihook.py:
--------------------------------------------------------------------------------
1 | import os
2 | import sys
3 | import imp
4 | import base64
5 |
6 | from Crypto.Cipher import AES
7 |
8 |
9 | SECRET = 'abcdefghijklmnop'
10 | SIGNATURE = 'AESENC:'
11 |
12 |
13 | class AESCypher(object):
14 | def __init__(self, secret=None, block_size=16, padding='$'):
15 | # block_size = 16 -> 128bits
16 | self.block_size = block_size
17 | self.padding = padding
18 | self.secret = secret or os.urandom(self.block_size)
19 |
20 | def add_padding(self, val):
21 | bsize, padding = self.block_size, self.padding
22 | return val + (bsize - len(val) % bsize) * padding
23 |
24 | def encode_aes(self, cipher, value):
25 | return base64.b64encode(cipher.encrypt(self.add_padding(value)))
26 |
27 | def decode_aes(self, chipher, value):
28 | return chipher.decrypt(base64.b64decode(value)).rstrip(self.padding)
29 |
30 | def encrypt(self, private_info):
31 | return self.encode_aes(AES.new(self.secret), private_info)
32 |
33 | def decrypt(self, value):
34 | return self.decode_aes(AES.new(self.secret), value)
35 |
36 |
37 | class BaseLoader(object):
38 | def modinfo(self, name, path):
39 | try:
40 | modinfo = imp.find_module(name.rsplit('.', 1)[-1], path)
41 | except ImportError:
42 | if '.' not in name:
43 | raise
44 | clean_path, clean_name = name.rsplit('.', 1)
45 | clean_path = [clean_path.replace('.', '/')]
46 | modinfo = imp.find_module(clean_name, clean_path)
47 |
48 | file, pathname, (suffix, mode, type_) = modinfo
49 | if type_ == imp.PY_SOURCE:
50 | filename = pathname
51 | elif type_ == imp.PY_COMPILED:
52 | filename = pathname[:-1]
53 | elif type_ == imp.PKG_DIRECTORY:
54 | filename = os.path.join(pathname, '__init__.py')
55 | else:
56 | return (None, None)
57 | return (filename, modinfo)
58 |
59 |
60 | class Loader(BaseLoader):
61 | def __init__(self, name, path):
62 | self.name = name
63 | self.path = path
64 |
65 | def is_package(self, val):
66 | """Flask requirement"""
67 | return None
68 |
69 | def load_module(self, name):
70 | if name in sys.modules:
71 | return sys.modules[name]
72 |
73 | filename, modinfo = self.modinfo(self.name, self.path)
74 | if filename is None and modinfo is None:
75 | return None
76 |
77 | file = modinfo[0] or open(filename, 'r')
78 | src = ''.join(file.readlines())
79 | src = self.decrypt_to(src[len(SIGNATURE):])
80 |
81 | module = imp.new_module(name)
82 | module.__file__ = filename
83 | module.__path__ = [os.path.dirname(os.path.abspath(file.name))]
84 | module.__loader__ = self
85 | sys.modules[name] = module
86 | exec(src, module.__dict__)
87 | print "encrypted module loaded: {0}".format(name)
88 | return module
89 |
90 | def decrypt_to(self, input):
91 | return AESCypher(SECRET).decrypt(input)
92 |
93 |
94 | class Finder(BaseLoader):
95 | def find_module(self, name, path=None):
96 | filename, modinfo = self.modinfo(name, path)
97 | if filename is None and modinfo is None:
98 | return None
99 |
100 | file = modinfo[0] or open(filename, 'r')
101 | if file.read(len(SIGNATURE)) == SIGNATURE:
102 | print "encrypted module found: {0} (at {1})".format(name, path)
103 | file.seek(0)
104 | return Loader(name, path)
105 |
106 |
107 | def install_hook():
108 | sys.meta_path.insert(0, Finder())
109 |
110 |
111 | def encrypt_all(location):
112 | cipher = AESCypher(SECRET)
113 |
114 | for root, dirs, files in os.walk(location):
115 | files = (f for f in files if f.endswith('.py'))
116 | for name in files:
117 | enc_name = '{0}/{1}-enc'.format(root, name)
118 | orig_name = '{0}/{1}'.format(root, name)
119 |
120 | with open(orig_name, 'r') as input:
121 | with open(enc_name, 'wb') as output:
122 | content = cipher.encrypt(''.join(input.readlines()))
123 | content = '{0}{1}'.format(SIGNATURE, content)
124 | output.write(content)
125 | os.rename(enc_name, orig_name)
126 |
127 |
128 | if __name__ == '__main__':
129 | encrypt_all(sys.argv[1])
130 |
--------------------------------------------------------------------------------
/import_hook/compiled/m.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | import ihook; ihook.install_hook()
4 |
5 |
6 | from manage import manager
7 | manager.run()
8 |
--------------------------------------------------------------------------------
/import_hook/compiled/requirements.txt:
--------------------------------------------------------------------------------
1 | pycrypto
2 |
--------------------------------------------------------------------------------
/import_hook/python/Makefile:
--------------------------------------------------------------------------------
1 | all:
2 | @ python ihook.py app/
3 | @ cp ihook.py m.py app/
4 |
5 | clean-build:
6 | @ find . -iname "*.py[co]" -delete
7 |
8 | clean:
9 | @ rm -rf ./app
10 |
11 | run: clean
12 | @ cp -R ../../app .
13 | @ find . -name "*.py[co]" -delete
14 | @ $(MAKE) && $(MAKE) clean-build
15 |
16 | .DEFAULT: all
17 | .PHONY: all clean clean-build run
18 |
--------------------------------------------------------------------------------
/import_hook/python/README.md:
--------------------------------------------------------------------------------
1 | # Python code Protection Mechanisms - Import Hook
2 |
3 | This mechanism will use an [import hook](https://www.python.org/dev/peps/pep-0302/)
4 | in order to detect encrypted modules on import time.
5 |
6 |
7 | ## Description
8 |
9 | In this example we will be using an [import hook](https://www.python.org/dev/peps/pep-0302/)
10 | to detect encrypted modules when they are being imported, the detection is done
11 | by verifying a signature in the encrypted files (in this case the signature is
12 | a simple string ``AESENC:``.
13 |
14 | The encrypted modules follow a simple format: ``AESENC:``,
15 | the signature is stripped, the code is decrypted and "evaluated" in the context
16 | of a in-memory created module.
17 |
18 | ## Pros
19 |
20 | 1. Simple to implement (just a command call is needed).
21 | 2. Strong encryption algorithms supported.
22 |
23 | ## Cons
24 |
25 | 1. The encryption key *must* be available in the server in order to run the
26 | application (unless it's possible to run it on demand by accessing the
27 | server over ssh and input the password in the command line, but still it's
28 | going to live in a in-memory object which can be inspected).
29 |
30 | 2. The decrypted code in memory can be inspected and dumped and decompiled from
31 | byte-code back to python.
32 |
--------------------------------------------------------------------------------
/import_hook/python/ihook.py:
--------------------------------------------------------------------------------
1 | import os
2 | import sys
3 | import imp
4 | import base64
5 |
6 | from Crypto.Cipher import AES
7 |
8 |
9 | SECRET = 'abcdefghijklmnop'
10 | SIGNATURE = 'AESENC:'
11 |
12 |
13 | class AESCypher(object):
14 | def __init__(self, secret=None, block_size=16, padding='$'):
15 | # block_size = 16 -> 128bits
16 | self.block_size = block_size
17 | self.padding = padding
18 | self.secret = secret or os.urandom(self.block_size)
19 |
20 | def add_padding(self, val):
21 | bsize, padding = self.block_size, self.padding
22 | return val + (bsize - len(val) % bsize) * padding
23 |
24 | def encode_aes(self, cipher, value):
25 | return base64.b64encode(cipher.encrypt(self.add_padding(value)))
26 |
27 | def decode_aes(self, chipher, value):
28 | return chipher.decrypt(base64.b64decode(value)).rstrip(self.padding)
29 |
30 | def encrypt(self, private_info):
31 | return self.encode_aes(AES.new(self.secret), private_info)
32 |
33 | def decrypt(self, value):
34 | return self.decode_aes(AES.new(self.secret), value)
35 |
36 |
37 | class BaseLoader(object):
38 | def modinfo(self, name, path):
39 | try:
40 | modinfo = imp.find_module(name.rsplit('.', 1)[-1], path)
41 | except ImportError:
42 | if '.' not in name:
43 | raise
44 | clean_path, clean_name = name.rsplit('.', 1)
45 | clean_path = [clean_path.replace('.', '/')]
46 | modinfo = imp.find_module(clean_name, clean_path)
47 |
48 | file, pathname, (suffix, mode, type_) = modinfo
49 | if type_ == imp.PY_SOURCE:
50 | filename = pathname
51 | elif type_ == imp.PY_COMPILED:
52 | filename = pathname[:-1]
53 | elif type_ == imp.PKG_DIRECTORY:
54 | filename = os.path.join(pathname, '__init__.py')
55 | else:
56 | return (None, None)
57 | return (filename, modinfo)
58 |
59 |
60 | class Loader(BaseLoader):
61 | def __init__(self, name, path):
62 | self.name = name
63 | self.path = path
64 |
65 | def is_package(self, val):
66 | """Flask requirement"""
67 | return None
68 |
69 | def load_module(self, name):
70 | if name in sys.modules:
71 | return sys.modules[name]
72 |
73 | filename, modinfo = self.modinfo(self.name, self.path)
74 | if filename is None and modinfo is None:
75 | return None
76 |
77 | file = modinfo[0] or open(filename, 'r')
78 | src = ''.join(file.readlines())
79 | src = self.decrypt(src[len(SIGNATURE):])
80 |
81 | module = imp.new_module(name)
82 | module.__file__ = filename
83 | module.__path__ = [os.path.dirname(os.path.abspath(file.name))]
84 | module.__loader__ = self
85 | sys.modules[name] = module
86 | exec(src, module.__dict__)
87 | print "encrypted module loaded: {0}".format(name)
88 | return module
89 |
90 | def decrypt(self, input):
91 | return AESCypher(SECRET).decrypt(input)
92 |
93 |
94 | class Finder(BaseLoader):
95 | def find_module(self, name, path=None):
96 | filename, modinfo = self.modinfo(name, path)
97 | if filename is None and modinfo is None:
98 | return None
99 |
100 | file = modinfo[0] or open(filename, 'r')
101 | if file.read(len(SIGNATURE)) == SIGNATURE:
102 | print "encrypted module found: {0} (at {1})".format(name, path)
103 | file.seek(0)
104 | return Loader(name, path)
105 |
106 |
107 | def install_hook():
108 | sys.meta_path.insert(0, Finder())
109 |
110 |
111 | def encrypt_all(location):
112 | cipher = AESCypher(SECRET)
113 |
114 | for root, dirs, files in os.walk(location):
115 | files = (f for f in files if f.endswith('.py'))
116 | for name in files:
117 | enc_name = '{0}/{1}-enc'.format(root, name)
118 | orig_name = '{0}/{1}'.format(root, name)
119 |
120 | with open(orig_name, 'r') as input:
121 | with open(enc_name, 'wb') as output:
122 | content = cipher.encrypt(''.join(input.readlines()))
123 | content = '{0}{1}'.format(SIGNATURE, content)
124 | output.write(content)
125 | os.rename(enc_name, orig_name)
126 |
127 |
128 | if __name__ == '__main__':
129 | encrypt_all(sys.argv[1])
130 |
--------------------------------------------------------------------------------
/import_hook/python/m.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | import ihook; ihook.install_hook()
4 |
5 |
6 | from manage import manager
7 | manager.run()
8 |
--------------------------------------------------------------------------------
/import_hook/python/requirements.txt:
--------------------------------------------------------------------------------
1 | pycrypto
2 |
--------------------------------------------------------------------------------