【问题标题】:How to obfuscate Python code effectively?如何有效地混淆 Python 代码?
【发布时间】:2011-03-21 14:56:34
【问题描述】:

我正在寻找如何隐藏我的 Python 源代码。

print "Hello World!" 

如何对这个示例进行编码,使其不可读?有人告诉我使用 base64,但我不确定如何使用。

【问题讨论】:

  • 请说得清楚一点。您是否只想对文件进行编码以便以后可以对其进行解码(例如,通过命令行)?或者你想创建一个你仍然可以通过python myfile.py 运行但对 myfile.py 进行编码的文件?
  • 你想隐藏什么?没有有效的方法来混淆 python,这样它就不能被简单地转换回人类可读的。如果您有有价值的代码,请将其转换为 C,或将其保存在服务器上。
  • semiuseless:我怀疑 30 行代码在 C 中也不会隐藏得那么好。
  • 现实情况是,如果您想混淆代码,Python 不是正确的语言。 This posting 就这一点进行了一些精彩的讨论。

标签: python


【解决方案1】:

这只是一个有限的一级混淆解决方案,但它是内置的:Python 有一个字节码编译器:

python -OO -m py_compile <your program.py>

生成一个包含字节码的.pyo 文件,其中删除了文档字符串等。您可以使用.py 扩展名重命名.pyo 文件,python &lt;your program.py&gt; 像您的程序一样运行,但不包含你的源代码。

PS:您获得的“有限”级别的混淆是可以恢复代码(使用一些变量名,但没有 cmets 和 docstrings)。请参阅第一条评论,了解如何操作。但是,在某些情况下,这种混淆程度可能就足够了。

PPS:如果您的程序导入像这样混淆的模块,那么您需要使用.pyc 后缀重命名它们(我不确定这是否会中断),或者您可以使用.pyo 并使用python -O ….pyo 运行它们(导入应该可以工作)。这将允许 Python 找到您的模块(否则,Python 会查找 .py 模块)。

【讨论】:

  • 不幸的是github.com/Mysterie/uncompyle2 能够恢复完整的源代码
  • @sherpya 是的,甚至(至少有一些)变量名也被恢复了(使用 Python 2.7)……不过,文档字符串和 cmets 都丢失了。无论如何,这远非完美的混淆方案。
  • 这不是混淆。这只是编译成字节码
  • @Tessaracter:它仍然是混淆的。不一定非常有效的混淆,但如果没有反编译器,它对人类来说基本上是乱码。对于解释型脚本语言,您的选择本质上是有限的;如果要运行代码,它还必须附带将其返回到可用源代码或字节代码所需的信息。当然,您可以用乱码名称替换非 API 名称,但它必须是一对一的映射,因此所有关联都保留。
【解决方案2】:

所以它不是人类可读的?

我的意思是所有文件都已编码!当你打开它时,你什么都看不懂..!这就是我想要的

您最多可以将源代码编译成字节码,然后只分发字节码。但这也是可逆的。字节码可以反编译成半可读的源代码。

Base64 对任何人来说都是微不足道的解码,因此它不能起到实际保护的作用,并且只会“隐藏”完全不识字的 PC 源。此外,如果您打算以任何方式实际运行该代码,则必须将解码器直接包含在脚本中(或您的发行版中的另一个脚本,需要由合法用户运行),这将立即泄露您的编码/加密。

混淆技术通常涉及 cmets/docs 剥离、名称修改、垃圾代码插入等,因此即使您反编译字节码,您也会得到不可读的源代码。但它们仍然是 Python 源,Python 不擅长变得难以理解。

如果您绝对需要保护某些功能,我建议您使用编译语言,如 C 或 C++,编译和分发 .so/.dll,然后使用 Python 绑定到受保护的代码。

【讨论】:

  • Python 不擅长变得难以阅读的混乱部分 并不真正成立.. 似乎有很多人能很好地完成这项任务。
  • 编译后的 C 代码与 .pyc 代码一样具有逆向工程能力,即使它需要更多的了解。这么说的话,如果电脑能读,那么人有足够的时间就可以读到。
  • 字节码可以反编译成半可读的源代码。 Uncompyle 甚至可以恢复 cmets。通过分发字节码隐藏源代码对python毫无意义。
【解决方案3】:

您可以使用base64 module 对字符串进行编码以阻止shoulder surfing,但如果有人可以访问您的文件,这不会阻止他们找到您的代码。

您可以在解码后使用compile() functioneval() function 执行您的代码。

>>> import base64
>>> mycode = "print 'Hello World!'"
>>> secret = base64.b64encode(mycode)
>>> secret
'cHJpbnQgJ2hlbGxvIFdvcmxkICEn'
>>> mydecode = base64.b64decode(secret)
>>> eval(compile(mydecode,'<string>','exec'))
Hello World!

因此,如果您有 30 行代码,您可能希望通过以下方式对其进行加密:

>>> f = open('myscript.py')
>>> encoded = base64.b64encode(f.read())

然后您需要编写第二个脚本来执行compile()eval(),这可能会将编码脚本作为字符串文字包含在triple quotes 中。所以它看起来像这样:

