最新消息:20210816 当前crifan.com域名已被污染,为防止失联,请关注(页面右下角的)公众号

【已解决】python的pyc文件的反编译

Python crifan 11196浏览 0评论
折腾:
【未解决】mac中electron-python用PyInstaller去打包可执行文件
看到打包期间生成的二进制pyc文件了:
打包目的是防止别人看到源代码
而印象中,python的pyc也是可以被反编译的
所以此处顺带去看看:
是否很容易把此处pyc反编译出源码
pyc 反编译
pyc python3 反编译
pyc python3 decompile
python反编译 – 在线工具
结果:
可能有部分代码没有反编成功!
反编译失败。
pyc文件反编译到Python源码_我的总结积累与分享-CSDN博客
wibiti/uncompyle2: Python 2.7 decompiler
只支持python2,此处自己是python 3,且是最新的3.8.0
pyc反编译 – 爱资料工具
永远等待中。放弃。
有什么工具可以将python编译好的代码.pyc反编译为.py? – 知乎
python文件反编译失败,鄙人能力有限,后期增强改进!
Python Decompiler Online
试了2次,结果:
要么是504错误
要么是:Error: Unknown error:0
How do I decompile Python 3.5 .pyc? – Stack Overflow
  • uncompyle6
  • pycdc
zrax/pycdc: C++ python bytecode disassembler and decompiler
python – Is it possible to decompile a compiled .pyc file into a .py file? – Stack Overflow
Decompile Python 3.3 .pyc using unpyc3 – Stack Overflow
  • unpyc3
