potisanのプログラミングメモ

趣味のプログラマーがプログラミング関係で気になったことや調べたことをいつでも忘れられるようにメモするブログです。はてなブログ無料版なので記事の上の方はたぶん広告です。記事中にも広告挿入されるみたいです。

Python3 PEファイルの情報を取得するクラス

標準ライブラリのみ使用してPEファイルの情報を取得するクラスのコードです。現状ではインポート、エクスポート情報のみ対応しています。おそらくインポート情報を持たないファイルだとエラーが発生します。

#test.py
from pefileinfo import *

with PEFileInfo(r"C:\Windows\System32\kernel32.dll") as pefile:
    for import_desc in pefile.get_import_descriptors():
        thunks = pefile.get_import_thunks(import_desc)
        name_orginals = tuple(thunk.name if thunk.name is not None else f"#{thunk.ordinal}" for thunk in thunks)
        print(f"{import_desc.name}: {', '.join(name_orginals)}")

    expdir = pefile.get_export_directory()
    pass
#pefileinfo.py
from contextlib import contextmanager
import ctypes
from dataclasses import dataclass
from datetime import datetime
from enum import IntEnum
from io import BytesIO
from typing import ClassVar, Iterator

from pefileinfo_nativestructure import *

class PEFileError(RuntimeError):
    pass

@dataclass(frozen=True)
class ImageDOSHeader:
    magic: bytes
    cblp: int
    cp: int
    crlc: int
    cparhdr: int
    minalloc: int
    maxalloc: int
    ss: int
    sp: int
    csum: int
    ip: int
    cs: int
    lfarlc: int
    ovno: int
    res: tuple[int]
    oemid: int
    oeminfo: int
    res2: tuple[int]
    lfanew: int

    def __init__(self, f: BytesIO):
        t = IMAGE_DOS_HEADER()
        f.readinto(t)
        object.__setattr__(self, "magic", t.e_magic)
        object.__setattr__(self, "cblp", t.e_cblp)
        object.__setattr__(self, "cp", t.e_cp)
        object.__setattr__(self, "crlc", t.e_crlc)
        object.__setattr__(self, "cparhdr", t.e_cparhdr)
        object.__setattr__(self, "minalloc", t.e_minalloc)
        object.__setattr__(self, "maxalloc", t.e_maxalloc)
        object.__setattr__(self, "ss", t.e_ss)
        object.__setattr__(self, "sp", t.e_sp)
        object.__setattr__(self, "csum", t.e_csum)
        object.__setattr__(self, "ip", t.e_ip)
        object.__setattr__(self, "cs", t.e_cs)
        object.__setattr__(self, "lfarlc", t.e_lfarlc)
        object.__setattr__(self, "ovno", t.e_ovno)
        object.__setattr__(self, "res", tuple(t.e_res))
        object.__setattr__(self, "oemid", t.e_oemid)
        object.__setattr__(self, "oeminfo", t.e_oeminfo)
        object.__setattr__(self, "res2", tuple(t.e_res2))
        object.__setattr__(self, "lfanew", t.e_lfanew)

@dataclass(frozen=True)
class ImageFileHeaderCharacteristics:
    value: int

    @property
    def stripped_relocations(self) -> bool:
        return self.value & 0x0001 != 0
    @property
    def is_exe_image(self) -> bool:
        return self.value & 0x0002 != 0
    @property
    def stripped_linenums(self) -> bool:
        return self.value & 0x0004 != 0
    @property
    def stripped_localsymbols(self) -> bool:
        return self.value & 0x0008 != 0
    @property
    def trims_ws_aggresive(self) -> bool:
        return self.value & 0x0010 != 0
    @property
    def awares_large_address(self) -> bool:
        return self.value & 0x0020 != 0
    @property
    def is_reversed_bytes_low(self) -> bool:
        return self.value & 0x0080 != 0
    @property
    def for_32bit_machine(self) -> bool:
        return self.value & 0x0100 != 0
    @property
    def stripped_debug(self) -> bool:
        return self.value & 0x0200 != 0
    @property
    def removable_run_from_swap(self) -> bool:
        return self.value & 0x0400 != 0
    @property
    def net_run_from_swap(self) -> bool:
        return self.value & 0x0800 != 0
    @property
    def system(self) -> bool:
        return self.value & 0x1000 != 0
    @property
    def dll(self) -> bool:
        return self.value & 0x2000 != 0
    @property
    def up_system_only(self) -> bool:
        return self.value & 0x4000 != 0
    @property
    def reversed_bytes_high(self) -> bool:
        return self.value & 0x8000 != 0