import base64
myscript = """IyBUaGlzIGlzIGEgc2FtcGxlIFB5d
              GhvbiBzY3JpcHQKcHJpbnQgIkhlbG
              xvIiwKcHJpbnQgIldvcmxkISIK"""
eval(compile(base64.b64decode(myscript),'<string>','exec'))

【讨论】:

  • 我想要我的 python 文件 [所有代码行都是编码] 我的意思是所有文件都被编码!当你打开它时,你什么都看不懂..!这就是我想要的
  • 也许你应该使用 Perl - RSA 加密前后的代码看起来是一样的。 SCNR。
  • 请记住,任何拥有该脚本的人都可以使用相同的 base64.b64decode 函数将文本解码回人类可读的 Python。
【解决方案4】:

您可以将代码嵌入到 C/C++ 中并编译 Embedding Python in Another Application

embedded.c

#include <Python.h>

int
main(int argc, char *argv[])
{
  Py_SetProgramName(argv[0]);  /* optional but recommended */
  Py_Initialize();
  PyRun_SimpleString("print('Hello world !')");
  Py_Finalize();
  return 0;
}

在 Ubuntu/Debian 中

$ sudo apt-get install python-dev

在 Centos/Redhat/Fedora 中

$ sudo yum install python-devel

编译

$ gcc -o embedded -fPIC -I/usr/include/python2.7 -lpython2.7 embedded.c

运行

$ chmod u+x ./embedded
$ time ./embedded
Hello world !

real  0m0.014s
user  0m0.008s
sys 0m0.004s

初始脚本:hello_world.py:

print('Hello World !')

运行脚本

$ time python hello_world.py
Hello World !

real  0m0.014s
user  0m0.008s
sys 0m0.004s

但是在编译后的文件中可能会找到一些 python 代码字符串

$ grep "Hello" ./embedded
Binary file ./embedded matches

$ grep "Hello World" ./embedded
$

如果你想要额外的混淆,你可以使用 base64

...
PyRun_SimpleString("import base64\n"
                  "base64_code = 'your python code in base64'\n"
                  "code = base64.b64decode(base64_code)\n"
                  "exec(code)");
...

例如:

创建代码的 base 64 字符串

$ base64 hello_world.py
cHJpbnQoJ0hlbGxvIFdvcmxkICEnKQoK

embedded_base64.c

#include <Python.h>

int
main(int argc, char *argv[])
{
  Py_SetProgramName(argv[0]);  /* optional but recommended */
  Py_Initialize();
  PyRun_SimpleString("import base64\n"
                    "base64_code = 'cHJpbnQoJ0hlbGxvIFdvcmxkICEnKQoK'\n"
                    "code = base64.b64decode(base64_code)\n"
                    "exec(code)\n");
  Py_Finalize();
  return 0;
}

所有命令

$ gcc -o embedded_base64 -fPIC -I/usr/include/python2.7 -lpython2.7 ./embedded_base64.c
$ chmod u+x ./embedded_base64

$ time ./embedded_base64
Hello World !

real  0m0.014s
user  0m0.008s
sys 0m0.004s

$ grep "Hello" ./embedded_base64
$

更新:

这个项目 (pyarmor) 也可能有帮助:

https://pypi.org/project/pyarmor/

【讨论】:

  • 将原始字符串编译成 C 语言在反汇编时仍然可见。是否可以在文件中加密你的 Python 并让 C 安全地解密它?
  • 不太确定……那样不是还能破译密码吗?关键不会仍然是隐含的吗? - 不要引用我的话
【解决方案5】:

赛通

似乎这个问题的 goto 答案是 Cython。我真的很惊讶没有其他人提到这一点?这是主页:https://cython.org

简而言之,这会将您的 python 转换为 C 并对其进行编译,从而使其与任何“正常”编译的可分发 C 程序一样受到保护。

