当前位置:   article > 正文

将Pyinstaller打包后的exe还原成PYC 反编译到.py的Python源码_pyinstxtractor转换后源码

pyinstxtractor转换后源码

# 将Pyinstaller打包后的exe还原成.py的Python源码 #

当前目录下新建解包文件 pyinstxtractor.py

使用方法:命令行 :

python pyinstxtractor.py xxx.exe
命令完成后当前就会有一个解包文件夹

如图

未加密的就会显示出 各种*.py *.pyc代码 或 *.pyd文件

pyc文件等同与源码 可以还原出详细的注释和源代码,就是明码,无任何加密

pyd文件相当于二进制代码,反编译只能看到汇编的代码,破解难度较大

pyinstxtractor.py代码:如下
  1. """
  2. PyInstaller Extractor v2.0 (Supports pyinstaller 6.6.0, 6.5.0, 6.4.0, 6.3.0, 6.2.0, 6.1.0, 6.0.0, 5.13.2, 5.13.1, 5.13.0, 5.12.0, 5.11.0, 5.10.1, 5.10.0, 5.9.0, 5.8.0, 5.7.0, 5.6.2, 5.6.1, 5.6, 5.5, 5.4.1, 5.4, 5.3, 5.2, 5.1, 5.0.1, 5.0, 4.10, 4.9, 4.8, 4.7, 4.6, 4.5.1, 4.5, 4.4, 4.3, 4.2, 4.1, 4.0, 3.6, 3.5, 3.4, 3.3, 3.2, 3.1, 3.0, 2.1, 2.0)
  3. Author : Extreme Coders
  4. E-mail : extremecoders(at)hotmail(dot)com
  5. Web : https://0xec.blogspot.com
  6. Date : 26-March-2020
  7. Url : https://github.com/extremecoders-re/pyinstxtractor
  8. For any suggestions, leave a comment on
  9. https://forum.tuts4you.com/topic/34455-pyinstaller-extractor/
  10. This script extracts a pyinstaller generated executable file.
  11. Pyinstaller installation is not needed. The script has it all.
  12. For best results, it is recommended to run this script in the
  13. same version of python as was used to create the executable.
  14. This is just to prevent unmarshalling errors(if any) while
  15. extracting the PYZ archive.
  16. Usage : Just copy this script to the directory where your exe resides
  17. and run the script with the exe file name as a parameter
  18. C:\\path\\to\\exe\\>python pyinstxtractor.py <filename>
  19. $ /path/to/exe/python pyinstxtractor.py <filename>
  20. Licensed under GNU General Public License (GPL) v3.
  21. You are free to modify this source.
  22. CHANGELOG
  23. ================================================
  24. Version 1.1 (Jan 28, 2014)
  25. -------------------------------------------------
  26. - First Release
  27. - Supports only pyinstaller 2.0
  28. Version 1.2 (Sept 12, 2015)
  29. -------------------------------------------------
  30. - Added support for pyinstaller 2.1 and 3.0 dev
  31. - Cleaned up code
  32. - Script is now more verbose
  33. - Executable extracted within a dedicated sub-directory
  34. (Support for pyinstaller 3.0 dev is experimental)
  35. Version 1.3 (Dec 12, 2015)
  36. -------------------------------------------------
  37. - Added support for pyinstaller 3.0 final
  38. - Script is compatible with both python 2.x & 3.x (Thanks to Moritz Kroll @ Avira Operations GmbH & Co. KG)
  39. Version 1.4 (Jan 19, 2016)
  40. -------------------------------------------------
  41. - Fixed a bug when writing pyc files >= version 3.3 (Thanks to Daniello Alto: https://github.com/Djamana)
  42. Version 1.5 (March 1, 2016)
  43. -------------------------------------------------
  44. - Added support for pyinstaller 3.1 (Thanks to Berwyn Hoyt for reporting)
  45. Version 1.6 (Sept 5, 2016)
  46. -------------------------------------------------
  47. - Added support for pyinstaller 3.2
  48. - Extractor will use a random name while extracting unnamed files.
  49. - For encrypted pyz archives it will dump the contents as is. Previously, the tool would fail.
  50. Version 1.7 (March 13, 2017)
  51. -------------------------------------------------
  52. - Made the script compatible with python 2.6 (Thanks to Ross for reporting)
  53. Version 1.8 (April 28, 2017)
  54. -------------------------------------------------
  55. - Support for sub-directories in .pyz files (Thanks to Moritz Kroll @ Avira Operations GmbH & Co. KG)
  56. Version 1.9 (November 29, 2017)
  57. -------------------------------------------------
  58. - Added support for pyinstaller 3.3
  59. - Display the scripts which are run at entry (Thanks to Michael Gillespie @ malwarehunterteam for the feature request)
  60. Version 2.0 (March 26, 2020)
  61. -------------------------------------------------
  62. - Project migrated to github
  63. - Supports pyinstaller 3.6
  64. - Added support for Python 3.7, 3.8
  65. - The header of all extracted pyc's are now automatically fixed
  66. """
  67. from __future__ import print_function
  68. import os
  69. import struct
  70. import marshal
  71. import zlib
  72. import sys
  73. from uuid import uuid4 as uniquename
  74. class CTOCEntry:
  75. def __init__(self, position, cmprsdDataSize, uncmprsdDataSize, cmprsFlag, typeCmprsData, name):
  76. self.position = position
  77. self.cmprsdDataSize = cmprsdDataSize
  78. self.uncmprsdDataSize = uncmprsdDataSize
  79. self.cmprsFlag = cmprsFlag
  80. self.typeCmprsData = typeCmprsData
  81. self.name = name
  82. class PyInstArchive:
  83. PYINST20_COOKIE_SIZE = 24 # For pyinstaller 2.0
  84. PYINST21_COOKIE_SIZE = 24 + 64 # For pyinstaller 2.1+
  85. MAGIC = b'MEI\014\013\012\013\016' # Magic number which identifies pyinstaller
  86. def __init__(self, path):
  87. self.filePath = path
  88. self.pycMagic = b'\0' * 4
  89. self.barePycList = [] # List of pyc's whose headers have to be fixed
  90. def open(self):
  91. try:
  92. self.fPtr = open(self.filePath, 'rb')
  93. self.fileSize = os.stat(self.filePath).st_size
  94. except:
  95. print('[!] Error: Could not open {0}'.format(self.filePath))
  96. return False
  97. return True
  98. def close(self):
  99. try:
  100. self.fPtr.close()
  101. except:
  102. pass
  103. def checkFile(self):
  104. print('[+] Processing {0}'.format(self.filePath))
  105. searchChunkSize = 8192
  106. endPos = self.fileSize
  107. self.cookiePos = -1
  108. if endPos < len(self.MAGIC):
  109. print('[!] Error : File is too short or truncated')
  110. return False
  111. while True:
  112. startPos = endPos - searchChunkSize if endPos >= searchChunkSize else 0
  113. chunkSize = endPos - startPos
  114. if chunkSize < len(self.MAGIC):
  115. break
  116. self.fPtr.seek(startPos, os.SEEK_SET)
  117. data = self.fPtr.read(chunkSize)
  118. offs = data.rfind(self.MAGIC)
  119. if offs != -1:
  120. self.cookiePos = startPos + offs
  121. break
  122. endPos = startPos + len(self.MAGIC) - 1
  123. if startPos == 0:
  124. break
  125. if self.cookiePos == -1:
  126. print('[!] Error : Missing cookie, unsupported pyinstaller version or not a pyinstaller archive')
  127. return False
  128. self.fPtr.seek(self.cookiePos + self.PYINST20_COOKIE_SIZE, os.SEEK_SET)
  129. if b'python' in self.fPtr.read(64).lower():
  130. print('[+] Pyinstaller version: 2.1+')
  131. self.pyinstVer = 21 # pyinstaller 2.1+
  132. else:
  133. self.pyinstVer = 20 # pyinstaller 2.0
  134. print('[+] Pyinstaller version: 2.0')
  135. return True
  136. def getCArchiveInfo(self):
  137. try:
  138. if self.pyinstVer == 20:
  139. self.fPtr.seek(self.cookiePos, os.SEEK_SET)
  140. # Read CArchive cookie
  141. (magic, lengthofPackage, toc, tocLen, pyver) = \
  142. struct.unpack('!8siiii', self.fPtr.read(self.PYINST20_COOKIE_SIZE))
  143. elif self.pyinstVer == 21:
  144. self.fPtr.seek(self.cookiePos, os.SEEK_SET)
  145. # Read CArchive cookie
  146. (magic, lengthofPackage, toc, tocLen, pyver, pylibname) = \
  147. struct.unpack('!8sIIii64s', self.fPtr.read(self.PYINST21_COOKIE_SIZE))
  148. except:
  149. print('[!] Error : The file is not a pyinstaller archive')
  150. return False
  151. self.pymaj, self.pymin = (pyver//100, pyver%100) if pyver >= 100 else (pyver//10, pyver%10)
  152. print('[+] Python version: {0}.{1}'.format(self.pymaj, self.pymin))
  153. # Additional data after the cookie
  154. tailBytes = self.fileSize - self.cookiePos - (self.PYINST20_COOKIE_SIZE if self.pyinstVer == 20 else self.PYINST21_COOKIE_SIZE)
  155. # Overlay is the data appended at the end of the PE
  156. self.overlaySize = lengthofPackage + tailBytes
  157. self.overlayPos = self.fileSize - self.overlaySize
  158. self.tableOfContentsPos = self.overlayPos + toc
  159. self.tableOfContentsSize = tocLen
  160. print('[+] Length of package: {0} bytes'.format(lengthofPackage))
  161. return True
  162. def parseTOC(self):
  163. # Go to the table of contents
  164. self.fPtr.seek(self.tableOfContentsPos, os.SEEK_SET)
  165. self.tocList = []
  166. parsedLen = 0
  167. # Parse table of contents
  168. while parsedLen < self.tableOfContentsSize:
  169. (entrySize, ) = struct.unpack('!i', self.fPtr.read(4))
  170. nameLen = struct.calcsize('!iIIIBc')
  171. (entryPos, cmprsdDataSize, uncmprsdDataSize, cmprsFlag, typeCmprsData, name) = \
  172. struct.unpack( \
  173. '!IIIBc{0}s'.format(entrySize - nameLen), \
  174. self.fPtr.read(entrySize - 4))
  175. try:
  176. name = name.decode("utf-8").rstrip("\0")
  177. except UnicodeDecodeError:
  178. newName = str(uniquename())
  179. print('[!] Warning: File name {0} contains invalid bytes. Using random name {1}'.format(name, newName))
  180. name = newName
  181. # Prevent writing outside the extraction directory
  182. if name.startswith("/"):
  183. name = name.lstrip("/")
  184. if len(name) == 0:
  185. name = str(uniquename())
  186. print('[!] Warning: Found an unamed file in CArchive. Using random name {0}'.format(name))
  187. self.tocList.append( \
  188. CTOCEntry( \
  189. self.overlayPos + entryPos, \
  190. cmprsdDataSize, \
  191. uncmprsdDataSize, \
  192. cmprsFlag, \
  193. typeCmprsData, \
  194. name \
  195. ))
  196. parsedLen += entrySize
  197. print('[+] Found {0} files in CArchive'.format(len(self.tocList)))
  198. def _writeRawData(self, filepath, data):
  199. nm = filepath.replace('\\', os.path.sep).replace('/', os.path.sep).replace('..', '__')
  200. nmDir = os.path.dirname(nm)
  201. if nmDir != '' and not os.path.exists(nmDir): # Check if path exists, create if not
  202. os.makedirs(nmDir)
  203. with open(nm, 'wb') as f:
  204. f.write(data)
  205. def extractFiles(self):
  206. print('[+] Beginning extraction...please standby')
  207. extractionDir = os.path.join(os.getcwd(), os.path.basename(self.filePath) + '_extracted')
  208. if not os.path.exists(extractionDir):
  209. os.mkdir(extractionDir)
  210. os.chdir(extractionDir)
  211. for entry in self.tocList:
  212. self.fPtr.seek(entry.position, os.SEEK_SET)
  213. data = self.fPtr.read(entry.cmprsdDataSize)
  214. if entry.cmprsFlag == 1:
  215. try:
  216. data = zlib.decompress(data)
  217. except zlib.error:
  218. print('[!] Error : Failed to decompress {0}'.format(entry.name))
  219. continue
  220. # Malware may tamper with the uncompressed size
  221. # Comment out the assertion in such a case
  222. assert len(data) == entry.uncmprsdDataSize # Sanity Check
  223. if entry.typeCmprsData == b'd' or entry.typeCmprsData == b'o':
  224. # d -> ARCHIVE_ITEM_DEPENDENCY
  225. # o -> ARCHIVE_ITEM_RUNTIME_OPTION
  226. # These are runtime options, not files
  227. continue
  228. basePath = os.path.dirname(entry.name)
  229. if basePath != '':
  230. # Check if path exists, create if not
  231. if not os.path.exists(basePath):
  232. os.makedirs(basePath)
  233. if entry.typeCmprsData == b's':
  234. # s -> ARCHIVE_ITEM_PYSOURCE
  235. # Entry point are expected to be python scripts
  236. print('[+] Possible entry point: {0}.pyc'.format(entry.name))
  237. if self.pycMagic == b'\0' * 4:
  238. # if we don't have the pyc header yet, fix them in a later pass
  239. self.barePycList.append(entry.name + '.pyc')
  240. self._writePyc(entry.name + '.pyc', data)
  241. elif entry.typeCmprsData == b'M' or entry.typeCmprsData == b'm':
  242. # M -> ARCHIVE_ITEM_PYPACKAGE
  243. # m -> ARCHIVE_ITEM_PYMODULE
  244. # packages and modules are pyc files with their header intact
  245. # From PyInstaller 5.3 and above pyc headers are no longer stored
  246. # https://github.com/pyinstaller/pyinstaller/commit/a97fdf
  247. if data[2:4] == b'\r\n':
  248. # < pyinstaller 5.3
  249. if self.pycMagic == b'\0' * 4:
  250. self.pycMagic = data[0:4]
  251. self._writeRawData(entry.name + '.pyc', data)
  252. else:
  253. # >= pyinstaller 5.3
  254. if self.pycMagic == b'\0' * 4:
  255. # if we don't have the pyc header yet, fix them in a later pass
  256. self.barePycList.append(entry.name + '.pyc')
  257. self._writePyc(entry.name + '.pyc', data)
  258. else:
  259. self._writeRawData(entry.name, data)
  260. if entry.typeCmprsData == b'z' or entry.typeCmprsData == b'Z':
  261. self._extractPyz(entry.name)
  262. # Fix bare pyc's if any
  263. self._fixBarePycs()
  264. def _fixBarePycs(self):
  265. for pycFile in self.barePycList:
  266. with open(pycFile, 'r+b') as pycFile:
  267. # Overwrite the first four bytes
  268. pycFile.write(self.pycMagic)
  269. def _writePyc(self, filename, data):
  270. with open(filename, 'wb') as pycFile:
  271. pycFile.write(self.pycMagic) # pyc magic
  272. if self.pymaj >= 3 and self.pymin >= 7: # PEP 552 -- Deterministic pycs
  273. pycFile.write(b'\0' * 4) # Bitfield
  274. pycFile.write(b'\0' * 8) # (Timestamp + size) || hash
  275. else:
  276. pycFile.write(b'\0' * 4) # Timestamp
  277. if self.pymaj >= 3 and self.pymin >= 3:
  278. pycFile.write(b'\0' * 4) # Size parameter added in Python 3.3
  279. pycFile.write(data)
  280. def _extractPyz(self, name):
  281. dirName = name + '_extracted'
  282. # Create a directory for the contents of the pyz
  283. if not os.path.exists(dirName):
  284. os.mkdir(dirName)
  285. with open(name, 'rb') as f:
  286. pyzMagic = f.read(4)
  287. assert pyzMagic == b'PYZ\0' # Sanity Check
  288. pyzPycMagic = f.read(4) # Python magic value
  289. if self.pycMagic == b'\0' * 4:
  290. self.pycMagic = pyzPycMagic
  291. elif self.pycMagic != pyzPycMagic:
  292. self.pycMagic = pyzPycMagic
  293. print('[!] Warning: pyc magic of files inside PYZ archive are different from those in CArchive')
  294. # Skip PYZ extraction if not running under the same python version
  295. if self.pymaj != sys.version_info.major or self.pymin != sys.version_info.minor:
  296. print('[!] Warning: This script is running in a different Python version than the one used to build the executable.')
  297. print('[!] Please run this script in Python {0}.{1} to prevent extraction errors during unmarshalling'.format(self.pymaj, self.pymin))
  298. print('[!] Skipping pyz extraction')
  299. return
  300. (tocPosition, ) = struct.unpack('!i', f.read(4))
  301. f.seek(tocPosition, os.SEEK_SET)
  302. try:
  303. toc = marshal.load(f)
  304. except:
  305. print('[!] Unmarshalling FAILED. Cannot extract {0}. Extracting remaining files.'.format(name))
  306. return
  307. print('[+] Found {0} files in PYZ archive'.format(len(toc)))
  308. # From pyinstaller 3.1+ toc is a list of tuples
  309. if type(toc) == list:
  310. toc = dict(toc)
  311. for key in toc.keys():
  312. (ispkg, pos, length) = toc[key]
  313. f.seek(pos, os.SEEK_SET)
  314. fileName = key
  315. try:
  316. # for Python > 3.3 some keys are bytes object some are str object
  317. fileName = fileName.decode('utf-8')
  318. except:
  319. pass
  320. # Prevent writing outside dirName
  321. fileName = fileName.replace('..', '__').replace('.', os.path.sep)
  322. if ispkg == 1:
  323. filePath = os.path.join(dirName, fileName, '__init__.pyc')
  324. else:
  325. filePath = os.path.join(dirName, fileName + '.pyc')
  326. fileDir = os.path.dirname(filePath)
  327. if not os.path.exists(fileDir):
  328. os.makedirs(fileDir)
  329. try:
  330. data = f.read(length)
  331. data = zlib.decompress(data)
  332. except:
  333. print('[!] Error: Failed to decompress {0}, probably encrypted. Extracting as is.'.format(filePath))
  334. open(filePath + '.encrypted', 'wb').write(data)
  335. else:
  336. self._writePyc(filePath, data)
  337. def main():
  338. if len(sys.argv) < 2:
  339. print('[+] Usage: pyinstxtractor.py <filename>')
  340. else:
  341. arch = PyInstArchive(sys.argv[1])
  342. if arch.open():
  343. if arch.checkFile():
  344. if arch.getCArchiveInfo():
  345. arch.parseTOC()
  346. arch.extractFiles()
  347. arch.close()
  348. print('[+] Successfully extracted pyinstaller archive: {0}'.format(sys.argv[1]))
  349. print('')
  350. print('You can now use a python decompiler on the pyc files within the extracted directory')
  351. return
  352. arch.close()
  353. if __name__ == '__main__':
  354. main()
'
运行

PYC文件解密方法:

用 uncompyle6 反编译

命令行:

  1. # 安装反编译工具
  2. pip install uncompyle6
  3. # 反编译到当前目录.下
  4. uncompyle6 test.pyc -o .

打开文件后直接看到源码

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号