@dataclass(frozen=True)
class ImageFileHeader:
    machine: int
    number_of_sections: int
    timedatestamp: datetime
    pointer_to_symboltable: int
    number_of_symbols: int
    size_of_optionalheader: int
    characteristics: ImageFileHeaderCharacteristics

    def __init__(self, f: BytesIO):
        t = IMAGE_FILE_HEADER()
        f.readinto(t)
        object.__setattr__(self, "machine", t.Machine)
        object.__setattr__(self, "number_of_sections", t.NumberOfSections)
        object.__setattr__(self, "timedatestamp", datetime.fromtimestamp(t.TimeDataStamp))
        object.__setattr__(self, "pointer_to_symboltable", t.PointerToSymbolTable)
        object.__setattr__(self, "number_of_symbols", t.NumberOfSymbols)
        object.__setattr__(self, "size_of_optionalheader", t.SizeOfOptionalHeader)
        object.__setattr__(self, "characteristics", ImageFileHeaderCharacteristics(t.Characteristics))

@dataclass(frozen=True)
class ImageDataDirectory:
    va: int
    size: int
    def __init__(self, raw: IMAGE_DATA_DIRECTORY):
        object.__setattr__(self, "va", raw.VirtualAddress)
        object.__setattr__(self, "size", raw.Size)

class ImageOptionalHeaderSubsystem(IntEnum):
    UNKNOWN = 0
    NATIVE = 1
    WINDOWS_GUI = 2
    WINDOWS_CUI = 3
    OS2_CUI = 5
    POSIX_CUI = 7
    WINDOWS_CE_GUI = 9
    EFI_APPLICATION = 10
    EFI_BOOT_SERVICE_DRIVER = 11
    EFI_RUNTIME_DRIVER = 12
    EFI_ROM = 13
    XBOX = 14
    WINDOWS_BOOT_APPLICATION = 16

@dataclass(frozen=True)
class ImageOptionalHeaderDLLCharacteristics:
    value: int

    @property
    def reserved1(self) -> bool: return self.value & 0x0001 != 0
    @property
    def reserved2(self) -> bool: return self.value & 0x0002 != 0
    @property
    def reserved3(self) -> bool: return self.value & 0x0004 != 0
    @property
    def reserved4(self) -> bool: return self.value & 0x0008 != 0
    @property
    def high_entrophy_va(self) -> bool: return self.value & 0x0020 != 0
    @property
    def dynamic_base(self) -> bool: return self.value & 0x0040 != 0
    @property
    def forces_integrity(self) -> bool: return self.value & 0x0080 != 0
    @property
    def nx_compat(self) -> bool: return self.value & 0x0100 != 0
    @property
    def no_isolation(self) -> bool: return self.value & 0x0200 != 0
    @property
    def no_seh(self) -> bool: return self.value & 0x0400 != 0
    @property
    def no_bind(self) -> bool: return self.value & 0x0800 != 0
    @property
    def appcontainer(self) -> bool: return self.value & 0x1000 != 0
    @property
    def wdm_driver(self) -> bool: return self.value & 0x2000 != 0
    @property
    def guard_cf(self) -> bool: return self.value & 0x4000 != 0
    @property
    def terminal_server_aware(self) -> bool: return self.value & 0x8000 != 0

