编写历程

使用网上的pyinstxtractor.py提取PyInstaller生成的exe文件, 发现无法用uncompyle6反编译提取的pyc文件, 报错。
对比原先的pyc文件, 和提取的文件, 发现:

提取的文件内容是一样的, 但文件头和原先的pyc文件不一样。(注意: 上图中数据e3是pyc文件内容部分的开始, 其前面是文件头)

然后, 对比PYZ_00…pyz_extracted文件夹里的文件, 也发现文件头不一样,
说明网上的pyinstxtractor.py有bug
仔细分析后, 重写pyinstxtractor.py, 代码如下:
(注意修改的部分, 和注释)

源代码

# coding:utf-8
# 改编自网上的pyinstxtractor.py
r"""
PyInstaller Extractor v2.1 (Supports pyinstaller 3.3+, 3.2, 3.1, 3.0, 2.1, 2.0)
Author : Extreme Coders
E-mail : extremecoders(at)hotmail(dot)com
Web    : https://0xec.blogspot.com
Date   : 29-November-2017
Url    : https://sourceforge.net/projects/pyinstallerextractor/For any suggestions, leave a comment on
https://forum.tuts4you.com/topic/34455-pyinstaller-extractor/This script extracts a pyinstaller generated executable file.
Pyinstaller installation is not needed. The script has it all.For best results, it is recommended to run this script in the
same version of python as was used to create the executable.
This is just to prevent unmarshalling errors(if any) while
extracting the PYZ archive.Usage : Just copy this script to the directory where your exe residesand run the script with the exe file name as a parameterC:\path\to\exe\>python pyinstxtractor.py <filename>
$ /path/to/exe/python pyinstxtractor.py <filename>Licensed under GNU General Public License (GPL) v3.
You are free to modify this source.CHANGELOG
================================================Version 1.1 (Jan 28, 2014)
-------------------------------------------------
- First Release
- Supports only pyinstaller 2.0Version 1.2 (Sept 12, 2015)
-------------------------------------------------
- Added support for pyinstaller 2.1 and 3.0 dev
- Cleaned up code
- Script is now more verbose
- Executable extracted within a dedicated sub-directory(Support for pyinstaller 3.0 dev is experimental)Version 1.3 (Dec 12, 2015)
-------------------------------------------------
- Added support for pyinstaller 3.0 final
- Script is compatible with both python 2.x & 3.x (Thanks to Moritz Kroll @ Avira Operations GmbH & Co. KG)Version 1.4 (Jan 19, 2016)
-------------------------------------------------
- Fixed a bug when writing pyc files >= version 3.3 (Thanks to Daniello Alto: https://github.com/Djamana)Version 1.5 (March 1, 2016)
-------------------------------------------------
- Added support for pyinstaller 3.1 (Thanks to Berwyn Hoyt for reporting)Version 1.6 (Sept 5, 2016)
-------------------------------------------------
- Added support for pyinstaller 3.2
- Extractor will use a random name while extracting unnamed files.
- For encrypted pyz archives it will dump the contents as is. Previously, the tool would fail.Version 1.7 (March 13, 2017)
-------------------------------------------------
- Made the script compatible with python 2.6 (Thanks to Ross for reporting)Version 1.8 (April 28, 2017)
-------------------------------------------------
- Support for sub-directories in .pyz files (Thanks to Moritz Kroll @ Avira Operations GmbH & Co. KG)Version 1.9 (November 29, 2017)
-------------------------------------------------
- Added support for pyinstaller 3.3
- Display the scripts which are run at entry (Thanks to Michael Gillespie @ malwarehunterteam for the feature request)***** 版本 2.0 (2020-12-13) *****
- 修复了提取pyc文件的bug。
***** 版本 2.1.1 (2021-2-23) *****
- 修复了从PYZ中提取pyc文件的bug, 兼容几乎所有Python3版本; 可直接提取pyz文件。
***** 版本 2.2 (2022-7-25) *****
- 兼容Python 3.10。
"""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.2'class CTOCEntry:def __init__(self, position, cmprsdDataSize, uncmprsdDataSize, cmprsFlag, typeCmprsData, name):self.position = positionself.cmprsdDataSize = cmprsdDataSizeself.uncmprsdDataSize = uncmprsdDataSizeself.cmprsFlag = cmprsFlagself.typeCmprsData = typeCmprsDataself.name = nameclass PyInstArchive:PYINST20_COOKIE_SIZE = 24           # For pyinstaller 2.0PYINST21_COOKIE_SIZE = 24 + 64      # For pyinstaller 2.1+MAGIC = b'MEI\014\013\012\013\016'  # Magic number which identifies pyinstallerdef __init__(self, path):self.filePath = pathdef open(self):try:self.fPtr = open(self.filePath, 'rb')self.fileSize = os.stat(self.filePath).st_sizeexcept:print('[*] Error: Could not open {0}'.format(self.filePath))return Falsereturn Truedef close(self):try:self.fPtr.close()except:passdef checkFile(self):print('[*] Processing {0}'.format(self.filePath))# Check if it is a 2.0 archiveself.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.0print('[*] Pyinstaller version: 2.0')return True# Check for pyinstaller 2.1+ before bailing outself.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 Trueprint('[*] Error : Unsupported pyinstaller version or not a pyinstaller archive')return Falsedef 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 Falseprint('[*] Python version: {0}'.format(self.pyver))# Overlay is the data appended at the end of the PEself.overlaySize = lengthofPackageself.overlayPos = self.fileSize - self.overlaySizeself.tableOfContentsPos = self.overlayPos + tocself.tableOfContentsSize = tocLenprint('[*] Length of package: {0} bytes'.format(self.overlaySize))return Truedef parseTOC(self):# Go to the table of contentsself.fPtr.seek(self.tableOfContentsPos, os.SEEK_SET)self.tocList = []parsedLen = 0# Parse table of contentswhile 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 += entrySizeprint('[*] 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部分pyverstr=str(self.pyver)if len(pyverstr)==2:magic=magics["%s.%s"%(pyverstr[0],pyverstr[1:])]else:magic=magics["%s.%s"%(pyverstr[0],pyverstr[2:])] # 兼容Python 3.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 notif 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 caseassert len(data) == entry.uncmprsdDataSize # Sanity Checkf=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 Checkdef _extractPyz(self, name):dirName =  name + '_extracted'# Create a directory for the contents of the pyzif not os.path.exists(dirName):os.mkdir(dirName)with open(name, 'rb') as f:pyzMagic = f.read(4)assert pyzMagic == b'PYZ\0' # Sanity CheckpycHeader = f.read(4) # Python magic valueif 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))returnprint('[*] Found {0} files in PYZ archive'.format(len(toc)))# From pyinstaller 3.1+ toc is a list of tuplesif type(toc) == list:toc = dict(toc)for key in toc.keys():(ispkg, pos, length) = toc[key]f.seek(pos, os.SEEK_SET)fileName = keytry:# for Python > 3.3 some keys are bytes object some are str objectfileName = key.decode('utf-8')except:pass# Make sure destination directory exists, ensuring we keep inside dirNamedestName = 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)continuewith open(destName + '.pyc', 'wb') as pycFile:pycFile.write(pycHeader)      # Write pyc magicpycFile.write(b'\0' * 4)      # Write timestampif self.pyver>=37: # 2.2.1版改进# 原来的代码: b'\0' * 4pycFile.write(b'\0' * 8)elif self.pyver>=33:pycFile.write(b'\0' * 4) # Size parameter added in Python 3.3pycFile.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 uncompyle6except ImportError:print("Warning: 你可能没有安装pyc反编译器")return# 2.1版加入的代码elif arch._checkPyz(sys.argv[1]):arch.pyver=100 # 默认pyverarch._extractPyz(sys.argv[1])arch.close()if __name__ == '__main__':main()

