从字节码重新组装.py文件

时间:2019-02-17 06:40:02

标签: python

问题陈述

我有一个文件(无扩展名),其中包含一些格式良好的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,但是我不能似乎可以正常工作。

1 个答案:

答案 0 :(得分:6)

uncompyle6的功能有些混乱。它以Python bytecode 开头,如果是Python 3.6或更高版本,则更准确地说是“ wordcode”。另外,它通常用于反编译包含字节码的Python编译文件。

从上面显示的内容来看,要做的是从由特定于版本的反汇编程序生成的字节码的文本表示开始,该反汇编程序随Python运行的版本一起提供(并且只能完全在其上运行)。

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

不过,请别担心,我写了一些其他工具来使您更接近想要到达的地方。具体来说,我编写了一个Python跨版本的 assembler 以匹配Python的内置 disassembler

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

使用它,您可以生成可以运行的真实Python字节码。而且,如果您编写的代码确实是从Python吐出的东西开始的,则可能可以将其反编译为高级Python。

但是, xasm 当前确实比上面需要更多的帮助。具体来说,它不会从操作码名称中猜测它们可以属于哪个Python版本。将操作码名称与可接受的Python版本进行匹配比您想象的要难得多。如果看到LOAD_CONST,则还需要考虑这条指令是占用2个字节还是3个字节。如果2个,则它是Python 3.6或更高版本,否则是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

请注意文件顶部注释中的所有其他信息,其中包含一些真正不可思议的内容,例如“堆栈大小”和“标志”。这个 其他大部分内容都需要存储在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的步骤。