@dataclass(frozen=True)
class ImageOptionalHeader:
    magic: int
    major_linkerver: int
    minor_linkerver: int
    size_code: int
    size_initializeddata: int
    size_uninitializeddata: int
    address_entrypoint: int
    basecode: int
    imagebase: int
    sectionalignment: int
    filealignment: int
    major_osver: int
    minor_osver: int
    major_imagever: int
    minor_imagever: int
    major_subsystemver: int
    minor_subsystemver: int
    win32_vervalue: int
    size_image: int
    size_headers: int
    checksum: int
    subsystem: ImageOptionalHeaderSubsystem
    dll_characteristics: ImageOptionalHeaderDLLCharacteristics
    size_stackreserve: int
    size_stackcommit: int
    size_heapreserve: int
    size_heapcommit: int
    loaderflags: int
    number_rvaandsizes: int
    datadir: tuple[ImageDataDirectory]

    def __init__(self, f: BytesIO, type: type):
        if type == IMAGE_OPTIONAL_HEADER32 or type == IMAGE_OPTIONAL_HEADER64:
            t = type()
            f.readinto(t)
        else:
            raise PEFileError("Invalid type")
        object.__setattr__(self, "magic", t.Magic)
        object.__setattr__(self, "major_linkerver", t.MajorLinkerVersion)
        object.__setattr__(self, "minor_linkerver", t.MinorLinkerVersion)
        object.__setattr__(self, "size_code", t.SizeOfCode)
        object.__setattr__(self, "size_initializeddata", t.SizeOfInitializedData)
        object.__setattr__(self, "size_uninitializeddata", t.SizeOfUninitializedData)
        object.__setattr__(self, "address_entrypoint", t.AddressOfEntryPoint)
        object.__setattr__(self, "basecode", t.BaseOfCode)
        object.__setattr__(self, "imagebase", t.ImageBase)
        object.__setattr__(self, "sectionalignment", t.SectionAlignment)
        object.__setattr__(self, "filealignment", t.FileAlignment)
        object.__setattr__(self, "major_osver", t.MajorOperatingSystemVersion)
        object.__setattr__(self, "minor_osver", t.MinorOperatingSystemVersion)
        object.__setattr__(self, "major_imagever", t.MajorImageVersion)
        object.__setattr__(self, "minor_imagever", t.MinorImageVersion)
        object.__setattr__(self, "major_subsystemver", t.MajorSubsystemVersion)
        object.__setattr__(self, "minor_subsystemver", t.MinorSubsystemVersion)
        object.__setattr__(self, "win32_vervalue", t.Win32VersionValue)
        object.__setattr__(self, "size_image", t.SizeOfImage)
        object.__setattr__(self, "size_headers", t.SizeOfHeaders)
        object.__setattr__(self, "checksum", t.CheckSum)
        object.__setattr__(self, "subsystem", ImageOptionalHeaderSubsystem(t.Subsystem))
        object.__setattr__(self, "dll_characteristics", ImageOptionalHeaderDLLCharacteristics(t.DllCharacteristics))
        object.__setattr__(self, "size_stackreserve", t.SizeOfStackReserve)
        object.__setattr__(self, "size_stackcommit", t.SizeOfStackCommit)
        object.__setattr__(self, "size_heapreserve", t.SizeOfHeapReserve)
        object.__setattr__(self, "size_heapcommit", t.SizeOfHeapCommit)
        object.__setattr__(self, "loaderflags", t.LoaderFlags)
        object.__setattr__(self, "number_rvaandsizes", t.NumberOfRvaAndSizes)
        object.__setattr__(self, "datadir", tuple(ImageDataDirectory(dir) for dir in t.DataDirectory))

    @property
    def datadir_export(self) -> ImageDataDirectory: return self.datadir[0]
    @property
    def datadir_import(self) -> ImageDataDirectory: return self.datadir[1]
    @property
    def datadir_resource(self) -> ImageDataDirectory: return self.datadir[2]
    @property
    def datadir_exception(self) -> ImageDataDirectory: return self.datadir[3]
    @property
    def datadir_security(self) -> ImageDataDirectory: return self.datadir[4]
    @property
    def datadir_basereloc(self) -> ImageDataDirectory: return self.datadir[5]
    @property
    def datadir_debug(self) -> ImageDataDirectory: return self.datadir[6]
    @property
    def datadir_architecture(self) -> ImageDataDirectory: return self.datadir[7]
    @property
    def datadir_globalptr(self) -> ImageDataDirectory: return self.datadir[8]
    @property
    def datadir_tls(self) -> ImageDataDirectory: return self.datadir[9]
    @property
    def datadir_load_config(self) -> ImageDataDirectory: return self.datadir[10]
    @property
    def datadir_bound_import(self) -> ImageDataDirectory: return self.datadir[11]
    @property
    def datadir_delay_iat(self) -> ImageDataDirectory: return self.datadir[12]
    @property
    def datadir_delay_import(self) -> ImageDataDirectory: return self.datadir[13]
    @property
    def datadir_com_desc(self) -> ImageDataDirectory: return self.datadir[14]

