折腾:
【未解决】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文件的反编译