uncompyle6工具的使用

uncompyle6是反编译pyc文件的一个Python库。
在Windows中,按Win+R键,输入cmd,启动命令提示符。
先输入命令回车:pip install uncompyle6
然后输入命令:python -m uncompyle6 文件名.pyc,等待一段时间后,就能看到反编译的输出结果了。
另外,使用命令python -m uncompyle6 文件名.pyc > 输出文件名.py 可以将反编译的输出结果写入特定的py文件里。

如果不想做这些繁琐的步骤,作者自己编写了一个调用uncompyle6的脚本,双击可以直接运行:

import sys,os,traceback
import uncompyle6.bin.uncompile as uncompiler
__version__='2.0.1'def run_uncompile(filename):flag=False # 监测sys.stderr中有无警告或错误消息_w=sys.stderr.writedef w(*arg,**kw):nonlocal flagflag=True_w(*arg,**kw)def start_check(): # 开始监测sys.stderr.write=wdef end_check():  # 停止监测sys.stderr.write=_wtofilename=filename[:-1]if os.path.isfile(tofilename):result=input("文件%s已存在,要替换它吗? "%tofilename)if not result.lower().startswith('y'):returntry:sys.stdout=open(tofilename,"w",encoding="utf-8")sys.argv[1]=filenamestart_check()uncompiler.main_bin()except Exception:end_check()print("文件%s反编译失败,错误消息详见%s"% (filename,tofilename),file=sys.stderr)#traceback.print_exc()traceback.print_exc(file=sys.stdout)else:end_check()if not flag:print("文件%s反编译成功"%filename,file=sys.stderr)else:print("文件%s反编译失败, 有警告或错误"%filename,file=sys.stderr)print("按Enter键继续...",end='',file=sys.stderr)input()finally:sys.stdout.close()if __name__=="__main__":try:if len(sys.argv)>1:files=sys.argv[1:]sys.argv[0]=uncompiler.__file__sys.argv[1:]=['']for file in files:if not file.endswith(".pyc"):print("警告: %s 可能不是pyc文件"%file,file=sys.stderr)run_uncompile(file)else:file=input("拖曳文件到本窗口,然后按回车 (或输入文件名):\n").strip('"')sys.argv[0]=uncompiler.__file__sys.argv.append('')run_uncompile(file)finally:sys.stdout=sys.__stdout__

