【问题标题】:How to protect python scripts? [duplicate]如何保护python脚本? [复制]
【发布时间】:2018-05-03 17:35:34
【问题描述】:

几年前,当我准备发布我的第一个 python 应用程序时,我不得不面对一个问题:如何混淆 python 脚本,使我的客户看不到清晰的代码?

【问题讨论】:

  • 其他问题的答案很老:所以这里有必要更新。

标签: python


【解决方案1】:

现在我有一个解决方案

构建混淆脚本

首先将 Python 脚本编译成代码对象

char *filename = "foo.py";
char *source = read_file( filename );
PyCodeObject *co = Py_CompileString( source, "<frozen foo>", Py_file_input );

接下来按以下方式更改此代码对象

  • 将字节码 co_code 包装在 try...finally 块中

    包装标题:

        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
    

    更改原始字节码:

        Increase oparg of each absolute jump instruction by the size of wrap header
    
        Obfuscate original byte code
    
        ...
    

    换行:

        LOAD_GLOBALS    N + 1 (__armor_exit__)
        CALL_FUNCTION   0
        POP_TOP
        END_FINALLY
    
  • 将函数名称__armor_enter__armor_exit__ 附加到co_consts

  • co_stacksize 增加 2

  • co_flags中设置CO_OBFUSCAED (0x80000000)标志

  • 递归更改co_consts中的所有代码对象

然后序列化这个改造后的代码对象,对其进行混淆以保护常量和文字字符串

char *string_code = marshal.dumps( co );
char *obfuscated_code = obfuscate_algorithm( string_code  );

最终生成混淆脚本

sprintf( buf, "__pyarmor__(__name__, __file__, b'%s')", obfuscated_code );
save_file( "dist/foo.py", buf );

混淆后的脚本是普通的Python脚本,长这样

__pyarmor__(__name__, __file__, b'\x01\x0a...')

运行混淆脚本

为了通过普通的 Python 解释器运行混淆脚本dist/foo.py, 模块builtins需要添加3个功能:__pyarmor____armor_enter____armor_exit__

  • __pyarmor__首先会被调用,它会从混淆代码中导入原始模块

    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__ 在代码对象执行后立即被调用

    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__

    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;
    }
    

有兴趣吗?访问https://github.com/dashingsoft/pyarmor

【讨论】:

    猜你喜欢
    • 2014-10-10
    • 2011-04-23
    • 1970-01-01
    • 2016-09-28
    • 1970-01-01
    • 2017-07-24
    • 2013-01-31
    相关资源
    最近更新 更多