-2

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?

Jondy Zhao
  • 121
  • 1
  • 4

1 Answers1

1

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 a try...finally block

    wrap 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__ to co_consts

  • Increase co_stacksize by 2

  • Set 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 code

    static 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 executed

    static 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 execution

    static 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

das-g
  • 9,718
  • 4
  • 38
  • 80
Jondy Zhao
  • 121
  • 1
  • 4