@dataclass(frozen=True)
class ImageSectionHeader:
    name: str
    virtual_size: int
    va: int
    size_of_rawdata: int
    pointer_to_rawdata: int
    pointer_to_relocations: int
    pointer_to_linenumbers: int
    number_of_relocations: int
    number_of_linenumbers: int
    characteristics: int
    def __init__(self, f: BytesIO):
        t = IMAGE_SECTION_HEADER()
        f.readinto(t)
        object.__setattr__(self, "name", bytes(t.Name).decode("utf-8").rstrip('\0'))
        object.__setattr__(self, "virtual_size", t.VirtualSize)
        object.__setattr__(self, "va", t.VirtualAddress)
        object.__setattr__(self, "size_of_rawdata", t.SizeOfRawData)
        object.__setattr__(self, "pointer_to_rawdata", t.PointerToRawData)
        object.__setattr__(self, "pointer_to_relocations", t.PointerToRelocations)
        object.__setattr__(self, "pointer_to_linenumbers", t.PointerToLinenumbers)
        object.__setattr__(self, "number_of_relocations", t.NumberOfRelocations)
        object.__setattr__(self, "number_of_linenumbers", t.NumberOfLinenumbers)
        object.__setattr__(self, "characteristics", t.Characteristics)

@dataclass(frozen=True)
class ImageImportDescriptor:
    characteristics: int
    timedatestamp: int
    forwarder_chain: int
    name: str
    name_rva: int
    firsthunk_rva: int
    def __init__(self, pefile: "PEFileInfo"):
        t = IMAGE_IMPORT_DESCRIPTOR()
        pefile.readinto(t)
        with pefile.filepointer_saver():
            object.__setattr__(self, "characteristics", t.Characteristics)
            object.__setattr__(self, "timedatestamp", t.TimeDateStamp)
            object.__setattr__(self, "forwarder_chain", t.ForwarderChain)
            object.__setattr__(self, "name_rva", t.Name)
            object.__setattr__(self, "firsthunk_rva", t.FirstThunk)
            if self.characteristics == 0:
                return
            object.__setattr__(self, "name", pefile.read_nullterminated_utf8_rva(self.name_rva))

@dataclass(frozen=True)
class ImageThunkData:
    ordinal: int|None = None
    data_rva: int|None = None
    name: str|None = None

    def __init__(self, pefile: "PEFileInfo", type: type):
        t = type()
        pefile.readinto(t)
        if t.Value == 0:
            return
        elif type == IMAGE_THUNK_DATA32:
            if t.Value & 0x80000000 != 0:
                object.__setattr__(self, "ordinal", t.Value & 0xffff)
            else:
                object.__setattr__(self, "data_rva", t.Value)
        elif type == IMAGE_THUNK_DATA64:
            if t.Value & 0x8000000000000000 != 0:
                object.__setattr__(self, "ordinal", t.Value & 0xffff)
            else:
                object.__setattr__(self, "data_rva", t.Value)
        else:
            raise PEFileError("Invalid operation.")

        if self.data_rva is not None:
            with pefile.filepointer_saver():
                # IMAGE_IMPORT_BY_NAME
                pefile.seek_rva(self.data_rva)
                object.__setattr__(self, "ordinal", int.from_bytes(pefile.read(2), "little"))
                object.__setattr__(self, "name", pefile.read_nullterminated_utf8())

