PyInstaller/Python代码的加密编译与反编译

createh53个月前 (02-01)技术教程34

Demo代码

import time
# 这是注释
start = time.time()
number = 0
print(number)

PyInstaller打包时加密.pyc代码

Python bytecode modules can be obfuscated with AES256 by specifying an encryption key on PyInstaller’s command line. Please note that it is still very easy to extract the key and get back the original bytecode, but it should prevent most forms of “casual” tampering. See Encrypting Python Bytecode for details.

程序入口的Python文件,如demo.py不会加密,只要加密import的模块,所以不要把主要代码写在入口文件上。

pyinstaller --key 123 demo.py

即下述生成的demo.pyc没有加密,其导入的模块打包于子文件PYZ-00.pyz中,解开可以看到其是有加密的后缀.pyc.encrypted。为何入口文件没加密?

demo.pyc
pyiboot01_bootstrap.pyc
pyimod00_crypto_key.pyc
pyimod01_archive.pyc
pyimod02_importers.pyc
pyimod03_ctypes.pyc
pyimod04_pywin32.pyc
pyi_rth_subprocess.pyc
PYZ-00.pyz
struct.pyc

解密PyInstaller加密的.pyc.encrypted代码

PyInstaller use tinyaes-py to encrypt Python bytecode from 4.2, previously it used Pycrypto. https://github.com/pyinstaller/pyinstaller/pull/4652

有两个方法:一是使用 pyinstxtractor-ng 解包PyInstaller打包的bundle中的demo.exe文件时附带解密掉;二是对 pyinstxtractor 解包后的 .pyc.encrypted 手动解密。

pyinstxtractor-ng附带解密pyc.encrypted


https://github.com/pyinstxtractor/pyinstxtractor-ng 集成解密功能,看它的代码应该也不是100%解密成功。

python .\pyinstxtractor-ng.py .\dist\demo\demo.exe

手动解密pyc.encrypted代码为.pyc

import sys

class Cipher(object):
    """
    This class is used only to decrypt Python modules.
    """
    def __init__(self):
        # At build-type the key is given to us from inside the spec file, at
        # bootstrap-time, we must look for it ourselves by trying to import
        # the generated 'pyi_crypto_key' module.
        import pyimod00_crypto_key
        key = pyimod00_crypto_key.key

        assert type(key) is str
        if len(key) > CRYPT_BLOCK_SIZE:
            self.key = key[0:CRYPT_BLOCK_SIZE]
        else:
            self.key = key.zfill(CRYPT_BLOCK_SIZE)
        assert len(self.key) == CRYPT_BLOCK_SIZE
        import tinyaes
        self._aesmod = tinyaes
        # Issue #1663: Remove the AES module from sys.modules list. Otherwise
        # it interferes with using 'tinyaes' module in users' code.
        del sys.modules['tinyaes']

    def __create_cipher(self, iv):
        # The 'AES' class is stateful, this factory method is used to
        # re-initialize the block cipher class with each call to xcrypt().
        return self._aesmod.AES(self.key.encode(), iv)

    def decrypt(self, data):
        cipher = self.__create_cipher(data[:CRYPT_BLOCK_SIZE])
        return cipher.CTR_xcrypt_buffer(data[CRYPT_BLOCK_SIZE:])

if __name__ == '__main__':
    import zlib

    CRYPT_BLOCK_SIZE = 16

    inf = open('/home/a/a.exe_extracted/PYZ-00.pyz_extracted/yamnet.pyc.encrypted', 'rb') # encrypted file input
    outf = open('yamnet.pyc', 'wb') # output file 

    cipher = Cipher()

    # Decrypt and decompress
    plaintext = zlib.decompress(cipher.decrypt(inf.read()))

    # Write pyc header
    # get from importlib.util.MAGIC_NUMBER.hex()
    outf.write(b'\x55\x0d\x0d\x0a\0\0\0\0')

    # Write decrypted data
    outf.write(plaintext)

    inf.close()
    outf.close()

解包PyInstaller打包的exe

PyInstaller Extractor is a Python script to extract the contents of a PyInstaller generated Windows executable file https://github.com/extremecoders-re/pyinstxtractor

更新:作者的新库
https://github.com/pyinstxtractor/pyinstxtractor-ng 集成反汇编xdis与解密Pyinstaller的加密代码。

反汇编disassembler

即反汇编(disassembler)字节码.pyc为用户可读的指令列表,
https://github.com/rocky/python-xdis 这个库支持跨Python版本反汇编,其安装完提供一个命令行工具
pydisasm.exepydisasm.exe .\demo.exe_extracted\demo.pyc的结果如下所示。

