折腾:
【未解决】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)
去试试
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
安装:
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文件
uncompyle6 ../pyc/mitmdumpLauncher.cpython-38.pyc > mitmdumpLauncher.py # file ../pyc/mitmdumpLauncher.cpython-38.pyc # Deparsing hit an internal grammar-rule bug
打开反编译后的py代码:
# 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个文件,大概对比看看效果是否也这么好。
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


中间是部分函数
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


反编译输出的文件,顶部和底部分别有提示:
# 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)
都可以用:
去反编译出来。
安装:
git clone https://github.com/rocky/python-uncompyle6.git pip install -e .
用法:
反编译pyc,输出结果到当前终端中
uncompyle6 xxx.pyc
举例:
uncompyle6 ../pyc/mitmdumpOtherApi.cpython-38.pyc
总体效果:非常完美
- 优点
- 代码的结构和函数和变量名,都完美的反编译出来了
- 和源代码,一模一样
- 总体来说,有95%的代码,都可以完美的解析出来
- 问题
- 代码缩进错误,导致后续代码逻辑不对
- 部分代码(函数)无法解析,报错
- 注释无法保留
- 这个是正常现象
补充:
(1)把反编译结果 输出到py文件:
uncompyle6 ../pyc/mitmdumpOtherApi.cpython-38.pyc > ../pyc/mitmdumpOtherApi.py
(2)具体语法详见-h帮助信息:
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文件的反编译