figment/unpyc3: Decompiler for Python 3.3 (forked from https://code.google.com/p/unpyc3)
https://github.com/figment/unpyc3
Google Code Archive – Long-term storage for Google Code Project Hosting.
andrew-tavera/unpyc37: Decompiler for Python 3.7 (forked from https://github.com/figment/unpyc3)
https://github.com/andrew-tavera/unpyc37
bytecode – Is it easy to fully decompile python compiled(*.pyc) files? – Stack Overflow
uncompyle6 · PyPI
rocky/python-uncompyle6: A cross-version Python bytecode decompiler
去试试
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
 git clone https://github.com/rocky/python-uncompyle6.git
Cloning into 'python-uncompyle6'...
remote: Enumerating objects: 24340, done.
remote: Total 24340 (delta 0), reused 0 (delta 0), pack-reused 24340
Receiving objects: 100% (24340/24340), 7.14 MiB | 737.00 KiB/s, done.
Resolving deltas: 100% (18719/18719), done.
limao@xx  ~/dev/tools/python_decompile cd python-uncompyle6
limao@xx  ~/dev/tools/python_decompile/python-uncompyle6   master  ll
total 320
-rw-r--r--   1 limao  CORP\Domain Users    34K  1  6 14:49 COPYING
-rw-r--r--   1 limao  CORP\Domain Users   5.9K  1  6 14:49 DECOMPYLE-2.4-CHANGELOG.txt
-rw-r--r--   1 limao  CORP\Domain Users    12K  1  6 14:49 HISTORY.md
-rw-r--r--   1 limao  CORP\Domain Users    10K  1  6 14:49 HOW-TO-REPORT-A-BUG.md
-rw-r--r--   1 limao  CORP\Domain Users   462B  1  6 14:49 MANIFEST.in
-rw-r--r--   1 limao  CORP\Domain Users   2.9K  1  6 14:49 Makefile
-rw-r--r--   1 limao  CORP\Domain Users    33K  1  6 14:49 NEWS.md
-rw-r--r--   1 limao  CORP\Domain Users   255B  1  6 14:49 PKG-INFO
-rw-r--r--   1 limao  CORP\Domain Users    12K  1  6 14:49 README.rst
-rw-r--r--   1 limao  CORP\Domain Users   3.5K  1  6 14:49 __pkginfo__.py
drwxr-xr-x  17 limao  CORP\Domain Users   544B  1  6 14:49 admin-tools
drwxr-xr-x   4 limao  CORP\Domain Users   128B  1  6 14:49 appveyor
-rw-r--r--   1 limao  CORP\Domain Users   2.6K  1  6 14:49 appveyor.yml
drwxr-xr-x   4 limao  CORP\Domain Users   128B  1  6 14:49 bin
-rwxr-xr-x   1 limao  CORP\Domain Users   204B  1  6 14:49 compile_tests
drwxr-xr-x  16 limao  CORP\Domain Users   512B  1  6 14:49 pytest
-rw-r--r--   1 limao  CORP\Domain Users    90B  1  6 14:49 requirements-dev.txt
-rw-r--r--   1 limao  CORP\Domain Users    60B  1  6 14:49 requirements.txt
-rwxr-xr-x   1 limao  CORP\Domain Users   133B  1  6 14:49 setup.cfg
-rwxr-xr-x   1 limao  CORP\Domain Users   1.6K  1  6 14:49 setup.py
drwxr-xr-x  62 limao  CORP\Domain Users   1.9K  1  6 14:49 test
-rw-r--r--   1 limao  CORP\Domain Users   509B  1  6 14:49 tox.ini
drwxr-xr-x  17 limao  CORP\Domain Users   544B  1  6 14:49 uncompyle6
which python
/Users/limao/.pyenv/shims/python
limao@xx  ~/dev/tools/python_decompile/python-uncompyle6   master  python --version
Python 3.8.0
安装:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 pip install -e .
Obtaining file:///Users/limao/dev/tools/python_decompile/python-uncompyle6
Collecting spark-parser<1.9.0,>=1.8.9
  Downloading https://files.pythonhosted.org/packages/49/14/d2e92845c424583a14a2ddd44d46dd0ca176e7a8cacd06095e5c0877d0cd/spark_parser-1.8.9-py38-none-any.whl
Collecting xdis<4.3.0,>=4.2.2
  Downloading https://files.pythonhosted.org/packages/c2/e9/38c9172f9db187e842708b2eb38d6517ed973a0321c789608e8cef47cf95/xdis-4.2.2-py37-none-any.whl (101kB)
     |████████████████████████████████| 102kB 790kB/s
Collecting click
  Using cached https://files.pythonhosted.org/packages/fa/37/45185cb5abbc30d7257104c434fe0b07e5a195a6847506c074527aa599ec/Click-7.0-py2.py3-none-any.whl
Installing collected packages: click, spark-parser, xdis, uncompyle6
  Running setup.py develop for uncompyle6
Successfully installed click-7.0 spark-parser-1.8.9 uncompyle6 xdis-4.2.2
limao@xx  ~/dev/tools/python_decompile/python-uncompyle6   master  which uncompyle6
/Users/limao/.pyenv/shims/uncompyle6
limao@xx  ~/dev/tools/python_decompile/python-uncompyle6   master  uncompyle6 --version
uncompyle6 3.6.2
去导出反编译后的文本为py文件
1
2
3
 uncompyle6 ../pyc/mitmdumpLauncher.cpython-38.pyc > mitmdumpLauncher.py
# file ../pyc/mitmdumpLauncher.cpython-38.pyc
# Deparsing hit an internal grammar-rule bug
打开反编译后的py代码:
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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# uncompyle6 version 3.6.2
# Python bytecode 3.8 (3413)
# Decompiled from: Python 3.8.0 (default, Jan  6 2020, 10:33:50)
# [Clang 10.0.1 (clang-1001.0.46.4)]
# Embedded file name: /Users/limao/dev/xx/crawler/mitmdumpUrlSaver/electron-python-example/pymitmdump/mitmdumpLauncher.py
# Size of source mod 2**32: 8092 bytes
import subprocess, os, re, logging
from utils import osIsMacOS, osIsWinows
def startMitmdumpSaver():
    """Start mitmdump saver"""
    logging.debug('startMitmdumpSaver')
    isUseShell = False
    curFileFolder = os.path.dirname(__file__)
    logging.debug('curFileFolder=%s', curFileFolder)
    Mitmdump_Mac = 'mitmdump_executable/mac/mitmdump'
    Mitmdump_Win = 'mitmdump_executable/win/mitmdump.exe'
    mitmdumpExecutablePath = None
    if osIsMacOS():
        mitmdumpExecutablePath = Mitmdump_Mac
    else:
        if osIsWinows():
            mitmdumpExecutablePath = Mitmdump_Win
        else:
            logging.debug('mitmdumpExecutablePath=%s', mitmdumpExecutablePath)
            mitmdumpExecutableFullPath = os.path.join(curFileFolder, mitmdumpExecutablePath)
            logging.debug('mitmdumpExecutableFullPath=%s', mitmdumpExecutableFullPath)
            mitmdumpExecutable = mitmdumpExecutableFullPath
            logging.debug('mitmdumpExecutable=%s', mitmdumpExecutable)
            mitmdumpScriptFilename = 'mitmdumpUrlSaver.py'
            logging.debug('mitmdumpScriptFilename=%s', mitmdumpScriptFilename)
            mitmdumpScriptFullPath = os.path.join(curFileFolder, mitmdumpScriptFilename)
            logging.debug('mitmdumpScriptFullPath=%s', mitmdumpScriptFullPath)
            mitmdumpScript = mitmdumpScriptFullPath
            logging.debug('mitmdumpScript=%s', mitmdumpScript)
            gConfig = {'mitmdumpExecutable':mitmdumpExecutable,
             'port':8081,
             'mitmdumpScript':mitmdumpScript}
            logging.debug('gConfig=%s', gConfig)
            shellCmdList = [
             '%s' % gConfig['mitmdumpExecutable'], '-p %s' % gConfig['port'], '-s %s' % gConfig['mitmdumpScript']]
            logging.debug('shellCmdList=%s', shellCmdList)
            shellCmdStr = ' '.join(shellCmdList)
            logging.debug('shellCmdStr=%s', shellCmdStr)
            if isUseShell:
                shellCmd = shellCmdStr
            else:
                shellCmd = shellCmdList
            logging.info('shellCmd=%s', shellCmd)
            curProcess = subprocess.Popen(shellCmd,
              stdout=(subprocess.PIPE),
              stderr=(subprocess.STDOUT),
              universal_newlines=True,
              shell=isUseShell)
            logging.debug('curProcess=%s', curProcess)
        while True:
            curLineOutput = curProcess.stdout.readline()
            curLineOutput = curLineOutput.strip()
            logging.debug('curLineOutput=%s', curLineOutput)
            yield curLineOutput
            returnCode = curProcess.poll()
            if returnCode is not None:
                logging.info('returnCode=%s', returnCode)
                respLineList = curProcess.stdout.readlines()
                logging.info('respLineList=%s', respLineList)
                yield respLineList
                break
 
 
def getMitmdumpStatus():
    """Get current mitmdump status"""
    logging.debug('getMitmdumpStatus')
    isRunning, processInfoList = False, []
    if osIsMacOS():
        shellCmdStr = 'ps aux | grep mitmdump'
    elif osIsWinows():
        shellCmdStr = 'tasklist | findstr mitmdump'
    logging.debug('shellCmdStr=%s', shellCmdStr)
    shellRespStr = subprocess.check_output(shellCmdStr,
      shell=True,
      universal_newlines=True)
    logging.debug('shellRespStr=%s', shellRespStr)
    singleLineList = shellRespStr.split(os.linesep)
    logging.debug('singleLineList=%s', singleLineList)
    for eachLineStr in singleLineList:
        logging.debug('eachLineStr=%s', eachLineStr)
        mitmdumpCmdPattern = '^\\w+\\s+(?P<pidStr>\\d+).+\\s+(?P<mitmdumpFile>\\S+mitmdump)\\s+-p\\s+(?P<portStr>\\d+)\\s+-s\\s+(?P<scriptFile>\\S+?\\.py)'
        foundMitmdump = re.search(mitmdumpCmdPattern, eachLineStr, re.IGNORECASE)
        logging.debug('foundMitmdump=%s', foundMitmdump)
        if foundMitmdump:
            isRunning = True
            matchedMitmdumpStr = foundMitmdump.group(0)
            logging.debug('matchedMitmdumpStr=%s', matchedMitmdumpStr)
            pidStr = foundMitmdump.group('pidStr')
            pidInt = int(pidStr)
            logging.debug('pidInt=%s', pidInt)
            mitmdumpFile = foundMitmdump.group('mitmdumpFile')
            logging.debug('mitmdumpFile=%s', mitmdumpFile)
            portStr = foundMitmdump.group('portStr')
            portInt = int(portStr)
            logging.debug('portInt=%s', portInt)
            scriptFile = foundMitmdump.group('scriptFile')
            logging.debug('scriptFile=%s', scriptFile)
            curProcessDict = {'pid':pidInt,
             'mitmdumpFile':mitmdumpFile,
             'port':portInt,
             'scriptFile':scriptFile}
            logging.debug('curProcessDict=%s', curProcessDict)
            processInfoList.append(curProcessDict)
        logging.info('isRunning=%s', isRunning)
        if processInfoList:
            for curIdx, eachProcess in enumerate(processInfoList):
                logging.info('[%d] %s', curIdx, eachProcess)
 
 
        else:
            return (
             isRunning, processInfoList)
 
 
def stopMitmdump():
    """Stop mitmdump"""
    shellCmdStr = 'killall mitmdump'
    logging.debug('shellCmdStr=%s', shellCmdStr)
    try:
        shellRespStr = subprocess.check_output(shellCmdStr,
          shell=True,
          universal_newlines=True)
        logging.info("shellCmdStr='%s' -> shellRespStr=%s", shellCmdStr, shellRespStr)
    except subprocess.CalledProcessError as procErr:
        try:
            logging.error("shellCmdStr='%s' -> procErr=%s", shellCmdStr, procErr)
            shellRespStr = None
        finally:
            procErr = None
            del procErr
    else:
        return shellRespStr
if __name__ == '__main__':
    from utils import loggingInit
    logFilename = 'mitmdumpLauncher.log'
    loggingInit(logFilename)
    stopMitmdump()
# NOTE: have internal decompilation grammar errors.
# Use -t option to show full context.
# not in loop:
#    break
#     L.  96     434  BREAK_LOOP          442  'to 442'
去对比源码:
除了个别问题和缺点:
  • 个别代码逻辑导致逻辑变化:
  • 注释丢失
之外,主体代码逻辑,全都在。
从反编译角度来说,可以说成功复原代码达95%左右
可以这么说:成功率和准确率极高。效果极其好。
顺带再去反编译另外4个文件,大概对比看看效果是否也这么好。
1
2
3
4
5
6
7
8
limao@xx  ~/dev/tools/python_decompile/python-uncompyle6   master  uncompyle6 ../pyc/mitmdumpOtherApi.cpython-38.pyc > ../pyc/mitmdumpOtherApi.py
limao@xx  ~/dev/tools/python_decompile/python-uncompyle6   master  uncompyle6 ../pyc/mitmdumpSaverApi.cpython-38.pyc > ../pyc/mitmdumpSaverApi.py
limao@xx  ~/dev/tools/python_decompile/python-uncompyle6   master  uncompyle6 ../pyc/mitmdumpUrlSaver.cpython-38.pyc > ../pyc/mitmdumpUrlSaver.py
 
 
# file ../pyc/mitmdumpUrlSaver.cpython-38.pyc
# Deparsing stopped due to parse error
limao@xx  ~/dev/tools/python_decompile/python-uncompyle6   master  uncompyle6 ../pyc/utils.cpython-38.pyc > ../pyc/utils.py
对比:
/Users/limao/dev/xx/crawler/mitmdumpUrlSaver/electron-python-example/pymitmdump/mitmdumpOtherApi.py
vs
/Users/limao/dev/tools/python_decompile/pyc/mitmdumpOtherApi.py
/Users/limao/dev/xx/crawler/mitmdumpUrlSaver/electron-python-example/pymitmdump/mitmdumpUrlSaver.py
vs
/Users/limao/dev/tools/python_decompile/pyc/mitmdumpUrlSaver.py
中间是部分函数
1
def request(self, flow):
出错无法解析出来的。
/Users/limao/dev/xx/crawler/mitmdumpUrlSaver/electron-python-example/pymitmdump/utils.py
vs
/Users/limao/dev/tools/python_decompile/pyc/utils.py
反编译输出的文件,顶部和底部分别有提示:
1
2
3
4
5
6
7
8
9
10
# uncompyle6 version 3.6.2
# Python bytecode 3.8 (3413)
# Decompiled from: Python 3.8.0 (default, Jan 6 2020, 10:33:50)
# [Clang 10.0.1 (clang-1001.0.46.4)]
# Embedded file name: /xxx/utils.py
# Size of source mod 2**32: 3988 bytes
 
...
 
# okay decompiling ../pyc/utils.cpython-38.pyc
【总结】
python代码,(比如PyInstaller打包)编译后的文件是:*.pyc
(其他类似二进制目标文件:*.pyo)
都可以用:
rocky/python-uncompyle6: A cross-version Python bytecode decompiler
去反编译出来。
安装:
1
2
git clone https://github.com/rocky/python-uncompyle6.git
pip install -e .
用法:
反编译pyc,输出结果到当前终端中
1
uncompyle6 xxx.pyc
举例:
1
uncompyle6 ../pyc/mitmdumpOtherApi.cpython-38.pyc
总体效果:非常完美
  • 优点
    • 代码的结构和函数和变量名,都完美的反编译出来了
      • 和源代码,一模一样
    • 总体来说,有95%的代码,都可以完美的解析出来
  • 问题
    • 代码缩进错误,导致后续代码逻辑不对
    • 部分代码(函数)无法解析,报错
    • 注释无法保留
      • 这个是正常现象
补充:
(1)把反编译结果 输出到py文件:
1
uncompyle6 ../pyc/mitmdumpOtherApi.cpython-38.pyc > ../pyc/mitmdumpOtherApi.py
(2)具体语法详见-h帮助信息:
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
44
45
46
47
48
 uncompyle6 -h
Usage:
  uncompyle6 [OPTIONS]... [ FILE | DIR]...
  uncompyle6 [--help | -h | --V | --version]
Examples:
  uncompyle6      foo.pyc bar.pyc       # decompile foo.pyc, bar.pyc to stdout
  uncompyle6 -o . foo.pyc bar.pyc       # decompile to ./foo.pyc_dis and ./bar.pyc_dis
  uncompyle6 -o /tmp /usr/lib/python1.5 # decompile whole library
Options:
  -o <path>     output decompiled files to this path:
                if multiple input files are decompiled, the common prefix
                is stripped from these names and the remainder appended to
                <path>
                  uncompyle6 -o /tmp bla/fasel.pyc bla/foo.pyc
                    -> /tmp/fasel.pyc_dis, /tmp/foo.pyc_dis
                  uncompyle6 -o /tmp bla/fasel.pyc bar/foo.pyc
                    -> /tmp/bla/fasel.pyc_dis, /tmp/bar/foo.pyc_dis
                  uncompyle6 -o /tmp /usr/lib/python1.5
                    -> /tmp/smtplib.pyc_dis ... /tmp/lib-tk/FixTk.pyc_dis
  --compile | -c <python-file>
                attempts a decompilation after compiling <python-file>
  -d            print timestamps
  -p <integer>  use <integer> number of processes
  -r            recurse directories looking for .pyc and .pyo files
  --fragments   use fragments deparser
  --verify      compare generated source with input byte-code
  --verify-run  compile generated source, run it and check exit code
  --syntax-verify compile generated source
  --linemaps    generated line number correspondencies between byte-code
                and generated source output
  --encoding  <encoding>
                use <encoding> in generated source according to pep-0263
  --help        show this message
 
 
Debugging Options:
  --asm     | -a        include byte-code       (disables --verify)
  --grammar | -g        show matching grammar
  --tree={before|after}
  -t {before|after}     include syntax before (or after) tree transformation
                        (disables --verify)
  --tree++ | -T         add template rules to --tree=before when possible
 
 
Extensions of generated files:
  '.pyc_dis' '.pyo_dis'   successfully decompiled (and verified if --verify)
    + '_unverified'       successfully decompile but --verify failed
    + '_failed'           decompile failed (contact author for enhancement)
【后记】
关于从py打包,比如用py2exe,则也是有破解工具的:
如何还原PyInstaller打包的可执行文件 – LauCyun’s Blog
python – How to decompile an exe file compiled by py2exe? – Stack Overflow
matiasb/unpy2exe: Extract .pyc files from executables created with py2exe
“Extract .pyc files from executables created with py2exe”
所以,这些打包方式没有太安全的。。
本身也很难安全。

转载请注明:在路上 » 【已解决】python的pyc文件的反编译

发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
82 queries in 0.545 seconds, using 22.28MB memory