@dataclass(frozen=True)
class ImageExportDirectory:
    characteristics: int
    timedatestamp: datetime
    majorver: int
    minorver: int
    name_rva: int
    name: str
    base: int
    funcs: tuple[int]
    name_rvas: tuple[int]
    names: tuple[str]
    nameordinals: tuple[int]
    funcs_rva: int
    names_rva: int
    nameordinals_rva: int

    def __init__(self, pefile: "PEFileInfo"):
        if pefile.optional_header.datadir_export.va == 0:
            return
        t = IMAGE_EXPORT_DIRECTORY()
        pefile.seek_rva(pefile.optional_header.datadir_export.va)
        pefile.readinto(t)
        object.__setattr__(self, "characteristics", t.Characteristics)
        object.__setattr__(self, "timedatestamp", datetime.fromtimestamp(t.TimeDateStamp))
        object.__setattr__(self, "majorver", t.MajorVersion)
        object.__setattr__(self, "minorver", t.MinorVersion)
        object.__setattr__(self, "name_rva", t.Name)
        object.__setattr__(self, "name", pefile.read_nullterminated_utf8_rva(self.name_rva))
        object.__setattr__(self, "base", t.Base)
        object.__setattr__(self, "funcs_rva", t.AddressOfFunctions)
        object.__setattr__(self, "names_rva", t.AddressOfNames)
        object.__setattr__(self, "nameordinals_rva", t.AddressOfNameOrdinals)

        # funcs
        int_size = 4 if pefile.has_nt_headers32 else 8
        pefile.seek_rva(self.funcs_rva)
        object.__setattr__(self, "funcs",
            tuple(int.from_bytes(pefile.read(int_size), "little") for i in range(t.NumberOfFunctions)))

        # names RVA
        pefile.seek_rva(self.names_rva)
        object.__setattr__(self, "names_rva",
            tuple(int.from_bytes(pefile.read(4), "little") for i in range(t.NumberOfNames)))

        #names
        object.__setattr__(self, "names",
            tuple(pefile.read_nullterminated_utf8_rva(rva) for rva in self.names_rva))

        # name ordinals
        pefile.seek_rva(self.nameordinals_rva)
        object.__setattr__(self, "nameordinals",
            tuple(int.from_bytes(pefile.read(2), "little") for i in range(t.NumberOfNames)))

