123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585 |
- # -*- coding: utf-8 -*-
- #-----------------------------------------------------------------------------
- # Copyright (c) 2013-2021, PyInstaller Development Team.
- #
- # Distributed under the terms of the GNU General Public License (version 2
- # or later) with exception for distributing the bootloader.
- #
- # The full license is in the file COPYING.txt, distributed with this software.
- #
- # SPDX-License-Identifier: (GPL-2.0-or-later WITH Bootloader-exception)
- #-----------------------------------------------------------------------------
- import struct
- # ::TODO:: #1920 revert to using pypi version
- import pefile
- from PyInstaller.compat import win32api
- def pefile_check_control_flow_guard(filename):
- """
- Checks if the specified PE file has CFG (Control Flow Guard) enabled.
- Parameters
- ----------
- filename : str
- Path to the PE file to inspect.
- Returns
- ----------
- bool
- True if file is a PE file with CFG enabled. False if CFG is not enabled or if file could not be processed using
- the pefile library.
- """
- try:
- pe = pefile.PE(filename, fast_load=True)
- # https://docs.microsoft.com/en-us/windows/win32/debug/pe-format
- # IMAGE_DLLCHARACTERISTICS_GUARD_CF = 0x4000
- return bool(pe.OPTIONAL_HEADER.DllCharacteristics & 0x4000)
- except Exception:
- return False
- # Ensures no code from the executable is executed.
- LOAD_LIBRARY_AS_DATAFILE = 2
- def getRaw(text):
- """
- Encodes text as UTF-16LE (Microsoft 'Unicode') for use in structs.
- """
- return text.encode('UTF-16LE')
- def decode(pathnm):
- h = win32api.LoadLibraryEx(pathnm, 0, LOAD_LIBRARY_AS_DATAFILE)
- res = win32api.EnumResourceNames(h, pefile.RESOURCE_TYPE['RT_VERSION'])
- if not len(res):
- return None
- data = win32api.LoadResource(h, pefile.RESOURCE_TYPE['RT_VERSION'], res[0])
- vs = VSVersionInfo()
- vs.fromRaw(data)
- win32api.FreeLibrary(h)
- return vs
- def nextDWord(offset):
- """
- Align `offset` to the next 4-byte boundary.
- """
- return ((offset + 3) >> 2) << 2
- class VSVersionInfo:
- """
- WORD wLength; // length of the VS_VERSION_INFO structure
- WORD wValueLength; // length of the Value member
- WORD wType; // 1 means text, 0 means binary
- WCHAR szKey[]; // Contains the Unicode string "VS_VERSION_INFO".
- WORD Padding1[];
- VS_FIXEDFILEINFO Value;
- WORD Padding2[];
- WORD Children[]; // zero or more StringFileInfo or VarFileInfo
- // structures (or both) that are children of the
- // current version structure.
- """
- def __init__(self, ffi=None, kids=None):
- self.ffi = ffi
- self.kids = kids or []
- def fromRaw(self, data):
- i, (sublen, vallen, wType, nm) = parseCommon(data)
- #vallen is length of the ffi, typ is 0, nm is 'VS_VERSION_INFO'.
- i = nextDWord(i)
- # Now a VS_FIXEDFILEINFO
- self.ffi = FixedFileInfo()
- j = self.ffi.fromRaw(data, i)
- i = j
- while i < sublen:
- j = i
- i, (csublen, cvallen, ctyp, nm) = parseCommon(data, i)
- if nm.strip() == 'StringFileInfo':
- sfi = StringFileInfo()
- k = sfi.fromRaw(csublen, cvallen, nm, data, i, j + csublen)
- self.kids.append(sfi)
- i = k
- else:
- vfi = VarFileInfo()
- k = vfi.fromRaw(csublen, cvallen, nm, data, i, j + csublen)
- self.kids.append(vfi)
- i = k
- i = j + csublen
- i = nextDWord(i)
- return i
- def toRaw(self):
- raw_name = getRaw('VS_VERSION_INFO')
- rawffi = self.ffi.toRaw()
- vallen = len(rawffi)
- typ = 0
- sublen = 6 + len(raw_name) + 2
- pad = b''
- if sublen % 4:
- pad = b'\000\000'
- sublen = sublen + len(pad) + vallen
- pad2 = b''
- if sublen % 4:
- pad2 = b'\000\000'
- tmp = b''.join([kid.toRaw() for kid in self.kids])
- sublen = sublen + len(pad2) + len(tmp)
- return struct.pack('hhh', sublen, vallen, typ) + raw_name + b'\000\000' + pad + rawffi + pad2 + tmp
- def __eq__(self, other):
- return self.toRaw() == other
- def __str__(self, indent=''):
- indent = indent + ' '
- tmp = [kid.__str__(indent + ' ') for kid in self.kids]
- tmp = ', \n'.join(tmp)
- return '\n'.join([
- "# UTF-8",
- "#",
- "# For more details about fixed file info 'ffi' see:",
- "# http://msdn.microsoft.com/en-us/library/ms646997.aspx",
- "VSVersionInfo(",
- indent + f"ffi={self.ffi.__str__(indent)},",
- indent + "kids=[",
- tmp,
- indent + "]",
- ")",
- ])
- def __repr__(self):
- return "versioninfo.VSVersionInfo(ffi=%r, kids=%r)" % (self.ffi, self.kids)
- def parseCommon(data, start=0):
- i = start + 6
- (wLength, wValueLength, wType) = struct.unpack('3h', data[start:i])
- i, text = parseUString(data, i, i + wLength)
- return i, (wLength, wValueLength, wType, text)
- def parseUString(data, start, limit):
- i = start
- while i < limit:
- if data[i:i + 2] == b'\000\000':
- break
- i += 2
- text = data[start:i].decode('UTF-16LE')
- i += 2
- return i, text
- class FixedFileInfo:
- """
- DWORD dwSignature; //Contains the value 0xFEEFO4BD
- DWORD dwStrucVersion; // binary version number of this structure.
- // The high-order word of this member contains
- // the major version number, and the low-order
- // word contains the minor version number.
- DWORD dwFileVersionMS; // most significant 32 bits of the file's binary
- // version number
- DWORD dwFileVersionLS; //
- DWORD dwProductVersionMS; // most significant 32 bits of the binary version
- // number of the product with which this file was
- // distributed
- DWORD dwProductVersionLS; //
- DWORD dwFileFlagsMask; // bitmask that specifies the valid bits in
- // dwFileFlags. A bit is valid only if it was
- // defined when the file was created.
- DWORD dwFileFlags; // VS_FF_DEBUG, VS_FF_PATCHED etc.
- DWORD dwFileOS; // VOS_NT, VOS_WINDOWS32 etc.
- DWORD dwFileType; // VFT_APP etc.
- DWORD dwFileSubtype; // 0 unless VFT_DRV or VFT_FONT or VFT_VXD
- DWORD dwFileDateMS;
- DWORD dwFileDateLS;
- """
- def __init__(
- self,
- filevers=(0, 0, 0, 0),
- prodvers=(0, 0, 0, 0),
- mask=0x3f,
- flags=0x0,
- OS=0x40004,
- fileType=0x1,
- subtype=0x0,
- date=(0, 0)
- ):
- self.sig = 0xfeef04bd
- self.strucVersion = 0x10000
- self.fileVersionMS = (filevers[0] << 16) | (filevers[1] & 0xffff)
- self.fileVersionLS = (filevers[2] << 16) | (filevers[3] & 0xffff)
- self.productVersionMS = (prodvers[0] << 16) | (prodvers[1] & 0xffff)
- self.productVersionLS = (prodvers[2] << 16) | (prodvers[3] & 0xffff)
- self.fileFlagsMask = mask
- self.fileFlags = flags
- self.fileOS = OS
- self.fileType = fileType
- self.fileSubtype = subtype
- self.fileDateMS = date[0]
- self.fileDateLS = date[1]
- def fromRaw(self, data, i):
- (
- self.sig,
- self.strucVersion,
- self.fileVersionMS,
- self.fileVersionLS,
- self.productVersionMS,
- self.productVersionLS,
- self.fileFlagsMask,
- self.fileFlags,
- self.fileOS,
- self.fileType,
- self.fileSubtype,
- self.fileDateMS,
- self.fileDateLS,
- ) = struct.unpack('13L', data[i:i + 52])
- return i + 52
- def toRaw(self):
- return struct.pack(
- '13L',
- self.sig,
- self.strucVersion,
- self.fileVersionMS,
- self.fileVersionLS,
- self.productVersionMS,
- self.productVersionLS,
- self.fileFlagsMask,
- self.fileFlags,
- self.fileOS,
- self.fileType,
- self.fileSubtype,
- self.fileDateMS,
- self.fileDateLS,
- )
- def __eq__(self, other):
- return self.toRaw() == other
- def __str__(self, indent=''):
- fv = (
- self.fileVersionMS >> 16, self.fileVersionMS & 0xffff,
- self.fileVersionLS >> 16, self.fileVersionLS & 0xffff,
- ) # yapf: disable
- pv = (
- self.productVersionMS >> 16, self.productVersionMS & 0xffff,
- self.productVersionLS >> 16, self.productVersionLS & 0xffff,
- ) # yapf: disable
- fd = (self.fileDateMS, self.fileDateLS)
- tmp = [
- 'FixedFileInfo(',
- '# filevers and prodvers should be always a tuple with four items: (1, 2, 3, 4)',
- '# Set not needed items to zero 0.',
- 'filevers=%s,' % (fv,),
- 'prodvers=%s,' % (pv,),
- "# Contains a bitmask that specifies the valid bits 'flags'r",
- 'mask=%s,' % hex(self.fileFlagsMask),
- '# Contains a bitmask that specifies the Boolean attributes of the file.',
- 'flags=%s,' % hex(self.fileFlags),
- '# The operating system for which this file was designed.',
- '# 0x4 - NT and there is no need to change it.',
- 'OS=%s,' % hex(self.fileOS),
- '# The general type of file.',
- '# 0x1 - the file is an application.',
- 'fileType=%s,' % hex(self.fileType),
- '# The function of the file.',
- '# 0x0 - the function is not defined for this fileType',
- 'subtype=%s,' % hex(self.fileSubtype),
- '# Creation date and time stamp.',
- 'date=%s' % (fd,),
- ')',
- ]
- return f'\n{indent} '.join(tmp)
- def __repr__(self):
- fv = (
- self.fileVersionMS >> 16, self.fileVersionMS & 0xffff,
- self.fileVersionLS >> 16, self.fileVersionLS & 0xffff,
- ) # yapf: disable
- pv = (
- self.productVersionMS >> 16, self.productVersionMS & 0xffff,
- self.productVersionLS >> 16, self.productVersionLS & 0xffff,
- ) # yapf: disable
- fd = (self.fileDateMS, self.fileDateLS)
- return (
- 'versioninfo.FixedFileInfo(filevers=%r, prodvers=%r, '
- 'mask=0x%x, flags=0x%x, OS=0x%x, '
- 'fileType=%r, subtype=0x%x, date=%r)' %
- (fv, pv, self.fileFlagsMask, self.fileFlags, self.fileOS, self.fileType, self.fileSubtype, fd)
- )
- class StringFileInfo(object):
- """
- WORD wLength; // length of the version resource
- WORD wValueLength; // length of the Value member in the current
- // VS_VERSION_INFO structure
- WORD wType; // 1 means text, 0 means binary
- WCHAR szKey[]; // Contains the Unicode string 'StringFileInfo'.
- WORD Padding[];
- StringTable Children[]; // list of zero or more String structures
- """
- def __init__(self, kids=None):
- self.name = 'StringFileInfo'
- self.kids = kids or []
- def fromRaw(self, sublen, vallen, name, data, i, limit):
- self.name = name
- while i < limit:
- st = StringTable()
- j = st.fromRaw(data, i, limit)
- self.kids.append(st)
- i = j
- return i
- def toRaw(self):
- raw_name = getRaw(self.name)
- vallen = 0
- typ = 1
- sublen = 6 + len(raw_name) + 2
- pad = b''
- if sublen % 4:
- pad = b'\000\000'
- tmp = b''.join([kid.toRaw() for kid in self.kids])
- sublen = sublen + len(pad) + len(tmp)
- return struct.pack('hhh', sublen, vallen, typ) + raw_name + b'\000\000' + pad + tmp
- def __eq__(self, other):
- return self.toRaw() == other
- def __str__(self, indent=''):
- new_indent = indent + ' '
- tmp = ', \n'.join(kid.__str__(new_indent) for kid in self.kids)
- return f'{indent}StringFileInfo(\n{new_indent}[\n{tmp}\n{new_indent}])'
- def __repr__(self):
- return 'versioninfo.StringFileInfo(%r)' % self.kids
- class StringTable:
- """
- WORD wLength;
- WORD wValueLength;
- WORD wType;
- WCHAR szKey[];
- String Children[]; // list of zero or more String structures.
- """
- def __init__(self, name=None, kids=None):
- self.name = name or ''
- self.kids = kids or []
- def fromRaw(self, data, i, limit):
- i, (cpsublen, cpwValueLength, cpwType, self.name) = parseCodePage(data, i, limit) # should be code page junk
- i = nextDWord(i)
- while i < limit:
- ss = StringStruct()
- j = ss.fromRaw(data, i, limit)
- i = j
- self.kids.append(ss)
- i = nextDWord(i)
- return i
- def toRaw(self):
- raw_name = getRaw(self.name)
- vallen = 0
- typ = 1
- sublen = 6 + len(raw_name) + 2
- tmp = []
- for kid in self.kids:
- raw = kid.toRaw()
- if len(raw) % 4:
- raw = raw + b'\000\000'
- tmp.append(raw)
- tmp = b''.join(tmp)
- sublen += len(tmp)
- return struct.pack('hhh', sublen, vallen, typ) + raw_name + b'\000\000' + tmp
- def __eq__(self, other):
- return self.toRaw() == other
- def __str__(self, indent=''):
- new_indent = indent + ' '
- tmp = (',\n' + new_indent).join(str(kid) for kid in self.kids)
- return f"{indent}StringTable(\n{new_indent}'{self.name}',\n{new_indent}[{tmp}])"
- def __repr__(self):
- return 'versioninfo.StringTable(%r, %r)' % (self.name, self.kids)
- class StringStruct:
- """
- WORD wLength;
- WORD wValueLength;
- WORD wType;
- WCHAR szKey[];
- WORD Padding[];
- String Value[];
- """
- def __init__(self, name=None, val=None):
- self.name = name or ''
- self.val = val or ''
- def fromRaw(self, data, i, limit):
- i, (sublen, vallen, typ, self.name) = parseCommon(data, i)
- limit = i + sublen
- i = nextDWord(i)
- i, self.val = parseUString(data, i, limit)
- return i
- def toRaw(self):
- raw_name = getRaw(self.name)
- raw_val = getRaw(self.val)
- # TODO: document the size of vallen and sublen.
- vallen = len(self.val) + 1 # Number of (wide-)characters, not bytes!
- typ = 1
- sublen = 6 + len(raw_name) + 2
- pad = b''
- if sublen % 4:
- pad = b'\000\000'
- sublen = sublen + len(pad) + (vallen * 2)
- return struct.pack('hhh', sublen, vallen, typ) + raw_name + b'\000\000' + pad + raw_val + b'\000\000'
- def __eq__(self, other):
- return self.toRaw() == other
- def __str__(self, indent=''):
- return "StringStruct('%s', '%s')" % (self.name, self.val)
- def __repr__(self):
- return 'versioninfo.StringStruct(%r, %r)' % (self.name, self.val)
- def parseCodePage(data, i, limit):
- i, (sublen, wValueLength, wType, nm) = parseCommon(data, i)
- return i, (sublen, wValueLength, wType, nm)
- class VarFileInfo:
- """
- WORD wLength; // length of the version resource
- WORD wValueLength; // length of the Value member in the current
- // VS_VERSION_INFO structure
- WORD wType; // 1 means text, 0 means binary
- WCHAR szKey[]; // Contains the Unicode string 'VarFileInfo'.
- WORD Padding[];
- Var Children[]; // list of zero or more Var structures
- """
- def __init__(self, kids=None):
- self.kids = kids or []
- def fromRaw(self, sublen, vallen, name, data, i, limit):
- self.sublen = sublen
- self.vallen = vallen
- self.name = name
- i = nextDWord(i)
- while i < limit:
- vs = VarStruct()
- j = vs.fromRaw(data, i, limit)
- self.kids.append(vs)
- i = j
- return i
- def toRaw(self):
- self.vallen = 0
- self.wType = 1
- self.name = 'VarFileInfo'
- raw_name = getRaw(self.name)
- sublen = 6 + len(raw_name) + 2
- pad = b''
- if sublen % 4:
- pad = b'\000\000'
- tmp = b''.join([kid.toRaw() for kid in self.kids])
- self.sublen = sublen + len(pad) + len(tmp)
- return struct.pack('hhh', self.sublen, self.vallen, self.wType) + raw_name + b'\000\000' + pad + tmp
- def __eq__(self, other):
- return self.toRaw() == other
- def __str__(self, indent=''):
- return indent + "VarFileInfo([%s])" % ', '.join(str(kid) for kid in self.kids)
- def __repr__(self):
- return 'versioninfo.VarFileInfo(%r)' % self.kids
- class VarStruct:
- """
- WORD wLength; // length of the version resource
- WORD wValueLength; // length of the Value member in the current
- // VS_VERSION_INFO structure
- WORD wType; // 1 means text, 0 means binary
- WCHAR szKey[]; // Contains the Unicode string 'Translation'
- // or a user-defined key string value
- WORD Padding[]; //
- WORD Value[]; // list of one or more values that are language
- // and code-page identifiers
- """
- def __init__(self, name=None, kids=None):
- self.name = name or ''
- self.kids = kids or []
- def fromRaw(self, data, i, limit):
- i, (self.sublen, self.wValueLength, self.wType, self.name) = parseCommon(data, i)
- i = nextDWord(i)
- for j in range(0, self.wValueLength, 2):
- kid = struct.unpack('h', data[i:i + 2])[0]
- self.kids.append(kid)
- i += 2
- return i
- def toRaw(self):
- self.wValueLength = len(self.kids) * 2
- self.wType = 0
- raw_name = getRaw(self.name)
- sublen = 6 + len(raw_name) + 2
- pad = b''
- if sublen % 4:
- pad = b'\000\000'
- self.sublen = sublen + len(pad) + self.wValueLength
- tmp = b''.join([struct.pack('h', kid) for kid in self.kids])
- return struct.pack('hhh', self.sublen, self.wValueLength, self.wType) + raw_name + b'\000\000' + pad + tmp
- def __eq__(self, other):
- return self.toRaw() == other
- def __str__(self, indent=''):
- return "VarStruct('%s', %r)" % (self.name, self.kids)
- def __repr__(self):
- return 'versioninfo.VarStruct(%r, %r)' % (self.name, self.kids)
- def SetVersion(exenm, versionfile):
- if isinstance(versionfile, VSVersionInfo):
- vs = versionfile
- else:
- # Read and parse the version file. It may have a byte order marker or encoding cookie - respect it if it does.
- from PyInstaller.utils.misc import decode
- with open(versionfile, 'rb') as fp:
- txt = decode(fp.read())
- vs = eval(txt)
- # Remember overlay
- pe = pefile.PE(exenm, fast_load=True)
- overlay_before = pe.get_overlay()
- pe.close()
- hdst = win32api.BeginUpdateResource(exenm, 0)
- win32api.UpdateResource(hdst, pefile.RESOURCE_TYPE['RT_VERSION'], 1, vs.toRaw())
- win32api.EndUpdateResource(hdst, 0)
- if overlay_before:
- # Check if the overlay is still present
- pe = pefile.PE(exenm, fast_load=True)
- overlay_after = pe.get_overlay()
- pe.close()
- # If the update removed the overlay data, re-append it
- if not overlay_after:
- with open(exenm, 'ab') as exef:
- exef.write(overlay_before)
|