一、编写'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文件¶

十一、查看转换结果¶