class PEFileInfo:
    __IMAGE_NT_OPTIONAL_HDR32_MAGIC: ClassVar[int] = 0x10b
    __IMAGE_NT_OPTIONAL_HDR64_MAGIC: ClassVar[int] = 0x20b

    __f: BytesIO
    __dos_hdr: ImageDOSHeader
    __nt_sign: int
    __file_hdr: ImageFileHeader
    __opt_hdr: IMAGE_OPTIONAL_HEADER32|IMAGE_OPTIONAL_HEADER64
    __sec_hdrs: tuple[ImageSectionHeader]

    def __init__(self, path: str):
        self.__f = f = open(path, "rb")
        self.__dos_hdr = ImageDOSHeader(f)
        if self.__dos_hdr.magic != b"MZ":
            raise PEFileError("Invalid PE file.")

        # IMAGE_NT_HEADERSの読み込み
        f.seek(self.__dos_hdr.lfanew, 0)
        self.__nt_sign = int.from_bytes(f.read(4), "little")
        if self.__nt_sign != 17744:
            raise PEFileError("Invalid PE file")
        self.__file_hdr = ImageFileHeader(f)
        # 対応機種は無視してオプションヘッダーのサイズとマジックナンバーで判別します。
        if self.__file_hdr.size_of_optionalheader == ctypes.sizeof(IMAGE_OPTIONAL_HEADER32):
            self.__opt_hdr = ImageOptionalHeader(f, IMAGE_OPTIONAL_HEADER32)
            if self.__opt_hdr.magic != self.__IMAGE_NT_OPTIONAL_HDR32_MAGIC:
                raise PEFileError("Invalid PE optional header.")
        elif self.__file_hdr.size_of_optionalheader == ctypes.sizeof(IMAGE_OPTIONAL_HEADER64):
            self.__opt_hdr = ImageOptionalHeader(f, IMAGE_OPTIONAL_HEADER64)
            if self.__opt_hdr.magic != self.__IMAGE_NT_OPTIONAL_HDR64_MAGIC:
                raise PEFileError("Invalid PE optional header.")
        else:
            raise PEFileError("Invalid or not-supported PE optional header.")

        # IMAGE_SECTION_HEADER
        self.__sec_hdrs = tuple(
            ImageSectionHeader(f) for i in range(self.__file_hdr.number_of_sections))

    def __enter__(self):
        return self

    def __exit__(self, *args):
        self.__f.close()
        return False

    def seek(self, offset: int, whence: int) -> int:
        return self.__f.seek(offset, whence)

    def seek_rva(self, rva: int) -> int:
        return self.seek(self.rva_to_offset(rva), 0)

    def read(self, size: int|None) -> bytes:
        return self.__f.read(size)

    def read_rva(self, size: int|None, rva: int) -> bytes:
        self.__f.seek(self.rva_to_offset(rva), 0)
        return self.__f.read(size)

    def readinto(self, buffer) -> int:
        return self.__f.readinto(buffer)

    def readinto_rva(self, buffer, rva: int) -> int:
        self.__f.seek(self.rva_to_offset(rva), 0)
        return self.__f.readinto(buffer)

    @contextmanager
    def filepointer_saver(self):
        x = self.__f.tell()
        yield
        self.__f.seek(x, 0)

    @property
    def dos_header(self) -> ImageDOSHeader:
        return self.__dos_hdr

    @property
    def nt_signature(self) -> int:
        return self.__nt_sign

    @property
    def file_header(self) -> ImageFileHeader:
        return self.__file_hdr

    @property
    def optional_header(self) -> ImageOptionalHeader:
        return self.__opt_hdr

    @property
    def has_nt_headers32(self) -> bool:
        if self.__opt_hdr.magic == self.__IMAGE_NT_OPTIONAL_HDR32_MAGIC:
            if self.__file_hdr.size_of_optionalheader == ctypes.sizeof(IMAGE_OPTIONAL_HEADER32):
                return True
        return False

    @property
    def has_nt_headers64(self) -> bool:
        if self.__opt_hdr.magic == self.__IMAGE_NT_OPTIONAL_HDR64_MAGIC:
            if self.__file_hdr.size_of_optionalheader == ctypes.sizeof(IMAGE_OPTIONAL_HEADER64):
                return True
        return False

    @property
    def section_headers(self) -> tuple[ImageSectionHeader]:
        return self.__sec_hdrs

    @property
    def section_names(self) -> tuple[str]:
        return tuple(hdr.name for hdr in self.__sec_hdrs)

    def find_section_by_name(self, name) -> ImageSectionHeader:
        for sec in self.section_headers:
            if sec.name == name:
                return sec
        raise PEFileError("Invalid section index.")

    def find_section_for_rva(self, rva: int) -> ImageSectionHeader:
        for sec in self.section_headers:
            if sec.va <= rva <= sec.va + sec.virtual_size:
                return sec
        raise PEFileError("Invalid RVA.")

    def rva_to_offset(self, rva: int) -> int:
        sec = self.find_section_for_rva(rva)
        return rva - sec.va + sec.pointer_to_rawdata

    def read_nullterminated_utf8(self, expand: int = 256) -> str:
        s = ""
        while self.__f:
            data = self.__f.read(expand)
            i = data.index(0)
            if i == -1:
                s = s + data.decode("utf-8")
                continue
            s = s + data[0:i].decode("utf-8")
            break
        return s

    def read_nullterminated_utf8_rva(self, rva: int, expand: int = 256) -> str:
        offset = self.rva_to_offset(rva)
        self.__f.seek(offset, 0)
        return self.read_nullterminated_utf8(expand)

    def enum_import_descriptors(self) -> Iterator[ImageImportDescriptor]:
        self.__f.seek(self.rva_to_offset(self.optional_header.datadir_import.va), 0)
        l = []
        while True:
            desc = ImageImportDescriptor(self)
            if desc.characteristics == 0:
                break
            yield desc

    def get_import_descriptors(self) -> tuple[ImageImportDescriptor]:
        return tuple(self.enum_import_descriptors())

    def enum_import_thunks(self, desc: ImageImportDescriptor) -> Iterator[ImageThunkData]:
        self.__f.seek(self.rva_to_offset(desc.firsthunk_rva))
        l = []
        thunk_type = IMAGE_THUNK_DATA32 if self.has_nt_headers32 else IMAGE_THUNK_DATA64
        while True:
            t = ImageThunkData(self, thunk_type)
            if t.ordinal is None and t.data_rva is None:
                break
            l.append(t)
        return tuple(l)

    def enum_original_import_thunks(self, desc: ImageImportDescriptor) -> Iterator[ImageThunkData]:
        self.__f.seek(self.rva_to_offset(desc.characteristics))
        l = []
        thunk_type = IMAGE_THUNK_DATA32 if self.has_nt_headers32 else IMAGE_THUNK_DATA64
        while True:
            t = ImageThunkData(self, thunk_type)
            if t.ordinal is None and t.data_rva is None:
                break
            l.append(t)
        return tuple(l)

    def get_import_thunks(self, desc: ImageImportDescriptor) -> tuple[ImageThunkData]:
        return tuple(self.enum_import_thunks(desc))

    def get_original_import_thunks(self, desc: ImageImportDescriptor) -> tuple[ImageThunkData]:
        return tuple(self.enum_original_import_thunks(desc))

    def get_export_directory(self) -> ImageExportDirectory|None:
        expdir = ImageExportDirectory(self)
        return expdir if hasattr(expdir, "characteristics") else None