# pydisasm version 6.0.4
# Python bytecode 3.8.0 (3413)
# Disassembled from Python 3.8.12 (default, Oct 12 2021, 03:01:40) [MSC v.1916 64 bit (AMD64)]
# Timestamp in code: 0 (1970-01-01 08:00:00)
# Source code size mod 2**32: 0 bytes
# Method Name:       
# Filename:          demo.py
# Argument count:    0
# Position-only argument count: 0
# Keyword-only arguments: 0
# Number of locals:  0
# Stack size:        2
# Flags:             0x00000040 (NOFREE)
# First Line:        1
# Constants:
#    0: 0
#    1: None
# Names:
#    0: time
#    1: start
#    2: number
#    3: print
  1:           0 LOAD_CONST           (0)
               2 LOAD_CONST           (None)
               4 IMPORT_NAME          (time)
               6 STORE_NAME           (time)

  3:           8 LOAD_NAME            (time)
              10 LOAD_METHOD          (time)
              12 CALL_METHOD          0
              14 STORE_NAME           (start)

  4:          16 LOAD_CONST           (0)
              18 STORE_NAME           (number)

  5:          20 LOAD_NAME            (print)
              22 LOAD_NAME            (number)
              24 CALL_FUNCTION        1
              26 POP_TOP
              28 LOAD_CONST           (None)
              30 RETURN_VALUE

反编译decompile

即反编译(decompiler)字节码.pyc为Python源代码。
https://github.com/rocky/python-decompile3 目前只支持Python 3.7-3.8,Python 3.9+还不支持。所以,使用高版本的Python,字节码被反编译的概率更小,更安全。反编译结果如下所示,源代码中的注释没有了。

$ decompyle3.exe .\demo.exe_extracted\demo.pyc
# decompyle3 version 3.8.0
# Python bytecode 3.8.0 (3413)
# Decompiled from: Python 3.8.12 (default, Oct 12 2021, 03:01:40) [MSC v.1916 64 bit (AMD64)]
# Embedded file name: demo.py
import time
start = time.time()
number = 0
print(number)
# okay decompiling .\demo.exe_extracted\demo.pyc

Use https://github.com/rocky/python-decompile3. Unlike Java, there is no pretty mature tool for Python code decompilation.

But, it may raise error: `ValueError: bad marshal data (unknown type code) sometimes.

For complete decrypt, this maybe help.

https://github.com/pyinstaller/pyinstaller/blob/253feb7e16cd3a6a6d3ea2fb80c3491d30671e39/PyInstaller/loader/pyimod02_archive.py#L265

F.Y.I

def extract(self, name):
    (typ, pos, length) = self.toc.get(name, (0, None, 0))
    if pos is None:
        return None
    with self.lib:
        self.lib.seek(self.start + pos)
        obj = self.lib.read(length)
    try:
        if self.cipher:
            obj = self.cipher.decrypt(obj)
        obj = zlib.decompress(obj)
        if typ in (PYZ_TYPE_MODULE, PYZ_TYPE_PKG):
            obj = marshal.loads(obj)
    except EOFError:
        raise ImportError("PYZ entry '%s' failed to unmarshal" % name)
    return typ, obj

相关文章

你的代码被反编译啊?如何防止java jar被反编译,大佬们快看过来

你的代码被反编译了嘛?我们将如何防止java jar被反编译,大佬们快看过来大家晚上好,这里是互联网技术学堂,今天来谈谈,如何防止java jar被反编译。如果你有兴趣,那就点赞、关注、分享吧。为什么...

安卓apk反编译、重新打包、签名全过程

apktool :https://ibotpeaches.github.io/Apktool/install/ 资源文件获取,可以提取出图片文件和布局文件进行使用查看 还可以将反编译之后的apk重新打...

Java反编译工具 JD-GUI安装使用(jdgui反编译jar)

我们知道,将源代码转换成二进制执行代码的过程叫“编译”,那么反编译就是将二进制执行代码转换成源代码。在java开发里,源代码是.java文件,然后经过编译后生成计算机识别的.class文件,但是.cl...

年年势头强劲,Java不死,尔等永远是太子

小编插一句(php是世界上最好的语言)不服来战。TIOBE 编程社区指数是编程语言流行度的指标,该榜单每月更新一次,指数基于全球技术工程师、课程和第三方供应商的数量。包括流行的搜索引擎,如谷歌、必应、...

MT管理器-简单实战-去更新提示(mt管理器基本教程)

去掉了烦人了启动页后,还有一个更新提示依旧很烦人。我们查看 apk 信息时可以看到它当前的版本号是 1,而这边的更新提示显示最新版本号是 1100。当然一般应用的更新提示都不会告诉你版本号,而是告诉你...

C++开发辅助工具推荐(好用的c++开发工具)

在面向C++的开发过程中,一些辅助工具的运用,往往会起到事半功倍的效果。下面是自己在学习工作中注意到的好用软件,现推荐给大家。Beyond CompareBeyond Compare是一款专业的文本文...