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

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

Python crifan 10783浏览 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
去试试
 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)
都可以用:
rocky/python-uncompyle6: A cross-version Python bytecode decompiler
去反编译出来。
安装:
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,则也是有破解工具的:
如何还原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.172 seconds, using 22.20MB memory