起因是笔者有个课,教图形图像学,作业的代码调不出老师的效果,于是就又有了hack的想法,把老师给的用来演示的exe文件反编译。主要是根据这篇文章的方法来的。将 exe 文件反编译成 Python 脚本_py_IA&IM的博客-CSDN博客
第一步
首先,在要反编译的exe文件所在路径,新建一个py脚本,然后把下面的代码复制进去,命名随便,记得住就行,比如我这里起名111.py
#!/usr/bin/python"""PyInstaller Extractor v1.8 (Supports pyinstaller 3.2, 3.1, 3.0, 2.1, 2.0)Author : Extreme CodersE-mail : extremecoders(at)hotmail(dot)comWeb : Date : 28-April-Url : /projects/pyinstallerextractor/For any suggestions, leave a comment on/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 thesame version of python as was used to create the executable.This is just to prevent unmarshalling errors(if any) whileextracting 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, )-------------------------------------------------- First Release- Supports only pyinstaller 2.0Version 1.2 (Sept 12, )-------------------------------------------------- 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, )-------------------------------------------------- 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, )-------------------------------------------------- Fixed a bug when writing pyc files >= version 3.3 (Thanks to Daniello Alto: /Djamana)Version 1.5 (March 1, )-------------------------------------------------- Added support for pyinstaller 3.1 (Thanks to Berwyn Hoyt for reporting)Version 1.6 (Sept 5, )-------------------------------------------------- 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, )-------------------------------------------------- Made the script compatible with python 2.6 (Thanks to Ross for reporting)Version 1.8 (April 28, )-------------------------------------------------- Support for sub-directories in .pyz files (Thanks to Moritz Kroll @ Avira Operations GmbH & Co. KG)""""""Author: In Ming LohEmail: inming.loh@Changes have been made to Version 1.8 (April 28, ).CHANGELOG================================================- Function extractFiles(self, custom_dir=None) has been modfied to allow custom output directory."""import osimport structimport marshalimport zlibimport sysimport impimport typesfrom uuid import uuid4 as uniquenameclass 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, custom_dir=None):print('[*] Beginning extraction...please standby')if custom_dir is None:extractionDir = os.path.join(os.getcwd(), os.path.basename(self.filePath) + '_extracted')if not os.path.exists(extractionDir):os.mkdir(extractionDir)os.chdir(extractionDir)else:if not os.path.exists(custom_dir):os.makedirs(custom_dir)os.chdir(custom_dir)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 Checkwith open(entry.name, 'wb') as f:f.write(data)if entry.typeCmprsData == b'z':self._extractPyz(entry.name)def _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 >= 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')returnarch.close()if __name__ == '__main__':main()
然后在exe所在路径cmd,执行下面这个格式的命令
Python 脚本名 要反编译的exe
例如我的脚本命名111.py,需要编译jiqiren.exe,如下图所示
第二步
执行上面的命令之后,结果如下图所示,并且当前路径出现一个文件夹【jiqiren.exe_extracted】
进入这个文件夹,找到jiqiren这个文件,定义文件类型为.pyc
第三步
安装uncompyle6库,命令行运行安装
pip install uncompyle6
然后在刚刚.pyc的路径cmd执行以下命令,把这个pyc反编译为jiqirenceshi1.py
uncompyle6 jiqiren.pyc>jiqirenceshi1.py
然后,然后有的人可能,编译出来就不对了,嘿嘿。如下图。那就要多一步了。
多一步
用UltraEdit32修改以下刚刚的pyc文件。打开之后是这样然后如下图步骤
走完上图步骤之后,第一行变成一堆20 20 20.....,把前面四个,改成42 0D 0D 0A,然后保存,再做一次文章的第三步,就上面那个uncompyle6那一步,就反编译好了,不过如果文件里面有中文,中文会变成问号。
最后
原理我不懂,前面我忘了中间我忘了后面我忘了,希望这篇文章能帮到你。
如果觉得《Python代码封装的可执行exe文件反编译为Py脚本》对你有帮助,请点赞、收藏,并留下你的观点哦!