结语
编写这个pyinstxtractor.py的目的, 是破解 - 木兰编程语言 …
功夫不负有心人, 我用uncompyle6工具成功提取了源代码(在这里:ulang - Gitcode)。
本文结束,以上是作者告诉后人的经验。

pyinstxtractor.py 的改进 - 反编译pyinstaller生成exe的工具相关推荐

  1. exe反编译_反编译Python生成exe软件(Py3-polySML)

    反编译对象为一篇文献上的软件,反编译只是为了了解一些源代码的逻辑. 过程参考文章:python3.7.4反编译生成的.exe 反编译对象:polySML 此对象为python打包,且未进行加密加壳软件 ...

  2. 超详细Pyinstaller打包exe+反编译Pyinstaller打包的exe教程+防止反编译教程

    在这里分享一些技巧和经验给大家.辛苦撰文分享,转载或引用请保留本文作者信息及文章链接. 作者的环境: win7+python3.5(anaconda3) 理论上,win7及以上的系统和python任意 ...

  3. 反编译python 生成的exe源码

    反编译python 生成的exe源码 记录反编译exe工具使用 工具准备 – pyinstxtractor.py – uncompyle6 – sublime Text(或者其他的二进制编辑工具) 一 ...

  4. Python:pyinstaller如何将一个项目的.py和相关资源文件打包生成.exe文件

    前言 写这篇博文真属于意外,因为小编不是以学术为目的的,而仅仅是以讨女朋友喜欢为目的,所以文中略有狗粮.撇开这些,相信大家找到为了找到一个可行方案已经寻寻觅觅良久,本文或许便是你的"真命天子 ...

  5. pyinstaller 生成exe之后不报毒的终极方法(亲测可用)

    网上看到很多pyinstaller生成exe报毒的实例,为了防止被杀,什么添加图标,或者在语句中去除-w,亲测结果都是一样.下面的方法可以完全不报毒,但是原理未知,知道的小伙伴请告知. 将网上流传的 ...

  6. python生成exe运行慢_利用pyinstaller生成exe文件碰到的一些问题及解决方法

    在"开源图像标注工具labelme的安装使用及汉化"这篇博客中,使用pyinstaller将labelme项目的入口python文件main.py打包,生成了main.exe文件, ...

  7. 利用pyinstaller生成exe文件碰到的一些问题及解决方法

    在"开源图像标注工具labelme的安装使用及汉化"这篇博客中,使用pyinstaller将labelme项目的入口python文件main.py打包,生成了main.exe文件, ...

  8. 成功解决pyinstaller生成exe缺少各种包的问题

    成功解决pyinstaller生成exe缺少各种包的问题 目录 解决问题 解决方法 解决问题 成功解决pyinstaller生成exe缺少各种包的问题 解决方法 将缺少包的路径,如找不到自定义的yol ...

  9. Python反编译pyinstaller或py2exe生成的exe可执行文件,获得源码

    目录 一.从exe文件中抽取pyc文件 二.将pyc文件反编译为py源码文件 三.更正.pyc文件的头信息 一.从exe文件中抽取pyc文件 直接到下列网址将代码clone下来: GitHub - c ...

最新文章

  1. 简单mysql 查询_MySQL简单查询详解
  2. Ubuntu下SSH设置
  3. javascript动画函数封装(升级版)
  4. spring依赖注入原理(转载)
  5. java面向对相取钱存钱_java面向对象(银行存款业务无客户)
  6. 4月30日火车票今日开售,多个线路“一秒没”
  7. pyinstaller 用法
  8. 搜索数据android ui,搜索概览  |  Android 开发者  |  Android Developers
  9. Gentoo 教程:基本系统安装
  10. 《推荐系统实践》笔记及自己的理解(一)
  11. Hive的nvl、coalesce、if、nvl2
  12. jQuery超详细入门教程
  13. 维吉尼亚密码及其破解
  14. 洁静,澳大利亚,昨天下午
  15. ajax 泛微oa表单js_泛微oa流程表单二次开发新人注意事项,
  16. easypoi needmerge失效_easyPoi报表导出
  17. 公司地址变更时公司可以开具发票吗?公司地址变更有哪些资料?
  18. 焦耳小偷-Joule_thief原理分析
  19. python 爬虫 抓取网站img图片
  20. 三校生计算机网络技术,辽宁省三校生高考复习题-[网络]第1章 计算机网络概述...

热门文章

  1. Qt6-在线获取和安装
  2. [转载]Python中包装(wrapping)与代理(delegation)
  3. Linux下Nginx的启动、停止等命令
  4. sql server 获取本机的ip地址
  5. 备库ORA-00313 ORA-00312 ORA-27037
  6. (转)如何有效地管理好技术团队?
  7. Spring @Resource
  8. Zookeeper 的基本使用
  9. ULONG64转CString
  10. 电机学Matlab仿真代码