一、编写'pyinstxtractor.py'的脚本文件,代码如下:

from __future__ import print_function
import os
import struct
import marshal
import zlib
import sys
import imp
import types
from uuid import uuid4 as uniquename
# 新加入的代码
try:
    from xdis.magics import magics
except ImportError:print("错误: 需使用pip安装xdis模块。")

__version__='2.1'

class CTOCEntry:
    def __init__(self, position, cmprsdDataSize, uncmprsdDataSize, cmprsFlag, typeCmprsData, name):
        self.position = position
        self.cmprsdDataSize = cmprsdDataSize
        self.uncmprsdDataSize = uncmprsdDataSize
        self.cmprsFlag = cmprsFlag
        self.typeCmprsData = typeCmprsData
        self.name = name

class PyInstArchive:
    PYINST20_COOKIE_SIZE = 24           # For pyinstaller 2.0
    PYINST21_COOKIE_SIZE = 24 + 64      # For pyinstaller 2.1+
    MAGIC = b'MEI\014\013\012\013\016'  # Magic number which identifies pyinstaller

    def __init__(self, path):
        self.filePath = path

    def open(self):
        try:
            self.fPtr = open(self.filePath, 'rb')
            self.fileSize = os.stat(self.filePath).st_size
        except:
            print('[*] Error: Could not open {0}'.format(self.filePath))
            return False
        return True

    def close(self):
        try:
            self.fPtr.close()
        except:
            pass

    def checkFile(self):
        print('[*] Processing {0}'.format(self.filePath))
        # Check if it is a 2.0 archive
        self.fPtr.seek(self.fileSize - self.PYINST20_COOKIE_SIZE, os.SEEK_SET)
        magicFromFile = self.fPtr.read(len(self.MAGIC))

        if magicFromFile == self.MAGIC:
            self.pyinstVer = 20     # pyinstaller 2.0
            print('[*] Pyinstaller version: 2.0')
            return True

        # Check for pyinstaller 2.1+ before bailing out
        self.fPtr.seek(self.fileSize - self.PYINST21_COOKIE_SIZE, os.SEEK_SET)
        magicFromFile = self.fPtr.read(len(self.MAGIC))

        if magicFromFile == self.MAGIC:
            print('[*] Pyinstaller version: 2.1+')
            self.pyinstVer = 21     # pyinstaller 2.1+
            return True

        print('[*] Error : Unsupported pyinstaller version or not a pyinstaller archive')
        return False

    def getCArchiveInfo(self):
        try:
            if self.pyinstVer == 20:
                self.fPtr.seek(self.fileSize - self.PYINST20_COOKIE_SIZE, os.SEEK_SET)

                # Read CArchive cookie
                (magic, lengthofPackage, toc, tocLen, self.pyver) = \
                struct.unpack('!8siiii', self.fPtr.read(self.PYINST20_COOKIE_SIZE))

            elif self.pyinstVer == 21:
                self.fPtr.seek(self.fileSize - self.PYINST21_COOKIE_SIZE, os.SEEK_SET)

                # Read CArchive cookie
                (magic, lengthofPackage, toc, tocLen, self.pyver, pylibname) = \
                struct.unpack('!8siiii64s', self.fPtr.read(self.PYINST21_COOKIE_SIZE))

        except:
            print('[*] Error : The file is not a pyinstaller archive')
            return False

        print('[*] Python version: {0}'.format(self.pyver))

        # Overlay is the data appended at the end of the PE
        self.overlaySize = lengthofPackage
        self.overlayPos = self.fileSize - self.overlaySize
        self.tableOfContentsPos = self.overlayPos + toc
        self.tableOfContentsSize = tocLen

        print('[*] Length of package: {0} bytes'.format(self.overlaySize))
        return True

    def parseTOC(self):
        # Go to the table of contents
        self.fPtr.seek(self.tableOfContentsPos, os.SEEK_SET)

        self.tocList = []
        parsedLen = 0

        # Parse table of contents
        while parsedLen < self.tableOfContentsSize:
            (entrySize, ) = struct.unpack('!i', self.fPtr.read(4))
            nameLen = struct.calcsize('!iiiiBc')

            (entryPos, cmprsdDataSize, uncmprsdDataSize, cmprsFlag, typeCmprsData, name) = \
            struct.unpack( \
                '!iiiBc{0}s'.format(entrySize - nameLen), \
                self.fPtr.read(entrySize - 4))

            name = name.decode('utf-8').rstrip('\0')
            if len(name) == 0:
                name = str(uniquename())
                print('[!] Warning: Found an unamed file in CArchive. Using random name {0}'.format(name))

            self.tocList.append( \
                                CTOCEntry(                      \
                                    self.overlayPos + entryPos, \
                                    cmprsdDataSize,             \
                                    uncmprsdDataSize,           \
                                    cmprsFlag,                  \
                                    typeCmprsData,              \
                                    name                        \
                                ))

            parsedLen += entrySize
        print('[*] Found {0} files in CArchive'.format(len(self.tocList)))

    def extractFiles(self):
        print('[*] Beginning extraction...please standby')
        extractionDir = os.path.join(os.getcwd(), os.path.basename(self.filePath) + '_extracted')

        if not os.path.exists(extractionDir):
            os.mkdir(extractionDir)

        os.chdir(extractionDir)
        # 新加入的代码:加入pyc文件的magic部分
        magic=magics[self.pyver/10]
        if self.pyver>=37: # 2.2.1版改进
            pycheader=magic+b'\x00'*12 # 文件头
        else:
            pycheader=magic+b'\x00'*8 # 文件头

        for entry in self.tocList:
            basePath = os.path.dirname(entry.name)
            if basePath != '':
                # Check if path exists, create if not
                if not os.path.exists(basePath):
                    os.makedirs(basePath)

            self.fPtr.seek(entry.position, os.SEEK_SET)
            data = self.fPtr.read(entry.cmprsdDataSize)

            if entry.cmprsFlag == 1:
                data = zlib.decompress(data)
                # Malware may tamper with the uncompressed size
                # Comment out the assertion in such a case
                assert len(data) == entry.uncmprsdDataSize # Sanity Check

            f=open(entry.name, 'wb')
            if entry.typeCmprsData == b's':
                print('[+] Possible entry point: {0}'.format(entry.name))
                f.write(pycheader+data)
                f.close()
            elif entry.typeCmprsData == b'z' or entry.typeCmprsData == b'Z':
                f.write(data)
                f.close()
                self._extractPyz(entry.name)
    # 2.1版加入的代码
    def _checkPyz(self,name):
        with open(name, 'rb') as f:
            pyzMagic = f.read(4)
            return pyzMagic == b'PYZ\0' # Sanity Check

    def _extractPyz(self, name):
        dirName =  name + '_extracted'
        # Create a directory for the contents of the pyz
        if not os.path.exists(dirName):
            os.mkdir(dirName)

        with open(name, 'rb') as f:
            pyzMagic = f.read(4)
            assert pyzMagic == b'PYZ\0' # Sanity Check

            pycHeader = f.read(4) # Python magic value

            if imp.get_magic() != pycHeader:
                print('[!] Warning: The script is running in a different python version than the one used to build the executable')
                print('    Run this script in Python{0} to prevent extraction errors(if any) during unmarshalling'.format(self.pyver))

            (tocPosition, ) = struct.unpack('!i', f.read(4))
            f.seek(tocPosition, os.SEEK_SET)

            try:
                toc = marshal.load(f)
            except:
                print('[!] Unmarshalling FAILED. Cannot extract {0}. Extracting remaining files.'.format(name))
                return

            print('[*] Found {0} files in PYZ archive'.format(len(toc)))

            # From pyinstaller 3.1+ toc is a list of tuples
            if type(toc) == list:
                toc = dict(toc)

            for key in toc.keys():
                (ispkg, pos, length) = toc[key]
                f.seek(pos, os.SEEK_SET)

                fileName = key
                try:
                    # for Python > 3.3 some keys are bytes object some are str object
                    fileName = key.decode('utf-8')
                except:
                    pass

                # Make sure destination directory exists, ensuring we keep inside dirName
                destName = os.path.join(dirName, fileName.replace("..", "__"))
                destDirName = os.path.dirname(destName)
                if not os.path.exists(destDirName):
                    os.makedirs(destDirName)

                try:
                    data = f.read(length)
                    data = zlib.decompress(data)
                except:
                    print('[!] Error: Failed to decompress {0}, probably encrypted. Extracting as is.'.format(fileName))
                    open(destName + '.pyc.encrypted', 'wb').write(data)
                    continue

                with open(destName + '.pyc', 'wb') as pycFile:
                    pycFile.write(pycHeader)      # Write pyc magic
                    pycFile.write(b'\0' * 4)      # Write timestamp

                    if self.pyver>=37: # 2.2.1版改进
                        # 原来的代码: b'\0' * 4
                        pycFile.write(b'\0' * 8)
                    elif self.pyver>=33:
                        pycFile.write(b'\0' * 4) # Size parameter added in Python 3.3
                    pycFile.write(data)

