【问题标题】:Reassemble .py file from bytecode从字节码重新组装 .py 文件
【发布时间】:2019-07-01 07:17:49
【问题描述】:

问题陈述

我有一个文件(无扩展名),其中包含一些格式良好的 python 操作码,我想将其重新组装成原始 .py 文件(或尽可能接近)。

重现问题

我可以像我拥有的​​那样重新创建一个文件。从名为test.py 的文件开始,其内容为:

a = 1
b = 2
print(a+b)

通过运行python3 -m dis test.py,我得到以下输出:

  1       0 LOAD_CONST               0 (1)
          2 STORE_NAME               0 (a)

  2       4 LOAD_CONST               1 (2)
          6 STORE_NAME               1 (b)

  3       8 LOAD_NAME                2 (print)
         10 LOAD_NAME                0 (a)
         12 LOAD_NAME                1 (b)
         14 BINARY_ADD
         16 CALL_FUNCTION            1
         18 POP_TOP
         20 LOAD_CONST               2 (None)
         22 RETURN_VALUE

我想从这个输出中重建原始的test.py 文件。

我的尝试

我已经尝试在输出上运行uncompyle6,但它会出错并显示以下消息:

ImportError: Unknown magic number 8224 in test.pyc

我不知道用于生成原始文件以获取幻数的原始python版本,也不知道幻数是否是文件中唯一缺少的东西。

很久以前有人在这里问过类似的问题:Reassembling Python bytecode to the original code? 建议的答案已经过时了,但即使在更新之后,当前的答案应该是使用uncompyle6,但我似乎无法理解上班。

【问题讨论】:

    标签: python


    【解决方案1】:

    对于uncompyle6 的作用存在一些混淆。它以 Python bytecode 开头,如果是 Python 3.6 或更高版本,则更准确地说是“wordcode”。或者,它通常用于反编译包含字节码的 Python 编译文件。

    从您上面显示的内容来看,我相信您想要做的是从版本特定的反汇编程序生成的字节码的 text 表示开始,该反汇编程序附带(并且仅完全适用) Python 正在运行的版本。

    这就是您从 uncompyle6 收到上述奇怪的“导入错误”消息的原因。它查看您奇怪地称为 Python 编译文件的文本文件的开头。该文件以 ASCII 编码字符串“1”开头,uncompyle6 根据特定的format for Python compiled file 进行解释,其中文件的开头包含某种 Python 编码的版本字符串,技术上称为“幻数” .

    不过,不要害怕,我已经编写了更多工具来让您更接近您想要到达的地方。具体来说,我写了一个 Python 跨版本的 assembler 来匹配 Python 的内置 disassembler

    这是在我的 github 项目中python-xasm

    使用它,您可以生成可以运行的真正 Python 字节码。如果你写的代码确实像 Python 吐出的东西,它可能可以被反编译回高级 Python。

    但是,xasm 目前确实需要比您上面的帮助更多的帮助。具体来说,它不会从操作码名称中猜测它们可以属于哪个 Python 版本。将操作码名称与可接受的 Python 版本匹配比您想象的还要困难。如果您看到LOAD_CONST,您还需要考虑这是指令占用 2 个字节还是 3 个字节。如果为 2,则它是 Python 3.6 或更高版本,否则它是 Python 哪个 Python 解释器。但我假设你不在乎,只要你想出的东西是一致的。

    所以有了上面的内容,现在回来解决你的问题。

    首先产生真正的字节码。你可以这样做

    import py_compile 
    py_compile.compile("/tmp/test.py", "/tmp/test.pyc", 'exec')
    

    现在不要使用内置的 python 反汇编器,而是使用我编写的跨版本反汇编器,它带有 xdis,称为 pydisasm,并使用 --asm 选项,它将在xasm-友好的方式:

    $ pydisasm --asm 
    # pydisasm version 4.0.0-git
    # Python bytecode 3.6 (3379)
    # Disassembled from Python 3.6.5 (default, Aug 12 2018, 16:37:27)
    # [GCC 4.2.1 Compatible Apple LLVM 9.1.0 (clang-902.0.39.2)]
    # Timestamp in code: 1554492841 (2019-04-05 15:34:01)
    # Source code size mod 2**32: 23 bytes
    
    # Method Name:       <module>
    # Filename:          exec
    # Argument count:    0
    # Kw-only arguments: 0
    # Number of locals:  0
    # Stack size:        3
    # Flags:             0x00000040 (NOFREE)
    # First Line:        1
    # Constants:
    #    0: 1
    #    1: 2
    #    2: None
    # Names:
    #    0: a
    #    1: b
    #    2: print
      1:
                LOAD_CONST           (1)
                STORE_NAME           (a)
    
      2:
                LOAD_CONST           (2)
                STORE_NAME           (b)
    
      3:
                LOAD_NAME            (print)
                LOAD_NAME            (a)
                LOAD_NAME            (b)
                BINARY_ADD
                CALL_FUNCTION        1
                POP_TOP
                LOAD_CONST           (None)
                RETURN_VALUE
    

    请注意文件顶部 cmets 中的所有其他信息,其中包含一些非常神秘的内容,例如“堆栈大小”和“标志”。这个 并且大多数其他内容需要存储在 Python 字节码文件中。

    所以把它保存到一个文件中,然后然后你可以把它组装成字节码。然后反编译。

    $ ./xasm/xasm_cli.py /tmp/test.pyasm
    Wrote /tmp/test.pyc
    $ uncompyle6 /tmp/test.pyc
    # uncompyle6 version 3.2.6
    # Python bytecode 3.6 (3379)
    # Decompiled from: Python 3.6.5 (default, Aug 12 2018, 16:37:27)
    # [GCC 4.2.1 Compatible Apple LLVM 9.1.0 (clang-902.0.39.2)]
    # Embedded file name: exec
    # Compiled at: 2019-04-05 15:34:01
    # Size of source mod 2**32: 23 bytes
    a = 1
    b = 2
    print(a + b)
    # okay decompiling /tmp/test.pyc
    

    我在哥伦比亚麦德林的 Pycon2018 上发表了与此相关的闪电演讲。很抱歉你错过了,但你可以在这里找到它的视频http://rocky.github.io/pycon2018-light.co

    它展示了如何:

    • 从 ASCII 编码的 Python 源文本生成 Python 编译文件,
    • 修改它以去除尾递归,
    • 将其写回 Python 编译文件,然后
    • 运行代码。

    当然,你不能反编译它,因为 没有 Python 可以轻易地模仿它——它是手工修改的。

    最后,您似乎还对字节码和源代码之间的关系感兴趣。所以我会提到 uncompyle6 有选项 --tree 和更详细的 --grammar ,它将显示从 Python 字节码重建 Python 所采取的步骤。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多