#pefileinfo_nativestructure.py
import ctypes

class IMAGE_DOS_HEADER(ctypes.LittleEndianStructure):
    _fields_ = (
        ("e_magic", ctypes.c_char*2),
        ("e_cblp", ctypes.c_uint16),
        ("e_cp", ctypes.c_uint16),
        ("e_crlc", ctypes.c_uint16),
        ("e_cparhdr", ctypes.c_uint16),
        ("e_minalloc", ctypes.c_uint16),
        ("e_maxalloc", ctypes.c_uint16),
        ("e_ss", ctypes.c_uint16),
        ("e_sp", ctypes.c_uint16),
        ("e_csum", ctypes.c_uint16),
        ("e_ip", ctypes.c_uint16),
        ("e_cs", ctypes.c_uint16),
        ("e_lfarlc", ctypes.c_uint16),
        ("e_ovno", ctypes.c_uint16),
        ("e_res", ctypes.c_uint16*4),
        ("e_oemid", ctypes.c_uint16),
        ("e_oeminfo", ctypes.c_uint16),
        ("e_res2", ctypes.c_uint16*10),
        ("e_lfanew", ctypes.c_uint16),
    )

class IMAGE_FILE_HEADER(ctypes.LittleEndianStructure):
    _fields_ = (
        ("Machine", ctypes.c_uint16),
        ("NumberOfSections", ctypes.c_uint16),
        ("TimeDataStamp", ctypes.c_uint32),
        ("PointerToSymbolTable", ctypes.c_uint32),
        ("NumberOfSymbols", ctypes.c_uint32),
        ("SizeOfOptionalHeader", ctypes.c_uint16),
        ("Characteristics", ctypes.c_uint16),
    )

class IMAGE_DATA_DIRECTORY(ctypes.LittleEndianStructure):
    _fields_ = (
        ("VirtualAddress", ctypes.c_uint32),
        ("Size", ctypes.c_uint32),
    )

class IMAGE_OPTIONAL_HEADER32(ctypes.LittleEndianStructure):
    _fields_ = (
        ("Magic", ctypes.c_uint16),
        ("MajorLinkerVersion", ctypes.c_uint8),
        ("MinorLinkerVersion", ctypes.c_uint8),
        ("SizeOfCode", ctypes.c_uint32),
        ("SizeOfInitializedData", ctypes.c_uint32),
        ("SizeOfUninitializedData", ctypes.c_uint32),
        ("AddressOfEntryPoint", ctypes.c_uint32),
        ("BaseOfCode", ctypes.c_uint32),
        ("BaseOfData", ctypes.c_uint32),
        ("ImageBase", ctypes.c_uint32),
        ("SectionAlignment", ctypes.c_uint32),
        ("FileAlignment", ctypes.c_uint32),
        ("MajorOperatingSystemVersion", ctypes.c_uint16),
        ("MinorOperatingSystemVersion", ctypes.c_uint16),
        ("MajorImageVersion", ctypes.c_uint16),
        ("MinorImageVersion", ctypes.c_uint16),
        ("MajorSubsystemVersion", ctypes.c_uint16),
        ("MinorSubsystemVersion", ctypes.c_uint16),
        ("Win32VersionValue", ctypes.c_uint32),
        ("SizeOfImage", ctypes.c_uint32),
        ("SizeOfHeaders", ctypes.c_uint32),
        ("CheckSum", ctypes.c_uint32),
        ("Subsystem", ctypes.c_uint16),
        ("DllCharacteristics", ctypes.c_uint16),
        ("SizeOfStackReserve", ctypes.c_uint32),
        ("SizeOfStackCommit", ctypes.c_uint32),
        ("SizeOfHeapReserve", ctypes.c_uint32),
        ("SizeOfHeapCommit", ctypes.c_uint32),
        ("LoaderFlags", ctypes.c_uint32),
        ("NumberOfRvaAndSizes", ctypes.c_uint32),
        ("DataDirectory", IMAGE_DATA_DIRECTORY*16),
    )

