如何恢复丢失的Python源代码如果它还在内存中运行

如何恢复丢失的Python源代码如果它还在内存中运行

使用 https://pypi.python.org/pypi/pyrasite/和https://pypi.python.org/pypi/uncompyle6 恢复还在内存中运行,但是已经丢失的源代码。

需要恢复的Python源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import sys
import json
import time
def test():
"""this is a function"""
print('test')
while True:
print('123')
time.sleep(3)
# test file
class Test():
"""this is a class"""
def __init__():
print('object')
def foo(self):
"""this is a method"""
for i in range(10):
print('foo')
test()

安装GDB(pyrasite需要)

1
# apt-get update && apt-get install gdb

安装pyrasite,它允许你连接一个Python shell到还在运行的进程上

1
# pip install pyrasite

安装uncompyle6,它允许你从内存代码对象中获取Python源代码

1
# pip install uncompyle6

找到还在运行进程的PID

1
# ps aux | grep python

使用pyrasite连接到PID生成一个交互式shell

1
# pyrasite-shell <PID>

注意:这一步操作并不是总是能成功,笔者在Ubuntu 16.04系统上连接失败,在Centos7.4系统上连接成功

找出你要恢复的函数和类

1
>>>dir()

如下图,我需要恢复的一个对象Test和一个函数test
运行效果

将函数和对象反编译为源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
[root@localhost ~]# pyrasite-shell 11062
Pyrasite Shell 2.0
Connected to 'python test.py'
Python 3.6.3 (default, Nov 1 2017, 18:31:17)
[GCC 4.8.5 20150623 (Red Hat 4.8.5-16)] on linux
Type "help", "copyright", "credits" or "license" for more information.
(DistantInteractiveConsole)
>>> import sys
>>> import uncompyle6
>>> dir()
['DistantInteractiveConsole', 'InteractiveConsole', 'ReverseConnection', 'ReversePythonConnection', 'ReversePythonShell', 'StringIO', 'Test', '__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', '__warningregistry__', 'json', 'pyrasite', 'socket', 'sys', 'test', 'threading', 'time', 'traceback', 'uncompyle6']
>>> dir(test)
['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
>>> dir(Test)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'foo']
>>> dir(Test.foo)
['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
>>> uncompyle6.main.decompile(3.6, test.__code__, sys.stdout)
# uncompyle6 version 2.16.0
# Python bytecode 3.6
# Decompiled from: Python 3.6.3 (default, Nov 1 2017, 18:31:17)
# [GCC 4.8.5 20150623 (Red Hat 4.8.5-16)]
# Embedded file name: test.py
print('test')
while True:
print('123')
time.sleep(3)<uncompyle6.semantics.pysource.SourceWalker object at 0x7f0a563a4ef0>
>>> uncompyle6.main.decompile(3.6, Test.foo.__code__, sys.stdout)
# uncompyle6 version 2.16.0
# Python bytecode 3.6
# Decompiled from: Python 3.6.3 (default, Nov 1 2017, 18:31:17)
# [GCC 4.8.5 20150623 (Red Hat 4.8.5-16)]
# Embedded file name: test.py
for i in range(10):
print('foo')<uncompyle6.semantics.pysource.SourceWalker object at 0x7f0a556dae10>
>>>

uncompyle6.main.decompile(bytecode_version, co, out=None, **kwargs)可以将函数字节码反编译为源码,其中前三个参数最重要:

  • bytecode_version:表示需要恢复的Python字节码的版本,可以选择2.7、3.6等版本
  • co:表示函数的字节码对象,不同版本的字节码对象不一样
    • 2.7版本的函数字节码对象:test.funccode或者test. code _
    • 2.7版本的对象字节码对象:Test.foo.im_func.func_code或者Test.foo.imfunc. code _
    • 3.6版本的函数字节码对象:test._ code _
    • 3.6版本的对象字节码对象:Test.foo._ code _
  • out:表示源码输出对象,可以是sys.stdout直接屏幕打印,也可以是文件对象
    运行效果

注意:字节码对象中并没有注释,所以恢复出来的代码中并没有注释,不过可以在_ doc _对象中找回,另外该uncompyle6.main.decompile()只能反编译常规函数,不适用与协程,所以对象中有多个方法必须一个一个的反编译,比较麻烦。

参考文章:https://gist.github.com/simonw/8aa492e59265c1a021f5c5618f9e6b12

文章目录
  1. 1. 需要恢复的Python源代码
  2. 2. 安装GDB(pyrasite需要)
  3. 3. 安装pyrasite,它允许你连接一个Python shell到还在运行的进程上
  4. 4. 安装uncompyle6,它允许你从内存代码对象中获取Python源代码
  5. 5. 找到还在运行进程的PID
  6. 6. 使用pyrasite连接到PID生成一个交互式shell
  7. 7. 找出你要恢复的函数和类
  8. 8. 将函数和对象反编译为源代码
|