When I prepared to distribute my first python application several years ago, I had to face a problem: how to obfuscate python scripts so no clear code shown to my customer?
-
Answers are v old on the other questions: so an update is warranted here. – WestCoastProjects Oct 29 '19 at 14:46
1 Answers
Now I have one solution
Build Obfuscated Script
First compile Python script to code object
char *filename = "foo.py";
char *source = read_file( filename );
PyCodeObject *co = Py_CompileString( source, "<frozen foo>", Py_file_input );
Next change this code object as the following ways
Wrap byte code
co_code
within atry...finally
blockwrap header:
LOAD_GLOBALS N (__armor_enter__) N = length of co_consts CALL_FUNCTION 0 POP_TOP SETUP_FINALLY X (jump to wrap footer) X = size of original byte code
changed original byte code:
Increase oparg of each absolute jump instruction by the size of wrap header Obfuscate original byte code ...
wrap footer:
LOAD_GLOBALS N + 1 (__armor_exit__) CALL_FUNCTION 0 POP_TOP END_FINALLY
Append function names
__armor_enter
,__armor_exit__
toco_consts
Increase
co_stacksize
by 2Set CO_OBFUSCAED (0x80000000) flag in
co_flags
Change all code objects in the
co_consts
recursively
Then serialize this reformed code object, obfuscate it to protect constants and literal strings
char *string_code = marshal.dumps( co );
char *obfuscated_code = obfuscate_algorithm( string_code );
Finally generate obfuscated script
sprintf( buf, "__pyarmor__(__name__, __file__, b'%s')", obfuscated_code );
save_file( "dist/foo.py", buf );
The obfuscated script is a normal Python script, it looks like this
__pyarmor__(__name__, __file__, b'\x01\x0a...')
Run Obfuscated Script
In order to run obfuscted script dist/foo.py
by common Python Interpreter,
there are 3 functions need to be added to module builtins
: __pyarmor__
, __armor_enter__
, __armor_exit__
__pyarmor__
will be called at first, it will import original module from obfuscated codestatic PyObject * __pyarmor__(char *name, char *pathname, unsigned char *obfuscated_code) { char *string_code = restore_obfuscated_code( obfuscated_code ); PyCodeObject *co = marshal.loads( string_code ); return PyImport_ExecCodeModuleEx( name, co, pathname ); }
__armor_enter__
is called as soon as code object is executedstatic PyObject * __armor_enter__(PyObject *self, PyObject *args) { // Got code object PyFrameObject *frame = PyEval_GetFrame(); PyCodeObject *f_code = frame->f_code; // Increase refcalls of this code object // Borrow co_names->ob_refcnt as call counter // Generally it will not increased by Python Interpreter PyObject *refcalls = f_code->co_names; refcalls->ob_refcnt ++; // Restore byte code if it's obfuscated if (IS_OBFUSCATED(f_code->co_flags)) { restore_byte_code(f_code->co_code); clear_obfuscated_flag(f_code); } Py_RETURN_NONE; }
__armor_exit__
is called so long as code object completed executionstatic PyObject * __armor_exit__(PyObject *self, PyObject *args) { // Got code object PyFrameObject *frame = PyEval_GetFrame(); PyCodeObject *f_code = frame->f_code; // Decrease refcalls of this code object PyObject *refcalls = f_code->co_names; refcalls->ob_refcnt --; // Obfuscate byte code only if this code object isn't used by any function // In multi-threads or recursive call, one code object may be referened // by many functions at the same time if (refcalls->ob_refcnt == 1) { obfuscate_byte_code(f_code->co_code); set_obfuscated_flag(f_code); } // Clear f_locals in this frame clear_frame_locals(frame); Py_RETURN_NONE; }
Interested? Visit https://github.com/dashingsoft/pyarmor

- 9,718
- 4
- 38
- 80

- 121
- 1
- 4