折腾:
【未解决】mac中electron-python用PyInstaller去打包可执行文件
看到打包期间生成的二进制pyc文件了:

打包目的是防止别人看到源代码
而印象中,python的pyc也是可以被反编译的
所以此处顺带去看看:
是否很容易把此处pyc反编译出源码
pyc 反编译
pyc python3 反编译
pyc python3 decompile

结果:
可能有部分代码没有反编成功!
反编译失败。
只支持python2,此处自己是python 3,且是最新的3.8.0

永远等待中。放弃。

python文件反编译失败,鄙人能力有限,后期增强改进!
试了2次,结果:
要么是504错误
要么是:Error: Unknown error:0

- uncompyle6
- pycdc
- unpyc3
figment/unpyc3: Decompiler for Python 3.3 (forked from https://code.google.com/p/unpyc3)
andrew-tavera/unpyc37: Decompiler for Python 3.7 (forked from https://github.com/figment/unpyc3)
去试试
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)
都可以用:
去反编译出来。
安装:
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,则也是有破解工具的:
“Extract .pyc files from executables created with py2exe”
所以,这些打包方式没有太安全的。。
本身也很难安全。
转载请注明:在路上 » 【已解决】python的pyc文件的反编译