【发布时间】:2018-05-03 17:35:34
【问题描述】:
几年前,当我准备发布我的第一个 python 应用程序时,我不得不面对一个问题:如何混淆 python 脚本,使我的客户看不到清晰的代码?
【问题讨论】:
-
其他问题的答案很老:所以这里有必要更新。
标签: python
几年前,当我准备发布我的第一个 python 应用程序时,我不得不面对一个问题:如何混淆 python 脚本,使我的客户看不到清晰的代码?
【问题讨论】:
标签: python
现在我有一个解决方案
首先将 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;
}
【讨论】: