winutils.py 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. #-----------------------------------------------------------------------------
  2. # Copyright (c) 2013-2023, PyInstaller Development Team.
  3. #
  4. # Distributed under the terms of the GNU General Public License (version 2
  5. # or later) with exception for distributing the bootloader.
  6. #
  7. # The full license is in the file COPYING.txt, distributed with this software.
  8. #
  9. # SPDX-License-Identifier: (GPL-2.0-or-later WITH Bootloader-exception)
  10. #-----------------------------------------------------------------------------
  11. """
  12. Utilities for Windows platform.
  13. """
  14. from PyInstaller import compat
  15. def get_windows_dir():
  16. """
  17. Return the Windows directory, e.g., C:\\Windows.
  18. """
  19. windir = compat.win32api.GetWindowsDirectory()
  20. if not windir:
  21. raise SystemExit("ERROR: Cannot determine Windows directory!")
  22. return windir
  23. def get_system_path():
  24. """
  25. Return the required Windows system paths.
  26. """
  27. sys_dir = compat.win32api.GetSystemDirectory()
  28. # Ensure C:\Windows\system32 and C:\Windows directories are always present in PATH variable.
  29. # C:\Windows\system32 is valid even for 64-bit Windows. Access do DLLs are transparently redirected to
  30. # C:\Windows\syswow64 for 64bit applactions.
  31. # See http://msdn.microsoft.com/en-us/library/aa384187(v=vs.85).aspx
  32. return [sys_dir, get_windows_dir()]
  33. def get_pe_file_machine_type(filename):
  34. """
  35. Return the machine type code from the header of the given PE file.
  36. """
  37. import pefile
  38. with pefile.PE(filename, fast_load=True) as pe:
  39. return pe.FILE_HEADER.Machine
  40. def set_exe_build_timestamp(exe_path, timestamp):
  41. """
  42. Modifies the executable's build timestamp by updating values in the corresponding PE headers.
  43. """
  44. import pefile
  45. with pefile.PE(exe_path, fast_load=True) as pe:
  46. # Manually perform a full load. We need it to load all headers, but specifying it in the constructor triggers
  47. # byte statistics gathering that takes forever with large files. So we try to go around that...
  48. pe.full_load()
  49. # Set build timestamp.
  50. # See: https://0xc0decafe.com/malware-analyst-guide-to-pe-timestamps
  51. timestamp = int(timestamp)
  52. # Set timestamp field in FILE_HEADER
  53. pe.FILE_HEADER.TimeDateStamp = timestamp
  54. # MSVC-compiled executables contain (at least?) one DIRECTORY_ENTRY_DEBUG entry that also contains timestamp
  55. # with same value as set in FILE_HEADER. So modify that as well, as long as it is set.
  56. debug_entries = getattr(pe, 'DIRECTORY_ENTRY_DEBUG', [])
  57. for debug_entry in debug_entries:
  58. if debug_entry.struct.TimeDateStamp:
  59. debug_entry.struct.TimeDateStamp = timestamp
  60. # Generate updated EXE data
  61. data = pe.write()
  62. # Rewrite the exe
  63. with open(exe_path, 'wb') as fp:
  64. fp.write(data)
  65. def update_exe_pe_checksum(exe_path):
  66. """
  67. Compute the executable's PE checksum, and write it to PE headers.
  68. This optional checksum is supposed to protect the executable against corruption but some anti-viral software have
  69. taken to flagging anything without it set correctly as malware. See issue #5579.
  70. """
  71. import pefile
  72. # Compute checksum using our equivalent of the MapFileAndCheckSumW - for large files, it is significantly faster
  73. # than pure-pyton pefile.PE.generate_checksum(). However, it requires the file to be on disk (i.e., cannot operate
  74. # on a memory buffer).
  75. try:
  76. checksum = compute_exe_pe_checksum(exe_path)
  77. except Exception as e:
  78. raise RuntimeError("Failed to compute PE checksum!") from e
  79. # Update the checksum
  80. with pefile.PE(exe_path, fast_load=True) as pe:
  81. pe.OPTIONAL_HEADER.CheckSum = checksum
  82. # Generate updated EXE data
  83. data = pe.write()
  84. # Rewrite the exe
  85. with open(exe_path, 'wb') as fp:
  86. fp.write(data)
  87. def compute_exe_pe_checksum(exe_path):
  88. """
  89. This is a replacement for the MapFileAndCheckSumW function. As noted in MSDN documentation, the Microsoft's
  90. implementation of MapFileAndCheckSumW internally calls its ASCII variant (MapFileAndCheckSumA), and therefore
  91. cannot handle paths that contain characters that are not representable in the current code page.
  92. See: https://docs.microsoft.com/en-us/windows/win32/api/imagehlp/nf-imagehlp-mapfileandchecksumw
  93. This function is based on Wine's implementation of MapFileAndCheckSumW, and due to being based entirely on
  94. the pure widechar-API functions, it is not limited by the current code page.
  95. """
  96. # ctypes bindings for relevant win32 API functions
  97. import ctypes
  98. from ctypes import windll, wintypes
  99. INVALID_HANDLE = wintypes.HANDLE(-1).value
  100. GetLastError = ctypes.windll.kernel32.GetLastError
  101. GetLastError.argtypes = ()
  102. GetLastError.restype = wintypes.DWORD
  103. CloseHandle = windll.kernel32.CloseHandle
  104. CloseHandle.argtypes = (
  105. wintypes.HANDLE, # hObject
  106. )
  107. CloseHandle.restype = wintypes.BOOL
  108. CreateFileW = windll.kernel32.CreateFileW
  109. CreateFileW.argtypes = (
  110. wintypes.LPCWSTR, # lpFileName
  111. wintypes.DWORD, # dwDesiredAccess
  112. wintypes.DWORD, # dwShareMode
  113. wintypes.LPVOID, # lpSecurityAttributes
  114. wintypes.DWORD, # dwCreationDisposition
  115. wintypes.DWORD, # dwFlagsAndAttributes
  116. wintypes.HANDLE, # hTemplateFile
  117. )
  118. CreateFileW.restype = wintypes.HANDLE
  119. CreateFileMappingW = windll.kernel32.CreateFileMappingW
  120. CreateFileMappingW.argtypes = (
  121. wintypes.HANDLE, # hFile
  122. wintypes.LPVOID, # lpSecurityAttributes
  123. wintypes.DWORD, # flProtect
  124. wintypes.DWORD, # dwMaximumSizeHigh
  125. wintypes.DWORD, # dwMaximumSizeLow
  126. wintypes.LPCWSTR, # lpName
  127. )
  128. CreateFileMappingW.restype = wintypes.HANDLE
  129. MapViewOfFile = windll.kernel32.MapViewOfFile
  130. MapViewOfFile.argtypes = (
  131. wintypes.HANDLE, # hFileMappingObject
  132. wintypes.DWORD, # dwDesiredAccess
  133. wintypes.DWORD, # dwFileOffsetHigh
  134. wintypes.DWORD, # dwFileOffsetLow
  135. wintypes.DWORD, # dwNumberOfBytesToMap
  136. )
  137. MapViewOfFile.restype = wintypes.LPVOID
  138. UnmapViewOfFile = windll.kernel32.UnmapViewOfFile
  139. UnmapViewOfFile.argtypes = (
  140. wintypes.LPCVOID, # lpBaseAddress
  141. )
  142. UnmapViewOfFile.restype = wintypes.BOOL
  143. GetFileSizeEx = windll.kernel32.GetFileSizeEx
  144. GetFileSizeEx.argtypes = (
  145. wintypes.HANDLE, # hFile
  146. wintypes.PLARGE_INTEGER, # lpFileSize
  147. )
  148. CheckSumMappedFile = windll.imagehlp.CheckSumMappedFile
  149. CheckSumMappedFile.argtypes = (
  150. wintypes.LPVOID, # BaseAddress
  151. wintypes.DWORD, # FileLength
  152. wintypes.PDWORD, # HeaderSum
  153. wintypes.PDWORD, # CheckSum
  154. )
  155. CheckSumMappedFile.restype = wintypes.LPVOID
  156. # Open file
  157. hFile = CreateFileW(
  158. ctypes.c_wchar_p(exe_path),
  159. 0x80000000, # dwDesiredAccess = GENERIC_READ
  160. 0x00000001 | 0x00000002, # dwShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE,
  161. None, # lpSecurityAttributes = NULL
  162. 3, # dwCreationDisposition = OPEN_EXISTING
  163. 0x80, # dwFlagsAndAttributes = FILE_ATTRIBUTE_NORMAL
  164. None # hTemplateFile = NULL
  165. )
  166. if hFile == INVALID_HANDLE:
  167. err = GetLastError()
  168. raise RuntimeError(f"Failed to open file {exe_path}! Error code: {err}")
  169. # Query file size
  170. fileLength = wintypes.LARGE_INTEGER(0)
  171. if GetFileSizeEx(hFile, fileLength) == 0:
  172. err = GetLastError()
  173. CloseHandle(hFile)
  174. raise RuntimeError(f"Failed to query file size file! Error code: {err}")
  175. fileLength = fileLength.value
  176. if fileLength > (2**32 - 1):
  177. raise RuntimeError("Executable size exceeds maximum allowed executable size on Windows (4 GiB)!")
  178. # Map the file
  179. hMapping = CreateFileMappingW(
  180. hFile,
  181. None, # lpFileMappingAttributes = NULL
  182. 0x02, # flProtect = PAGE_READONLY
  183. 0, # dwMaximumSizeHigh = 0
  184. 0, # dwMaximumSizeLow = 0
  185. None # lpName = NULL
  186. )
  187. if not hMapping:
  188. err = GetLastError()
  189. CloseHandle(hFile)
  190. raise RuntimeError(f"Failed to map file! Error code: {err}")
  191. # Create map view
  192. baseAddress = MapViewOfFile(
  193. hMapping,
  194. 4, # dwDesiredAccess = FILE_MAP_READ
  195. 0, # dwFileOffsetHigh = 0
  196. 0, # dwFileOffsetLow = 0
  197. 0 # dwNumberOfBytesToMap = 0
  198. )
  199. if baseAddress == 0:
  200. err = GetLastError()
  201. CloseHandle(hMapping)
  202. CloseHandle(hFile)
  203. raise RuntimeError(f"Failed to create map view! Error code: {err}")
  204. # Finally, compute the checksum
  205. headerSum = wintypes.DWORD(0)
  206. checkSum = wintypes.DWORD(0)
  207. ret = CheckSumMappedFile(baseAddress, fileLength, ctypes.byref(headerSum), ctypes.byref(checkSum))
  208. if ret is None:
  209. err = GetLastError()
  210. # Cleanup
  211. UnmapViewOfFile(baseAddress)
  212. CloseHandle(hMapping)
  213. CloseHandle(hFile)
  214. if ret is None:
  215. raise RuntimeError(f"CheckSumMappedFile failed! Error code: {err}")
  216. return checkSum.value