虽然有限制。我自己并没有深入探索它们,因为当我开始阅读它们时,我出于自己的目的放弃了这个想法。但它可能仍然适用于你的。从本质上讲,您不能充分利用 Python,因为它提供的动态性很棒。我突然想到的一个主要问题是关键字参数不可用:(您必须仅使用位置参数编写函数调用。我没有确认这一点,但我怀疑您可以使用条件导入或评估。我是不知道如何处理多态性...

无论如何,如果您不想在事后混淆庞大的代码库,或者理想情况下,如果您一开始就考虑使用 Cython,那么这是一个非常值得注意的选择。

【讨论】:

  • 如果您要对这样的事情投反对票,请发表评论!这是一个有效的答案。如果你不是 Cython 的粉丝,也许你可以说为什么?请注意,我指出它有缺点。然而,它也是更安全的途径之一,甚至被一些标准库推荐,例如PyInstaller 文档处理这个问题!
  • 更多关于已编译二进制文件的信息:Are executables produced with Cython really free of the source code?.
  • 好东西,@Basj!谢谢
  • 您提到的大多数限制并不真正正确 - 有额外的语法增加了静态类型(主要是为了速度),但大多数普通 Python 程序应该不加改变地运行,包括关键字参数、条件导入、@987654323 @,多态性。肯定有一些不兼容的地方(主要与内省有关),但你提到的所有东西都应该有效(并且应该在 2018 年也有效)
  • 哦,是吗?凉爽的!我将不得不更深入地研究这一点。你能推荐任何值得注意的文档、示例等来源吗?
【解决方案6】:

也许你可以试试pyconcrete

.pyc加密为.pye并在导入时解密

通过库 OpenAES 加密和解密

用法

完全加密

  • 将您所有的.py 转换为*.pye

    $ pyconcrete-admin.py compile --source={your py script}  --pye
    $ pyconcrete-admin.py compile --source={your py module dir} --pye
    
  • 删除*.py*.pyc或将*.pye复制到其他文件夹

  • main.py加密为main.pye,普通python无法执行。 您必须使用 pyconcrete 来处理 main.pye 脚本。 pyconcrete(exe) 将安装在您的系统路径中(例如:/usr/local/bin)

    pyconcrete main.pye
    src/*.pye  # your libs
    

部分加密(pyconcrete as lib)

  • 下载pyconcrete源码并通过setup.py安装

    $ python setup.py install \
      --install-lib={your project path} \
      --install-scripts={where you want to execute pyconcrete-admin.py and pyconcrete(exe)}
    
  • 在主脚本中导入 pyconcrete

  • 推荐项目布局

    main.py       # import pyconcrete and your lib
    pyconcrete/*  # put pyconcrete lib in project root, keep it as original files
    src/*.pye     # your libs
    

【讨论】:

    【解决方案7】:

    如果你想制作一个半混淆代码,你可以这样写:

    import base64
    import zlib
    def run(code): exec(zlib.decompress(base64.b16decode(code)))
    def enc(code): return base64.b16encode(zlib.compress(code))
    

    并制作一个像这样的文件(使用上面的代码):

    f = open('something.py','w')
    f.write("code=" + enc("""
    print("test program")
    print(raw_input("> "))"""))
    f.close()
    

    文件“something.py”:

    code = '789CE352008282A2CCBC120DA592D4E212203B3FBD28315749930B215394581E9F9957500A5463A7A0A4A90900ADFB0FF9'
    

    只需导入“something.py”并运行run(something.code) 即可运行文件中的代码。

    一个技巧是通过设计使代码难以阅读:永远不要记录任何内容,如果必须,只给出函数的输出,而不是它的工作原理。使变量名称非常广泛,电影参考或相反示例:btmnsfavclr = 16777215 其中“btmnsfavclr”表示“蝙蝠侠最喜欢的颜色”,值为16777215 或“ffffff”的十进制形式或白色。请记住混合使用不同风格的命名来保留那些讨厌的人的代码。另外,请使用本网站上的提示:Top 11 Tips to Develop Unmaintainable Code

    【讨论】:

    • 您提供了不止一个答案。关于“使代码难以阅读设计”的第二部分应拆分为一个完整的单独答案以进行投票/评论。
    • 关于第二部分,如果您故意让阅读和处理变得很糟糕,您如何建议作为原始开发人员维护您的代码?这个计划只对你永远不必回去修改自己的小脚本有意义。在这种情况下,你为什么还要关心混淆?我猜只是为了增加安全性?如果重点是“复制保护”,我认为对于一些“下午项目”来说,这不值得。
    【解决方案8】:

    查看这些用于混淆缩小 python代码的工具:

    使用 --obfuscate 和 --gzip 运行时 pyminifier 的示例 .py 输出:

    $ pyminifier --obfuscate --gzip /tmp/tumult.py

    #!/usr/bin/env python3
    import zlib, base64
    exec(zlib.decompress(base64.b64decode('eJx1kcFOwzAMhu95ClMO66apu0/KAQEbE5eJC+IUpa27haVJ5Ljb+vakLYJx4JAoiT/7/+3c3626SKvSuBW6M4Sej96Jq9y1wRM/E3kSexnIOBZObrSNKI7Sl59YsWDq1wLMiEKNrenoYCqB1woDwzXF9nn2rskZd1jDh+9mhOD8DVvAQ8WdtrZfwg74aNwp7ZpnMXHUaltk878ybR/ZNKbSjP8JPWk6wdn72ntodQ8lQucIrdGlxaHgq3QgKqtjhCY/zlN6jQ0oZZxhpfKItlkuNB3icrE4XYbDwEBICRP6NjG1rri3YyzK356CtsGwZuNd/o0kYitvrBd18qgmj3kcwoTckYPtJPAyCVzSKPCMNErs85+rMINdp1tUSspMqVYbp1Q2DWKTJpcGURRDr9DIJs8wJFlKq+qzZRaQ4lAnVRuJgjFynj36Ol7SX/iQXr8ANfezCw==')))
    # Created by pyminifier.py (https://github.com/liftoff/pyminifier)
    

    此输出对应于 40 行的原始输入脚本,如 here 所示。

    【讨论】:

    • pyarmor 的 python 文件大小限制为 32kB,否则您需要支付许可证。 pyminifier 似乎不再被维护了?
    【解决方案9】:

    有多种混淆代码的方法。这里只是一个例子:

    (lambda _, __, ___, ____, _____, ______, _______, ________:
        getattr(
            __import__(True.__class__.__name__[_] + [].__class__.__name__[__]),
            ().__class__.__eq__.__class__.__name__[:__] +
            ().__iter__().__class__.__name__[_____:________]
        )(
            _, (lambda _, __, ___: _(_, __, ___))(
                lambda _, __, ___:
                    chr(___ % __) + _(_, __, ___ // __) if ___ else
                    (lambda: _).func_code.co_lnotab,
                _ << ________,
                (((_____ << ____) + _) << ((___ << _____) - ___)) + (((((___ << __)
                - _) << ___) + _) << ((_____ << ____) + (_ << _))) + (((_______ <<
                __) - _) << (((((_ << ___) + _)) << ___) + (_ << _))) + (((_______
                << ___) + _) << ((_ << ______) + _)) + (((_______ << ____) - _) <<
                ((_______ << ___))) + (((_ << ____) - _) << ((((___ << __) + _) <<
                __) - _)) - (_______ << ((((___ << __) - _) << __) + _)) + (_______
                << (((((_ << ___) + _)) << __))) - ((((((_ << ___) + _)) << __) +
                _) << ((((___ << __) + _) << _))) + (((_______ << __) - _) <<
                (((((_ << ___) + _)) << _))) + (((___ << ___) + _) << ((_____ <<
                _))) + (_____ << ______) + (_ << ___)
            )
        )
    )(
        *(lambda _, __, ___: _(_, __, ___))(
            (lambda _, __, ___:
                [__(___[(lambda: _).func_code.co_nlocals])] +
                _(_, __, ___[(lambda _: _).func_code.co_nlocals:]) if ___ else []
            ),
            lambda _: _.func_code.co_argcount,
            (
                lambda _: _,
                lambda _, __: _,
                lambda _, __, ___: _,
                lambda _, __, ___, ____: _,
                lambda _, __, ___, ____, _____: _,
                lambda _, __, ___, ____, _____, ______: _,
                lambda _, __, ___, ____, _____, ______, _______: _,
                lambda _, __, ___, ____, _____, ______, _______, ________: _
            )
        )
    )
    

    【讨论】:

    • 你是怎么想到的?
    • 这会在 Python 2 中打印“Hello world!”(它在 Python 3 中崩溃)。与往常一样,混淆事物比理解事物容易得多。
    • 这很有趣
    • 虽然这很聪明也很有趣,但它并没有真正解释如何它是如何产生的,而这正是最初的问题关于。这不是“给出一个混淆代码的例子”。
    • 如果添加更多解释,这将是一个很好的答案。
    【解决方案10】:

    努伊特卡

    我真的会推荐 Nuitka 而不是 Cython。 Nuitka 还将 Python 编译为原生平台代码,提供与编译后的 C 代码类似的混淆级别。

    python -m pip install nuitka
    python -m nuitka --follow-imports --include-package urllib3.util.ssl_ myprogram.py
    ./myprogram.bin
    
    • --follow-imports 在包含所有导入的模块方面做得很好。
    • --include-package 如果在启动编译程序时某些导入被隐藏并且丢失,传递额外的包会很有帮助。

    添加标志 --onefile-standalone 如果这样可以获得要分发的包。

    我也使用了这里引用的pyarmor,但是pyarmor的核心pytransform.sopytransform.dll共享对象是闭源的,这在我的项目中是一个障碍。

    【讨论】:

    • 你为什么推荐它而不是 Cython?
    • Cython 是一种低级解决方案。您可以编译单个文件或以某种方式手动组装它们并获得共享对象文件。 Nuitka 是高级别的,搜索依赖项并将它们包含在单个发行版中是由 Nuitka 以非常简单的方式完成的。
    • 很抱歉提出一个旧线程。这些(非开源)文件在最新的 pyarmor 中是否仍然存在?试图弄清楚 pyarmor 是否可以安全使用
    • 在这里回答我自己的问题:软件包中安装了二进制文件。这些是存储在以下位置的封闭源文件:github.com/dashingsoft/pyarmor-core
    【解决方案11】:

    我会像这样屏蔽代码:

    def MakeSC():
        c = raw_input(" Encode: ")
        sc = "\\x" + "\\x".join("{0:x}".format(ord(c)) for c in c)
        print "\n shellcode =('" + sc + "'); exec(shellcode)"; MakeSC();
    

    明文:

    import os; os.system("whoami")
    

    编码:

    Payload = ('\x69\x6d\x70\x6f\x72\x74\x20\x6f\x73\x3b\x20\x6f\x73\x2e\x73\x79\x73\x74\x65\x6d\x28\x22\x77\x68\x6f\x61\x6d\x69\x22\x29'); exec(Payload);
    

    【讨论】:

      【解决方案12】:

      也许您应该考虑使用像truecrypt volume 这样简单的东西来存储源代码,因为这似乎是您关心的问题。您可以在USB密钥上创建加密文件,或者只是加密整个卷(如果代码将适合),以便您可以在一天结束时使用您的钥匙。

      要编译,您可以使用类似于PyInstallerpy2exe @以创建独立的可执行文件。如果您真的想加倍努力,请查看packer or compression utility 以添加更多混淆。如果这些都不是一种选项,则至少可以将脚本编译为字节码,因此它不会立即可读。请记住,这些方法仅仅会慢慢尝试调试或分解您的程序。

      【讨论】:

        【解决方案13】:

        这样做的最佳方法是首先生成一个.c文件,然后用tcc编译到.pyd文件要求

        1. tcc
        2. pyobfuscate
        3. Cython

        安装:

        sudo pip install -U cython
        

        要混淆您的 .py 文件:

        pyobfuscate.py myfile.py >obfuscated.py
        

        生成.c文件,

        1. 在您的 .py 文件中添加 init&lt;filename&gt;() 函数可选

        2. cython --embed file.py

        3. cp Python.h tcc\include

        4. tcc file.c -o file.pyd -shared -I\path\to\Python\include -L\path\to\Python\lib

        5. 将.pyd文件导入app.exe

        【讨论】:

        • “将.pyd文件进入app.exe导入.pyd文件”。您在解释中的任何地方都没有提及app.exe。你能告诉我更多吗?
        • 只需将pyd文件导入您的主应用程序(app.exe是指应用程序的打包版本) span>
        • 非常感谢。我的问题是使用调用其他几个 mymodule1.py、module2.py 的 main.py 来完成。也许你可以在这里找到正确的问题正确答案:stackoverflow.com/posts/comments/108403128?noredirect=1 span>
        • 关于编译二进制文件中存在的信息:Are executables produced with Cython really free of the source code?。 span>
        • pyobfuscate就像一个添加的图层一样,在编译cython和gcc之后,它非常不太可能有人能够分解 span>
        【解决方案14】:

        我知道这是一个老问题。只想添加我有趣的混淆“Hello world!”在 Python 3 和一些技巧中;)

        #//'written in c++'
        
        #include <iostream.h>
        #define true false
        import os
        n = int(input())
        _STACK_CALS=  [ ];
        _i_CountCals__= (0x00)
        while os.urandom(0x00 >> 0x01) or (1 & True):
          _i_CountCals__+= 0o0;break;# call shell command echo "hello world" > text.txt
        ""#print'hello'
        __cal__= getattr( __builtins__  ,'c_DATATYPE_hFILE_radnom'[ 0x00 ]+'.h'[-1]+'getRndint'[3].lower() )
        _o0wiXSysRdrct   =eval (  __cal__(0x63) + __cal__(104) + 'r_RUN_CALLER'[0] );
        _i1CLS_NATIVE=  getattr (__builtins__ ,__cal__(101)+__cal__(118  )+_o0wiXSysRdrct ( 0b1100001 )+'LINE 2'[0].lower( ))#line 2 kernel call
        __executeMAIN_0x07453320abef  =_i1CLS_NATIVE ( 'map');
        def _Main():
            raise 0x06;return 0 # exit program with exit code 0
        def _0o7af():_i1CLS_NATIVE('_int'.replace('_', 'programMain'[:2]))(''.join(  __executeMAIN_0x07453320abef( _o0wiXSysRdrct ,_STACK_CALS)));return;_Main()
        for _INCREAMENT in [0]*1024:
            _STACK_CALS= [0x000 >> 0x001 ,True&False&True&False ,'c++', 'h', 'e', 'l', 'o',' ', 'w', 'o', 'r', 'l', 'd']
           
        #if
        for _INCREAMENT in [0]*1024:
            _STACK_CALS= [40, 111, 41, 46, 46] * n
            
        """"""#print'word'
        while True:
            break;
        _0o7af();
        while os.urandom(0x00 >> 0xfa) or (1 & True): # print "Hello, world!"
          _i_CountCals__-= 0o0;break;
          while os.urandom(0x00 >> 0x01) or (1 & True):
              _i_CountCals__ += 0o0;
              break;
        

        可以手动进行,我的建议是:

        • eval 和/或exec 与加密字符串一起使用

        • 使用[ord(i) for i in s]/''.join(map(chr, [list of chars goes here]))作为简单的加密/解密

        • 使用晦涩的变量名

        • 让它不可读

        • 不要只写 1 或 True,写 1&amp;True&amp;0x00000001 ;)

        • 使用不同的数字系统

        • 在第 10 行添加令人困惑的 cmets,例如在第 10 行添加“第 2 行”或在 while 循环中添加“它返回 0”。

        • 使用__builtins__

        • 使用getattrsetattr

        【讨论】:

          【解决方案15】:

          我最近偶然发现了这篇博文:Python Source Obfuscation using ASTs,其中作者谈到了使用内置 AST 模块对 python 源文件进行混淆。编译后的二进制文件将用于 HitB CTF,因此具有严格的混淆要求。

          由于您可以访问各个 AST 节点,因此使用这种方法可以对源文件进行任意修改。根据您执行的转换,生成的二进制文件可能/可能与非混淆源完全相同。

          【讨论】:

            【解决方案16】:

            试试这个 python 混淆器:

            pyob.oxyry.com pyob.oxyry.c

            __all__ = ['foo']
            
            a = 'a'
            _b = 'b'
            
            def foo():
                print(a)
            
            def bar():
                print(_b)
            
            def _baz():
                print(a + _b)
            
            foo()
            bar()
            _baz()
            

            将翻译成

            __all__ =['foo']#line:1
            OO00OO0OO0O00O0OO ='a'#line:3
            _O00OO0000OO0O0O0O ='b'#line:4
            def foo ():#line:6
                print (OO00OO0OO0O00O0OO )#line:7
            def O0000000OOOO00OO0 ():#line:9
                print (_O00OO0000OO0O0O0O )#line:10
            def _OOO00000O000O0OOO ():#line:12
                print (OO00OO0OO0O00O0OO +_O00OO0000OO0O0O0O )#line:13
            foo ()#line:15
            O0000000OOOO00OO0 ()#line:16
            _OOO00000O000O0OOO ()#line:17
            

            【讨论】:

              【解决方案17】:

              操作

              https://github.com/QQuick/Opy

              Opy 会混淆你广泛的、真实的、多模块的 Python 源代码免费!你为每个项目选择要混淆的内容和 什么不是,通过编辑配置文件:

              You can recursively exclude all identifiers of certain modules from obfuscation.
              You can exclude human readable configuration files containing Python code.
              You can use getattr, setattr, exec and eval by excluding the identifiers they use.
              You can even obfuscate module file names and string literals.
              You can run your obfuscated code from any platform.
              

              与发布的其他一些选项不同,这适用于 Python 2 和 3!它也是免费/开源的,而且它不像其他一些工具那样仅是在线工具(除非您付费)。

              诚然,我自己仍在评估它,但它的所有初始测试都运行良好。看来这正是我想要的!

              官方版本作为一个独立的实用程序运行,最初的设计是你将一个脚本放到你想要混淆的目录的根目录中,连同一个配置文件来定义你想要使用的细节/选项。我不喜欢那个计划,所以我从项目中添加了一个分支,允许您从库中导入和使用该工具。这样,您可以将其直接滚动到更包含内容的打包脚本中。 (您当然可以在 bash/batch 中包装多个 py 脚本,但我认为纯 python 解决方案是理想的)。我要求将我的 fork 合并到原始作品中,但万一这永远不会发生,这里是我修订版本的 url:

              https://github.com/BuvinJT/Opy

              【讨论】:

                【解决方案18】:

                正如其他答案所述,确实没有什么好的方法。 Base64 可以解码。字节码可以被反编译。 Python 最初只是解释性的,大多数解释性语言都试图加快机器解释的速度,而不是让人类解释变得困难。

                Python 被设计成可读和可共享的,而不是被混淆的。关于如何格式化代码的语言决策是为了提高不同作者的可读性。

                混淆 python 代码并不能真正与语言相结合。重新评估混淆代码的原因。

                【讨论】:

                  【解决方案19】:

                  有两种方法可以混淆 python 脚本

                  • 混淆每个代码对象的字节码
                  • 混淆python模块的整个代码对象

                  混淆 Python 脚本

                  • 编译python源文件为代码对象

                    char * filename = "xxx.py";
                    char * source = read_file( filename );
                    PyObject *co = Py_CompileString( source, filename, Py_file_input );
                    
                  • 迭代代码对象,将每个代码对象的字节码包装成如下格式

                    0   JUMP_ABSOLUTE            n = 3 + len(bytecode)    
                    3
                    ...
                    ... Here it's obfuscated bytecode
                    ...
                    
                    n   LOAD_GLOBAL              ? (__armor__)
                    n+3 CALL_FUNCTION            0
                    n+6 POP_TOP
                    n+7 JUMP_ABSOLUTE            0
                    
                  • 序列化代码对象并对其进行混淆

                    char *original_code = marshal.dumps( co );
                    char *obfuscated_code = obfuscate_algorithm( original_code  );
                    
                  • 创建包装脚本“xxx.py”,${obfuscated_code}代表上一步生成的字符串常量。

                    __pyarmor__(__name__, b'${obfuscated_code}')
                    

                  运行或导入混淆的 Python 脚本

                  导入或运行此包装脚本时,第一条语句是调用一个CFunction:

                  int __pyarmor__(char *name, unsigned char *obfuscated_code) 
                  {
                    char *original_code = resotre_obfuscated_code( obfuscated_code );
                    PyObject *co = marshal.loads( original_code );
                    PyObject *mod = PyImport_ExecCodeModule( name, co );
                  }
                  

                  这个函数接受2个参数:模块名和混淆代码,然后

                  • 恢复混淆代码
                  • 通过原始代码创建代码对象
                  • 导入原始模块(这将导致在 追溯)

                  运行或导入混淆字节码

                  模块导入后,调用此模块中的任何代码对象时 第一次,从上面描述的包装字节码,我们 知道

                  • 第一个操作JUMP_ABSOLUTE跳转到偏移n

                  • 在偏移 n 处,指令是调用 PyCFunction。这个功能 将恢复偏移量 3 和 n 之间的那些混淆字节码,并且 将原始字节码放在偏移量 0

                  • 函数调用后,最后一条指令跳转回 偏移量 0。现在执行真正的字节码

                  参考Pyarmor

                  【讨论】:

                    【解决方案20】:

                    这是我在CISCINGPYTHON中所做的事情的非常方向的方法。它目前是部分测试的。我在这个状态发布,因为我认为它可能是有用的。

                    有两个参数:

                    • 逗号分隔的输入文件列表
                    • 逗号分隔的输出文件列表

                    这是它的所作所为:

                    1. 通过查看方法签名和分配的左侧,以及所有导入别名来查找所有变量名称。
                    2. 隔离一些我不想修改的东西。
                    3. 用无意义的令牌替换剩下的名称。
                    4. 未检测我被隔离的东西。

                    它会像这个

                    这样的代码
                    degreeIncrement = 90
                    durationIncrement = 0.25
                    def GetEditGlyphParams(self, waveform, editIndex):
                        segments = waveform.leftSegments
                        waveformFunctionCount =  len(self.waveformFunctions)
                        totalParameterCount = 0
                        segmentIndex = 0
                        while segmentIndex < len(segments):
                            segment = segments[segmentIndex]
                            segmentParameterCount = len(self.sineFunctions)
                            if segment.type == "line":
                                segmentParameterCount = len(self.lineFunctions)
                    

                    ...进入代码如下:

                    a6 = 90 # degreeIncrement = 90
                    a7 = 0.25 # durationIncrement = 0.25
                    def a8(a9, a10, a11): # def GetEditGlyphParams(self, waveform, editIndex):
                        a12 = a10.leftSegments # segments = waveform.leftSegments
                        a13 =  len(a9.a5) # waveformFunctionCount =  len(self.waveformFunctions)
                        a14 = 0 # totalParameterCount = 0
                        a15 = 0 # segmentIndex = 0
                        while a15 < len(a12): # while segmentIndex < len(segments):
                            a16 = a12[a15] # segment = segments[segmentIndex]
                            a17 = len(a9.a3) # segmentParameterCount = len(self.sineFunctions)
                            if a16.a332 == "line": # if segment.type == "line":
                                a17 = len(a9.a4) # segmentParameterCount = len(self.lineFunctions)
                    
                    如果需要,可以省略CMET。

                    这是它的代码:

                    import sys, re
                    sourceDirectory = sys.argv[1]
                    print("sourceDirectory", sourceDirectory)
                    sourceFiles = sys.argv[2].split(",")
                    targetFiles = sys.argv[3].split(",")
                    
                    if len(sourceFiles) != len(targetFiles):
                        raise Exception("Source file count must match target file count. Use comma to separate.")
                    
                    print("uglify", sys.argv[1])
                    
                    names = []
                    translations = []
                    
                    class Analyser:
                        def AnalyseLines(self, lines):
                            for line in lines:
                                self._AnalyseLine(line)
                        def _AnalyseLine(self, line):
                            parts = self._GetParts(line)
                            if len(parts) > 1 and parts[0] == "import":
                                self._AnalyseImport(parts)
                            if len(parts) > 1 and parts[0] == "class":
                                self._AnalyseClass(parts)
                            if len(parts) > 1 and parts[1] == "=":
                                self._AnalyseAssignment(parts)
                            if len(parts) > 1 and parts[0] == "def":
                                self._AnalyseDef(parts)
                        def _GetParts(self, line):
                            minusTabs = line.strip().replace("\t", " ")
                            minusOpenSquare = minusTabs.replace("[", " ")
                            minusCloseSquare = minusOpenSquare.replace("]", " ")
                            minusDoubleSpace = minusCloseSquare.replace("  ", " ")
                            parts = minusDoubleSpace.split(" ")
                            while "#" in parts:
                                del parts[-1]
                            while len(parts) > 0 and parts[0] == "":
                                del parts[0]
                            nonEmptyParts = []
                            for part in parts:
                                if len(part) > 0:
                                    nonEmptyParts.append(part)
                            return nonEmptyParts
                        def _AddName(self, name, elementType):
                            nameToAppend = name # + " " + elementType
                            if nameToAppend in names:
                                return
                            if nameToAppend == "sin" or nameToAppend == "value":
                                print("--> adding", nameToAppend, "as", elementType)
                            names.append(nameToAppend)
                            translation = "a" + str(len(names))
                            translations.append((name, translation))
                        def _AnalyseImport(self, parts):
                            if len(parts) == 4 and parts[0] == "import" and parts[2] == "as":
                                self._AddName(parts[3], "import")
                        def _AnalyseClass(self, parts):
                            p1 = parts[1].split(":")
                            p2 = p1[0].split("(")
                            self._AddName(p2[0], "class")
                        def _AnalyseAssignment(self, parts):
                            mutableName = parts[0].split(".")[0]
                            self._AddName(mutableName, "assignment")
                        def _AnalyseDef(self, parts):
                            methodNameParts = parts[1].split("(")
                            if methodNameParts[0] == "__init__":
                                return
                            self._AddName(methodNameParts[0], "method")
                            if len(methodNameParts) > 1:
                                self._AddName(methodNameParts[1].replace(",", "").replace("):", ""), "param1")
                            for part in parts[2:]:
                                params = part.split(",")
                                for param in params:
                                    if param != "":
                                        if param.replace(":", "").replace(")", "") == "value":
                                            print("found value amongst", parts)
                                        self._AddName(param.replace(":", "").replace(")", ""), "paramN")
                    
                    class Translator:
                        def TranslateLines(self, content):
                            oldLines = content.split("\n")
                            content = content.replace('"', "_QUOTE_").replace("\\", "_BACKSLASH_")
                            for (oldWord, newWord) in translations:
                                content = re.sub(r"\b%s\b" % oldWord, newWord, content)
                            content = content.replace("_QUOTE_", '"').replace("_BACKSLASH_", "\\")
                            newLines = content.split("\n")
                            for i in range(len(newLines) - 1):
                                if newLines[i] != "":
                                    newLines[i] += " # " + oldLines[i].strip()
                            return "\n".join(newLines)
                        def TranslateLines2(self, content):
                            oldLines = content.split("\n")
                            newLines = []
                            for lineNumber, oldLine in enumerate(oldLines):
                                # print("translating line of length", len(oldLine), ":", oldLine)
                                content = oldLine.split(" # ")[0]
                                if len(content.strip(" \t")) > 0:
                                    content = content.replace('"', "_QUOTE_").replace("\\", "_BACKSLASH_")
                                    for (oldWord, newWord) in translations:
                                        try:
                                            content = re.sub(r"\b%s\b" % oldWord, newWord, content)
                                        except:
                                            print("problem translating", oldWord, "into", newWord)
                                            raise Exception("error in translation")
                                    content = content.replace("_QUOTE_", '"').replace("_BACKSLASH_", "\\")
                                    newLines.append(content + " # " + oldLine.strip())
                            return "\n".join(newLines)
                    
                    lines = []
                    
                    for i, sourceFileName in enumerate(sourceFiles):
                        names.append(sourceFileName)
                        targetFileName = targetFiles[i]
                        translations.append((sourceFileName, targetFileName))
                    
                    for sourceFileName in sourceFiles:
                        fullFileName = sourceDirectory + sourceFileName + ".py"
                        sourceFile = open(fullFileName, 'r')
                        content = sourceFile.read()
                        fileLines = content.split("\n")
                        lines.extend(fileLines)
                        print("found", len(fileLines), "lines in", sourceFileName)
                    
                    print("----------------")
                    print("found a total of", len(lines), "lines")
                    print("----------------")
                    analyser = Analyser()
                    analyser.AnalyseLines(lines)
                    
                    for i, name in enumerate(names):
                        if len(name) < 1:
                            print("deleting name", i, "because it is zero length")
                            names.remove(name)
                            translation = translations[i]
                            translations.remove(translation)
                    
                    # print(names)
                    
                    # raise Exception("Not implemented beyond here.")
                    translator = Translator()
                    
                    for i, sourceFileName in enumerate(sourceFiles):
                        print("translating", sourceFileName, "into", targetFiles[i])
                        fullFileName = sourceDirectory + sourceFileName + ".py"
                        targetFileName = sourceDirectory + targetFiles[i] + ".py"
                        sourceFile = open(fullFileName, 'r')
                        content = sourceFile.read()
                        targetFile = open(targetFileName, 'w')
                        fileLines = content.split("\n")
                        newContent = translator.TranslateLines2(content)
                        targetFile.write(newContent)
                        sourceFile.close()
                        targetFile.close()
                    
                    # print(len(lines), "lines, starting with", lines[0])
                    # print(names)
                    # print(translations)
                    

                    【讨论】:

                    • 嗨。这个设施已经完成了吗?谢谢
                    • @ henrythornton,嗨,我只是用我使用的最新代码更新了答案。它现在从多个文件中读取变量名称并使它们混淆。自原始帖子以来,我通过了更多的测试。我发现并修复了一些错误。我不会说这是免费的错误:) span>
                    【解决方案21】:

                    现在简单使用pyarmor库,大多数解决方案都有相当的时代,pyarmor是新的,简单有效的大型代码花瓶。..

                    要知道如何使用它,请访问下面的链接(只有一分钟的视频,不能比这更简单)

                    https://www.youtube.com/watch?v=94VxtGI-KqQ

                    【讨论】:

                      【解决方案22】:

                      我会以教学的方式写下我的答案...

                      首先在 Python 解释器中输入:

                      import this
                      

                      然后,查看 Python 发行版中 Lib 目录中的文件 this.py 并尝试了解它的作用。

                      之后,查看文档中的eval 函数:

                      help(eval)
                      

                      现在您应该找到了一种保护您的代码的有趣方法。但要小心,因为这只适用于比你聪明的人! (我并不是要冒犯,任何聪明到能理解你所做的事情的人都可以扭转它)。

                      【讨论】:

                        【解决方案23】:

                        尝试将您的 hello world python 代码粘贴到以下站点:

                        http://enscryption.com/encrypt-and-obfuscate-scripts.html

                        它将为您生成一个复杂的加密和混淆但功能齐全的脚本。看看你是否可以破解脚本并揭示实际代码。或者看看它提供的复杂程度是否满足您对安心的需求。

                        通过本网站为您生成的加密脚本应该可以在任何安装了 python 的 Unix 系统上运行。

                        如果您想以另一种方式加密,我强烈建议您编写自己的加密/混淆算法(如果安全性对您很重要)。这样,除了你之外,没有人能弄清楚它是如何工作的。但是,要让它真正发挥作用,您必须花费大量时间来确保没有任何漏洞可以让手头有很多时间的人利用。并确保您使用对 Unix 系统来说已经很自然的工具......即 openssl 或 base64。这样一来,您的加密脚本就更便携了。

                        【讨论】:

                          猜你喜欢
                          • 1970-01-01
                          • 2014-03-08
                          • 1970-01-01
                          • 2014-05-09
                          • 2023-04-08
                          • 1970-01-01
                          • 2011-10-09
                          • 1970-01-01
                          • 1970-01-01
                          相关资源
                          最近更新 更多