def main():
    if len(sys.argv) < 2:
        print('[*] Usage: pyinstxtractor.py <filename>')

    else:
        arch = PyInstArchive(sys.argv[1])
        if arch.open():
            if arch.checkFile():
                if arch.getCArchiveInfo():
                    arch.parseTOC()
                    arch.extractFiles()
                    arch.close()
                    print('[*] Successfully extracted pyinstaller archive: {0}'.format(sys.argv[1]))
                    print('')
                    print('''You can now use a python decompiler \
on the pyc files within the extracted directory''')
                    # 加入的代码
                    try:
                        import uncompyle6
                    except ImportError:
                        print("Warning: 你可能没有安装pyc反编译器")

                    return
            # 2.1版加入的代码
            elif arch._checkPyz(sys.argv[1]):
                arch.pyver=100 # 默认pyver
                arch._extractPyz(sys.argv[1])

            arch.close()

if __name__ == '__main__':
    main()

二、将上面的py脚本文件和需要反编译的exe文件放在同一文件夹下面

图一

三、进入cmd视图执行命令进入该文件夹下

图二

四、执行命令'python pyinstxtractor.py ping_gongju.exe'进行反编译

图三

注意:这里反编译出来的文件都是pyc文件

五、执行反编译脚本成功后自动生成后缀为extracted的文件夹,打开此文件夹

