Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-pefile for openSUSE:Factory checked in at 2023-04-04 21:16:53 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-pefile (Old) and /work/SRC/openSUSE:Factory/.python-pefile.new.19717 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-pefile" Tue Apr 4 21:16:53 2023 rev:7 rq:1076772 version:2023.2.7 Changes: -------- --- /work/SRC/openSUSE:Factory/python-pefile/python-pefile.changes 2022-06-07 11:45:24.899253698 +0200 +++ /work/SRC/openSUSE:Factory/.python-pefile.new.19717/python-pefile.changes 2023-04-04 21:16:54.747848472 +0200 @@ -1,0 +2,13 @@ +Sun Mar 26 19:55:48 UTC 2023 - Dirk Müller <dmuel...@suse.com> + +- update to 2023.2.7: + * accept dot in valid charset for name + * Remove future from dependencies + * Add machine types + * Incorporate PEP 238 and PEP 3120 + * Generate GUID fields of CV_INFO_PDB70 readable by Python + * Dynamic relocations support + * Add Export Hash Method + * Loosen export symbol validation + +------------------------------------------------------------------- Old: ---- pefile-2022.5.30.tar.gz New: ---- pefile-2023.2.7.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-pefile.spec ++++++ --- /var/tmp/diff_new_pack.BF2zup/_old 2023-04-04 21:16:55.855855536 +0200 +++ /var/tmp/diff_new_pack.BF2zup/_new 2023-04-04 21:16:55.863855587 +0200 @@ -1,7 +1,7 @@ # # spec file for package python-pefile # -# Copyright (c) 2022 SUSE LLC +# Copyright (c) 2023 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -20,7 +20,7 @@ %define skip_python2 1 Name: python-pefile -Version: 2022.5.30 +Version: 2023.2.7 Release: 0 Summary: A python module to work with PE (pertable executable) files License: BSD-3-Clause @@ -30,10 +30,6 @@ BuildRequires: %{python_module setuptools} BuildRequires: fdupes BuildRequires: python-rpm-macros -Requires: python-future -# SECTION test requirements -BuildRequires: %{python_module future} -# /SECTION BuildArch: noarch %python_subpackages ++++++ pefile-2022.5.30.tar.gz -> pefile-2023.2.7.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pefile-2022.5.30/.gitignore new/pefile-2023.2.7/.gitignore --- old/pefile-2022.5.30/.gitignore 2021-09-03 16:11:44.000000000 +0200 +++ new/pefile-2023.2.7/.gitignore 2023-01-07 20:42:11.000000000 +0100 @@ -21,3 +21,7 @@ /dist /pefile.egg-info .DS_Store +pefile.code-workspace +.vscode/* +pefile.ipynb +.ipynb_checkpoints/* diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pefile-2022.5.30/.travis.yml new/pefile-2023.2.7/.travis.yml --- old/pefile-2022.5.30/.travis.yml 2021-05-13 20:15:20.000000000 +0200 +++ new/pefile-2023.2.7/.travis.yml 1970-01-01 01:00:00.000000000 +0100 @@ -1,34 +0,0 @@ -language: python3 -cache: pip3 -matrix: - include: - - python: 3.8 - - python: 3.9 - dist: xenial - - python: nightly - dist: xenial - - python: pypy3 -before_install: -- if test -n "$encrypted_b0257fbf4222_key" && test -n "$encrypted_b0257fbf4222_iv"; then - openssl aes-256-cbc -K $encrypted_b0257fbf4222_key -iv $encrypted_b0257fbf4222_iv -in tests/test_data.tar.bz2.enc -out tests/test_data.tar.bz2 -d; - tar jxf tests/test_data.tar.bz2 -C tests; - fi -install: -- pip install -U pip pytest-cov codecov -- pip install -I -e . -script: -- chmod +x ./run_tests.py ./setup.py -- python setup.py test -- python setup.py build -- python setup.py sdist -- python setup.py bdist -- python setup.py install -- coverage run --source=pefile --omit="*_test*,*__init__*,*test_lib*" ./run_tests.py -after_success: -- coveralls --verbose -addons: - artifacts: - debug: true - paths: - - $(git ls-files -o | grep .txt | grep -v pefile.egg-info | tr "\n" ":") - - codecov diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pefile-2022.5.30/LICENSE new/pefile-2023.2.7/LICENSE --- old/pefile-2022.5.30/LICENSE 2022-05-30 15:25:22.000000000 +0200 +++ new/pefile-2023.2.7/LICENSE 2023-01-07 20:12:32.000000000 +0100 @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2004-2022 Ero Carrera +Copyright (c) 2004-2023 Ero Carrera Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pefile-2022.5.30/PKG-INFO new/pefile-2023.2.7/PKG-INFO --- old/pefile-2022.5.30/PKG-INFO 2022-05-30 16:07:24.178253700 +0200 +++ new/pefile-2023.2.7/PKG-INFO 2023-02-07 13:19:22.005508400 +0100 @@ -1,9 +1,9 @@ Metadata-Version: 2.1 Name: pefile -Version: 2022.5.30 +Version: 2023.2.7 Summary: Python PE parsing module Home-page: https://github.com/erocarrera/pefile -Download-URL: https://github.com/erocarrera/pefile/releases/download/v2022.5.30/pefile-2022.5.30.tar.gz +Download-URL: https://github.com/erocarrera/pefile/releases/download/v2023.2.7/pefile-2023.2.7.tar.gz Author: Ero Carrera Author-email: ero.carr...@gmail.com License: MIT @@ -31,4 +31,4 @@ standard use. To the best of my knowledge most of the abuse is handled gracefully. -Copyright (c) 2005-2022 Ero Carrera <ero.carr...@gmail.com> +Copyright (c) 2005-2023 Ero Carrera <ero.carr...@gmail.com> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pefile-2022.5.30/ordlookup/__init__.py new/pefile-2023.2.7/ordlookup/__init__.py --- old/pefile-2022.5.30/ordlookup/__init__.py 2021-09-03 16:05:28.000000000 +0200 +++ new/pefile-2023.2.7/ordlookup/__init__.py 2023-01-07 20:14:04.000000000 +0100 @@ -1,5 +1,3 @@ -from __future__ import absolute_import -import sys from . import ws2_32 from . import oleaut32 @@ -15,18 +13,9 @@ b"oleaut32.dll": oleaut32.ord_names, } -PY3 = sys.version_info > (3,) -if PY3: - - def formatOrdString(ord_val): - return "ord{}".format(ord_val).encode() - - -else: - - def formatOrdString(ord_val): - return b"ord%d" % ord_val +def formatOrdString(ord_val): + return "ord{}".format(ord_val).encode() def ordLookup(libname, ord_val, make_name=False): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pefile-2022.5.30/pefile.egg-info/PKG-INFO new/pefile-2023.2.7/pefile.egg-info/PKG-INFO --- old/pefile-2022.5.30/pefile.egg-info/PKG-INFO 2022-05-30 16:07:23.000000000 +0200 +++ new/pefile-2023.2.7/pefile.egg-info/PKG-INFO 2023-02-07 13:19:21.000000000 +0100 @@ -1,9 +1,9 @@ Metadata-Version: 2.1 Name: pefile -Version: 2022.5.30 +Version: 2023.2.7 Summary: Python PE parsing module Home-page: https://github.com/erocarrera/pefile -Download-URL: https://github.com/erocarrera/pefile/releases/download/v2022.5.30/pefile-2022.5.30.tar.gz +Download-URL: https://github.com/erocarrera/pefile/releases/download/v2023.2.7/pefile-2023.2.7.tar.gz Author: Ero Carrera Author-email: ero.carr...@gmail.com License: MIT @@ -31,4 +31,4 @@ standard use. To the best of my knowledge most of the abuse is handled gracefully. -Copyright (c) 2005-2022 Ero Carrera <ero.carr...@gmail.com> +Copyright (c) 2005-2023 Ero Carrera <ero.carr...@gmail.com> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pefile-2022.5.30/pefile.egg-info/SOURCES.txt new/pefile-2023.2.7/pefile.egg-info/SOURCES.txt --- old/pefile-2022.5.30/pefile.egg-info/SOURCES.txt 2022-05-30 16:07:23.000000000 +0200 +++ new/pefile-2023.2.7/pefile.egg-info/SOURCES.txt 2023-02-07 13:19:21.000000000 +0100 @@ -1,5 +1,4 @@ .gitignore -.travis.yml LICENSE MANIFEST.in README @@ -13,5 +12,4 @@ pefile.egg-info/PKG-INFO pefile.egg-info/SOURCES.txt pefile.egg-info/dependency_links.txt -pefile.egg-info/requires.txt pefile.egg-info/top_level.txt \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pefile-2022.5.30/pefile.egg-info/requires.txt new/pefile-2023.2.7/pefile.egg-info/requires.txt --- old/pefile-2022.5.30/pefile.egg-info/requires.txt 2022-05-30 16:07:23.000000000 +0200 +++ new/pefile-2023.2.7/pefile.egg-info/requires.txt 1970-01-01 01:00:00.000000000 +0100 @@ -1 +0,0 @@ -future diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pefile-2022.5.30/pefile.py new/pefile-2023.2.7/pefile.py --- old/pefile-2022.5.30/pefile.py 2022-05-30 15:25:27.000000000 +0200 +++ new/pefile-2023.2.7/pefile.py 2023-02-06 23:43:49.000000000 +0100 @@ -1,5 +1,5 @@ #!/usr/bin/python -# -*- coding: utf-8 -*- + """pefile, Portable Executable reader module All the PE file basic structures are available with their default names as @@ -13,11 +13,11 @@ standard use. To the best of my knowledge most of the abuse is handled gracefully. -Copyright (c) 2005-2022 Ero Carrera <ero.carr...@gmail.com> +Copyright (c) 2005-2023 Ero Carrera <ero.carr...@gmail.com> """ __author__ = "Ero Carrera" -__version__ = "2022.5.30" +__version__ = "2023.2.7" __contact__ = "ero.carr...@gmail.com" import collections @@ -28,8 +28,10 @@ import math import string import mmap +import uuid from collections import Counter +from typing import Union from hashlib import sha1 from hashlib import sha256 from hashlib import sha512 @@ -106,7 +108,7 @@ MAX_DLL_LENGTH = 0x200 MAX_SYMBOL_NAME_LENGTH = 0x200 -# Lmit maximum number of sections before processing of sections will stop +# Limit maximum number of sections before processing of sections will stop MAX_SECTIONS = 0x800 # The global maximum number of resource entries to parse per file @@ -278,7 +280,7 @@ machine_types = [ - ("IMAGE_FILE_MACHINE_UNKNOWN", 0), + ("IMAGE_FILE_MACHINE_UNKNOWN", 0x0), ("IMAGE_FILE_MACHINE_I386", 0x014C), ("IMAGE_FILE_MACHINE_R3000", 0x0162), ("IMAGE_FILE_MACHINE_R4000", 0x0166), @@ -305,6 +307,11 @@ ("IMAGE_FILE_MACHINE_TRICORE", 0x0520), ("IMAGE_FILE_MACHINE_CEF", 0x0CEF), ("IMAGE_FILE_MACHINE_EBC", 0x0EBC), + ("IMAGE_FILE_MACHINE_RISCV32", 0x5032), + ("IMAGE_FILE_MACHINE_RISCV64", 0x5064), + ("IMAGE_FILE_MACHINE_RISCV128", 0x5128), + ("IMAGE_FILE_MACHINE_LOONGARCH32", 0x6232), + ("IMAGE_FILE_MACHINE_LOONGARCH64", 0x6264), ("IMAGE_FILE_MACHINE_AMD64", 0x8664), ("IMAGE_FILE_MACHINE_M32R", 0x9041), ("IMAGE_FILE_MACHINE_ARM64", 0xAA64), @@ -900,7 +907,7 @@ @lru_cache(maxsize=2048, copy=True) def set_format(format): - __format__ = "<" + __format_str__ = "<" __unpacked_data_elms__ = [] __field_offsets__ = {} __keys__ = [] @@ -910,7 +917,7 @@ for elm in format: if "," in elm: elm_type, elm_name = elm.split(",", 1) - __format__ += elm_type + __format_str__ += elm_type __unpacked_data_elms__.append(None) elm_names = elm_name.split(",") @@ -930,10 +937,10 @@ # all the possible members referring to the data. __keys__.append(names) - __format_length__ = struct.calcsize(__format__) + __format_length__ = struct.calcsize(__format_str__) return ( - __format__, + __format_str__, __unpacked_data_elms__, __field_offsets__, __keys__, @@ -950,7 +957,7 @@ def __init__(self, format, name=None, file_offset=None): # Format is forced little endian, for big endian non Intel platforms - self.__format__ = "<" + self.__format_str__ = "<" self.__keys__ = [] self.__format_length__ = 0 self.__field_offsets__ = {} @@ -962,7 +969,7 @@ d = tuple(d) ( - self.__format__, + self.__format_str__, self.__unpacked_data_elms__, self.__field_offsets__, self.__keys__, @@ -976,8 +983,8 @@ else: self.name = format[0] - def __get_format__(self): - return self.__format__ + def __get_format__(self) -> str: + return self.__format_str__ def get_field_absolute_offset(self, field_name): """Return the offset within the field for the requested field in the structure.""" @@ -1021,7 +1028,7 @@ if count_zeroes(data) == len(data): self.__all_zeroes__ = True - self.__unpacked_data_elms__ = struct.unpack(self.__format__, data) + self.__unpacked_data_elms__ = struct.unpack(self.__format_str__, data) for idx, val in enumerate(self.__unpacked_data_elms__): for key in self.__keys__[idx]: setattr(self, key, val) @@ -1031,18 +1038,16 @@ new_values = [] for idx, val in enumerate(self.__unpacked_data_elms__): - + new_val = None for key in self.__keys__[idx]: new_val = getattr(self, key) - # In the case of unions, when the first changed value # is picked the loop is exited if new_val != val: break - new_values.append(new_val) - return struct.pack(self.__format__, *new_values) + return struct.pack(self.__format_str__, *new_values) def __str__(self): return "\n".join(self.dump()) @@ -1152,6 +1157,10 @@ self.pe = argd["pe"] del argd["pe"] + self.PointerToRawData = None + self.VirtualAddress = None + self.SizeOfRawData = None + self.Misc_VirtualSize = None Structure.__init__(self, *argl, **argd) self.PointerToRawData_adj = None self.VirtualAddress_adj = None @@ -1204,17 +1213,20 @@ if length is not None: end = offset + length - else: + elif self.SizeOfRawData is not None: end = offset + self.SizeOfRawData + else: + end = offset - if ignore_padding: + if ignore_padding and end is not None and offset is not None: end = min(end, offset + self.Misc_VirtualSize) # PointerToRawData is not adjusted here as we might want to read any possible # extra bytes that might get cut off by aligning the start (and hence cutting # something off the end) - if end > self.PointerToRawData + self.SizeOfRawData: - end = self.PointerToRawData + self.SizeOfRawData + if self.PointerToRawData is not None and self.SizeOfRawData is not None: + if end > self.PointerToRawData + self.SizeOfRawData: + end = self.PointerToRawData + self.SizeOfRawData return self.pe.__data__[offset:end] def __setattr__(self, name, val): @@ -1447,7 +1459,7 @@ def __init__(self, format, name=None, file_offset=None): ( - self.__format__, + self.__format_str__, self.__format_length__, self.__field_offsets__, self.__keys__, @@ -1713,6 +1725,15 @@ """ +class DynamicRelocationData(DataContainer): + """Holds dynamic relocation information. + + struct: IMAGE_DYNAMIC_RELOCATION structure + symbol: Symbol to which dynamic relocations must be applied + relocations: List of dynamic relocations for this symbol (BaseRelocationData instances) + """ + + class BaseRelocationData(DataContainer): """Holds base relocation information. @@ -1785,6 +1806,7 @@ struct: IMAGE_LOAD_CONFIG_DIRECTORY structure name: dll name + dynamic_relocations: dynamic relocation information, if present """ @@ -2277,18 +2299,25 @@ # Check if an imported name uses the valid accepted characters expected in # mangled function names. If the symbol's characters don't fall within this # charset we will assume the name is invalid. - +# The dot "." character comes from: https://github.com/erocarrera/pefile/pull/346 +# All other symbols can be inserted by adding a name with that symbol to a .def file, +# and passing it to link.exe (See export_test.py) allowed_function_name = b( - string.ascii_lowercase + string.ascii_uppercase + string.digits + "_?@$()<>" + string.ascii_lowercase + string.ascii_uppercase + string.digits ) @lru_cache(maxsize=2048) -def is_valid_function_name(s): +def is_valid_function_name( + s: Union[str, bytes, bytearray], relax_allowed_characters: bool = False +) -> bool: + allowed_extra = b"._?@$()<>" + if relax_allowed_characters: + allowed_extra = b"!\"#$%&'()*+,-./:<>?[\\]^_`{|}~@" return ( s is not None and isinstance(s, (str, bytes, bytearray)) - and all(c in allowed_function_name for c in set(s)) + and all((c in allowed_function_name or c in allowed_extra) for c in set(s)) ) @@ -2630,6 +2659,27 @@ ("H,Data",), ) + __IMAGE_IMPORT_CONTROL_TRANSFER_DYNAMIC_RELOCATION_format__ = ( + "IMAGE_IMPORT_CONTROL_TRANSFER_DYNAMIC_RELOCATION", + ("I:12,PageRelativeOffset", "I:1,IndirectCall", "I:19,IATIndex"), + ) + + __IMAGE_INDIR_CONTROL_TRANSFER_DYNAMIC_RELOCATION_format__ = ( + "IMAGE_INDIR_CONTROL_TRANSFER_DYNAMIC_RELOCATION", + ( + "I:12,PageRelativeOffset", + "I:1,IndirectCall", + "I:1,RexWPrefix", + "I:1,CfgCheck", + "I:1,Reserved", + ), + ) + + __IMAGE_SWITCHTABLE_BRANCH_DYNAMIC_RELOCATION_format__ = ( + "IMAGE_SWITCHTABLE_BRANCH_DYNAMIC_RELOCATION", + ("I:12,PageRelativeOffset", "I:4,RegisterNumber"), + ) + __IMAGE_TLS_DIRECTORY_format__ = ( "IMAGE_TLS_DIRECTORY", ( @@ -2678,10 +2728,28 @@ "I,SEHandlerTable", "I,SEHandlerCount", "I,GuardCFCheckFunctionPointer", - "I,Reserved2", + "I,GuardCFDispatchFunctionPointer", "I,GuardCFFunctionTable", "I,GuardCFFunctionCount", "I,GuardFlags", + "H,CodeIntegrityFlags", + "H,CodeIntegrityCatalog", + "I,CodeIntegrityCatalogOffset", + "I,CodeIntegrityReserved", + "I,GuardAddressTakenIatEntryTable", + "I,GuardAddressTakenIatEntryCount", + "I,GuardLongJumpTargetTable", + "I,GuardLongJumpTargetCount", + "I,DynamicValueRelocTable", + "I,CHPEMetadataPointer", + "I,GuardRFFailureRoutine", + "I,GuardRFFailureRoutineFunctionPointer", + "I,DynamicValueRelocTableOffset", + "H,DynamicValueRelocTableSection", + "H,Reserved2", + "I,GuardRFVerifyStackPointerFunctionPointer" "I,HotPatchTableOffset", + "I,Reserved3", + "I,EnclaveConfigurationPointer", ), ) @@ -2709,13 +2777,57 @@ "Q,SEHandlerTable", "Q,SEHandlerCount", "Q,GuardCFCheckFunctionPointer", - "Q,Reserved2", + "Q,GuardCFDispatchFunctionPointer", "Q,GuardCFFunctionTable", "Q,GuardCFFunctionCount", "I,GuardFlags", + "H,CodeIntegrityFlags", + "H,CodeIntegrityCatalog", + "I,CodeIntegrityCatalogOffset", + "I,CodeIntegrityReserved", + "Q,GuardAddressTakenIatEntryTable", + "Q,GuardAddressTakenIatEntryCount", + "Q,GuardLongJumpTargetTable", + "Q,GuardLongJumpTargetCount", + "Q,DynamicValueRelocTable", + "Q,CHPEMetadataPointer", + "Q,GuardRFFailureRoutine", + "Q,GuardRFFailureRoutineFunctionPointer", + "I,DynamicValueRelocTableOffset", + "H,DynamicValueRelocTableSection", + "H,Reserved2", + "Q,GuardRFVerifyStackPointerFunctionPointer", + "I,HotPatchTableOffset", + "I,Reserved3", + "Q,EnclaveConfigurationPointer", ), ) + __IMAGE_DYNAMIC_RELOCATION_TABLE_format__ = ( + "IMAGE_DYNAMIC_RELOCATION_TABLE", + ("I,Version", "I,Size"), + ) + + __IMAGE_DYNAMIC_RELOCATION_format__ = ( + "IMAGE_DYNAMIC_RELOCATION", + ("I,Symbol", "I,BaseRelocSize"), + ) + + __IMAGE_DYNAMIC_RELOCATION64_format__ = ( + "IMAGE_DYNAMIC_RELOCATION64", + ("Q,Symbol", "I,BaseRelocSize"), + ) + + __IMAGE_DYNAMIC_RELOCATION_V2_format__ = ( + "IMAGE_DYNAMIC_RELOCATION_V2", + ("I,HeaderSize", "I,FixupInfoSize", "I,Symbol", "I,SymbolGroup", "I,Flags"), + ) + + __IMAGE_DYNAMIC_RELOCATION64_V2_format__ = ( + "IMAGE_DYNAMIC_RELOCATION64_V2", + ("I,HeaderSize", "I,FixupInfoSize", "Q,Symbol", "I,SymbolGroup", "I,Flags"), + ) + __IMAGE_BOUND_IMPORT_DESCRIPTOR_format__ = ( "IMAGE_BOUND_IMPORT_DESCRIPTOR", ("I,TimeDateStamp", "H,OffsetModuleName", "H,NumberOfModuleForwarderRefs"), @@ -2772,6 +2884,12 @@ # The number of imports parsed in this file self.__total_import_symbols = 0 + self.dynamic_relocation_format_by_symbol = { + 3: PE.__IMAGE_IMPORT_CONTROL_TRANSFER_DYNAMIC_RELOCATION_format__, + 4: PE.__IMAGE_INDIR_CONTROL_TRANSFER_DYNAMIC_RELOCATION_format__, + 5: PE.__IMAGE_SWITCHTABLE_BRANCH_DYNAMIC_RELOCATION_format__, + } + fast_load = fast_load if fast_load is not None else globals()["fast_load"] try: self.__parse__(name, data, fast_load) @@ -2819,6 +2937,28 @@ return structure + def __unpack_data_with_bitfields__(self, format, data, file_offset): + """Apply structure format to raw data. + + Returns an unpacked structure object if successful, None otherwise. + """ + + structure = StructureWithBitfields(format, file_offset=file_offset) + + try: + structure.__unpack__(data) + except PEFormatError as err: + self.__warnings.append( + 'Corrupt header "{0}" at file offset {1}. Exception: {2}'.format( + format[0], file_offset, err + ) + ) + return None + + self.__structures__.append(structure) + + return structure + def __parse__(self, fname, data, fast_load): """Parse a Portable Executable file. @@ -2865,8 +3005,8 @@ # zero) or 15% (if non-zero) of the file's contents. There are # legitimate PEs where 0x00 bytes are close to 50% of the whole # file's contents. - if (byte == 0 and 1.0 * byte_count / len(self.__data__) > 0.5) or ( - byte != 0 and 1.0 * byte_count / len(self.__data__) > 0.15 + if (byte == 0 and byte_count / len(self.__data__) > 0.5) or ( + byte != 0 and byte_count / len(self.__data__) > 0.15 ): self.__warnings.append( ( @@ -3848,8 +3988,10 @@ """""" if self.PE_TYPE == OPTIONAL_HEADER_MAGIC_PE: + load_config_dir_sz = self.get_dword_at_rva(rva) format = self.__IMAGE_LOAD_CONFIG_DIRECTORY_format__ elif self.PE_TYPE == OPTIONAL_HEADER_MAGIC_PE_PLUS: + load_config_dir_sz = self.get_dword_at_rva(rva) format = self.__IMAGE_LOAD_CONFIG_DIRECTORY64_format__ else: self.__warnings.append( @@ -3858,6 +4000,17 @@ ) return None + # load config directory size can be less than represented by 'format' variable, + # generate truncated format which correspond load config directory size + fields_counter = 0 + cumulative_sz = 0 + for field in format[1]: + fields_counter += 1 + cumulative_sz += STRUCT_SIZEOF_TYPES[field.split(",")[0]] + if cumulative_sz == load_config_dir_sz: + break + format = (format[0], format[1][:fields_counter]) + load_config_struct = None try: load_config_struct = self.__unpack_data__( @@ -3873,11 +4026,113 @@ if not load_config_struct: return None - return LoadConfigData(struct=load_config_struct) + dynamic_relocations = None + if fields_counter > 35: + dynamic_relocations = self.parse_dynamic_relocations( + load_config_struct.DynamicValueRelocTableOffset, + load_config_struct.DynamicValueRelocTableSection, + ) + + return LoadConfigData( + struct=load_config_struct, dynamic_relocations=dynamic_relocations + ) + + def parse_dynamic_relocations( + self, dynamic_value_reloc_table_offset, dynamic_value_reloc_table_section + ): + if not dynamic_value_reloc_table_offset: + return None + if not dynamic_value_reloc_table_section: + return None + + if dynamic_value_reloc_table_section > len(self.sections): + return None + + section = self.sections[dynamic_value_reloc_table_section - 1] + rva = section.VirtualAddress + dynamic_value_reloc_table_offset + image_dynamic_reloc_table_struct = None + reloc_table_size = Structure( + self.__IMAGE_DYNAMIC_RELOCATION_TABLE_format__ + ).sizeof() + try: + image_dynamic_reloc_table_struct = self.__unpack_data__( + self.__IMAGE_DYNAMIC_RELOCATION_TABLE_format__, + self.get_data(rva, reloc_table_size), + file_offset=self.get_offset_from_rva(rva), + ) + except PEFormatError: + self.__warnings.append( + "Invalid IMAGE_DYNAMIC_RELOCATION_TABLE information. Can't read " + "data at RVA: 0x%x" % rva + ) + + if image_dynamic_reloc_table_struct.Version != 1: + self.__warnings.append( + "No pasring available for IMAGE_DYNAMIC_RELOCATION_TABLE.Version = %d", + image_dynamic_reloc_table_struct.Version, + ) + return None + + rva += reloc_table_size + end = rva + image_dynamic_reloc_table_struct.Size + dynamic_relocations = [] + + while rva < end: + format = self.__IMAGE_DYNAMIC_RELOCATION_format__ + + if self.PE_TYPE == OPTIONAL_HEADER_MAGIC_PE_PLUS: + format = self.__IMAGE_DYNAMIC_RELOCATION64_format__ + + rlc_size = Structure(format).sizeof() + + try: + dynamic_rlc = self.__unpack_data__( + format, + self.get_data(rva, rlc_size), + file_offset=self.get_offset_from_rva(rva), + ) + except PEFormatError: + self.__warnings.append( + "Invalid relocation information. Can't read " + "data at RVA: 0x%x" % rva + ) + dynamic_rlc = None + + if not dynamic_rlc: + break + + rva += rlc_size + symbol = dynamic_rlc.Symbol + size = dynamic_rlc.BaseRelocSize + + if 3 <= symbol <= 5: + relocations = self.parse_image_base_relocation_list( + rva, size, self.dynamic_relocation_format_by_symbol[symbol] + ) + dynamic_relocations.append( + DynamicRelocationData( + struct=dynamic_rlc, symbol=symbol, relocations=relocations + ) + ) + + if symbol > 5: + relocations = self.parse_image_base_relocation_list(rva, size) + dynamic_relocations.append( + DynamicRelocationData( + struct=dynamic_rlc, symbol=symbol, relocations=relocations + ) + ) + + rva += size + + return dynamic_relocations def parse_relocations_directory(self, rva, size): """""" + return self.parse_image_base_relocation_list(rva, size) + + def parse_image_base_relocation_list(self, rva, size, fmt=None): rlc_size = Structure(self.__IMAGE_BASE_RELOCATION_format__).sizeof() end = rva + size @@ -3921,9 +4176,14 @@ ) break - reloc_entries = self.parse_relocations( - rva + rlc_size, rlc.VirtualAddress, rlc.SizeOfBlock - rlc_size - ) + if fmt is None: + reloc_entries = self.parse_relocations( + rva + rlc_size, rlc.VirtualAddress, rlc.SizeOfBlock - rlc_size + ) + else: + reloc_entries = self.parse_relocations_with_format( + rva + rlc_size, rlc.VirtualAddress, rlc.SizeOfBlock - rlc_size, fmt + ) relocations.append(BaseRelocationData(struct=rlc, entries=reloc_entries)) @@ -3944,7 +4204,7 @@ return [] entries = [] - offsets_and_type = [] + offsets_and_type = set() for idx in range(int(len(data) / 2)): entry = self.__unpack_data__( @@ -3965,9 +4225,8 @@ "at RVA: 0x%x" % (reloc_offset + rva) ) break - if len(offsets_and_type) >= 1000: - offsets_and_type.pop() - offsets_and_type.insert(0, (reloc_offset, reloc_type)) + + offsets_and_type.add((reloc_offset, reloc_type)) entries.append( RelocationData( @@ -3978,6 +4237,46 @@ return entries + def parse_relocations_with_format(self, data_rva, rva, size, format): + """""" + + try: + data = self.get_data(data_rva, size) + file_offset = self.get_offset_from_rva(data_rva) + except PEFormatError: + self.__warnings.append(f"Bad RVA in relocation data: 0x{data_rva:x}") + return [] + + entry_size = StructureWithBitfields(format).sizeof() + entries = [] + offsets = set() + for idx in range(int(len(data) / entry_size)): + + entry = self.__unpack_data_with_bitfields__( + format, + data[idx * entry_size : (idx + 1) * entry_size], + file_offset=file_offset, + ) + + if not entry: + break + + reloc_offset = entry.PageRelativeOffset + if reloc_offset in offsets: + self.__warnings.append( + "Overlapping offsets in relocation data " + "at RVA: 0x%x" % (reloc_offset + rva) + ) + break + offsets.add(reloc_offset) + + entries.append( + RelocationData(struct=entry, base_rva=rva, rva=reloc_offset + rva) + ) + file_offset += entry_size + + return entries + def parse_debug_directory(self, rva, size): """""" @@ -4024,13 +4323,13 @@ __CV_INFO_PDB70_format__ = [ "CV_INFO_PDB70", [ - "I,CvSignature", + "4s,CvSignature", "I,Signature_Data1", # Signature is of GUID type "H,Signature_Data2", "H,Signature_Data3", - "8s,Signature_Data4", - # 'H,Signature_Data5', - # 'I,Signature_Data6', + "B,Signature_Data4", + "B,Signature_Data5", + "6s,Signature_Data6", "I,Age", ], ] @@ -4051,6 +4350,27 @@ dbg_type = self.__unpack_data__( __CV_INFO_PDB70_format__, dbg_type_data, dbg_type_offset ) + if dbg_type is not None: + dbg_type.Signature_Data6_value = struct.unpack( + ">Q", b"\0\0" + dbg_type.Signature_Data6 + )[0] + dbg_type.Signature_String = ( + str( + uuid.UUID( + fields=( + dbg_type.Signature_Data1, + dbg_type.Signature_Data2, + dbg_type.Signature_Data3, + dbg_type.Signature_Data4, + dbg_type.Signature_Data5, + dbg_type.Signature_Data6_value, + ) + ) + ) + .replace("-", "") + .upper() + + f"{dbg_type.Age:X}" + ) elif dbg_type_data[:4] == b"NB10": # pdb2.0 @@ -5049,7 +5369,7 @@ symbol_name = self.get_string_at_rva( symbol_name_address, MAX_SYMBOL_NAME_LENGTH ) - if not is_valid_function_name(symbol_name): + if not is_valid_function_name(symbol_name, relax_allowed_characters=True): export_parsing_loop_completed_normally = False break try: @@ -5336,6 +5656,16 @@ raise Exception("Invalid hashing algorithm specified") def get_imphash(self): + """Return the imphash of the PE file. + + Creates a hash based on imported symbol names and their specific order within + the executable: + https://www.mandiant.com/resources/blog/tracking-malware-import-hashing + + Returns: + the hexdigest of the MD5 hash of the exported symbols. + """ + impstrs = [] exts = ["ocx", "sys", "dll"] if not hasattr(self, "DIRECTORY_ENTRY_IMPORT"): @@ -5373,6 +5703,31 @@ return md5(",".join(impstrs).encode()).hexdigest() + def get_exphash(self): + """Return the exphash of the PE file. + + Similar to imphash, but based on exported symbol names and their specific order. + + Returns: + the hexdigest of the SHA256 hash of the exported symbols. + """ + + if not hasattr(self, "DIRECTORY_ENTRY_EXPORT"): + return "" + + if not hasattr(self.DIRECTORY_ENTRY_EXPORT, "symbols"): + return "" + + export_list = [ + e.name.decode().lower() + for e in self.DIRECTORY_ENTRY_EXPORT.symbols + if e and e.name is not None + ] + if len(export_list) == 0: + return "" + + return sha256(",".join(export_list).encode()).hexdigest() + def parse_import_directory(self, rva, size, dllnames_only=False): """Walk and parse the import directory.""" @@ -6060,6 +6415,13 @@ """Checks if the PE file has relocation directory""" return hasattr(self, "DIRECTORY_ENTRY_BASERELOC") + def has_dynamic_relocs(self): + if hasattr(self, "DIRECTORY_ENTRY_LOAD_CONFIG"): + if self.DIRECTORY_ENTRY_LOAD_CONFIG.dynamic_relocations: + return True + + return False + def print_info(self, encoding="utf-8"): """Print all the PE header information in a human readable from.""" print(self.dump_info(encoding=encoding)) @@ -7132,30 +7494,84 @@ relocation_difference ) if hasattr(self, "DIRECTORY_ENTRY_LOAD_CONFIG"): - if self.DIRECTORY_ENTRY_LOAD_CONFIG.struct.LockPrefixTable: - self.DIRECTORY_ENTRY_LOAD_CONFIG.struct.LockPrefixTable += ( - relocation_difference - ) - if self.DIRECTORY_ENTRY_LOAD_CONFIG.struct.EditList: - self.DIRECTORY_ENTRY_LOAD_CONFIG.struct.EditList += ( - relocation_difference - ) - if self.DIRECTORY_ENTRY_LOAD_CONFIG.struct.SecurityCookie: - self.DIRECTORY_ENTRY_LOAD_CONFIG.struct.SecurityCookie += ( - relocation_difference - ) - if self.DIRECTORY_ENTRY_LOAD_CONFIG.struct.SEHandlerTable: - self.DIRECTORY_ENTRY_LOAD_CONFIG.struct.SEHandlerTable += ( - relocation_difference - ) - if self.DIRECTORY_ENTRY_LOAD_CONFIG.struct.GuardCFCheckFunctionPointer: - self.DIRECTORY_ENTRY_LOAD_CONFIG.struct.GuardCFCheckFunctionPointer += ( + load_config = self.DIRECTORY_ENTRY_LOAD_CONFIG.struct + if ( + hasattr(load_config, "LockPrefixTable") + and load_config.LockPrefixTable + ): + load_config.LockPrefixTable += relocation_difference + if hasattr(load_config, "EditList") and load_config.EditList: + load_config.EditList += relocation_difference + if ( + hasattr(load_config, "SecurityCookie") + and load_config.SecurityCookie + ): + load_config.SecurityCookie += relocation_difference + if ( + hasattr(load_config, "SEHandlerTable") + and load_config.SEHandlerTable + ): + load_config.SEHandlerTable += relocation_difference + if ( + hasattr(load_config, "GuardCFCheckFunctionPointer") + and load_config.GuardCFCheckFunctionPointer + ): + load_config.GuardCFCheckFunctionPointer += relocation_difference + if ( + hasattr(load_config, "GuardCFDispatchFunctionPointer") + and load_config.GuardCFDispatchFunctionPointer + ): + load_config.GuardCFDispatchFunctionPointer += relocation_difference + if ( + hasattr(load_config, "GuardCFFunctionTable") + and load_config.GuardCFFunctionTable + ): + load_config.GuardCFFunctionTable += relocation_difference + if ( + hasattr(load_config, "GuardAddressTakenIatEntryTable") + and load_config.GuardAddressTakenIatEntryTable + ): + load_config.GuardAddressTakenIatEntryTable += relocation_difference + if ( + hasattr(load_config, "GuardLongJumpTargetTable") + and load_config.GuardLongJumpTargetTable + ): + load_config.GuardLongJumpTargetTable += relocation_difference + if ( + hasattr(load_config, "DynamicValueRelocTable") + and load_config.DynamicValueRelocTable + ): + load_config.DynamicValueRelocTable += relocation_difference + if ( + self.PE_TYPE == OPTIONAL_HEADER_MAGIC_PE_PLUS + and hasattr(load_config, "CHPEMetadataPointer") + and load_config.CHPEMetadataPointer + ): + load_config.CHPEMetadataPointer += relocation_difference + if ( + hasattr(load_config, "GuardRFFailureRoutine") + and load_config.GuardRFFailureRoutine + ): + load_config.GuardRFFailureRoutine += relocation_difference + if ( + hasattr(load_config, "GuardRFFailureRoutineFunctionPointer") + and load_config.GuardRFFailureRoutineFunctionPointer + ): + load_config.GuardRFVerifyStackPointerFunctionPointer += ( relocation_difference ) - if self.DIRECTORY_ENTRY_LOAD_CONFIG.struct.GuardCFFunctionTable: - self.DIRECTORY_ENTRY_LOAD_CONFIG.struct.GuardCFFunctionTable += ( + if ( + hasattr(load_config, "GuardRFVerifyStackPointerFunctionPointer") + and load_config.GuardRFVerifyStackPointerFunctionPointer + ): + load_config.GuardRFVerifyStackPointerFunctionPointer += ( relocation_difference ) + if ( + hasattr(load_config, "EnclaveConfigurationPointer") + and load_config.EnclaveConfigurationPointer + ): + load_config.EnclaveConfigurationPointer += relocation_difference def verify_checksum(self): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pefile-2022.5.30/peutils.py new/pefile-2023.2.7/peutils.py --- old/pefile-2022.5.30/peutils.py 2022-05-30 15:25:51.000000000 +0200 +++ new/pefile-2023.2.7/peutils.py 2023-01-07 20:42:11.000000000 +0100 @@ -1,18 +1,11 @@ -# -*- coding: Latin-1 -*- + """peutils, Portable Executable utilities module -Copyright (c) 2005-2022 Ero Carrera <ero.carr...@gmail.com> +Copyright (c) 2005-2023 Ero Carrera <ero.carr...@gmail.com> All rights reserved. """ -from __future__ import division -from future import standard_library - -standard_library.install_aliases() -from builtins import range -from builtins import object - import os import re import string @@ -586,7 +579,7 @@ if s_entropy > 7.4: total_compressed_data += s_length - if ((1.0 * total_compressed_data) / total_pe_data_length) > 0.2: + if (total_compressed_data / total_pe_data_length) > 0.2: has_significant_amount_of_compressed_data = True return has_significant_amount_of_compressed_data diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pefile-2022.5.30/setup.py new/pefile-2023.2.7/setup.py --- old/pefile-2022.5.30/setup.py 2022-05-30 15:24:42.000000000 +0200 +++ new/pefile-2023.2.7/setup.py 2023-02-06 23:44:23.000000000 +0100 @@ -5,8 +5,6 @@ import re import sys -if sys.version_info.major == 3: - from io import open try: from setuptools import setup, Command @@ -16,8 +14,8 @@ from unittest import TestLoader, TextTestRunner -os.environ['COPY_EXTENDED_ATTRIBUTES_DISABLE'] = 'true' -os.environ['COPYFILE_DISABLE'] = 'true' +os.environ["COPY_EXTENDED_ATTRIBUTES_DISABLE"] = "true" +os.environ["COPYFILE_DISABLE"] = "true" def _read_doc(): @@ -25,12 +23,8 @@ Parse docstring from file 'pefile.py' and avoid importing this module directly. """ - if sys.version_info.major == 2: - with open('pefile.py', 'r') as f: - tree = ast.parse(f.read()) - else: - with open('pefile.py', 'r', encoding='utf-8') as f: - tree = ast.parse(f.read()) + with open("pefile.py", "r", encoding="utf-8") as f: + tree = ast.parse(f.read()) return ast.get_docstring(tree) @@ -42,18 +36,15 @@ __version__, __author__, __contact__, """ regex = attr_name + r"\s+=\s+['\"](.+)['\"]" - if sys.version_info.major == 2: - with open('pefile.py', 'r') as f: - match = re.search(regex, f.read()) - else: - with open('pefile.py', 'r', encoding='utf-8') as f: - match = re.search(regex, f.read()) + with open("pefile.py", "r", encoding="utf-8") as f: + match = re.search(regex, f.read()) # Second item in the group is the value of attribute. return match.group(1) class TestCommand(Command): """Run tests.""" + user_options = [] def initialize_options(self): @@ -63,33 +54,33 @@ pass def run(self): - test_suite = TestLoader().discover('./tests', pattern='*_test.py') + test_suite = TestLoader().discover("./tests", pattern="*_test.py") test_results = TextTestRunner(verbosity=2).run(test_suite) -setup(name = 'pefile', - version = _read_attr('__version__'), - description = 'Python PE parsing module', - author = _read_attr('__author__'), - author_email = _read_attr('__contact__'), - url = 'https://github.com/erocarrera/pefile', - download_url='https://github.com/erocarrera/pefile/releases/download/v2022.5.30/pefile-2022.5.30.tar.gz', - keywords = ['pe', 'exe', 'dll', 'pefile', 'pecoff'], - classifiers = [ - 'Development Status :: 5 - Production/Stable', - 'Intended Audience :: Developers', - 'Intended Audience :: Science/Research', - 'Natural Language :: English', - 'Operating System :: OS Independent', - 'Programming Language :: Python', - 'Topic :: Software Development :: Libraries :: Python Modules'], - long_description = "\n".join(_read_doc().split('\n')), +setup( + name="pefile", + version=_read_attr("__version__"), + description="Python PE parsing module", + author=_read_attr("__author__"), + author_email=_read_attr("__contact__"), + url="https://github.com/erocarrera/pefile", + download_url="https://github.com/erocarrera/pefile/releases/download/v2023.2.7/pefile-2023.2.7.tar.gz", + keywords=["pe", "exe", "dll", "pefile", "pecoff"], + classifiers=[ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "Intended Audience :: Science/Research", + "Natural Language :: English", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Topic :: Software Development :: Libraries :: Python Modules", + ], + long_description="\n".join(_read_doc().split("\n")), cmdclass={"test": TestCommand}, - py_modules = ['pefile', 'peutils'], + py_modules=["pefile", "peutils"], python_requires=">=3.6.0", - packages = ['ordlookup'], - install_requires=[ - 'future', - ], + packages=["ordlookup"], + install_requires=[], license="MIT", )