Obfuscation Modes in PyArmor

3 minute read

Published:

I think one of the unique features provided by PyArmor is that it lets the users to configure the ways to obfuscate the codes.

This is achieved through obfuscation modes. Here’s what written on the docs.

PyArmor could obfuscate the scripts in many modes in order to balance the security and performance. In most of cases, the default mode works fine. But if the performance is to be bottle-block or in some special cases, maybe you need understand what the differences of these modes and obfuscate the scripts in different mode so that they could work as desired.

In a nutshell and apart from the advanced mode, PyArmor provides two modes of obfuscation. The first one is for code object, while the second one is for the whole module.

Code Object Obfuscation Modes

A python module might consist of one or more functions. In a python module file, generally there are many functions and each function consists of its code object.

The followings are the available modes for code object obfuscation:

  • obf_code = 0

    The code object of each function won't be obfuscated

  • obf_code = 1 (default)

    The code object of each function will be obfuscated in different ways depending on the selected wrap mode

  • obf_code = 2

    Same as obf_code = 1, yet the code object's bytecode is obfuscated using a more complex algorithm. This causes the obfuscation process slower than obf_code = 1

Since obf_code = 1 and obf_code = 2 depend on the chosen wrap mode, here are the available wrap modes.

  • wrap_mode = 0

    When the code object is called first time, PyArmor will do the followings.

    a) Execute __armor__ function. This function will restore the obfuscated bytecode of the code object
    b) The deobfuscated bytecode is executed

    After performing the above actions, the code object of the function won't be re-obfuscated.

  • wrap_mode = 1 (default)

    Using this mode, he code object of each function will be wrapped with a try...finally block. When this code object is called first time, PyArmor will do the followings:

    a) Execute __armor_enter__ function. This function restores the obfuscated bytecode of the code object
    b) Execute the deobfuscated bytecode
    c) Execute __armor_exit__ function. This function will obfuscate the bytecode again

    As you can see, the bytecode will be re-obfuscated each time it has been executed. That's the primary purpose of the try...finally block in this case.

Module Obfuscation Modes

These modes are used to configure how PyArmor should obfuscate the whole module.

The available modes are:

  • obf_mod = 1 (default)

    The obfuscated module will have the following content.


    __pyarmor__(__name__, __file__, b'\x02\x0a...', 1)

    The third parameter is the obfuscated bytecode of the module. It's generated by the following way:


    PyObject *co = Py_CompileString( source, filename, Py_file_input );
    obfuscate_each_function_in_module( co, obf_code );
    char *original_code = marshal.dumps( co );
    char *obfuscated_code = obfuscate_whole_module( original_code );
    sprintf( buffer, "__pyarmor__(__name__, __file__, b'%s', 1)", obfuscated_code );

    In short, the above code does the followings:

    a) Generates the code object of the module
    b) Obfuscates code object for each function
    c) Serializes the module's code object (with the obfuscated code object for each function)
    d) Obfuscates the serialized module's code object

    Please check the Code Object Obfuscation Modes section for all the configurations available for obfuscating a code object.

  • obf_mod = 0

    The obfuscated module will have the following content.


    __pyarmor__(__name__, __file__, b'\x02\x0a...', 0)

    This mode does not obfuscate the code object of the whole module. The third parameter of the above script denotes the serialized module's code object. This third parameter is generated by the followings:


    PyObject *co = Py_CompileString( source, filename, Py_file_input );
    obfuscate_each_function_in_module( co, obf_code );
    char *original_code = marshal.dumps( co );
    sprintf( buffer, "__pyarmor__(__name__, __file__, b'%s', 0)", original_code );

For more detail explanation on how a code object is obfuscated, please visit this post.