class IMAGE_OPTIONAL_HEADER64(ctypes.LittleEndianStructure):
    _fields_ = (
        ("Magic", ctypes.c_uint16),
        ("MajorLinkerVersion", ctypes.c_uint8),
        ("MinorLinkerVersion", ctypes.c_uint8),
        ("SizeOfCode", ctypes.c_uint32),
        ("SizeOfInitializedData", ctypes.c_uint32),
        ("SizeOfUninitializedData", ctypes.c_uint32),
        ("AddressOfEntryPoint", ctypes.c_uint32),
        ("BaseOfCode", ctypes.c_uint32),
        ("ImageBase", ctypes.c_uint64),
        ("SectionAlignment", ctypes.c_uint32),
        ("FileAlignment", ctypes.c_uint32),
        ("MajorOperatingSystemVersion", ctypes.c_uint16),
        ("MinorOperatingSystemVersion", ctypes.c_uint16),
        ("MajorImageVersion", ctypes.c_uint16),
        ("MinorImageVersion", ctypes.c_uint16),
        ("MajorSubsystemVersion", ctypes.c_uint16),
        ("MinorSubsystemVersion", ctypes.c_uint16),
        ("Win32VersionValue", ctypes.c_uint32),
        ("SizeOfImage", ctypes.c_uint32),
        ("SizeOfHeaders", ctypes.c_uint32),
        ("CheckSum", ctypes.c_uint32),
        ("Subsystem", ctypes.c_uint16),
        ("DllCharacteristics", ctypes.c_uint16),
        ("SizeOfStackReserve", ctypes.c_uint64),
        ("SizeOfStackCommit", ctypes.c_uint64),
        ("SizeOfHeapReserve", ctypes.c_uint64),
        ("SizeOfHeapCommit", ctypes.c_uint64),
        ("LoaderFlags", ctypes.c_uint32),
        ("NumberOfRvaAndSizes", ctypes.c_uint32),
        ("DataDirectory", IMAGE_DATA_DIRECTORY*16),
    )

class IMAGE_SECTION_HEADER(ctypes.LittleEndianStructure):
    _fields_ = (
        ("Name", ctypes.c_uint8 * 8),
        ("VirtualSize", ctypes.c_uint32),
        ("VirtualAddress", ctypes.c_uint32),
        ("SizeOfRawData", ctypes.c_uint32),
        ("PointerToRawData", ctypes.c_uint32),
        ("PointerToRelocations", ctypes.c_uint32),
        ("PointerToLinenumbers", ctypes.c_uint32),
        ("NumberOfRelocations", ctypes.c_uint16),
        ("NumberOfLinenumbers", ctypes.c_uint16),
        ("Characteristics", ctypes.c_uint32),
    )

class IMAGE_IMPORT_DESCRIPTOR(ctypes.LittleEndianStructure):
    _fields_ = (
        ("Characteristics", ctypes.c_uint32),
        ("TimeDateStamp", ctypes.c_uint32),
        ("ForwarderChain", ctypes.c_uint32),
        ("Name", ctypes.c_uint32),
        ("FirstThunk", ctypes.c_uint32),
    )

class IMAGE_THUNK_DATA32(ctypes.LittleEndianStructure):
    _fields_ = (
        ("Value", ctypes.c_uint32),
    )

class IMAGE_THUNK_DATA64(ctypes.LittleEndianStructure):
    _fields_ = (
        ("Value", ctypes.c_uint64),
    )

class IMAGE_EXPORT_DIRECTORY(ctypes.LittleEndianStructure):
    _fields_ = (
        ("Characteristics", ctypes.c_uint32),
        ("TimeDateStamp", ctypes.c_uint32),
        ("MajorVersion", ctypes.c_uint16),
        ("MinorVersion", ctypes.c_uint16),
        ("Name", ctypes.c_uint32),
        ("Base", ctypes.c_uint32),
        ("NumberOfFunctions", ctypes.c_uint32),
        ("NumberOfNames", ctypes.c_uint32),
        ("AddressOfFunctions", ctypes.c_uint32),
        ("AddressOfNames", ctypes.c_uint32),
        ("AddressOfNameOrdinals", ctypes.c_uint32),
    )