图四

六、打开文件夹发现一个名为'ping_gongju'的文件

图五

七、给名为'ping_gongju'的文件添加.pyc后缀

7.1 右键ping_gongju选择属性

图六

7.2 添加.pyc并确定

图七

八、下载反编译pyc文件所用的工具包uncompyle6

图八

注意:有successfully字样代表下载成功

九、修改'magic'

9.1 对于从pyinstaller提取出来的pyc文件并不能直接反编译,入口运行类共16字节的 'magic' 和 '时间戳'被去掉了.

9.2 正常编译py文件为pyc文件

9.2.1 先把名为'ping_gongju'的文件放在名为'wenjian'的文件夹里

9.2.2 进入CMD视图执行命令'cd C:\Users\zq\Desktop\wenjianC:\Users\zq\Desktop\wenjian'进入py文件所在的文件夹

9.2.3 执行命令'python -m py_compile ping_gongju.py'将py文件转换为pyc文件

图九

9.2.4 生成一个'pycache'的文件夹,包含转换后的'ping_gongju.cpython-38'的pyc文件

图十

9.2.5 使用文本编辑器UltraEdit分别打开正常编译的pyc文件ping_gongju.cpython-38和通过pyinstxtractor.py反编译的pyc文件ping_gongju

图十一

图十二

9.2.6 通过UltraEdit向pyinstaller提取的文件添加头信息

图十三

图十四

9.2.7 根据正常编译pyc文件替换pyinstaller提取的pyc文件

图十五

9.2.8 替换后结果如下

图十六

十、10 进入pyinstaller提取的pyc文件所在文件夹执行命令'uncompyle6 ping_gongju.pyc > ping_gongju.py'将pyc文件反编译成py文件

图十七

十一、查看转换结果

图十八

图十九