/* $Id: ldrPE.cpp $ */
/** @file
 * IPRT - Binary Image Loader, Portable Executable (PE).
 */

/*
 * Copyright (C) 2006-2023 Oracle and/or its affiliates.
 *
 * This file is part of VirtualBox base platform packages, as
 * available from https://www.virtualbox.org.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation, in version 3 of the
 * License.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see <https://www.gnu.org/licenses>.
 *
 * The contents of this file may alternatively be used under the terms
 * of the Common Development and Distribution License Version 1.0
 * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
 * in the VirtualBox distribution, in which case the provisions of the
 * CDDL are applicable instead of those of the GPL.
 *
 * You may elect to license modified versions of this file under the
 * terms and conditions of either the GPL or the CDDL or both.
 *
 * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
 */


/*********************************************************************************************************************************
*   Header Files                                                                                                                 *
*********************************************************************************************************************************/
#define LOG_GROUP RTLOGGROUP_LDR
#include <iprt/ldr.h>
#include "internal/iprt.h"

#include <iprt/assert.h>
#include <iprt/asm.h>
#include <iprt/dbg.h>
#include <iprt/err.h>
#include <iprt/latin1.h>
#include <iprt/log.h>
#include <iprt/md5.h>
#include <iprt/mem.h>
#include <iprt/path.h>
#include <iprt/sha.h>
#include <iprt/string.h>
#include <iprt/utf16.h>
#include <iprt/x86.h>
#if !defined(IPRT_WITHOUT_LDR_VERIFY) || !defined(IPRT_WITHOUT_LDR_PAGE_HASHING)
# include <iprt/zero.h>
#endif
#ifndef IPRT_WITHOUT_LDR_VERIFY
# include <iprt/crypto/pkcs7.h>
# include <iprt/crypto/spc.h>
# include <iprt/crypto/x509.h>
#endif
#include <iprt/formats/codeview.h>
#include <iprt/formats/pecoff.h>
#include "internal/ldr.h"


/*********************************************************************************************************************************
*   Defined Constants And Macros                                                                                                 *
*********************************************************************************************************************************/
/** Converts rva to a type.
 * @param   pvBits  Pointer to base of image bits.
 * @param   rva     Relative virtual address.
 * @param   type    Type.
 */
#define PE_RVA2TYPE(pvBits, rva, type)  ((type) ((uintptr_t)pvBits + (uintptr_t)(rva)) )

/** The max size of the security directory. */
#ifdef IN_RING3
# define RTLDRMODPE_MAX_SECURITY_DIR_SIZE   _4M
#else
# define RTLDRMODPE_MAX_SECURITY_DIR_SIZE   _1M
#endif


/*********************************************************************************************************************************
*   Structures and Typedefs                                                                                                      *
*********************************************************************************************************************************/
/**
 * The PE loader structure.
 */
typedef struct RTLDRMODPE
{
    /** Core module structure. */
    RTLDRMODINTERNAL        Core;
    /** Pointer to internal copy of image bits.
     * @todo the reader should take care of this. */
    void                   *pvBits;
    /** The offset of the NT headers. */
    RTFOFF                  offNtHdrs;
    /** The offset of the first byte after the section table. */
    RTFOFF                  offEndOfHdrs;

    /** The machine type (IMAGE_FILE_HEADER::Machine). */
    uint16_t                u16Machine;
    /** The file flags (IMAGE_FILE_HEADER::Characteristics). */
    uint16_t                fFile;
    /** Number of sections (IMAGE_FILE_HEADER::NumberOfSections). */
    unsigned                cSections;
    /** Pointer to an array of the section headers related to the file. */
    PIMAGE_SECTION_HEADER   paSections;

    /** The RVA of the entry point (IMAGE_OPTIONAL_HEADER32::AddressOfEntryPoint). */
    RTUINTPTR               uEntryPointRVA;
    /** The base address of the image at link time (IMAGE_OPTIONAL_HEADER32::ImageBase). */
    RTUINTPTR               uImageBase;
    /** The size of the loaded image (IMAGE_OPTIONAL_HEADER32::SizeOfImage). */
    uint32_t                cbImage;
    /** Size of the header (IMAGE_OPTIONAL_HEADER32::SizeOfHeaders). */
    uint32_t                cbHeaders;
    /** Section alignment (IMAGE_OPTIONAL_HEADER32::SectionAlignment). */
    uint32_t                uSectionAlign;
    /** The image timestamp. */
    uint32_t                uTimestamp;
    /** The number of imports.  UINT32_MAX if not determined. */
    uint32_t                cImports;
    /** Set if the image is 64-bit, clear if 32-bit. */
    bool                    f64Bit;
    /** The import data directory entry. */
    IMAGE_DATA_DIRECTORY    ImportDir;
    /** The base relocation data directory entry. */
    IMAGE_DATA_DIRECTORY    RelocDir;
    /** The export data directory entry. */
    IMAGE_DATA_DIRECTORY    ExportDir;
    /** The debug directory entry. */
    IMAGE_DATA_DIRECTORY    DebugDir;
    /** The security directory entry. */
    IMAGE_DATA_DIRECTORY    SecurityDir;
    /** The exception data directory entry. */
    IMAGE_DATA_DIRECTORY    ExceptionDir;

    /** Offset of the first PKCS \#7 SignedData signature if present. */
    uint32_t                offPkcs7SignedData;
    /** Size of the first PKCS \#7 SignedData. */
    uint32_t                cbPkcs7SignedData;

    /** Copy of the optional header field DllCharacteristics. */
    uint16_t                fDllCharacteristics;
} RTLDRMODPE;
/** Pointer to the instance data for a PE loader module. */
typedef RTLDRMODPE *PRTLDRMODPE;


/**
 * PE Loader module operations.
 *
 * The PE loader has one operation which is a bit different between 32-bit and 64-bit PE images,
 * and for historical and performance reasons have been split into separate functions. Thus the
 * PE loader extends the RTLDROPS structure with this one entry.
 */
typedef struct RTLDROPSPE
{
    /** The usual ops. */
    RTLDROPS Core;

    /**
     * Resolves all imports.
     *
     * @returns iprt status code.
     * @param   pModPe          Pointer to the PE loader module structure.
     * @param   pvBitsR         Where to read raw image bits. (optional)
     * @param   pvBitsW         Where to store the imports. The size of this buffer is equal or
     *                          larger to the value returned by pfnGetImageSize().
     * @param   pfnGetImport    The callback function to use to resolve imports (aka unresolved externals).
     * @param   pvUser          User argument to pass to the callback.
     */
    DECLCALLBACKMEMBER(int, pfnResolveImports,(PRTLDRMODPE pModPe, const void *pvBitsR, void *pvBitsW, PFNRTLDRIMPORT pfnGetImport, void *pvUser));

    /** Dummy entry to make sure we've initialized it all. */
    RTUINT  uDummy;
} RTLDROPSPE, *PRTLDROPSPE;


/**
 * PE hash context union.
 */
typedef union RTLDRPEHASHCTXUNION
{
    RTSHA512CONTEXT Sha512;
    RTSHA384CONTEXT Sha384;
    RTSHA256CONTEXT Sha256;
    RTSHA1CONTEXT   Sha1;
    RTMD5CONTEXT    Md5;
} RTLDRPEHASHCTXUNION;
/** Pointer to a PE hash context union. */
typedef RTLDRPEHASHCTXUNION *PRTLDRPEHASHCTXUNION;


/**
 * PE hash digests
 */
typedef union RTLDRPEHASHRESUNION
{
    uint8_t abSha512[RTSHA512_HASH_SIZE];
    uint8_t abSha384[RTSHA384_HASH_SIZE];
    uint8_t abSha256[RTSHA256_HASH_SIZE];
    uint8_t abSha1[RTSHA1_HASH_SIZE];
    uint8_t abMd5[RTMD5_HASH_SIZE];
} RTLDRPEHASHRESUNION;
/** Pointer to a PE hash work set. */
typedef RTLDRPEHASHRESUNION *PRTLDRPEHASHRESUNION;

/**
 * Special places to watch out for when hashing a PE image.
 */
typedef struct RTLDRPEHASHSPECIALS
{
    uint32_t    cbToHash;
    uint32_t    offCksum;
    uint32_t    cbCksum;
    uint32_t    offSecDir;
    uint32_t    cbSecDir;
    uint32_t    offEndSpecial;
} RTLDRPEHASHSPECIALS;
/** Pointer to the structure with the special hash places. */
typedef RTLDRPEHASHSPECIALS *PRTLDRPEHASHSPECIALS;


#ifndef IPRT_WITHOUT_LDR_VERIFY
/**
 * Parsed data for one signature.
 */
typedef struct RTLDRPESIGNATUREONE
{
    /** The outer content info wrapper. */
    PRTCRPKCS7CONTENTINFO       pContentInfo;
    /** Pointer to the decoded SignedData inside the ContentInfo member. */
    PRTCRPKCS7SIGNEDDATA        pSignedData;
    /** Pointer to the indirect data content. */
    PRTCRSPCINDIRECTDATACONTENT pIndData;
    /** The digest type employed by the signature. */
    RTDIGESTTYPE                enmDigest;
    /** Set if we've already validate the image hash. */
    bool                        fValidatedImageHash;
    /** The signature number. */
    uint16_t                    iSignature;
    /** Hash result. */
    RTLDRPEHASHRESUNION         HashRes;
} RTLDRPESIGNATUREONE;
/** Pointer to the parsed data of one signature. */
typedef RTLDRPESIGNATUREONE *PRTLDRPESIGNATUREONE;

/**
 * Parsed signature data.
 */
typedef struct RTLDRPESIGNATURE
{
    /** Pointer to the raw signatures.  This is allocated in the continuation of
     * this structure to keep things simple.  The size is given by  the security
     * export directory. */
    WIN_CERTIFICATE const      *pRawData;
    /** The outer content info wrapper (primary signature). */
    RTCRPKCS7CONTENTINFO        PrimaryContentInfo;
    /** The info for the primary signature. */
    RTLDRPESIGNATUREONE         Primary;
    /** Number of nested signatures (zero if none). */
    uint16_t                    cNested;
    /** Pointer to an array of nested signatures (NULL if none). */
    PRTLDRPESIGNATUREONE        paNested;
    /** Hash scratch data. */
    RTLDRPEHASHCTXUNION         HashCtx;
} RTLDRPESIGNATURE;
/** Pointed to SigneData parsing stat and output. */
typedef RTLDRPESIGNATURE *PRTLDRPESIGNATURE;
#endif


/*********************************************************************************************************************************
*   Internal Functions                                                                                                           *
*********************************************************************************************************************************/
static void rtldrPEConvert32BitOptionalHeaderTo64Bit(PIMAGE_OPTIONAL_HEADER64 pOptHdr);
static void rtldrPEConvert32BitLoadConfigTo64Bit(PIMAGE_LOAD_CONFIG_DIRECTORY64 pLoadCfg);
static int  rtldrPEApplyFixups(PRTLDRMODPE pModPe, const void *pvBitsR, void *pvBitsW, RTUINTPTR BaseAddress, RTUINTPTR OldBaseAddress);
#ifndef IPRT_WITHOUT_LDR_PAGE_HASHING
static int  rtLdrPE_QueryPageHashes(PRTLDRMODPE pModPe, RTDIGESTTYPE enmDigest, void *pvBuf, size_t cbBuf, size_t *pcbRet);
static uint32_t rtLdrPE_GetHashablePages(PRTLDRMODPE pModPe);
#endif



/**
 * Reads a section of a PE image given by RVA + size, using mapped bits if
 * available or allocating heap memory and reading from the file.
 *
 * @returns IPRT status code.
 * @param   pThis               Pointer to the PE loader module structure.
 * @param   pvBits              Read only bits if available. NULL if not.
 * @param   uRva                The RVA to read at.
 * @param   cbMem               The number of bytes to read.
 * @param   ppvMem              Where to return the memory on success (heap or
 *                              inside pvBits).
 */
static int rtldrPEReadPartByRva(PRTLDRMODPE pThis, const void *pvBits, uint32_t uRva, uint32_t cbMem, void const **ppvMem)
{
    *ppvMem = NULL;
    if (!cbMem)
        return VINF_SUCCESS;

    /*
     * Use bits if we've got some.
     */
    if (pvBits)
    {
        *ppvMem = (uint8_t const *)pvBits + uRva;
        return VINF_SUCCESS;
    }
    if (pThis->pvBits)
    {
        *ppvMem = (uint8_t const *)pThis->pvBits + uRva;
        return VINF_SUCCESS;
    }

    /*
     * Allocate a buffer and read the bits from the file (or whatever).
     */
    if (!pThis->Core.pReader)
        return VERR_ACCESS_DENIED;

    uint8_t *pbMem = (uint8_t *)RTMemAllocZ(cbMem);
    if (!pbMem)
        return VERR_NO_MEMORY;
    *ppvMem = pbMem;

    /* Do the reading on a per section base. */
    uint64_t const cbFile = pThis->Core.pReader->pfnSize(pThis->Core.pReader);
    for (;;)
    {
        /* Translate the RVA into a file offset. */
        uint32_t offFile  = uRva;
        uint32_t cbToRead = cbMem;
        uint32_t cbToAdv  = cbMem;

        if (uRva < pThis->paSections[0].VirtualAddress)
        {
            /* Special header section. */
            cbToRead = pThis->paSections[0].VirtualAddress - uRva;
            if (cbToRead > cbMem)
                cbToRead = cbMem;
            cbToAdv = cbToRead;

            /* The following capping is an approximation. */
            uint32_t offFirstRawData = RT_ALIGN(pThis->cbHeaders, _4K);
            if (   pThis->paSections[0].PointerToRawData > 0
                && pThis->paSections[0].SizeOfRawData > 0)
                offFirstRawData = pThis->paSections[0].PointerToRawData;
            if (offFile >= offFirstRawData)
                cbToRead = 0;
            else if (offFile + cbToRead > offFirstRawData)
                cbToRead = offFile - offFirstRawData;
        }
        else
        {
            /* Find the matching section and its mapping size. */
            uint32_t j          = 0;
            uint32_t cbMapping  = 0;
            uint32_t offSection = 0;
            while (j < pThis->cSections)
            {
                cbMapping = (j + 1 < pThis->cSections ? pThis->paSections[j + 1].VirtualAddress : pThis->cbImage)
                          - pThis->paSections[j].VirtualAddress;
                offSection = uRva - pThis->paSections[j].VirtualAddress;
                if (offSection < cbMapping)
                    break;
                j++;
            }
            if (j >= cbMapping)
                break; /* This shouldn't happen, just return zeros if it does. */

            /* Adjust the sizes and calc the file offset. */
            if (offSection + cbToAdv > cbMapping)
                cbToAdv = cbToRead = cbMapping - offSection;

            if (   pThis->paSections[j].PointerToRawData > 0
                && pThis->paSections[j].SizeOfRawData > 0)
            {
                offFile = offSection;
                if (offFile + cbToRead > pThis->paSections[j].SizeOfRawData)
                    cbToRead = pThis->paSections[j].SizeOfRawData - offFile;
                offFile += pThis->paSections[j].PointerToRawData;
            }
            else
            {
                offFile  = UINT32_MAX;
                cbToRead = 0;
            }
        }

        /* Perform the read after adjusting a little (paranoia). */
        if (offFile > cbFile)
            cbToRead = 0;
        if (cbToRead)
        {
            if ((uint64_t)offFile + cbToRead > cbFile)
                cbToRead = (uint32_t)(cbFile - (uint64_t)offFile);
            int rc = pThis->Core.pReader->pfnRead(pThis->Core.pReader, pbMem, cbToRead, offFile);
            if (RT_FAILURE(rc))
            {
                RTMemFree((void *)*ppvMem);
                *ppvMem = NULL;
                return rc;
            }
        }

        /* Advance */
        if (cbMem <= cbToAdv)
            break;
        cbMem -= cbToAdv;
        pbMem += cbToAdv;
        uRva  += cbToAdv;
    }

    return VINF_SUCCESS;
}


/**
 * Reads a part of a PE file from the file and into a heap block.
 *
 * @returns IRPT status code.
 * @param   pThis               Pointer to the PE loader module structure..
 * @param   offFile             The file offset.
 * @param   cbMem               The number of bytes to read.
 * @param   ppvMem              Where to return the heap block with the bytes on
 *                              success.
 */
static int rtldrPEReadPartFromFile(PRTLDRMODPE pThis, uint32_t offFile, uint32_t cbMem, void const **ppvMem)
{
    *ppvMem = NULL;
    if (!cbMem)
        return VINF_SUCCESS;

    /*
     * Allocate a buffer and read the bits from the file (or whatever).
     */
    if (!pThis->Core.pReader)
        return VERR_ACCESS_DENIED;

    uint8_t *pbMem = (uint8_t *)RTMemAlloc(cbMem);
    if (!pbMem)
        return VERR_NO_MEMORY;

    int rc = pThis->Core.pReader->pfnRead(pThis->Core.pReader, pbMem, cbMem, offFile);
    if (RT_FAILURE(rc))
    {
        RTMemFree((void *)*ppvMem);
        return rc;
    }

    *ppvMem = pbMem;
    return VINF_SUCCESS;
}


/**
 * Reads a part of a PE image into memory one way or another.
 *
 * Either the RVA or the offFile must be valid.  We'll prefer the RVA if
 * possible.
 *
 * @returns IPRT status code.
 * @param   pThis               Pointer to the PE loader module structure.
 * @param   pvBits              Read only bits if available. NULL if not.
 * @param   uRva                The RVA to read at.
 * @param   offFile             The file offset.
 * @param   cbMem               The number of bytes to read.
 * @param   ppvMem              Where to return the memory on success (heap or
 *                              inside pvBits).
 */
static int rtldrPEReadPart(PRTLDRMODPE pThis, const void *pvBits, RTFOFF offFile, RTLDRADDR uRva,
                           uint32_t cbMem, void const **ppvMem)
{
    if (   uRva == NIL_RTLDRADDR
        || uRva         > pThis->cbImage
        || cbMem        > pThis->cbImage
        || uRva + cbMem > pThis->cbImage)
    {
        if (offFile < 0 || offFile >= UINT32_MAX)
            return VERR_INVALID_PARAMETER;
        return rtldrPEReadPartFromFile(pThis, (uint32_t)offFile, cbMem, ppvMem);
    }
    return rtldrPEReadPartByRva(pThis, pvBits, (uint32_t)uRva, cbMem, ppvMem);
}


/**
 * Frees up memory returned by rtldrPEReadPart*.
 *
 * @param   pThis               Pointer to the PE loader module structure..
 * @param   pvBits              Read only bits if available. NULL if not..
 * @param   pvMem               The memory we were given by the reader method.
 */
static void rtldrPEFreePart(PRTLDRMODPE pThis, const void *pvBits, void const *pvMem)
{
    if (!pvMem)
        return;

    if (pvBits        && (uintptr_t)pvMem - (uintptr_t)pvBits        < pThis->cbImage)
        return;
    if (pThis->pvBits && (uintptr_t)pvMem - (uintptr_t)pThis->pvBits < pThis->cbImage)
        return;

    RTMemFree((void *)pvMem);
}


/**
 * Reads a section of a PE image given by RVA + size.
 *
 * @returns IPRT status code.
 * @param   pThis               Pointer to the PE loader module structure.
 * @param   pvBits              Read only bits if available. NULL if not.
 * @param   uRva                The RVA to read at.
 * @param   cbMem               The number of bytes to read.
 * @param   pvDst               The destination buffer.
 */
static int rtldrPEReadPartByRvaInfoBuf(PRTLDRMODPE pThis, const void *pvBits, uint32_t uRva, uint32_t cbMem, void *pvDst)
{
    /** @todo consider optimizing this.   */
    const void *pvSrc = NULL;
    int rc = rtldrPEReadPartByRva(pThis, pvBits, uRva, cbMem, &pvSrc);
    if (RT_SUCCESS(rc))
    {
        memcpy(pvDst, pvSrc, cbMem);
        rtldrPEFreePart(pThis, NULL, pvSrc);
    }
    return rc;
}





/** @interface_method_impl{RTLDROPS,pfnGetImageSize} */
static DECLCALLBACK(size_t) rtldrPEGetImageSize(PRTLDRMODINTERNAL pMod)
{
    PRTLDRMODPE pModPe = (PRTLDRMODPE)pMod;
    return pModPe->cbImage;
}


/**
 * Reads the image into memory.
 *
 * @returns iprt status code.
 * @param   pModPe      The PE module.
 * @param   pvBits      Where to store the bits, this buffer is at least pItem->Core.cbImage in size.
 */
static int rtldrPEGetBitsNoImportsNorFixups(PRTLDRMODPE pModPe, void *pvBits)
{
    /*
     * Both these checks are related to pfnDone().
     */
    PRTLDRREADER pReader = pModPe->Core.pReader;
    if (!pReader)
    {
        AssertMsgFailed(("You've called done!\n"));
        return VERR_WRONG_ORDER;
    }
    if (!pvBits)
        return VERR_NO_MEMORY;

    /*
     * Zero everything (could be done per section).
     */
    memset(pvBits, 0, pModPe->cbImage);

#ifdef PE_FILE_OFFSET_EQUALS_RVA
    /*
     * Read the entire image / file.
     */
    const uint64_t cbRawImage = pReader->pfnSize(pReader)
    rc = pReader->pfnRead(pReader, pvBits, RT_MIN(pModPe->cbImage, cbRawImage), 0);
    if (RT_FAILURE(rc))
        Log(("rtldrPE: %s: Reading %#x bytes at offset %#x failed, %Rrc!!! (the entire image)\n",
             pReader->pfnLogName(pReader), RT_MIN(pModPe->cbImage, cbRawImage), 0, rc));
#else

    /*
     * Read the headers.
     */
    int rc = pReader->pfnRead(pReader, pvBits, pModPe->cbHeaders, 0);
    if (RT_SUCCESS(rc))
    {
        /*
         * Read the sections.
         */
        PIMAGE_SECTION_HEADER pSH = pModPe->paSections;
        for (unsigned cLeft = pModPe->cSections; cLeft > 0; cLeft--, pSH++)
            if (   pSH->SizeOfRawData
                && pSH->Misc.VirtualSize
                && !(pSH->Characteristics & IMAGE_SCN_TYPE_NOLOAD))
            {
                uint32_t const cbToRead = RT_MIN(pSH->SizeOfRawData, pModPe->cbImage - pSH->VirtualAddress);
                Assert(pSH->VirtualAddress <= pModPe->cbImage);

                rc = pReader->pfnRead(pReader, (uint8_t *)pvBits + pSH->VirtualAddress, cbToRead, pSH->PointerToRawData);
                if (RT_FAILURE(rc))
                {
                    Log(("rtldrPE: %s: Reading %#x bytes at offset %#x failed, %Rrc - section #%d '%.*s'!!!\n",
                         pReader->pfnLogName(pReader), pSH->SizeOfRawData, pSH->PointerToRawData, rc,
                         pSH - pModPe->paSections, sizeof(pSH->Name), pSH->Name));
                    break;
                }
            }
    }
    else
        Log(("rtldrPE: %s: Reading %#x bytes at offset %#x failed, %Rrc!!!\n",
             pReader->pfnLogName(pReader), pModPe->cbHeaders, 0, rc));
#endif
    return rc;
}


/**
 * Reads the bits into the internal buffer pointed to by PRTLDRMODPE::pvBits.
 *
 * @returns iprt status code.
 * @param   pModPe      The PE module.
 */
static int rtldrPEReadBits(PRTLDRMODPE pModPe)
{
    Assert(!pModPe->pvBits);
    void *pvBitsW = RTMemAllocZ(pModPe->cbImage);
    if (!pvBitsW)
        return VERR_NO_MEMORY;
    int rc = rtldrPEGetBitsNoImportsNorFixups(pModPe, pvBitsW);
    if (RT_SUCCESS(rc))
        pModPe->pvBits = pvBitsW;
    else
        RTMemFree(pvBitsW);
    return rc;
}


/** @interface_method_impl{RTLDROPS,pfnGetBits} */
static DECLCALLBACK(int) rtldrPEGetBits(PRTLDRMODINTERNAL pMod, void *pvBits, RTUINTPTR BaseAddress, PFNRTLDRIMPORT pfnGetImport, void *pvUser)
{
    PRTLDRMODPE pModPe = (PRTLDRMODPE)pMod;

    /*
     * Read the image.
     */
    int rc = rtldrPEGetBitsNoImportsNorFixups(pModPe, pvBits);
    if (RT_SUCCESS(rc))
    {
        /*
         * Resolve imports.
         */
        if (pfnGetImport)
            rc = ((PRTLDROPSPE)pMod->pOps)->pfnResolveImports(pModPe, pvBits, pvBits, pfnGetImport, pvUser);
        if (RT_SUCCESS(rc))
        {
            /*
             * Apply relocations.
             */
            rc = rtldrPEApplyFixups(pModPe, pvBits, pvBits, BaseAddress, pModPe->uImageBase);
            if (RT_SUCCESS(rc))
                return rc;
            AssertMsgFailed(("Failed to apply fixups. rc=%Rrc\n", rc));
        }
#ifndef IN_SUP_HARDENED_R3
        else
            AssertMsgFailed(("Failed to resolve imports. rc=%Rrc\n", rc));
#endif
    }
    return rc;
}


/* The image_thunk_data32/64 structures are not very helpful except for getting RSI. keep them around till all the code has been converted. */
typedef struct _IMAGE_THUNK_DATA32
{
    union
    {
        uint32_t  ForwarderString;
        uint32_t  Function;
        uint32_t  Ordinal;
        uint32_t  AddressOfData;
    } u1;
} IMAGE_THUNK_DATA32;
typedef IMAGE_THUNK_DATA32 *PIMAGE_THUNK_DATA32;


/** @copydoc RTLDROPSPE::pfnResolveImports */
static DECLCALLBACK(int) rtldrPEResolveImports32(PRTLDRMODPE pModPe, const void *pvBitsR, void *pvBitsW, PFNRTLDRIMPORT pfnGetImport, void *pvUser)
{
    /*
     * Check if there is actually anything to work on.
     */
    if (    !pModPe->ImportDir.VirtualAddress
        ||  !pModPe->ImportDir.Size)
        return 0;

    /*
     * Walk the IMAGE_IMPORT_DESCRIPTOR table.
     */
    int                         rc = VINF_SUCCESS;
    PIMAGE_IMPORT_DESCRIPTOR    pImps;
    for (pImps = PE_RVA2TYPE(pvBitsR, pModPe->ImportDir.VirtualAddress, PIMAGE_IMPORT_DESCRIPTOR);
         !rc && pImps->Name != 0 && pImps->FirstThunk != 0;
         pImps++)
    {
        AssertReturn(pImps->Name < pModPe->cbImage, VERR_BAD_EXE_FORMAT);
        const char *pszModName = PE_RVA2TYPE(pvBitsR, pImps->Name, const char *);
        AssertReturn(pImps->FirstThunk < pModPe->cbImage, VERR_BAD_EXE_FORMAT);
        AssertReturn(pImps->u.OriginalFirstThunk < pModPe->cbImage, VERR_BAD_EXE_FORMAT);

        Log3(("RTLdrPE: Import descriptor: %s\n", pszModName));
        Log4(("RTLdrPE:   OriginalFirstThunk = %#RX32\n"
              "RTLdrPE:   TimeDateStamp      = %#RX32\n"
              "RTLdrPE:   ForwarderChain     = %#RX32\n"
              "RTLdrPE:   Name               = %#RX32\n"
              "RTLdrPE:   FirstThunk         = %#RX32\n",
              pImps->u.OriginalFirstThunk, pImps->TimeDateStamp,
              pImps->ForwarderChain, pImps->Name, pImps->FirstThunk));

        /*
         * Walk the thunks table(s).
         */
        PIMAGE_THUNK_DATA32 pFirstThunk = PE_RVA2TYPE(pvBitsW, pImps->FirstThunk, PIMAGE_THUNK_DATA32); /* update this. */
        PIMAGE_THUNK_DATA32 pThunk      = pImps->u.OriginalFirstThunk == 0                              /* read from this. */
                                        ? PE_RVA2TYPE(pvBitsR, pImps->FirstThunk, PIMAGE_THUNK_DATA32)
                                        : PE_RVA2TYPE(pvBitsR, pImps->u.OriginalFirstThunk, PIMAGE_THUNK_DATA32);
        while (!rc && pThunk->u1.Ordinal != 0)
        {
            RTUINTPTR Value = 0;
            if (pThunk->u1.Ordinal & IMAGE_ORDINAL_FLAG32)
            {
                rc = pfnGetImport(&pModPe->Core, pszModName, NULL, IMAGE_ORDINAL32(pThunk->u1.Ordinal), &Value, pvUser);
                Log4((RT_SUCCESS(rc) ? "RTLdrPE:  %RTptr #%u\n" : "RTLdrPE:  %08RX32 #%u rc=%Rrc\n",
                      (uint32_t)Value, IMAGE_ORDINAL32(pThunk->u1.Ordinal), rc));
            }
            else if (   pThunk->u1.Ordinal > 0
                     && pThunk->u1.Ordinal < pModPe->cbImage)
            {
                rc = pfnGetImport(&pModPe->Core, pszModName,
                                  PE_RVA2TYPE(pvBitsR, (char*)(uintptr_t)pThunk->u1.AddressOfData + 2, const char *),
                                  ~0U, &Value, pvUser);
                Log4((RT_SUCCESS(rc) ? "RTLdrPE:  %RTptr %s\n" : "RTLdrPE:  %08RX32 %s rc=%Rrc\n",
                      (uint32_t)Value, PE_RVA2TYPE(pvBitsR, (char*)(uintptr_t)pThunk->u1.AddressOfData + 2, const char *), rc));
            }
            else
            {
                AssertMsgFailed(("bad import data thunk!\n"));
                rc = VERR_BAD_EXE_FORMAT;
            }
            pFirstThunk->u1.Function = (uint32_t)Value;
            if (pFirstThunk->u1.Function != Value)
            {
                AssertMsgFailed(("external symbol address to big!\n"));
                rc = VERR_ADDRESS_CONFLICT; /** @todo get me a better error status code. */
            }
            pThunk++;
            pFirstThunk++;
        }
    }

    return rc;
}


/* The image_thunk_data32/64 structures are not very helpful except for getting RSI. keep them around till all the code has been converted. */
typedef struct _IMAGE_THUNK_DATA64
{
    union
    {
        uint64_t  ForwarderString;
        uint64_t  Function;
        uint64_t  Ordinal;
        uint64_t  AddressOfData;
    } u1;
} IMAGE_THUNK_DATA64;
typedef IMAGE_THUNK_DATA64 *PIMAGE_THUNK_DATA64;


/** @copydoc RTLDROPSPE::pfnResolveImports */
static DECLCALLBACK(int) rtldrPEResolveImports64(PRTLDRMODPE pModPe, const void *pvBitsR, void *pvBitsW,
                                                 PFNRTLDRIMPORT pfnGetImport, void *pvUser)
{
    /*
     * Check if there is actually anything to work on.
     */
    if (    !pModPe->ImportDir.VirtualAddress
        ||  !pModPe->ImportDir.Size)
        return 0;

    /*
     * Walk the IMAGE_IMPORT_DESCRIPTOR table.
     */
    int                         rc = VINF_SUCCESS;
    PIMAGE_IMPORT_DESCRIPTOR    pImps;
    for (pImps = PE_RVA2TYPE(pvBitsR, pModPe->ImportDir.VirtualAddress, PIMAGE_IMPORT_DESCRIPTOR);
         !rc && pImps->Name != 0 && pImps->FirstThunk != 0;
         pImps++)
    {
        AssertReturn(pImps->Name < pModPe->cbImage, VERR_BAD_EXE_FORMAT);
        const char *pszModName = PE_RVA2TYPE(pvBitsR, pImps->Name, const char *);
        AssertReturn(pImps->FirstThunk < pModPe->cbImage, VERR_BAD_EXE_FORMAT);
        AssertReturn(pImps->u.OriginalFirstThunk < pModPe->cbImage, VERR_BAD_EXE_FORMAT);

        Log3(("RTLdrPE: Import descriptor: %s\n", pszModName));
        Log4(("RTLdrPE:   OriginalFirstThunk = %#RX32\n"
              "RTLdrPE:   TimeDateStamp      = %#RX32\n"
              "RTLdrPE:   ForwarderChain     = %#RX32\n"
              "RTLdrPE:   Name               = %#RX32\n"
              "RTLdrPE:   FirstThunk         = %#RX32\n",
              pImps->u.OriginalFirstThunk, pImps->TimeDateStamp,
              pImps->ForwarderChain, pImps->Name, pImps->FirstThunk));

        /*
         * Walk the thunks table(s).
         */
        PIMAGE_THUNK_DATA64 pFirstThunk = PE_RVA2TYPE(pvBitsW, pImps->FirstThunk, PIMAGE_THUNK_DATA64); /* update this. */
        PIMAGE_THUNK_DATA64 pThunk      = pImps->u.OriginalFirstThunk == 0                              /* read from this. */
                                        ? PE_RVA2TYPE(pvBitsR, pImps->FirstThunk, PIMAGE_THUNK_DATA64)
                                        : PE_RVA2TYPE(pvBitsR, pImps->u.OriginalFirstThunk, PIMAGE_THUNK_DATA64);
        while (!rc && pThunk->u1.Ordinal != 0)
        {
            RTUINTPTR Value = 0;
            if (pThunk->u1.Ordinal & IMAGE_ORDINAL_FLAG64)
            {
                rc = pfnGetImport(&pModPe->Core, pszModName, NULL, (unsigned)IMAGE_ORDINAL64(pThunk->u1.Ordinal), &Value, pvUser);
                Log4((RT_SUCCESS(rc) ? "RTLdrPE:  %016RX64 #%u\n" : "RTLdrPE:  %016RX64 #%u rc=%Rrc\n",
                      (uint64_t)Value, (unsigned)IMAGE_ORDINAL64(pThunk->u1.Ordinal), rc));
            }
            else if (   pThunk->u1.Ordinal > 0
                     && pThunk->u1.Ordinal < pModPe->cbImage)
            {
                /** @todo add validation of the string pointer! */
                rc = pfnGetImport(&pModPe->Core, pszModName, PE_RVA2TYPE(pvBitsR, (uintptr_t)pThunk->u1.AddressOfData + 2, const char *),
                                  ~0U, &Value, pvUser);
                Log4((RT_SUCCESS(rc) ? "RTLdrPE:  %016RX64 %s\n" : "RTLdrPE:  %016RX64 %s rc=%Rrc\n",
                      (uint64_t)Value, PE_RVA2TYPE(pvBitsR, (uintptr_t)pThunk->u1.AddressOfData + 2, const char *), rc));
            }
            else
            {
                AssertMsgFailed(("bad import data thunk!\n"));
                rc = VERR_BAD_EXE_FORMAT;
            }
            pFirstThunk->u1.Function = Value;
            pThunk++;
            pFirstThunk++;
        }
    }

    return rc;
}


/**
 * Applies fixups.
 */
static int rtldrPEApplyFixups(PRTLDRMODPE pModPe, const void *pvBitsR, void *pvBitsW, RTUINTPTR BaseAddress,
                              RTUINTPTR OldBaseAddress)
{
    if (    !pModPe->RelocDir.VirtualAddress
        ||  !pModPe->RelocDir.Size)
        return 0;

    /*
     * Apply delta fixups iterating fixup chunks.
     */
    PIMAGE_BASE_RELOCATION  pbr = PE_RVA2TYPE(pvBitsR, pModPe->RelocDir.VirtualAddress, PIMAGE_BASE_RELOCATION);
    PIMAGE_BASE_RELOCATION  pBaseRelocs = pbr;
    unsigned                cbBaseRelocs = pModPe->RelocDir.Size;
    RTUINTPTR               uDelta = BaseAddress - OldBaseAddress;
    Log2(("RTLdrPE: Fixups: uDelta=%#RTptr BaseAddress=%#RTptr OldBaseAddress=%#RTptr\n", uDelta, BaseAddress, OldBaseAddress));
    Log4(("RTLdrPE: BASERELOC: VirtualAddres=%RX32 Size=%RX32\n", pModPe->RelocDir.VirtualAddress, pModPe->RelocDir.Size));
    Assert(sizeof(*pbr) == sizeof(uint32_t) * 2);

    while (   (uintptr_t)pbr - (uintptr_t)pBaseRelocs + 8 < cbBaseRelocs /* 8= VirtualAddress and SizeOfBlock members */
           && pbr->SizeOfBlock >= 8)
    {
        uint16_t   *pwoffFixup   = (uint16_t *)(pbr + 1);
        uint32_t    cRelocations = (pbr->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(uint16_t);
        Log3(("RTLdrPE: base relocs for %#010x, size %#06x (%d relocs)\n", pbr->VirtualAddress, pbr->SizeOfBlock, cRelocations));

        /* Some bound checking just to be sure it works... */
        if ((uintptr_t)pbr - (uintptr_t)pBaseRelocs + pbr->SizeOfBlock > cbBaseRelocs)
            cRelocations = (uint32_t)(  (((uintptr_t)pBaseRelocs + cbBaseRelocs) - (uintptr_t)pbr - sizeof(IMAGE_BASE_RELOCATION))
                                      / sizeof(uint16_t) );

        /*
         * Loop thru the fixups in this chunk.
         */
        while (cRelocations != 0)
        {
            /*
             * Common fixup
             */
            static const char * const s_apszReloc[16] =
            {
                "ABS", "HIGH", "LOW", "HIGHLOW", "HIGHADJ", "MIPS_JMPADDR", "RES6", "RES7",
                "RES8", "IA64_IMM64", "DIR64", "HIGH3ADJ", "RES12", "RES13", "RES14", "RES15"
            }; NOREF(s_apszReloc);
            union
            {
                uint16_t   *pu16;
                uint32_t   *pu32;
                uint64_t   *pu64;
            } u;
            const int offFixup  = *pwoffFixup & 0xfff;
            u.pu32 = PE_RVA2TYPE(pvBitsW, offFixup + pbr->VirtualAddress, uint32_t *);
            const int fType     = *pwoffFixup >> 12;
            Log4(("RTLdrPE: %08x %s\n", offFixup + pbr->VirtualAddress, s_apszReloc[fType]));
            switch (fType)
            {
                case IMAGE_REL_BASED_HIGHLOW:   /* 32-bit, add delta. */
                    *u.pu32 += (uint32_t)uDelta;
                    break;
                case IMAGE_REL_BASED_DIR64:     /* 64-bit, add delta. */
                    *u.pu64 += (RTINTPTR)uDelta;
                    break;
                case IMAGE_REL_BASED_ABSOLUTE:  /* Alignment placeholder. */
                    break;
                /* odd ones */
                case IMAGE_REL_BASED_LOW:       /* 16-bit, add 1st 16-bit part of the delta. */
                    *u.pu16 += (uint16_t)uDelta;
                    break;
                case IMAGE_REL_BASED_HIGH:      /* 16-bit, add 2nd 16-bit part of the delta. */
                    *u.pu16 += (uint16_t)(uDelta >> 16);
                    break;
                /* never ever seen these next two, and I'm not 100% sure they are correctly implemented here. */
                case IMAGE_REL_BASED_HIGHADJ:
                {
                    if (cRelocations <= 1)
                    {
                        AssertMsgFailed(("HIGHADJ missing 2nd record!\n"));
                        return VERR_BAD_EXE_FORMAT;
                    }
                    cRelocations--;
                    pwoffFixup++;
                    int32_t i32 = (uint32_t)(*u.pu16 << 16) | *pwoffFixup;
                    i32 += (uint32_t)uDelta;
                    i32 += 0x8000; //??
                    *u.pu16 = (uint16_t)(i32 >> 16);
                    break;
                }
                case IMAGE_REL_BASED_HIGH3ADJ:
                {
                    if (cRelocations <= 2)
                    {
                        AssertMsgFailed(("HIGHADJ3 missing 2nd record!\n"));
                        return VERR_BAD_EXE_FORMAT;
                    }
                    cRelocations -= 2;
                    pwoffFixup++;
                    int64_t i64 = ((uint64_t)*u.pu16 << 32) | *(uint32_t *)pwoffFixup++;
                    i64 += (int64_t)uDelta << 16; //??
                    i64 += 0x80000000;//??
                    *u.pu16 = (uint16_t)(i64 >> 32);
                    break;
                }
                default:
                    AssertMsgFailed(("Unknown fixup type %d offset=%#x\n", fType, offFixup));
                    break;
            }

            /*
             * Next offset/type
             */
            pwoffFixup++;
            cRelocations--;
        } /* while loop */

        /*
         * Next Fixup chunk. (i.e. next page)
         */
        pbr = (PIMAGE_BASE_RELOCATION)((uintptr_t)pbr + pbr->SizeOfBlock);
    } /* while loop */

    return 0;
}


/** @interface_method_impl{RTLDROPS,pfnRelocate} */
static DECLCALLBACK(int) rtldrPERelocate(PRTLDRMODINTERNAL pMod, void *pvBits, RTUINTPTR NewBaseAddress, RTUINTPTR OldBaseAddress,
                                         PFNRTLDRIMPORT pfnGetImport, void *pvUser)
{
    PRTLDRMODPE pModPe = (PRTLDRMODPE)pMod;

    /*
     * Do we have to read the image bits?
     */
    if (!pModPe->pvBits)
    {
        int rc = rtldrPEReadBits(pModPe);
        if (RT_FAILURE(rc))
            return rc;
    }

    /*
     * Process imports.
     */
    int rc = ((PRTLDROPSPE)pMod->pOps)->pfnResolveImports(pModPe, pModPe->pvBits, pvBits, pfnGetImport, pvUser);
    if (RT_SUCCESS(rc))
    {
        /*
         * Apply relocations.
         */
        rc = rtldrPEApplyFixups(pModPe, pModPe->pvBits, pvBits, NewBaseAddress, OldBaseAddress);
        AssertRC(rc);
    }
    return rc;
}


/**
 * Internal worker for pfnGetSymbolEx and pfnQueryForwarderInfo.
 *
 * @returns IPRT status code.
 * @param   pModPe              The PE module instance.
 * @param   iOrdinal            The symbol ordinal, UINT32_MAX if named symbol.
 * @param   pszSymbol           The symbol name.
 * @param   ppvBits             The image bits pointer (input/output).
 * @param   puRvaExport         Where to return the symbol RVA.
 * @param   puOrdinal           Where to return the ordinal number. Optional.
 */
static int rtLdrPE_ExportToRva(PRTLDRMODPE pModPe, uint32_t iOrdinal, const char *pszSymbol,
                               const void **ppvBits, uint32_t *puRvaExport, uint32_t *puOrdinal)
{
    /*
     * Check if there is actually anything to work on.
     */
    if (   !pModPe->ExportDir.VirtualAddress
        || !pModPe->ExportDir.Size)
        return VERR_SYMBOL_NOT_FOUND;

    /*
     * No bits supplied? Do we need to read the bits?
     */
    void const *pvBits = *ppvBits;
    if (!pvBits)
    {
        if (!pModPe->pvBits)
        {
            int rc = rtldrPEReadBits(pModPe);
            if (RT_FAILURE(rc))
                return rc;
        }
        *ppvBits = pvBits = pModPe->pvBits;
    }

    PIMAGE_EXPORT_DIRECTORY pExpDir = PE_RVA2TYPE(pvBits, pModPe->ExportDir.VirtualAddress, PIMAGE_EXPORT_DIRECTORY);
    int                     iExpOrdinal = 0;    /* index into address table. */
    if (iOrdinal != UINT32_MAX)
    {
        /*
         * Find ordinal export: Simple table lookup.
         */
        if (    iOrdinal >= pExpDir->Base + RT_MAX(pExpDir->NumberOfNames, pExpDir->NumberOfFunctions)
            ||  iOrdinal < pExpDir->Base)
            return VERR_SYMBOL_NOT_FOUND;
        iExpOrdinal = iOrdinal - pExpDir->Base;
    }
    else
    {
        /*
         * Find Named Export: Do binary search on the name table.
         */
        uint32_t   *paRVANames = PE_RVA2TYPE(pvBits, pExpDir->AddressOfNames, uint32_t *);
        uint16_t   *paOrdinals = PE_RVA2TYPE(pvBits, pExpDir->AddressOfNameOrdinals, uint16_t *);
        int         iStart = 1;
        int         iEnd = pExpDir->NumberOfNames;

        for (;;)
        {
            /* end of search? */
            if (iStart > iEnd)
            {
#ifdef RT_STRICT
                /* do a linear search just to verify the correctness of the above algorithm */
                for (unsigned i = 0; i < pExpDir->NumberOfNames; i++)
                {
                    AssertMsg(i == 0 || strcmp(PE_RVA2TYPE(pvBits, paRVANames[i], const char *), PE_RVA2TYPE(pvBits, paRVANames[i - 1], const char *)) > 0,
                              ("bug in binary export search!!!\n"));
                    AssertMsg(strcmp(PE_RVA2TYPE(pvBits, paRVANames[i], const char *), pszSymbol) != 0,
                              ("bug in binary export search!!!\n"));
                }
#endif
                return VERR_SYMBOL_NOT_FOUND;
            }

            int i  = (iEnd - iStart) / 2 + iStart;
            const char *pszExpName  = PE_RVA2TYPE(pvBits, paRVANames[i - 1], const char *);
            int         diff        = strcmp(pszExpName, pszSymbol);
            if (diff > 0)       /* pszExpName > pszSymbol: search chunck before i */
                iEnd = i - 1;
            else if (diff)      /* pszExpName < pszSymbol: search chunk after i */
                iStart = i + 1;
            else                /* pszExpName == pszSymbol */
            {
                iExpOrdinal = paOrdinals[i - 1];
                break;
            }
        } /* binary search thru name table */
    }

    /*
     * Found export (iExpOrdinal).
     */
    uint32_t *paAddress = PE_RVA2TYPE(pvBits, pExpDir->AddressOfFunctions, uint32_t *);
    *puRvaExport = paAddress[iExpOrdinal];
    if (puOrdinal)
        *puOrdinal = iExpOrdinal;
    return VINF_SUCCESS;
}


/** @interface_method_impl{RTLDROPS,pfnGetSymbolEx} */
static DECLCALLBACK(int) rtldrPEGetSymbolEx(PRTLDRMODINTERNAL pMod, const void *pvBits, RTUINTPTR BaseAddress,
                                            uint32_t iOrdinal, const char *pszSymbol, RTUINTPTR *pValue)
{
    PRTLDRMODPE pThis = (PRTLDRMODPE)pMod;
    uint32_t uRvaExport;
    int rc = rtLdrPE_ExportToRva(pThis, iOrdinal, pszSymbol, &pvBits, &uRvaExport, NULL);
    if (RT_SUCCESS(rc))
    {

        uint32_t offForwarder = uRvaExport - pThis->ExportDir.VirtualAddress;
        if (offForwarder >= pThis->ExportDir.Size)
            /* Get plain export address */
            *pValue = PE_RVA2TYPE(BaseAddress, uRvaExport, RTUINTPTR);
        else
        {
            /* Return the approximate length of the forwarder buffer. */
            const char *pszForwarder = PE_RVA2TYPE(pvBits, uRvaExport, const char *);
            *pValue = sizeof(RTLDRIMPORTINFO) + RTStrNLen(pszForwarder, offForwarder - pThis->ExportDir.Size);
            rc = VERR_LDR_FORWARDER;
        }
    }
    return rc;
}


/** @interface_method_impl{RTLDROPS,pfnQueryForwarderInfo} */
static DECLCALLBACK(int) rtldrPE_QueryForwarderInfo(PRTLDRMODINTERNAL pMod, const void *pvBits,  uint32_t iOrdinal,
                                                    const char *pszSymbol, PRTLDRIMPORTINFO pInfo, size_t cbInfo)
{
    AssertReturn(cbInfo >= sizeof(*pInfo), VERR_INVALID_PARAMETER);

    PRTLDRMODPE pThis = (PRTLDRMODPE)pMod;
    uint32_t uRvaExport;
    int rc = rtLdrPE_ExportToRva(pThis, iOrdinal, pszSymbol, &pvBits, &uRvaExport, &iOrdinal);
    if (RT_SUCCESS(rc))
    {
        uint32_t offForwarder = uRvaExport - pThis->ExportDir.VirtualAddress;
        if (offForwarder < pThis->ExportDir.Size)
        {
            const char *pszForwarder = PE_RVA2TYPE(pvBits, uRvaExport, const char *);

            /*
             * Parse and validate the string.  We must make sure it's valid
             * UTF-8, so we restrict it to ASCII.
             */
            const char *pszEnd = RTStrEnd(pszForwarder, offForwarder - pThis->ExportDir.Size);
            if (pszEnd)
            {
                /* The module name. */
                char ch;
                uint32_t off = 0;
                while ((ch = pszForwarder[off]) != '.' && ch != '\0')
                {
                    if (RT_UNLIKELY((uint8_t)ch >= 0x80))
                        return VERR_LDR_BAD_FORWARDER;
                    off++;
                }
                if (RT_UNLIKELY(ch != '.'))
                    return VERR_LDR_BAD_FORWARDER;
                uint32_t const offDot = off;
                off++;

                /* The function name or ordinal number. Ordinals starts with a hash. */
                uint32_t iImpOrdinal;
                if (pszForwarder[off] != '#')
                {
                    iImpOrdinal = UINT32_MAX;
                    while ((ch = pszForwarder[off]) != '\0')
                    {
                        if (RT_UNLIKELY((uint8_t)ch >= 0x80))
                            return VERR_LDR_BAD_FORWARDER;
                        off++;
                    }
                    if (RT_UNLIKELY(off == offDot + 1))
                        return VERR_LDR_BAD_FORWARDER;
                }
                else
                {
                    rc = RTStrToUInt32Full(&pszForwarder[off + 1], 10, &iImpOrdinal);
                    if (RT_UNLIKELY(rc != VINF_SUCCESS || iImpOrdinal > UINT16_MAX))
                        return VERR_LDR_BAD_FORWARDER;
                }

                /*
                 * Enough buffer?
                 */
                uint32_t cbNeeded = RT_UOFFSETOF_DYN(RTLDRIMPORTINFO, szModule[iImpOrdinal != UINT32_MAX ? offDot + 1 : off + 1]);
                if (cbNeeded > cbInfo)
                    return VERR_BUFFER_OVERFLOW;

                /*
                 * Fill in the return buffer.
                 */
                pInfo->iSelfOrdinal = iOrdinal;
                pInfo->iOrdinal     = iImpOrdinal;
                if (iImpOrdinal == UINT32_MAX)
                {
                    pInfo->pszSymbol = &pInfo->szModule[offDot + 1];
                    memcpy(&pInfo->szModule[0], pszForwarder, off + 1);
                }
                else
                {
                    pInfo->pszSymbol = NULL;
                    memcpy(&pInfo->szModule[0], pszForwarder, offDot);
                }
                pInfo->szModule[offDot] = '\0';
                rc = VINF_SUCCESS;
            }
            else
                rc = VERR_LDR_BAD_FORWARDER;
        }
        else
            rc = VERR_LDR_NOT_FORWARDER;
    }
    return rc;
}


/**
 * Slow version of rtldrPEEnumSymbols that'll work without all of the image
 * being accessible.
 *
 * This is mainly for use in debuggers and similar.
 */
static int rtldrPEEnumSymbolsSlow(PRTLDRMODPE pThis, unsigned fFlags, RTUINTPTR BaseAddress,
                                  PFNRTLDRENUMSYMS pfnCallback, void *pvUser)
{
    /*
     * We enumerates by ordinal, which means using a slow linear search for
     * getting any name
     */
    PCIMAGE_EXPORT_DIRECTORY pExpDir = NULL;
    int rc = rtldrPEReadPartByRva(pThis, NULL, pThis->ExportDir.VirtualAddress, pThis->ExportDir.Size,
                                  (void const **)&pExpDir);
    if (RT_FAILURE(rc))
        return rc;
    uint32_t const cOrdinals = RT_MAX(pExpDir->NumberOfNames, pExpDir->NumberOfFunctions);

    uint32_t const *paAddress  = NULL;
    rc = rtldrPEReadPartByRva(pThis, NULL, pExpDir->AddressOfFunctions, cOrdinals * sizeof(uint32_t),
                              (void const **)&paAddress);
    uint32_t const *paRVANames = NULL;
    if (RT_SUCCESS(rc) && pExpDir->NumberOfNames)
        rc = rtldrPEReadPartByRva(pThis, NULL, pExpDir->AddressOfNames, pExpDir->NumberOfNames * sizeof(uint32_t),
                                  (void const **)&paRVANames);
    uint16_t const *paOrdinals = NULL;
    if (RT_SUCCESS(rc) && pExpDir->NumberOfNames)
        rc = rtldrPEReadPartByRva(pThis, NULL, pExpDir->AddressOfNameOrdinals, pExpDir->NumberOfNames * sizeof(uint16_t),
                                  (void const **)&paOrdinals);
    if (RT_SUCCESS(rc))
    {
        uint32_t uNamePrev = 0;
        for (uint32_t uOrdinal = 0; uOrdinal < cOrdinals; uOrdinal++)
        {
            if (paAddress[uOrdinal] /* needed? */)
            {
                /*
                 * Look for name.
                 */
                uint32_t    uRvaName = UINT32_MAX;
                /* Search from previous + 1 to the end.  */
                unsigned    uName = uNamePrev + 1;
                while (uName < pExpDir->NumberOfNames)
                {
                    if (paOrdinals[uName] == uOrdinal)
                    {
                        uRvaName = paRVANames[uName];
                        uNamePrev = uName;
                        break;
                    }
                    uName++;
                }
                if (uRvaName == UINT32_MAX)
                {
                    /* Search from start to the previous. */
                    uName = 0;
                    for (uName = 0 ; uName <= uNamePrev; uName++)
                    {
                        if (paOrdinals[uName] == uOrdinal)
                        {
                            uRvaName = paRVANames[uName];
                            uNamePrev = uName;
                            break;
                        }
                    }
                }

                /*
                 * Get address.
                 */
                uint32_t  uRVAExport = paAddress[uOrdinal];
                RTUINTPTR Value;
                if (uRVAExport - pThis->ExportDir.VirtualAddress >= pThis->ExportDir.Size)
                    Value = PE_RVA2TYPE(BaseAddress, uRVAExport, RTUINTPTR);
                else if (!(fFlags & RTLDR_ENUM_SYMBOL_FLAGS_NO_FWD))
                    Value = RTLDR_ENUM_SYMBOL_FWD_ADDRESS;
                else
                    continue;

                /* Read in the name if found one. */
                char szAltName[32];
                const char *pszName = NULL;
                if (uRvaName != UINT32_MAX)
                {
                    uint32_t cbName = 0x1000 - (uRvaName & 0xfff);
                    if (cbName < 10 || cbName > 512)
                        cbName = 128;
                    rc = rtldrPEReadPartByRva(pThis, NULL, uRvaName, cbName, (void const **)&pszName);
                    while (RT_SUCCESS(rc) && RTStrNLen(pszName, cbName) == cbName)
                    {
                        rtldrPEFreePart(pThis, NULL, pszName);
                        pszName = NULL;
                        if (cbName >= _4K)
                            break;
                        cbName += 128;
                        rc = rtldrPEReadPartByRva(pThis, NULL, uRvaName, cbName, (void const **)&pszName);
                    }
                }
                if (!pszName)
                {
                    RTStrPrintf(szAltName, sizeof(szAltName), "Ordinal%#x", uOrdinal);
                    pszName = szAltName;
                }

                /*
                 * Call back.
                 */
                rc = pfnCallback(&pThis->Core, pszName, uOrdinal + pExpDir->Base, Value, pvUser);
                if (pszName != szAltName && pszName)
                    rtldrPEFreePart(pThis, NULL, pszName);
                if (rc)
                    break;
            }
        }
    }

    rtldrPEFreePart(pThis, NULL, paOrdinals);
    rtldrPEFreePart(pThis, NULL, paRVANames);
    rtldrPEFreePart(pThis, NULL, paAddress);
    rtldrPEFreePart(pThis, NULL, pExpDir);
    return rc;

}


/** @interface_method_impl{RTLDROPS,pfnEnumSymbols} */
static DECLCALLBACK(int) rtldrPEEnumSymbols(PRTLDRMODINTERNAL pMod, unsigned fFlags, const void *pvBits, RTUINTPTR BaseAddress,
                                            PFNRTLDRENUMSYMS pfnCallback, void *pvUser)
{
    PRTLDRMODPE pModPe = (PRTLDRMODPE)pMod;
    NOREF(fFlags); /* ignored ... */

    /*
     * Check if there is actually anything to work on.
     */
    if (   !pModPe->ExportDir.VirtualAddress
        || !pModPe->ExportDir.Size)
        return VERR_SYMBOL_NOT_FOUND;

    /*
     * No bits supplied? Do we need to read the bits?
     */
    if (!pvBits)
    {
        if (!pModPe->pvBits)
        {
            int rc = rtldrPEReadBits(pModPe);
            if (RT_FAILURE(rc))
                return rtldrPEEnumSymbolsSlow(pModPe, fFlags, BaseAddress, pfnCallback, pvUser);
        }
        pvBits = pModPe->pvBits;
    }

    /*
     * We enumerates by ordinal, which means using a slow linear search for
     * getting any name
     */
    PIMAGE_EXPORT_DIRECTORY pExpDir = PE_RVA2TYPE(pvBits, pModPe->ExportDir.VirtualAddress, PIMAGE_EXPORT_DIRECTORY);
    uint32_t   *paAddress  = PE_RVA2TYPE(pvBits, pExpDir->AddressOfFunctions, uint32_t *);
    uint32_t   *paRVANames = PE_RVA2TYPE(pvBits, pExpDir->AddressOfNames, uint32_t *);
    uint16_t   *paOrdinals = PE_RVA2TYPE(pvBits, pExpDir->AddressOfNameOrdinals, uint16_t *);
    uint32_t    uNamePrev  = 0;
    unsigned    cOrdinals  = RT_MAX(pExpDir->NumberOfNames, pExpDir->NumberOfFunctions);
    for (unsigned uOrdinal = 0; uOrdinal < cOrdinals; uOrdinal++)
    {
        if (paAddress[uOrdinal] /* needed? */)
        {
            /*
             * Look for name.
             */
            const char *pszName = NULL;
            /* Search from previous + 1 to the end.  */
            uint32_t uName = uNamePrev + 1;
            while (uName < pExpDir->NumberOfNames)
            {
                if (paOrdinals[uName] == uOrdinal)
                {
                    pszName = PE_RVA2TYPE(pvBits, paRVANames[uName], const char *);
                    uNamePrev = uName;
                    break;
                }
                uName++;
            }
            if (!pszName)
            {
                /* Search from start to the previous. */
                uName = 0;
                for (uName = 0 ; uName <= uNamePrev; uName++)
                {
                    if (paOrdinals[uName] == uOrdinal)
                    {
                        pszName = PE_RVA2TYPE(pvBits, paRVANames[uName], const char *);
                        uNamePrev = uName;
                        break;
                    }
                }
            }

            /*
             * Get address.
             */
            uint32_t  uRVAExport = paAddress[uOrdinal];
            RTUINTPTR Value;
            if (uRVAExport - pModPe->ExportDir.VirtualAddress >= pModPe->ExportDir.Size)
                Value = PE_RVA2TYPE(BaseAddress, uRVAExport, RTUINTPTR);
            else if (!(fFlags & RTLDR_ENUM_SYMBOL_FLAGS_NO_FWD))
                Value = RTLDR_ENUM_SYMBOL_FWD_ADDRESS;
            else
                continue;

            /*
             * Call back.
             */
            int rc = pfnCallback(pMod, pszName, uOrdinal + pExpDir->Base, Value, pvUser);
            if (rc)
                return rc;
        }
    }

    return VINF_SUCCESS;
}


/** @interface_method_impl{RTLDROPS,pfnEnumDbgInfo} */
static DECLCALLBACK(int) rtldrPE_EnumDbgInfo(PRTLDRMODINTERNAL pMod, const void *pvBits,
                                             PFNRTLDRENUMDBG pfnCallback, void *pvUser)
{
    PRTLDRMODPE pModPe = (PRTLDRMODPE)pMod;
    int rc;

    /*
     * Debug info directory empty?
     */
    if (   !pModPe->DebugDir.VirtualAddress
        || !pModPe->DebugDir.Size)
        return VINF_SUCCESS;

    /*
     * Allocate temporary memory for a path buffer (this code is also compiled
     * and maybe even used in stack starved environments).
     */
    char *pszPath = (char *)RTMemTmpAlloc(RTPATH_MAX);
    if (!pszPath)
        return VERR_NO_TMP_MEMORY;

    /*
     * Get the debug directory.
     */
    if (!pvBits)
        pvBits = pModPe->pvBits;

    PCIMAGE_DEBUG_DIRECTORY paDbgDir;
    int rcRet = rtldrPEReadPartByRva(pModPe, pvBits, pModPe->DebugDir.VirtualAddress, pModPe->DebugDir.Size,
                                     (void const **)&paDbgDir);
    if (RT_FAILURE(rcRet))
    {
        RTMemTmpFree(pszPath);
        return rcRet;
    }

    /*
     * Enumerate the debug directory.
     */
    uint32_t const cEntries = pModPe->DebugDir.Size / sizeof(paDbgDir[0]);
    for (uint32_t i = 0; i < cEntries; i++)
    {
        if (paDbgDir[i].PointerToRawData < pModPe->offEndOfHdrs)
            continue;
        if (paDbgDir[i].SizeOfData < 4)
            continue;

        void const     *pvPart = NULL;
        RTLDRDBGINFO    DbgInfo;
        RT_ZERO(DbgInfo.u);
        DbgInfo.iDbgInfo    = i;
        DbgInfo.offFile     = paDbgDir[i].PointerToRawData;
        DbgInfo.LinkAddress =    paDbgDir[i].AddressOfRawData < pModPe->cbImage
                              && paDbgDir[i].AddressOfRawData >= pModPe->offEndOfHdrs
                            ? paDbgDir[i].AddressOfRawData : NIL_RTLDRADDR;
        DbgInfo.cb          = paDbgDir[i].SizeOfData;
        DbgInfo.pszExtFile  = NULL;

        rc = VINF_SUCCESS;
        switch (paDbgDir[i].Type)
        {
            case IMAGE_DEBUG_TYPE_CODEVIEW:
                DbgInfo.enmType = RTLDRDBGINFOTYPE_CODEVIEW;
                DbgInfo.u.Cv.cbImage    = pModPe->cbImage;
                DbgInfo.u.Cv.uMajorVer  = paDbgDir[i].MajorVersion;
                DbgInfo.u.Cv.uMinorVer  = paDbgDir[i].MinorVersion;
                DbgInfo.u.Cv.uTimestamp = paDbgDir[i].TimeDateStamp;
                if (   paDbgDir[i].SizeOfData < RTPATH_MAX
                    && paDbgDir[i].SizeOfData > 16
                    && (   DbgInfo.LinkAddress != NIL_RTLDRADDR
                        || DbgInfo.offFile > 0)
                    )
                {
                    rc = rtldrPEReadPart(pModPe, pvBits, DbgInfo.offFile, DbgInfo.LinkAddress, paDbgDir[i].SizeOfData, &pvPart);
                    if (RT_SUCCESS(rc))
                    {
                        PCCVPDB20INFO pCv20 = (PCCVPDB20INFO)pvPart;
                        if (   pCv20->u32Magic   == CVPDB20INFO_MAGIC
                            && pCv20->offDbgInfo == 0
                            && paDbgDir[i].SizeOfData > RT_UOFFSETOF(CVPDB20INFO, szPdbFilename) )
                        {
                            DbgInfo.enmType             = RTLDRDBGINFOTYPE_CODEVIEW_PDB20;
                            DbgInfo.u.Pdb20.cbImage     = pModPe->cbImage;
                            DbgInfo.u.Pdb20.uTimestamp  = pCv20->uTimestamp;
                            DbgInfo.u.Pdb20.uAge        = pCv20->uAge;
                            DbgInfo.pszExtFile          = (const char *)&pCv20->szPdbFilename[0];
                        }
                        else if (   pCv20->u32Magic == CVPDB70INFO_MAGIC
                                 && paDbgDir[i].SizeOfData > RT_UOFFSETOF(CVPDB70INFO, szPdbFilename) )
                        {
                            PCCVPDB70INFO pCv70 = (PCCVPDB70INFO)pCv20;
                            DbgInfo.enmType             = RTLDRDBGINFOTYPE_CODEVIEW_PDB70;
                            DbgInfo.u.Pdb70.cbImage     = pModPe->cbImage;
                            DbgInfo.u.Pdb70.Uuid        = pCv70->PdbUuid;
                            DbgInfo.u.Pdb70.uAge        = pCv70->uAge;
                            DbgInfo.pszExtFile          = (const char *)&pCv70->szPdbFilename[0];
                        }
                    }
                    else
                        rcRet = rc;
                }
                break;

            case IMAGE_DEBUG_TYPE_MISC:
                DbgInfo.enmType = RTLDRDBGINFOTYPE_UNKNOWN;
                if (   paDbgDir[i].SizeOfData < RTPATH_MAX
                    && paDbgDir[i].SizeOfData > RT_UOFFSETOF(IMAGE_DEBUG_MISC, Data))
                {
                    DbgInfo.enmType             = RTLDRDBGINFOTYPE_CODEVIEW_DBG;
                    DbgInfo.u.Dbg.cbImage       = pModPe->cbImage;
                    if (DbgInfo.LinkAddress != NIL_RTLDRADDR)
                        DbgInfo.u.Dbg.uTimestamp = paDbgDir[i].TimeDateStamp;
                    else
                        DbgInfo.u.Dbg.uTimestamp = pModPe->uTimestamp; /* NT4 SP1 ntfs.sys hack. Generic? */

                    rc = rtldrPEReadPart(pModPe, pvBits, DbgInfo.offFile, DbgInfo.LinkAddress, paDbgDir[i].SizeOfData, &pvPart);
                    if (RT_SUCCESS(rc))
                    {
                        PCIMAGE_DEBUG_MISC pMisc = (PCIMAGE_DEBUG_MISC)pvPart;
                        if (   pMisc->DataType == IMAGE_DEBUG_MISC_EXENAME
                            && pMisc->Length   == paDbgDir[i].SizeOfData)
                        {
                            if (!pMisc->Unicode)
                                DbgInfo.pszExtFile      = (const char *)&pMisc->Data[0];
                            else
                            {
                                rc = RTUtf16ToUtf8Ex((PCRTUTF16)&pMisc->Data[0],
                                                     (pMisc->Length - RT_UOFFSETOF(IMAGE_DEBUG_MISC, Data)) / sizeof(RTUTF16),
                                                     &pszPath, RTPATH_MAX, NULL);
                                if (RT_SUCCESS(rc))
                                    DbgInfo.pszExtFile = pszPath;
                                else
                                    rcRet = rc; /* continue without a filename. */
                            }
                        }
                    }
                    else
                        rcRet = rc; /* continue without a filename. */
                }
                break;

            case IMAGE_DEBUG_TYPE_COFF:
                DbgInfo.enmType = RTLDRDBGINFOTYPE_COFF;
                DbgInfo.u.Coff.cbImage    = pModPe->cbImage;
                DbgInfo.u.Coff.uMajorVer  = paDbgDir[i].MajorVersion;
                DbgInfo.u.Coff.uMinorVer  = paDbgDir[i].MinorVersion;
                DbgInfo.u.Coff.uTimestamp = paDbgDir[i].TimeDateStamp;
                break;

            default:
                DbgInfo.enmType = RTLDRDBGINFOTYPE_UNKNOWN;
                break;
        }

        /* Fix (hack) the file name encoding.  We don't have Windows-1252 handy,
           so we'll be using Latin-1 as a reasonable approximation.
           (I don't think we know exactly which encoding this is anyway, as
           it's probably the current ANSI/Windows code page for the process
           generating the image anyways.) */
        if (DbgInfo.pszExtFile && DbgInfo.pszExtFile != pszPath)
        {
            rc = RTLatin1ToUtf8Ex(DbgInfo.pszExtFile,
                                  paDbgDir[i].SizeOfData - ((uintptr_t)DbgInfo.pszExtFile - (uintptr_t)pvBits),
                                  &pszPath, RTPATH_MAX, NULL);
            if (RT_FAILURE(rc))
            {
                rcRet = rc;
                DbgInfo.pszExtFile = NULL;
            }
        }
        if (DbgInfo.pszExtFile)
            RTPathChangeToUnixSlashes(pszPath, true /*fForce*/);

        rc = pfnCallback(pMod, &DbgInfo, pvUser);
        rtldrPEFreePart(pModPe, pvBits, pvPart);
        if (rc != VINF_SUCCESS)
        {
            rcRet = rc;
            break;
        }
    }

    rtldrPEFreePart(pModPe, pvBits, paDbgDir);
    RTMemTmpFree(pszPath);
    return rcRet;
}


/** @interface_method_impl{RTLDROPS,pfnEnumSegments} */
static DECLCALLBACK(int) rtldrPE_EnumSegments(PRTLDRMODINTERNAL pMod, PFNRTLDRENUMSEGS pfnCallback, void *pvUser)
{
    PRTLDRMODPE pModPe = (PRTLDRMODPE)pMod;
    RTLDRSEG    SegInfo;

    /*
     * The first section is a fake one covering the headers.
     */
    SegInfo.pszName     = "NtHdrs";
    SegInfo.cchName     = 6;
    SegInfo.SelFlat     = 0;
    SegInfo.Sel16bit    = 0;
    SegInfo.fFlags      = 0;
    SegInfo.fProt       = RTMEM_PROT_READ;
    SegInfo.Alignment   = 1;
    SegInfo.LinkAddress = pModPe->uImageBase;
    SegInfo.RVA         = 0;
    SegInfo.offFile     = 0;
    SegInfo.cb          = pModPe->cbHeaders;
    SegInfo.cbFile      = pModPe->cbHeaders;
    SegInfo.cbMapped    = pModPe->cbHeaders;
    if (!(pModPe->paSections[0].Characteristics & IMAGE_SCN_TYPE_NOLOAD))
        SegInfo.cbMapped = pModPe->paSections[0].VirtualAddress;
    int rc = pfnCallback(pMod, &SegInfo, pvUser);

    /*
     * Then all the normal sections.
     */
    PCIMAGE_SECTION_HEADER pSh = pModPe->paSections;
    for (uint32_t i = 0; i < pModPe->cSections && rc == VINF_SUCCESS; i++, pSh++)
    {
        char szName[32];
        SegInfo.pszName         = (const char *)&pSh->Name[0];
        SegInfo.cchName         = (uint32_t)RTStrNLen(SegInfo.pszName, sizeof(pSh->Name));
        if (SegInfo.cchName >= sizeof(pSh->Name))
        {
            memcpy(szName, &pSh->Name[0], sizeof(pSh->Name));
            szName[sizeof(pSh->Name)] = '\0';
            SegInfo.pszName = szName;
        }
        else if (SegInfo.cchName == 0)
        {
            SegInfo.pszName = szName;
            SegInfo.cchName = (uint32_t)RTStrPrintf(szName, sizeof(szName), "UnamedSect%02u", i);
        }
        SegInfo.SelFlat         = 0;
        SegInfo.Sel16bit        = 0;
        SegInfo.fFlags          = 0;
        SegInfo.fProt           = RTMEM_PROT_NONE;
        if (pSh->Characteristics & IMAGE_SCN_MEM_READ)
            SegInfo.fProt       |= RTMEM_PROT_READ;
        if (pSh->Characteristics & IMAGE_SCN_MEM_WRITE)
            SegInfo.fProt       |= RTMEM_PROT_WRITE;
        if (pSh->Characteristics & IMAGE_SCN_MEM_EXECUTE)
            SegInfo.fProt       |= RTMEM_PROT_EXEC;
        SegInfo.Alignment       = (pSh->Characteristics & IMAGE_SCN_ALIGN_MASK) >> IMAGE_SCN_ALIGN_SHIFT;
        if (SegInfo.Alignment > 0)
            SegInfo.Alignment   = RT_BIT_64(SegInfo.Alignment - 1);
        else
            SegInfo.Alignment   = pModPe->uSectionAlign;
        if (pSh->Characteristics & IMAGE_SCN_TYPE_NOLOAD)
        {
            SegInfo.LinkAddress = NIL_RTLDRADDR;
            SegInfo.RVA         = NIL_RTLDRADDR;
            SegInfo.cbMapped    = 0;
        }
        else
        {
            SegInfo.LinkAddress = pSh->VirtualAddress + pModPe->uImageBase;
            SegInfo.RVA         = pSh->VirtualAddress;
            SegInfo.cbMapped    = RT_ALIGN(pSh->Misc.VirtualSize, SegInfo.Alignment);
            if (i + 1 < pModPe->cSections && !(pSh[1].Characteristics & IMAGE_SCN_TYPE_NOLOAD))
                SegInfo.cbMapped = pSh[1].VirtualAddress - pSh->VirtualAddress;
        }
        SegInfo.cb              = pSh->Misc.VirtualSize;
        if (pSh->PointerToRawData == 0 || pSh->SizeOfRawData == 0)
        {
            SegInfo.offFile     = -1;
            SegInfo.cbFile      = 0;
        }
        else
        {
            SegInfo.offFile     = pSh->PointerToRawData;
            SegInfo.cbFile      = pSh->SizeOfRawData;
        }

        rc = pfnCallback(pMod, &SegInfo, pvUser);
    }

    return rc;
}


/** @interface_method_impl{RTLDROPS,pfnLinkAddressToSegOffset} */
static DECLCALLBACK(int) rtldrPE_LinkAddressToSegOffset(PRTLDRMODINTERNAL pMod, RTLDRADDR LinkAddress,
                                                        uint32_t *piSeg, PRTLDRADDR poffSeg)
{
    PRTLDRMODPE pModPe = (PRTLDRMODPE)pMod;

    LinkAddress -= pModPe->uImageBase;

    /* Special header segment. */
    if (LinkAddress < pModPe->paSections[0].VirtualAddress)
    {
        *piSeg   = 0;
        *poffSeg = LinkAddress;
        return VINF_SUCCESS;
    }

    /*
     * Search the normal sections. (Could do this in binary fashion, they're
     * sorted, but too much bother right now.)
     */
    if (LinkAddress > pModPe->cbImage)
        return VERR_LDR_INVALID_LINK_ADDRESS;
    uint32_t                i       = pModPe->cSections;
    PCIMAGE_SECTION_HEADER  paShs   = pModPe->paSections;
    while (i-- > 0)
        if (!(paShs[i].Characteristics & IMAGE_SCN_TYPE_NOLOAD))
        {
            uint32_t uAddr = paShs[i].VirtualAddress;
            if (LinkAddress >= uAddr)
            {
                *poffSeg = LinkAddress - uAddr;
                *piSeg   = i + 1;
                return VINF_SUCCESS;
            }
        }

    return VERR_LDR_INVALID_LINK_ADDRESS;
}


/** @interface_method_impl{RTLDROPS,pfnLinkAddressToRva} */
static DECLCALLBACK(int) rtldrPE_LinkAddressToRva(PRTLDRMODINTERNAL pMod, RTLDRADDR LinkAddress, PRTLDRADDR pRva)
{
    PRTLDRMODPE pModPe = (PRTLDRMODPE)pMod;

    LinkAddress -= pModPe->uImageBase;
    if (LinkAddress > pModPe->cbImage)
        return VERR_LDR_INVALID_LINK_ADDRESS;
    *pRva = LinkAddress;

    return VINF_SUCCESS;
}


/** @interface_method_impl{RTLDROPS,pfnSegOffsetToRva} */
static DECLCALLBACK(int) rtldrPE_SegOffsetToRva(PRTLDRMODINTERNAL pMod, uint32_t iSeg, RTLDRADDR offSeg,
                                                PRTLDRADDR pRva)
{
    PRTLDRMODPE pModPe = (PRTLDRMODPE)pMod;

    if (iSeg > pModPe->cSections)
        return VERR_LDR_INVALID_SEG_OFFSET;

    /** @todo should validate offSeg here... too lazy right now. */
    if (iSeg == 0)
        *pRva = offSeg;
    else if (!(pModPe->paSections[iSeg - 1].Characteristics & IMAGE_SCN_TYPE_NOLOAD))
        *pRva = offSeg + pModPe->paSections[iSeg - 1].VirtualAddress;
    else
        return VERR_LDR_INVALID_SEG_OFFSET;
    return VINF_SUCCESS;
}


/** @interface_method_impl{RTLDROPS,pfnRvaToSegOffset} */
static DECLCALLBACK(int) rtldrPE_RvaToSegOffset(PRTLDRMODINTERNAL pMod, RTLDRADDR Rva,
                                                uint32_t *piSeg, PRTLDRADDR poffSeg)
{
    PRTLDRMODPE pModPe = (PRTLDRMODPE)pMod;
    int rc = rtldrPE_LinkAddressToSegOffset(pMod, Rva + pModPe->uImageBase, piSeg, poffSeg);
    if (RT_FAILURE(rc))
        rc = VERR_LDR_INVALID_RVA;
    return rc;
}


/**
 * Worker for rtLdrPE_QueryProp and rtLdrPE_QueryImportModule that counts the
 * number of imports, storing the result in RTLDRMODPE::cImports.
 *
 * @returns IPRT status code.
 * @param   pThis           The PE module instance.
 * @param   pvBits          Image bits if the caller had them available, NULL if
 *                          not. Saves a couple of file accesses.
 */
static int rtLdrPE_CountImports(PRTLDRMODPE pThis, void const *pvBits)
{
    PCIMAGE_IMPORT_DESCRIPTOR paImpDescs;
    int rc = rtldrPEReadPartByRva(pThis, pvBits, pThis->ImportDir.VirtualAddress, pThis->ImportDir.Size,
                                  (void const **)&paImpDescs);
    if (RT_SUCCESS(rc))
    {
        uint32_t const cMax = pThis->ImportDir.Size / sizeof(IMAGE_IMPORT_DESCRIPTOR);
        uint32_t       i = 0;
        while (   i < cMax
               && paImpDescs[i].Name > pThis->offNtHdrs
               && paImpDescs[i].Name < pThis->cbImage
               && paImpDescs[i].FirstThunk > pThis->offNtHdrs
               && paImpDescs[i].FirstThunk < pThis->cbImage)
            i++;
        pThis->cImports = i;

        rtldrPEFreePart(pThis, pvBits, paImpDescs);
    }
    return rc;
}

/**
 * Worker for rtLdrPE_QueryImportModule and rtLdrPE_QueryInternalName that
 * copies a zero termianted string at the given RVA into the RTLdrQueryPropEx
 * output buffer.
 *
 * @returns IPRT status code. If VERR_BUFFER_OVERFLOW, pcbBuf is required size.
 * @param   pThis           The PE module instance.
 * @param   pvBits          Image bits if the caller had them available, NULL if
 *                          not. Saves a couple of file accesses.
 * @param   uRvaString      The RVA of the string to copy.
 * @param   cbMaxString     The max string length.
 * @param   pvBuf           The output buffer.
 * @param   cbBuf           The buffer size.
 * @param   pcbRet          Where to return the number of bytes we've returned
 *                          (or in case of VERR_BUFFER_OVERFLOW would have).
 */
static int rtLdrPE_QueryNameAtRva(PRTLDRMODPE pThis, void const *pvBits, uint32_t uRvaString, uint32_t cbMaxString,
                                  void *pvBuf, size_t cbBuf, size_t *pcbRet)
{
    int rc;
    if (   uRvaString >= pThis->cbHeaders
        && uRvaString < pThis->cbImage)
    {
        /*
         * Limit the string.
         */
        uint32_t cbMax = pThis->cbImage - uRvaString;
        if (cbMax > cbMaxString)
            cbMax = cbMaxString;
        char *pszString;
        rc = rtldrPEReadPartByRva(pThis, pvBits, uRvaString, cbMax, (void const **)&pszString);
        if (RT_SUCCESS(rc))
        {
            /*
             * Make sure it's null terminated and valid UTF-8 encoding.
             *
             * Which encoding this really is isn't defined, I think,
             * but we need to make sure we don't get bogus UTF-8 into
             * the process, so making sure it's valid UTF-8 is a good
             * as anything else since it covers ASCII.
             */
            size_t cchString = RTStrNLen(pszString, cbMaxString);
            if (cchString < cbMaxString)
            {
                rc = RTStrValidateEncodingEx(pszString, cchString, 0 /*fFlags*/);
                if (RT_SUCCESS(rc))
                {
                    /*
                     * Copy out the result and we're done.
                     * (We have to do all the cleanup code though, so no return success here.)
                     */
                    *pcbRet = cchString + 1;
                    if (cbBuf >= cchString + 1)
                        memcpy(pvBuf, pszString, cchString + 1);
                    else
                        rc = VERR_BUFFER_OVERFLOW;
                }
            }
            else
                rc = VERR_BAD_EXE_FORMAT;
            rtldrPEFreePart(pThis, pvBits, pszString);
        }
    }
    else
        rc = VERR_BAD_EXE_FORMAT;
    return rc;
}


/**
 * Worker for rtLdrPE_QueryProp that retrievs the name of an import DLL.
 *
 * @returns IPRT status code. If VERR_BUFFER_OVERFLOW, pcbBuf is required size.
 * @param   pThis           The PE module instance.
 * @param   pvBits          Image bits if the caller had them available, NULL if
 *                          not. Saves a couple of file accesses.
 * @param   iImport         The index of the import table descriptor to fetch
 *                          the name from.
 * @param   pvBuf           The output buffer.
 * @param   cbBuf           The buffer size.
 * @param   pcbRet          Where to return the number of bytes we've returned
 *                          (or in case of VERR_BUFFER_OVERFLOW would have).
 */
static int rtLdrPE_QueryImportModule(PRTLDRMODPE pThis, void const *pvBits, uint32_t iImport,
                                     void *pvBuf, size_t cbBuf, size_t *pcbRet)
{
    /*
     * Make sure we got the import count.
     */
    int rc;
    if (pThis->cImports == UINT32_MAX)
    {
        rc = rtLdrPE_CountImports(pThis, pvBits);
        if (RT_FAILURE(rc))
            return rc;
    }

    /*
     * Check the index first, converting it to an RVA.
     */
    if (iImport < pThis->cImports)
    {
        uint32_t offEntry = iImport * sizeof(IMAGE_IMPORT_DESCRIPTOR) + pThis->ImportDir.VirtualAddress;

        /*
         * Retrieve the import table descriptor.
         * Using 1024 as the max name length (should be more than enough).
         */
        PCIMAGE_IMPORT_DESCRIPTOR pImpDesc;
        rc = rtldrPEReadPartByRva(pThis, pvBits, offEntry, sizeof(*pImpDesc), (void const **)&pImpDesc);
        if (RT_SUCCESS(rc))
        {
            rc = rtLdrPE_QueryNameAtRva(pThis, pvBits, pImpDesc->Name, 1024 /*cchMaxString*/, pvBuf, cbBuf, pcbRet);
            rtldrPEFreePart(pThis, pvBits, pImpDesc);
        }
    }
    else
        rc = VERR_NOT_FOUND;

    if (RT_SUCCESS(rc))
        return VINF_SUCCESS;

    *pcbRet = 0;
    return rc;
}


/**
 * Worker for rtLdrPE_QueryProp that retrieves the internal module name.
 *
 * @returns IPRT status code. If VERR_BUFFER_OVERFLOW, pcbBuf is required size.
 * @param   pThis           The PE module instance.
 * @param   pvBits          Image bits if the caller had them available, NULL if
 *                          not. Saves a couple of file accesses.
 * @param   pvBuf           The output buffer.
 * @param   cbBuf           The buffer size.
 * @param   pcbRet          Where to return the number of bytes we've returned
 *                          (or in case of VERR_BUFFER_OVERFLOW would have).
 */
static int rtLdrPE_QueryInternalName(PRTLDRMODPE pThis, void const *pvBits, void *pvBuf, size_t cbBuf, size_t *pcbRet)
{
    *pcbRet = 0;

    if (   pThis->ExportDir.Size < sizeof(IMAGE_EXPORT_DIRECTORY)
        || pThis->ExportDir.VirtualAddress == 0)
        return VERR_NOT_FOUND;

    PCIMAGE_EXPORT_DIRECTORY pExpDir;
    int rc = rtldrPEReadPartByRva(pThis, pvBits, pThis->ExportDir.VirtualAddress, sizeof(*pExpDir), (void const **)&pExpDir);
    if (RT_SUCCESS(rc))
    {
        rc = rtLdrPE_QueryNameAtRva(pThis, pvBits, pExpDir->Name, 1024 /*cchMaxString*/, pvBuf, cbBuf, pcbRet);
        rtldrPEFreePart(pThis, pvBits, pExpDir);
    }

    return rc;
}


/**
 * Worker for rtLdrPE_QueryProp that retrieves unwind information.
 *
 * @returns IPRT status code. If VERR_BUFFER_OVERFLOW, pcbBuf is required size.
 * @param   pThis           The PE module instance.
 * @param   pvBits          Image bits if the caller had them available, NULL if
 *                          not. Saves a couple of file accesses.
 * @param   pvBuf           The output buffer.
 * @param   cbBuf           The buffer size.
 * @param   pcbRet          Where to return the number of bytes we've returned
 *                          (or in case of VERR_BUFFER_OVERFLOW would have).
 */
static int rtLdrPE_QueryUnwindTable(PRTLDRMODPE pThis, void const *pvBits, void *pvBuf, size_t cbBuf, size_t *pcbRet)
{
    int rc;
    uint32_t const cbSrc = pThis->ExceptionDir.Size;
    if (   cbSrc > 0
        && pThis->ExceptionDir.VirtualAddress > 0)
    {
        *pcbRet = cbSrc;
        if (cbBuf >= cbSrc)
            rc = rtldrPEReadPartByRvaInfoBuf(pThis, pvBits, pThis->ExceptionDir.VirtualAddress, cbSrc, pvBuf);
        else
            rc = VERR_BUFFER_OVERFLOW;
    }
    else
    {
        *pcbRet = 0;
        rc = VERR_NOT_FOUND;
    }
    return rc;
}


/** @interface_method_impl{RTLDROPS,pfnQueryProp} */
static DECLCALLBACK(int) rtldrPE_QueryProp(PRTLDRMODINTERNAL pMod, RTLDRPROP enmProp, void const *pvBits,
                                           void *pvBuf, size_t cbBuf, size_t *pcbRet)
{
    PRTLDRMODPE pModPe = (PRTLDRMODPE)pMod;
    switch (enmProp)
    {
        case RTLDRPROP_TIMESTAMP_SECONDS:
            Assert(*pcbRet == cbBuf);
            if (cbBuf == sizeof(int32_t))
                *(int32_t *)pvBuf = pModPe->uTimestamp;
            else if (cbBuf == sizeof(int64_t))
                *(int64_t *)pvBuf = pModPe->uTimestamp;
            else
                AssertFailedReturn(VERR_INTERNAL_ERROR_3);
            break;

        case RTLDRPROP_IS_SIGNED:
            Assert(cbBuf == sizeof(bool));
            Assert(*pcbRet == cbBuf);
            *(bool *)pvBuf = pModPe->offPkcs7SignedData != 0;
            break;

        case RTLDRPROP_PKCS7_SIGNED_DATA:
        {
            if (pModPe->cbPkcs7SignedData == 0)
                return VERR_NOT_FOUND;
            Assert(pModPe->offPkcs7SignedData > pModPe->SecurityDir.VirtualAddress);

            *pcbRet = pModPe->cbPkcs7SignedData;
            if (cbBuf < pModPe->cbPkcs7SignedData)
                return VERR_BUFFER_OVERFLOW;
            return pModPe->Core.pReader->pfnRead(pModPe->Core.pReader, pvBuf, pModPe->cbPkcs7SignedData,
                                                 pModPe->offPkcs7SignedData);
        }

#ifndef IPRT_WITHOUT_LDR_PAGE_HASHING
        case RTLDRPROP_HASHABLE_PAGES:
            *pcbRet = sizeof(uint32_t);
            *(uint32_t *)pvBuf = rtLdrPE_GetHashablePages(pModPe);
            return VINF_SUCCESS;

        case RTLDRPROP_SHA1_PAGE_HASHES:
            return rtLdrPE_QueryPageHashes(pModPe, RTDIGESTTYPE_SHA1, pvBuf, cbBuf, pcbRet);

        case RTLDRPROP_SHA256_PAGE_HASHES:
            return rtLdrPE_QueryPageHashes(pModPe, RTDIGESTTYPE_SHA256, pvBuf, cbBuf, pcbRet);
#endif

        case RTLDRPROP_SIGNATURE_CHECKS_ENFORCED:
            Assert(cbBuf == sizeof(bool));
            Assert(*pcbRet == cbBuf);
            *(bool *)pvBuf = pModPe->offPkcs7SignedData > 0
                          && (pModPe->fDllCharacteristics & IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY);
            break;

        case RTLDRPROP_IMPORT_COUNT:
            Assert(cbBuf == sizeof(uint32_t));
            Assert(*pcbRet == cbBuf);
            if (pModPe->cImports == UINT32_MAX)
            {
                int rc = rtLdrPE_CountImports(pModPe, pvBits);
                if (RT_FAILURE(rc))
                    return rc;
            }
            *(uint32_t *)pvBuf = pModPe->cImports;
            break;

        case RTLDRPROP_IMPORT_MODULE:
            Assert(cbBuf >= sizeof(uint32_t));
            return rtLdrPE_QueryImportModule(pModPe, pvBits, *(uint32_t *)pvBuf, pvBuf, cbBuf, pcbRet);

        case RTLDRPROP_FILE_OFF_HEADER:
            Assert(cbBuf == sizeof(uint32_t) || cbBuf == sizeof(uint64_t));
            if (cbBuf == sizeof(uint32_t))
                *(uint32_t *)pvBuf = pModPe->offNtHdrs;
            else
                *(uint64_t *)pvBuf = pModPe->offNtHdrs;
            return VINF_SUCCESS;

        case RTLDRPROP_INTERNAL_NAME:
            return rtLdrPE_QueryInternalName(pModPe, pvBits, pvBuf, cbBuf, pcbRet);

        case RTLDRPROP_UNWIND_TABLE:
            return rtLdrPE_QueryUnwindTable(pModPe, pvBits, pvBuf, cbBuf, pcbRet);

        case RTLDRPROP_UNWIND_INFO:
        {
            uint32_t uRva = *(uint32_t const *)pvBuf;
            if (uRva < pModPe->cbImage)
            {
                uint32_t cbLeft   = pModPe->cbImage - uRva;
                uint32_t cbToRead = (uint32_t)RT_MIN(cbLeft, cbBuf);
                *pcbRet = cbToRead;
                return rtldrPEReadPartByRvaInfoBuf(pModPe, pvBits, uRva, cbToRead, pvBuf);
            }
            *pcbRet = 0;
            return VINF_SUCCESS;
        }

        default:
            return VERR_NOT_FOUND;
    }
    return VINF_SUCCESS;
}



/*
 * Lots of Authenticode fun ahead.
 */


/**
 * Initializes the hash context.
 *
 * @returns VINF_SUCCESS or VERR_NOT_SUPPORTED.
 * @param   pHashCtx            The hash context union.
 * @param   enmDigest           The hash type we're calculating..
 */
static int rtLdrPE_HashInit(PRTLDRPEHASHCTXUNION pHashCtx, RTDIGESTTYPE enmDigest)
{
    switch (enmDigest)
    {
        case RTDIGESTTYPE_SHA512:  RTSha512Init(&pHashCtx->Sha512); break;
        case RTDIGESTTYPE_SHA384:  RTSha384Init(&pHashCtx->Sha384); break;
        case RTDIGESTTYPE_SHA256:  RTSha256Init(&pHashCtx->Sha256); break;
        case RTDIGESTTYPE_SHA1:    RTSha1Init(&pHashCtx->Sha1); break;
        case RTDIGESTTYPE_MD5:     RTMd5Init(&pHashCtx->Md5); break;
        default:                   AssertFailedReturn(VERR_NOT_SUPPORTED);
    }
    return VINF_SUCCESS;
}


/**
 * Updates the hash with more data.
 *
 * @param   pHashCtx            The hash context union.
 * @param   enmDigest           The hash type we're calculating..
 * @param   pvBuf               Pointer to a buffer with bytes to add to thash.
 * @param   cbBuf               How many bytes to add from @a pvBuf.
 */
static void rtLdrPE_HashUpdate(PRTLDRPEHASHCTXUNION pHashCtx, RTDIGESTTYPE enmDigest, void const *pvBuf, size_t cbBuf)
{
    switch (enmDigest)
    {
        case RTDIGESTTYPE_SHA512:  RTSha512Update(&pHashCtx->Sha512, pvBuf, cbBuf); break;
        case RTDIGESTTYPE_SHA384:  RTSha384Update(&pHashCtx->Sha384, pvBuf, cbBuf); break;
        case RTDIGESTTYPE_SHA256:  RTSha256Update(&pHashCtx->Sha256, pvBuf, cbBuf); break;
        case RTDIGESTTYPE_SHA1:    RTSha1Update(&pHashCtx->Sha1, pvBuf, cbBuf); break;
        case RTDIGESTTYPE_MD5:     RTMd5Update(&pHashCtx->Md5, pvBuf, cbBuf); break;
        default:                   AssertReleaseFailed();
    }
}


/**
 * Finalizes the hash calculations.
 *
 * @param   pHashCtx            The hash context union.
 * @param   enmDigest           The hash type we're calculating..
 * @param   pHashRes            The hash result union.
 */
static void rtLdrPE_HashFinalize(PRTLDRPEHASHCTXUNION pHashCtx, RTDIGESTTYPE enmDigest, PRTLDRPEHASHRESUNION pHashRes)
{
    switch (enmDigest)
    {
        case RTDIGESTTYPE_SHA512:  RTSha512Final(&pHashCtx->Sha512, pHashRes->abSha512); break;
        case RTDIGESTTYPE_SHA384:  RTSha384Final(&pHashCtx->Sha384, pHashRes->abSha384); break;
        case RTDIGESTTYPE_SHA256:  RTSha256Final(&pHashCtx->Sha256, pHashRes->abSha256); break;
        case RTDIGESTTYPE_SHA1:    RTSha1Final(&pHashCtx->Sha1, pHashRes->abSha1); break;
        case RTDIGESTTYPE_MD5:     RTMd5Final(pHashRes->abMd5, &pHashCtx->Md5); break;
        default:                   AssertReleaseFailed();
    }
}


/**
 * Returns the digest size for the given digest type.
 *
 * @returns Size in bytes.
 * @param   enmDigest           The hash type in question.
 */
static uint32_t rtLdrPE_HashGetHashSize(RTDIGESTTYPE enmDigest)
{
    switch (enmDigest)
    {
        case RTDIGESTTYPE_SHA512:  return RTSHA512_HASH_SIZE;
        case RTDIGESTTYPE_SHA384:  return RTSHA384_HASH_SIZE;
        case RTDIGESTTYPE_SHA256:  return RTSHA256_HASH_SIZE;
        case RTDIGESTTYPE_SHA1:    return RTSHA1_HASH_SIZE;
        case RTDIGESTTYPE_MD5:     return RTMD5_HASH_SIZE;
        default:                   AssertReleaseFailedReturn(0);
    }
}


#ifndef IPRT_WITHOUT_LDR_VERIFY
/**
 * Checks if the hash type is supported.
 *
 * @returns true/false.
 * @param   enmDigest           The hash type in question.
 */
static bool rtLdrPE_HashIsSupported(RTDIGESTTYPE enmDigest)
{
    switch (enmDigest)
    {
        case RTDIGESTTYPE_SHA512:
        case RTDIGESTTYPE_SHA384:
        case RTDIGESTTYPE_SHA256:
        case RTDIGESTTYPE_SHA1:
        case RTDIGESTTYPE_MD5:
            return true;
        default:
            return false;
    }
}
#endif


/**
 * Calculate the special too watch out for when hashing the image.
 *
 * @returns IPRT status code.
 * @param   pModPe              The PE module.
 * @param   pPlaces             The structure where to store the special places.
 * @param   pErrInfo            Optional error info.
 */
static int rtldrPe_CalcSpecialHashPlaces(PRTLDRMODPE pModPe, PRTLDRPEHASHSPECIALS pPlaces, PRTERRINFO pErrInfo)
{
    /*
     * If we're here despite a missing signature, we need to get the file size.
     */
    pPlaces->cbToHash = pModPe->SecurityDir.VirtualAddress;
    if (pPlaces->cbToHash == 0)
    {
        uint64_t cbFile = pModPe->Core.pReader->pfnSize(pModPe->Core.pReader);
        pPlaces->cbToHash = (uint32_t)cbFile;
        if (pPlaces->cbToHash != (uint64_t)cbFile)
            return RTErrInfoSetF(pErrInfo, VERR_LDRVI_FILE_LENGTH_ERROR, "File is too large: %RTfoff", cbFile);
    }

    /*
     * Calculate the special places.
     */
    pPlaces->offCksum       = (uint32_t)pModPe->offNtHdrs
                            + (pModPe->f64Bit
                               ? RT_UOFFSETOF(IMAGE_NT_HEADERS64, OptionalHeader.CheckSum)
                               : RT_UOFFSETOF(IMAGE_NT_HEADERS32, OptionalHeader.CheckSum));
    pPlaces->cbCksum        = RT_SIZEOFMEMB(IMAGE_NT_HEADERS32, OptionalHeader.CheckSum);
    pPlaces->offSecDir      = (uint32_t)pModPe->offNtHdrs
                            + (pModPe->f64Bit
                               ? RT_UOFFSETOF(IMAGE_NT_HEADERS64, OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY])
                               : RT_UOFFSETOF(IMAGE_NT_HEADERS32, OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY]));
    pPlaces->cbSecDir       = sizeof(IMAGE_DATA_DIRECTORY);
    pPlaces->offEndSpecial  = pPlaces->offSecDir + pPlaces->cbSecDir;
    return VINF_SUCCESS;
}


/**
 * Calculates the whole image hash.
 *
 * The Authenticode_PE.docx version 1.0 explains how the hash is calculated,
 * points 8 thru 14 are bogus.  If you study them a little carefully, it is
 * clear that the algorithm will only work if the raw data for the section have
 * no gaps between them or in front of them.  So, this elaborate section sorting
 * by PointerToRawData and working them section by section could simply be
 * replaced by one point:
 *
 *      8. Add all the file content between SizeOfHeaders and the
 *         attribute certificate table to the hash.  Then finalize
 *         the hash.
 *
 * Not sure if Microsoft is screwing with us on purpose here or whether they
 * assigned some of this work to less talented engineers and tech writers.  I
 * love fact that they say it's "simplified" and should yield the correct hash
 * for "almost all" files.  Stupid, Stupid, Microsofties!!
 *
 * My simplified implementation that just hashes the entire file up to the
 * signature or end of the file produces the same SHA1 values as "signtool
 * verify /v" does both for edited executables with gaps between/before/after
 * sections raw data and normal executables without any gaps.
 *
 * @returns IPRT status code.
 * @param   pModPe      The PE module.
 * @param   pvScratch   Scratch buffer.
 * @param   cbScratch   Size of the scratch buffer.
 * @param   enmDigest   The hash digest type we're calculating.
 * @param   pHashCtx    Hash context scratch area.
 * @param   pHashRes    Hash result buffer.
 * @param   pErrInfo    Optional error info buffer.
 */
static int rtldrPE_HashImageCommon(PRTLDRMODPE pModPe, void *pvScratch, uint32_t cbScratch, RTDIGESTTYPE enmDigest,
                                   PRTLDRPEHASHCTXUNION pHashCtx, PRTLDRPEHASHRESUNION pHashRes, PRTERRINFO pErrInfo)
{
    int rc = rtLdrPE_HashInit(pHashCtx, enmDigest);
    if (RT_FAILURE(rc))
        return rc;

    /*
     * Calculate the special places.
     */
    RTLDRPEHASHSPECIALS SpecialPlaces = { 0, 0, 0, 0, 0, 0 }; /* shut up gcc */
    rc = rtldrPe_CalcSpecialHashPlaces(pModPe, &SpecialPlaces, pErrInfo);
    if (RT_FAILURE(rc))
        return rc;

    /*
     * Work our way thru the image data.
     */
    uint32_t off = 0;
    while (off < SpecialPlaces.cbToHash)
    {
        uint32_t cbRead = RT_MIN(SpecialPlaces.cbToHash - off, cbScratch);
        uint8_t *pbCur  = (uint8_t *)pvScratch;
        rc = pModPe->Core.pReader->pfnRead(pModPe->Core.pReader, pbCur, cbRead, off);
        if (RT_FAILURE(rc))
            return RTErrInfoSetF(pErrInfo, VERR_LDRVI_READ_ERROR_HASH, "Hash read error at %#x: %Rrc (cbRead=%#zx)",
                                 off, rc, cbRead);

        if (off < SpecialPlaces.offEndSpecial)
        {
            if (off < SpecialPlaces.offCksum)
            {
                /* Hash everything up to the checksum. */
                uint32_t cbChunk = RT_MIN(SpecialPlaces.offCksum - off, cbRead);
                rtLdrPE_HashUpdate(pHashCtx, enmDigest, pbCur, cbChunk);
                pbCur  += cbChunk;
                cbRead -= cbChunk;
                off    += cbChunk;
            }

            if (off < SpecialPlaces.offCksum + SpecialPlaces.cbCksum && off >= SpecialPlaces.offCksum)
            {
                /* Skip the checksum */
                uint32_t cbChunk = RT_MIN(SpecialPlaces.offCksum + SpecialPlaces.cbCksum - off, cbRead);
                pbCur  += cbChunk;
                cbRead -= cbChunk;
                off    += cbChunk;
            }

            if (off < SpecialPlaces.offSecDir && off >= SpecialPlaces.offCksum + SpecialPlaces.cbCksum)
            {
                /* Hash everything between the checksum and the data dir entry. */
                uint32_t cbChunk = RT_MIN(SpecialPlaces.offSecDir - off, cbRead);
                rtLdrPE_HashUpdate(pHashCtx, enmDigest,  pbCur, cbChunk);
                pbCur  += cbChunk;
                cbRead -= cbChunk;
                off    += cbChunk;
            }

            if (off < SpecialPlaces.offSecDir + SpecialPlaces.cbSecDir && off >= SpecialPlaces.offSecDir)
            {
                /* Skip the security data directory entry. */
                uint32_t cbChunk = RT_MIN(SpecialPlaces.offSecDir + SpecialPlaces.cbSecDir - off, cbRead);
                pbCur  += cbChunk;
                cbRead -= cbChunk;
                off    += cbChunk;
            }
        }

        rtLdrPE_HashUpdate(pHashCtx, enmDigest, pbCur, cbRead);

        /* Advance */
        off += cbRead;
    }

    /*
     * If there isn't a signature, experiments with signtool indicates that we
     * have to zero padd the file size until it's a multiple of 8.  (This is
     * most likely to give 64-bit values in the certificate a natural alignment
     * when memory mapped.)
     */
    if (   pModPe->SecurityDir.VirtualAddress != SpecialPlaces.cbToHash
        && SpecialPlaces.cbToHash != RT_ALIGN_32(SpecialPlaces.cbToHash, WIN_CERTIFICATE_ALIGNMENT))
    {
        static const uint8_t s_abZeros[WIN_CERTIFICATE_ALIGNMENT] = { 0,0,0,0, 0,0,0,0 };
        rtLdrPE_HashUpdate(pHashCtx, enmDigest, s_abZeros,
                           RT_ALIGN_32(SpecialPlaces.cbToHash, WIN_CERTIFICATE_ALIGNMENT) - SpecialPlaces.cbToHash);
    }

    /*
     * Done. Finalize the hashes.
     */
    rtLdrPE_HashFinalize(pHashCtx, enmDigest, pHashRes);
    return VINF_SUCCESS;
}

#ifndef IPRT_WITHOUT_LDR_PAGE_HASHING

/**
 * Returns the size of the page hashes, including the terminator entry.
 *
 * Used for handling RTLDRPROP_HASHABLE_PAGES.
 *
 * @returns Number of page hashes.
 * @param   pModPe              The PE module.
 */
static uint32_t rtLdrPE_GetHashablePages(PRTLDRMODPE pModPe)
{
    uint32_t const  cbPage = _4K;
    uint32_t        cPages = 1; /* termination entry */

    /* Add implicit header section: */
    cPages += (pModPe->cbHeaders + cbPage - 1) / cbPage;

    /* Add on disk pages for each section.  Each starts with a fresh page and
       we ASSUMES that it is page aligned (in memory). */
    for (uint32_t i = 0; i < pModPe->cSections; i++)
    {
        uint32_t const cbRawData = pModPe->paSections[i].SizeOfRawData;
        if (cbRawData > 0)
            cPages += (cbRawData + cbPage - 1) / cbPage;
    }

    return cPages;
}


/**
 * Worker for rtLdrPE_QueryPageHashes.
 *
 * Keep in mind that rtldrPE_VerifyAllPageHashes does similar work, so some
 * fixes may apply both places.
 */
static int rtLdrPE_CalcPageHashes(PRTLDRMODPE pModPe, RTDIGESTTYPE const enmDigest, uint32_t const cbHash,
                                  uint8_t *pbDst, uint8_t *pbScratch, uint32_t cbScratch, uint32_t const cbPage)
{
    /*
     * Calculate the special places.
     */
    RTLDRPEHASHSPECIALS SpecialPlaces = { 0, 0, 0, 0, 0, 0 }; /* shut up gcc */
    int rc = rtldrPe_CalcSpecialHashPlaces(pModPe, &SpecialPlaces, NULL);
    if (RT_FAILURE(rc))
        return rc;

    /*
     * Walk section table and hash the pages in each.  Because the headers are
     * in an implicit section, the loop advancing is a little funky.
     */
    int32_t const   cSections   = pModPe->cSections;
    int32_t         iSection    = -1;
    uint32_t        offRawData  = 0;
    uint32_t        cbRawData   = pModPe->cbHeaders;
    uint32_t        offLastPage = 0;

    uint32_t const  cbScratchReadMax = cbScratch / cbPage * cbPage;
    uint32_t        cbScratchRead    = 0;
    uint32_t        offScratchRead   = 0;

    for (;;)
    {
        /*
         * Process the pages in this section.
         */
        uint32_t cPagesInSection = (cbRawData + cbPage - 1) / cbPage;
        for (uint32_t iPage = 0; iPage < cPagesInSection; iPage++)
        {
            uint32_t const offPageInSect = iPage * cbPage;
            uint32_t const offPageInFile = offRawData + offPageInSect;
            uint32_t const cbPageInFile  = RT_MIN(cbPage, cbRawData - offPageInSect);
            offLastPage = offPageInFile;

            /* Calculate and output the page offset. */
            *(uint32_t *)pbDst = offPageInFile;
            pbDst += sizeof(uint32_t);

            /*
             * Read/find in the raw page.
             */
            /* Did we get a cache hit? */
            uint8_t *pbCur = pbScratch;
            if (   offPageInFile + cbPageInFile <= offScratchRead + cbScratchRead
                && offPageInFile                >= offScratchRead)
                pbCur += offPageInFile - offScratchRead;
            /* Missed, read more. */
            else
            {
                offScratchRead = offPageInFile;
                cbScratchRead  = SpecialPlaces.cbToHash - offPageInFile;
                if (cbScratchRead > cbScratchReadMax)
                    cbScratchRead = cbScratchReadMax;
                rc = pModPe->Core.pReader->pfnRead(pModPe->Core.pReader, pbCur, cbScratchRead, offScratchRead);
                if (RT_FAILURE(rc))
                    return VERR_LDRVI_READ_ERROR_HASH;
            }

            /*
             * Hash it.
             */
            RTLDRPEHASHCTXUNION HashCtx;
            rc = rtLdrPE_HashInit(&HashCtx, enmDigest);
            AssertRCReturn(rc, rc);

            /* Deal with special places. */
            uint32_t cbLeft = cbPageInFile;
            if (offPageInFile < SpecialPlaces.offEndSpecial)
            {
                uint32_t off = offPageInFile;
                if (off < SpecialPlaces.offCksum)
                {
                    /* Hash everything up to the checksum. */
                    uint32_t cbChunk = RT_MIN(SpecialPlaces.offCksum - off, cbLeft);
                    rtLdrPE_HashUpdate(&HashCtx, enmDigest, pbCur, cbChunk);
                    pbCur  += cbChunk;
                    cbLeft -= cbChunk;
                    off    += cbChunk;
                }

                if (off < SpecialPlaces.offCksum + SpecialPlaces.cbCksum && off >= SpecialPlaces.offCksum)
                {
                    /* Skip the checksum */
                    uint32_t cbChunk = RT_MIN(SpecialPlaces.offCksum + SpecialPlaces.cbCksum - off, cbLeft);
                    pbCur  += cbChunk;
                    cbLeft -= cbChunk;
                    off    += cbChunk;
                }

                if (off < SpecialPlaces.offSecDir && off >= SpecialPlaces.offCksum + SpecialPlaces.cbCksum)
                {
                    /* Hash everything between the checksum and the data dir entry. */
                    uint32_t cbChunk = RT_MIN(SpecialPlaces.offSecDir - off, cbLeft);
                    rtLdrPE_HashUpdate(&HashCtx, enmDigest, pbCur, cbChunk);
                    pbCur  += cbChunk;
                    cbLeft -= cbChunk;
                    off    += cbChunk;
                }

                if (off < SpecialPlaces.offSecDir + SpecialPlaces.cbSecDir && off >= SpecialPlaces.offSecDir)
                {
                    /* Skip the security data directory entry. */
                    uint32_t cbChunk = RT_MIN(SpecialPlaces.offSecDir + SpecialPlaces.cbSecDir - off, cbLeft);
                    pbCur  += cbChunk;
                    cbLeft -= cbChunk;
                    off    += cbChunk;
                }
            }

            rtLdrPE_HashUpdate(&HashCtx, enmDigest, pbCur, cbLeft);
            if (cbPageInFile < cbPage)
                rtLdrPE_HashUpdate(&HashCtx, enmDigest, g_abRTZero4K, cbPage - cbPageInFile);

            /*
             * Finish the hash calculation storing it in the table.
             */
            rtLdrPE_HashFinalize(&HashCtx, enmDigest, (PRTLDRPEHASHRESUNION)pbDst);
            pbDst += cbHash;
        }

        /*
         * Advance to the next section.
         */
        iSection++;
        if (iSection >= cSections)
            break;
        offRawData = pModPe->paSections[iSection].PointerToRawData;
        cbRawData  = pModPe->paSections[iSection].SizeOfRawData;
    }

    /*
     * Add the terminator entry.
     */
    *(uint32_t *)pbDst = offLastPage + cbPage;
    RT_BZERO(&pbDst[sizeof(uint32_t)], cbHash);

    return VINF_SUCCESS;
}


/**
 * Creates the page hash table for the image.
 *
 * Used for handling RTLDRPROP_SHA1_PAGE_HASHES and
 * RTLDRPROP_SHA256_PAGE_HASHES.
 *
 * @returns IPRT status code.
 * @param   pModPe              The PE module.
 * @param   enmDigest           The digest to use when hashing the pages.
 * @param   pvBuf               Where to return the page hash table.
 * @param   cbBuf               The size of the buffer @a pvBuf points to.
 * @param   pcbRet              Where to return the output/needed size.
 */
static int rtLdrPE_QueryPageHashes(PRTLDRMODPE pModPe, RTDIGESTTYPE enmDigest, void *pvBuf, size_t cbBuf, size_t *pcbRet)
{
    /*
     * Check that we've got enough buffer space.
     */
    uint32_t const cbPage   = _4K;
    uint32_t const cEntries = rtLdrPE_GetHashablePages(pModPe);
    uint32_t const cbHash   = rtLdrPE_HashGetHashSize(enmDigest);
    AssertReturn(cbHash > 0, VERR_INTERNAL_ERROR_3);

    size_t const   cbNeeded = (size_t)(cbHash + 4) * cEntries;
    *pcbRet = cbNeeded;
    if (cbNeeded > cbBuf)
        return VERR_BUFFER_OVERFLOW;

    /*
     * Allocate a scratch buffer and call worker to do the real job.
     */
# ifdef IN_RING0
    uint32_t    cbScratch = _256K - _4K;
# else
    uint32_t    cbScratch = _1M;
# endif
    void       *pvScratch = RTMemTmpAlloc(cbScratch);
    if (!pvScratch)
    {
        cbScratch = _4K;
        pvScratch = RTMemTmpAlloc(cbScratch);
        if (!pvScratch)
            return VERR_NO_TMP_MEMORY;
    }

    int rc = rtLdrPE_CalcPageHashes(pModPe, enmDigest, cbHash, (uint8_t *)pvBuf, (uint8_t *)pvScratch, cbScratch, cbPage);

    RTMemTmpFree(pvScratch);
    return rc;
}

#endif /* !IPRT_WITHOUT_LDR_PAGE_HASHING */
#ifndef IPRT_WITHOUT_LDR_VERIFY

/**
 * Verifies image preconditions not checked by the open validation code.
 *
 * @returns IPRT status code.
 * @param   pModPe              The PE module.
 * @param   pErrInfo            Optional error info buffer.
 */
static int rtldrPE_VerifySignatureImagePrecoditions(PRTLDRMODPE pModPe, PRTERRINFO pErrInfo)
{
    /*
     * Validate the sections.  While doing so, track the amount of section raw
     * section data in the file so we can use this to validate the signature
     * table location later.
     */
    uint32_t offNext = pModPe->cbHeaders; /* same */
    for (uint32_t i = 0; i < pModPe->cSections; i++)
        if (pModPe->paSections[i].SizeOfRawData > 0)
        {
            uint64_t offEnd = (uint64_t)pModPe->paSections[i].PointerToRawData + pModPe->paSections[i].SizeOfRawData;
            if (offEnd > offNext)
            {
                if (offEnd >= _2G)
                    return RTErrInfoSetF(pErrInfo, VERR_LDRVI_SECTION_RAW_DATA_VALUES,
                                         "Section %#u specifies file data after 2GB: PointerToRawData=%#x SizeOfRawData=%#x",
                                         i, pModPe->paSections[i].PointerToRawData, pModPe->paSections[i].SizeOfRawData);
                offNext = (uint32_t)offEnd;
            }
        }
    uint32_t offEndOfSectionData = offNext;

    /*
     * Validate the signature.
     */
    if (!pModPe->SecurityDir.Size)
        return RTErrInfoSet(pErrInfo, VERR_LDRVI_NOT_SIGNED, "Not signed.");

    uint32_t const offSignature = pModPe->SecurityDir.VirtualAddress;
    uint32_t const cbSignature  = pModPe->SecurityDir.Size;
    if (   cbSignature  <= sizeof(WIN_CERTIFICATE)
        || cbSignature  >= RTLDRMODPE_MAX_SECURITY_DIR_SIZE
        || offSignature >= _2G)
        return RTErrInfoSetF(pErrInfo, VERR_LDRVI_INVALID_SECURITY_DIR_ENTRY,
                             "Invalid security data dir entry: cb=%#x off=%#x", cbSignature, offSignature);

    if (offSignature < offEndOfSectionData)
        return RTErrInfoSetF(pErrInfo, VERR_LDRVI_INVALID_SECURITY_DIR_ENTRY,
                             "Invalid security data dir entry offset: %#x offEndOfSectionData=%#x",
                             offSignature, offEndOfSectionData);

    if (RT_ALIGN_32(offSignature, WIN_CERTIFICATE_ALIGNMENT) != offSignature)
        return RTErrInfoSetF(pErrInfo, VERR_LDRVI_INVALID_SECURITY_DIR_ENTRY,
                             "Misaligned security dir entry offset: %#x (alignment=%#x)",
                             offSignature, WIN_CERTIFICATE_ALIGNMENT);


    return VINF_SUCCESS;
}


/**
 * Reads and checks the raw signature data.
 *
 * @returns IPRT status code.
 * @param   pModPe              The PE module.
 * @param   ppSignature         Where to return the pointer to the parsed
 *                              signature data.  Pass to
 *                              rtldrPE_VerifySignatureDestroy when done.
 * @param   pErrInfo            Optional error info buffer.
 */
static int rtldrPE_VerifySignatureRead(PRTLDRMODPE pModPe, PRTLDRPESIGNATURE *ppSignature, PRTERRINFO pErrInfo)
{
    *ppSignature = NULL;
    AssertReturn(pModPe->SecurityDir.Size > 0, VERR_INTERNAL_ERROR_2);

    /*
     * Allocate memory for reading and parsing it.
     */
    if (pModPe->SecurityDir.Size >= RTLDRMODPE_MAX_SECURITY_DIR_SIZE)
        return RTErrInfoSetF(pErrInfo, VERR_LDRVI_INVALID_SECURITY_DIR_ENTRY,
                             "Signature directory is to large: %#x", pModPe->SecurityDir.Size);

    PRTLDRPESIGNATURE pSignature = (PRTLDRPESIGNATURE)RTMemTmpAllocZ(sizeof(*pSignature) + 64 + pModPe->SecurityDir.Size);
    if (!pSignature)
        return RTErrInfoSetF(pErrInfo, VERR_LDRVI_NO_MEMORY_SIGNATURE, "Failed to allocate %zu bytes",
                             sizeof(*pSignature) + 64 + pModPe->SecurityDir.Size);
    pSignature->pRawData = RT_ALIGN_PT(pSignature + 1, 64, WIN_CERTIFICATE const *);


    /*
     * Read it.
     */
    int rc = pModPe->Core.pReader->pfnRead(pModPe->Core.pReader, (void *)pSignature->pRawData,
                                           pModPe->SecurityDir.Size, pModPe->SecurityDir.VirtualAddress);
    if (RT_SUCCESS(rc))
    {
        /*
         * Check the table we've read in.
         */
        uint32_t               cbLeft = pModPe->SecurityDir.Size;
        WIN_CERTIFICATE const *pEntry = pSignature->pRawData;
        for (;;)
        {
            if (   cbLeft           < sizeof(*pEntry)
                || pEntry->dwLength > cbLeft
                || pEntry->dwLength < sizeof(*pEntry))
                rc = RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_HDR_LENGTH,
                                   "Bad WIN_CERTIFICATE length: %#x  (max %#x, signature=%u)",
                                   pEntry->dwLength, cbLeft, 0);
            else if (pEntry->wRevision != WIN_CERT_REVISION_2_0)
                rc = RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_HDR_REVISION,
                                   "Unsupported WIN_CERTIFICATE revision value: %#x (signature=%u)",
                                   pEntry->wRevision, 0);
            else if (pEntry->wCertificateType != WIN_CERT_TYPE_PKCS_SIGNED_DATA)
                rc = RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_HDR_TYPE,
                                   "Unsupported WIN_CERTIFICATE certificate type: %#x (signature=%u)",
                                   pEntry->wCertificateType, 0);
            else
            {
                /* advance */
                uint32_t cbEntry = RT_ALIGN(pEntry->dwLength, WIN_CERTIFICATE_ALIGNMENT);
                if (cbEntry >= cbLeft)
                    break;
                cbLeft -= cbEntry;
                pEntry = (WIN_CERTIFICATE *)((uintptr_t)pEntry + cbEntry);

                /* For now, only one entry is supported. */
                rc = RTErrInfoSet(pErrInfo, VERR_LDRVI_BAD_CERT_MULTIPLE, "Multiple WIN_CERTIFICATE entries are not supported.");
            }
            break;
        }
        if (RT_SUCCESS(rc))
        {
            *ppSignature = pSignature;
            return VINF_SUCCESS;
        }
    }
    else
        rc = RTErrInfoSetF(pErrInfo, VERR_LDRVI_READ_ERROR_SIGNATURE, "Signature read error: %Rrc", rc);
    RTMemTmpFree(pSignature);
    return rc;
}


/**
 * Destroys the parsed signature.
 *
 * @param   pModPe              The PE module.
 * @param   pSignature          The signature data to destroy.
 */
static void rtldrPE_VerifySignatureDestroy(PRTLDRMODPE pModPe, PRTLDRPESIGNATURE pSignature)
{
    RT_NOREF_PV(pModPe);
    RTCrPkcs7ContentInfo_Delete(&pSignature->PrimaryContentInfo);
    if (pSignature->paNested)
    {
        RTMemTmpFree(pSignature->paNested);
        pSignature->paNested = NULL;
    }
    RTMemTmpFree(pSignature);
}


/**
 * Handles nested signatures.
 *
 * @returns IPRT status code.
 * @param   pSignature          The signature status structure.  Returns with
 *                              cNested = 0 and paNested = NULL if no nested
 *                              signatures.
 * @param   pErrInfo            Where to return extended error info (optional).
 */
static int rtldrPE_VerifySignatureDecodeNested(PRTLDRPESIGNATURE pSignature, PRTERRINFO pErrInfo)
{
    Assert(pSignature->cNested == 0);
    Assert(pSignature->paNested == NULL);

    /*
     * Count nested signatures.
     */
    uint32_t cNested = 0;
    for (uint32_t iSignerInfo = 0; iSignerInfo < pSignature->Primary.pSignedData->SignerInfos.cItems; iSignerInfo++)
    {
        PCRTCRPKCS7SIGNERINFO pSignerInfo = pSignature->Primary.pSignedData->SignerInfos.papItems[iSignerInfo];
        for (uint32_t iAttrib = 0; iAttrib < pSignerInfo->UnauthenticatedAttributes.cItems; iAttrib++)
        {
            PCRTCRPKCS7ATTRIBUTE pAttrib = pSignerInfo->UnauthenticatedAttributes.papItems[iAttrib];
            if (pAttrib->enmType == RTCRPKCS7ATTRIBUTETYPE_MS_NESTED_SIGNATURE)
            {
                Assert(pAttrib->uValues.pContentInfos);
                cNested += pAttrib->uValues.pContentInfos->cItems;
            }
        }
    }
    if (!cNested)
        return VINF_SUCCESS;

    /*
     * Allocate and populate the info structures.
     */
    pSignature->paNested = (PRTLDRPESIGNATUREONE)RTMemTmpAllocZ(sizeof(pSignature->paNested[0]) * cNested);
    if (!pSignature->paNested)
        return RTErrInfoSetF(pErrInfo, VERR_NO_TMP_MEMORY, "Failed to allocate space for %u nested signatures", cNested);
    pSignature->cNested = cNested;

    cNested = 0;
    for (uint32_t iSignerInfo = 0; iSignerInfo < pSignature->Primary.pSignedData->SignerInfos.cItems; iSignerInfo++)
    {
        PCRTCRPKCS7SIGNERINFO pSignerInfo = pSignature->Primary.pSignedData->SignerInfos.papItems[iSignerInfo];
        for (uint32_t iAttrib = 0; iAttrib < pSignerInfo->UnauthenticatedAttributes.cItems; iAttrib++)
        {
            PCRTCRPKCS7ATTRIBUTE pAttrib = pSignerInfo->UnauthenticatedAttributes.papItems[iAttrib];
            if (pAttrib->enmType == RTCRPKCS7ATTRIBUTETYPE_MS_NESTED_SIGNATURE)
            {
                for (uint32_t iItem = 0; iItem < pAttrib->uValues.pContentInfos->cItems; iItem++, cNested++)
                {
                    PRTLDRPESIGNATUREONE  pInfo        = &pSignature->paNested[cNested];
                    PRTCRPKCS7CONTENTINFO pContentInfo = pAttrib->uValues.pContentInfos->papItems[iItem];
                    pInfo->pContentInfo = pContentInfo;
                    pInfo->iSignature   = cNested;

                    if (RTCrPkcs7ContentInfo_IsSignedData(pInfo->pContentInfo))
                    { /* likely */ }
                    else
                        return RTErrInfoSetF(pErrInfo, VERR_LDRVI_EXPECTED_INDIRECT_DATA_CONTENT_OID, /** @todo error code*/
                                             "Nested#%u: PKCS#7 is not 'signedData': %s", cNested, pInfo->pContentInfo->ContentType.szObjId);
                    PRTCRPKCS7SIGNEDDATA pSignedData = pContentInfo->u.pSignedData;
                    pInfo->pSignedData = pSignedData;

                    /*
                     * Check the authenticode bits.
                     */
                    if (!strcmp(pSignedData->ContentInfo.ContentType.szObjId, RTCRSPCINDIRECTDATACONTENT_OID))
                    { /* likely */ }
                    else
                        return RTErrInfoSetF(pErrInfo, VERR_LDRVI_EXPECTED_INDIRECT_DATA_CONTENT_OID,
                                             "Nested#%u: Unknown pSignedData.ContentInfo.ContentType.szObjId value: %s (expected %s)",
                                             cNested, pSignedData->ContentInfo.ContentType.szObjId, RTCRSPCINDIRECTDATACONTENT_OID);
                    pInfo->pIndData = pSignedData->ContentInfo.u.pIndirectDataContent;
                    Assert(pInfo->pIndData);

                    /*
                     * Check that things add up.
                     */
                    int rc = RTCrPkcs7SignedData_CheckSanity(pSignedData,
                                                             RTCRPKCS7SIGNEDDATA_SANITY_F_AUTHENTICODE
                                                             | RTCRPKCS7SIGNEDDATA_SANITY_F_ONLY_KNOWN_HASH
                                                             | RTCRPKCS7SIGNEDDATA_SANITY_F_SIGNING_CERT_PRESENT,
                                                             pErrInfo, "SD");
                    if (RT_SUCCESS(rc))
                        rc = RTCrSpcIndirectDataContent_CheckSanityEx(pInfo->pIndData,
                                                                      pSignedData,
                                                                      RTCRSPCINDIRECTDATACONTENT_SANITY_F_ONLY_KNOWN_HASH,
                                                                      pErrInfo);
                    if (RT_SUCCESS(rc))
                    {
                        PCRTCRX509ALGORITHMIDENTIFIER pDigestAlgorithm = &pInfo->pIndData->DigestInfo.DigestAlgorithm;
                        pInfo->enmDigest = RTCrX509AlgorithmIdentifier_GetDigestType(pDigestAlgorithm,
                                                                                     true /*fPureDigestsOnly*/);
                        AssertReturn(pInfo->enmDigest != RTDIGESTTYPE_INVALID, VERR_INTERNAL_ERROR_4); /* Checked above! */
                    }
                    else
                        return rc;
                }
            }
        }
    }

    return VINF_SUCCESS;
}


/**
 * Decodes the raw signature.
 *
 * @returns IPRT status code.
 * @param   pModPe              The PE module.
 * @param   pSignature          The signature data.
 * @param   pErrInfo            Optional error info buffer.
 */
static int rtldrPE_VerifySignatureDecode(PRTLDRMODPE pModPe, PRTLDRPESIGNATURE pSignature, PRTERRINFO pErrInfo)
{
    WIN_CERTIFICATE const  *pEntry = pSignature->pRawData;
    AssertReturn(pEntry->wCertificateType == WIN_CERT_TYPE_PKCS_SIGNED_DATA, VERR_INTERNAL_ERROR_2);
    AssertReturn(pEntry->wRevision        == WIN_CERT_REVISION_2_0, VERR_INTERNAL_ERROR_2);
    RT_NOREF_PV(pModPe);

    RTASN1CURSORPRIMARY PrimaryCursor;
    RTAsn1CursorInitPrimary(&PrimaryCursor,
                            &pEntry->bCertificate[0],
                            pEntry->dwLength - RT_UOFFSETOF(WIN_CERTIFICATE, bCertificate),
                            pErrInfo,
                            &g_RTAsn1DefaultAllocator,
                            0,
                            "WinCert");

    PRTLDRPESIGNATUREONE pInfo = &pSignature->Primary;
    pInfo->pContentInfo = &pSignature->PrimaryContentInfo;
    int rc = RTCrPkcs7ContentInfo_DecodeAsn1(&PrimaryCursor.Cursor, 0, pInfo->pContentInfo, "CI");
    if (RT_SUCCESS(rc))
    {
        if (RTCrPkcs7ContentInfo_IsSignedData(pInfo->pContentInfo))
        {
            pInfo->pSignedData = pInfo->pContentInfo->u.pSignedData;

            /*
             * Decode the authenticode bits.
             */
            if (!strcmp(pInfo->pSignedData->ContentInfo.ContentType.szObjId, RTCRSPCINDIRECTDATACONTENT_OID))
            {
                pInfo->pIndData = pInfo->pSignedData->ContentInfo.u.pIndirectDataContent;
                Assert(pInfo->pIndData);

                /*
                 * Check that things add up.
                 */
                rc = RTCrPkcs7SignedData_CheckSanity(pInfo->pSignedData,
                                                     RTCRPKCS7SIGNEDDATA_SANITY_F_AUTHENTICODE
                                                     | RTCRPKCS7SIGNEDDATA_SANITY_F_ONLY_KNOWN_HASH
                                                     | RTCRPKCS7SIGNEDDATA_SANITY_F_SIGNING_CERT_PRESENT,
                                                     pErrInfo, "SD");
                if (RT_SUCCESS(rc))
                    rc = RTCrSpcIndirectDataContent_CheckSanityEx(pInfo->pIndData,
                                                                  pInfo->pSignedData,
                                                                  RTCRSPCINDIRECTDATACONTENT_SANITY_F_ONLY_KNOWN_HASH,
                                                                  pErrInfo);
                if (RT_SUCCESS(rc))
                {
                    PCRTCRX509ALGORITHMIDENTIFIER pDigestAlgorithm = &pInfo->pIndData->DigestInfo.DigestAlgorithm;
                    pInfo->enmDigest = RTCrX509AlgorithmIdentifier_GetDigestType(pDigestAlgorithm, true /*fPureDigestsOnly*/);
                    AssertReturn(pInfo->enmDigest != RTDIGESTTYPE_INVALID, VERR_INTERNAL_ERROR_4); /* Checked above! */

                    /*
                     * Deal with nested signatures.
                     */
                    rc = rtldrPE_VerifySignatureDecodeNested(pSignature, pErrInfo);
                }
            }
            else
                rc = RTErrInfoSetF(pErrInfo, VERR_LDRVI_EXPECTED_INDIRECT_DATA_CONTENT_OID,
                                   "Unknown pSignedData.ContentInfo.ContentType.szObjId value: %s (expected %s)",
                                   pInfo->pSignedData->ContentInfo.ContentType.szObjId, RTCRSPCINDIRECTDATACONTENT_OID);
        }
        else
            rc = RTErrInfoSetF(pErrInfo, VERR_LDRVI_EXPECTED_INDIRECT_DATA_CONTENT_OID, /** @todo error code*/
                               "PKCS#7 is not 'signedData': %s", pInfo->pContentInfo->ContentType.szObjId);
    }
    return rc;
}



static int rtldrPE_VerifyAllPageHashes(PRTLDRMODPE pModPe, PCRTCRSPCSERIALIZEDOBJECTATTRIBUTE pAttrib, RTDIGESTTYPE enmDigest,
                                       void *pvScratch, size_t cbScratch, uint32_t iSignature, PRTERRINFO pErrInfo)
{
    AssertReturn(cbScratch >= _4K, VERR_INTERNAL_ERROR_3);

    /*
     * Calculate the special places.
     */
    RTLDRPEHASHSPECIALS SpecialPlaces = { 0, 0, 0, 0, 0, 0 }; /* shut up gcc */
    int rc = rtldrPe_CalcSpecialHashPlaces(pModPe, &SpecialPlaces, pErrInfo);
    if (RT_FAILURE(rc))
        return rc;

    uint32_t const cbHash = rtLdrPE_HashGetHashSize(enmDigest);
    uint32_t const cPages = pAttrib->u.pPageHashes->RawData.Asn1Core.cb / (cbHash + 4);
    if (cPages * (cbHash + 4) != pAttrib->u.pPageHashes->RawData.Asn1Core.cb)
        return RTErrInfoSetF(pErrInfo, VERR_LDRVI_PAGE_HASH_TAB_SIZE_OVERFLOW,
                             "Signature #%u - Page hashes size issue in: cb=%#x cbHash=%#x",
                             iSignature, pAttrib->u.pPageHashes->RawData.Asn1Core.cb, cbHash);

    /*
     * Walk the table.
     */
    uint32_t const  cbScratchReadMax = cbScratch & ~(uint32_t)(_4K - 1);
    uint32_t        cbScratchRead    = 0;
    uint32_t        offScratchRead   = 0;

    uint32_t        offPrev    = 0;
#ifdef COMPLICATED_AND_WRONG
    uint32_t        offSectEnd = pModPe->cbHeaders;
    uint32_t        iSh        = UINT32_MAX;
#endif
    uint8_t const  *pbHashTab  = pAttrib->u.pPageHashes->RawData.Asn1Core.uData.pu8;
    for (uint32_t iPage = 0; iPage < cPages - 1; iPage++)
    {
        /* Decode the page offset. */
        uint32_t const offPageInFile = RT_MAKE_U32_FROM_U8(pbHashTab[0], pbHashTab[1], pbHashTab[2], pbHashTab[3]);
        if (RT_UNLIKELY(offPageInFile >= SpecialPlaces.cbToHash))
            return RTErrInfoSetF(pErrInfo, VERR_LDRVI_PAGE_HASH_TAB_TOO_LONG,
                                 "Signature #%u - Page hash entry #%u is beyond the signature table start: %#x, %#x",
                                 iSignature, iPage, offPageInFile, SpecialPlaces.cbToHash);
        if (RT_UNLIKELY(offPageInFile < offPrev))
            return RTErrInfoSetF(pErrInfo, VERR_LDRVI_PAGE_HASH_TAB_NOT_STRICTLY_SORTED,
                                 "Signature #%u - Page hash table is not strictly sorted: entry #%u @%#x, previous @%#x\n",
                                 iSignature, iPage, offPageInFile, offPrev);

#ifdef COMPLICATED_AND_WRONG
        /* Figure out how much to read and how much to zero.  Need keep track
           of the on-disk section boundraries. */
        if (offPageInFile >= offSectEnd)
        {
            iSh++;
            if (   iSh < pModPe->cSections
                && offPageInFile - pModPe->paSections[iSh].PointerToRawData < pModPe->paSections[iSh].SizeOfRawData)
                offSectEnd = pModPe->paSections[iSh].PointerToRawData + pModPe->paSections[iSh].SizeOfRawData;
            else
            {
                iSh = 0;
                while (   iSh < pModPe->cSections
                       && offPageInFile - pModPe->paSections[iSh].PointerToRawData >= pModPe->paSections[iSh].SizeOfRawData)
                    iSh++;
                if (iSh < pModPe->cSections)
                    offSectEnd = pModPe->paSections[iSh].PointerToRawData + pModPe->paSections[iSh].SizeOfRawData;
                else
                    return RTErrInfoSetF(pErrInfo, VERR_PAGE_HASH_TAB_HASHES_NON_SECTION_DATA,
                                         "Signature #%u - Page hash entry #%u isn't in any section: %#x",
                                         iSignature, iPage, offPageInFile);
            }
        }

#else
        /* Figure out how much to read and how much take as zero.  Use the next
           page offset and the signature as upper boundraries.  */
#endif
        uint32_t cbPageInFile = _4K;
#ifdef COMPLICATED_AND_WRONG
        if (offPageInFile + cbPageInFile > offSectEnd)
            cbPageInFile = offSectEnd - offPageInFile;
#else
        if (iPage + 1 < cPages)
        {
            uint32_t offNextPage = RT_MAKE_U32_FROM_U8(pbHashTab[0 + 4 + cbHash], pbHashTab[1 + 4 + cbHash],
                                                       pbHashTab[2 + 4 + cbHash], pbHashTab[3 + 4 + cbHash]);
            if (offNextPage - offPageInFile < cbPageInFile)
                cbPageInFile = offNextPage - offPageInFile;
        }
#endif

        if (offPageInFile + cbPageInFile > SpecialPlaces.cbToHash)
            cbPageInFile = SpecialPlaces.cbToHash - offPageInFile;

        /* Did we get a cache hit? */
        uint8_t *pbCur = (uint8_t *)pvScratch;
        if (   offPageInFile + cbPageInFile <= offScratchRead + cbScratchRead
            && offPageInFile          >= offScratchRead)
            pbCur += offPageInFile - offScratchRead;
        /* Missed, read more. */
        else
        {
            offScratchRead = offPageInFile;
#ifdef COMPLICATED_AND_WRONG
            cbScratchRead  = offSectEnd - offPageInFile;
#else
            cbScratchRead  = SpecialPlaces.cbToHash - offPageInFile;
#endif
            if (cbScratchRead > cbScratchReadMax)
                cbScratchRead = cbScratchReadMax;
            rc = pModPe->Core.pReader->pfnRead(pModPe->Core.pReader, pbCur, cbScratchRead, offScratchRead);
            if (RT_FAILURE(rc))
                return RTErrInfoSetF(pErrInfo, VERR_LDRVI_READ_ERROR_HASH,
                                     "Signature #%u - Page hash read error at %#x: %Rrc (cbScratchRead=%#zx)",
                                     iSignature, offScratchRead, rc, cbScratchRead);
        }

        /*
         * Hash it.
         */
        RTLDRPEHASHCTXUNION HashCtx;
        rc = rtLdrPE_HashInit(&HashCtx, enmDigest);
        AssertRCReturn(rc, rc);

        /* Deal with special places. */
        uint32_t       cbLeft = cbPageInFile;
        if (offPageInFile < SpecialPlaces.offEndSpecial)
        {
            uint32_t off = offPageInFile;
            if (off < SpecialPlaces.offCksum)
            {
                /* Hash everything up to the checksum. */
                uint32_t cbChunk = RT_MIN(SpecialPlaces.offCksum - off, cbLeft);
                rtLdrPE_HashUpdate(&HashCtx, enmDigest, pbCur, cbChunk);
                pbCur  += cbChunk;
                cbLeft -= cbChunk;
                off    += cbChunk;
            }

            if (off < SpecialPlaces.offCksum + SpecialPlaces.cbCksum && off >= SpecialPlaces.offCksum)
            {
                /* Skip the checksum */
                uint32_t cbChunk = RT_MIN(SpecialPlaces.offCksum + SpecialPlaces.cbCksum - off, cbLeft);
                pbCur  += cbChunk;
                cbLeft -= cbChunk;
                off    += cbChunk;
            }

            if (off < SpecialPlaces.offSecDir && off >= SpecialPlaces.offCksum + SpecialPlaces.cbCksum)
            {
                /* Hash everything between the checksum and the data dir entry. */
                uint32_t cbChunk = RT_MIN(SpecialPlaces.offSecDir - off, cbLeft);
                rtLdrPE_HashUpdate(&HashCtx, enmDigest, pbCur, cbChunk);
                pbCur  += cbChunk;
                cbLeft -= cbChunk;
                off    += cbChunk;
            }

            if (off < SpecialPlaces.offSecDir + SpecialPlaces.cbSecDir && off >= SpecialPlaces.offSecDir)
            {
                /* Skip the security data directory entry. */
                uint32_t cbChunk = RT_MIN(SpecialPlaces.offSecDir + SpecialPlaces.cbSecDir - off, cbLeft);
                pbCur  += cbChunk;
                cbLeft -= cbChunk;
                off    += cbChunk;
            }
        }

        rtLdrPE_HashUpdate(&HashCtx, enmDigest, pbCur, cbLeft);
        if (cbPageInFile < _4K)
            rtLdrPE_HashUpdate(&HashCtx, enmDigest, g_abRTZero4K, _4K - cbPageInFile);

        /*
         * Finish the hash calculation and compare the result.
         */
        RTLDRPEHASHRESUNION HashRes;
        rtLdrPE_HashFinalize(&HashCtx, enmDigest, &HashRes);

        pbHashTab += 4;
        if (memcmp(pbHashTab, &HashRes, cbHash) != 0)
            return RTErrInfoSetF(pErrInfo, VERR_LDRVI_PAGE_HASH_MISMATCH,
                                 "Signature #%u - Page hash failed for page #%u, @%#x, %#x bytes: %.*Rhxs != %.*Rhxs",
                                 iSignature, iPage, offPageInFile, cbPageInFile, (size_t)cbHash, pbHashTab,
                                 (size_t)cbHash, &HashRes);
        pbHashTab += cbHash;
        offPrev = offPageInFile;
    }

    /*
     * Check that the last table entry has a hash value of zero.
     */
    if (!ASMMemIsZero(pbHashTab + 4, cbHash))
        return RTErrInfoSetF(pErrInfo, VERR_LDRVI_PAGE_HASH_TAB_TOO_LONG,
                             "Signature #%u - Malformed final page hash table entry: #%u %#010x %.*Rhxs",
                             iSignature, cPages - 1, RT_MAKE_U32_FROM_U8(pbHashTab[0], pbHashTab[1], pbHashTab[2], pbHashTab[3]),
                             (size_t)cbHash, pbHashTab + 4);
    return VINF_SUCCESS;
}


static int rtldrPE_VerifySignatureValidateOnePageHashes(PRTLDRMODPE pModPe, PRTLDRPESIGNATUREONE pInfo,
                                                        void *pvScratch, uint32_t cbScratch, PRTERRINFO pErrInfo)
{
    /*
     * Compare the page hashes if present.
     *
     * Seems the difference between V1 and V2 page hash attributes is
     * that v1 uses SHA-1 while v2 uses SHA-256. The data structures
     * seems to be identical otherwise.  Initially we assumed the digest
     * algorithm was supposed to be RTCRSPCINDIRECTDATACONTENT::DigestInfo,
     * i.e. the same as for the whole image hash.  The initial approach
     * worked just fine, but this makes more sense.
     *
     * (See also comments in osslsigncode.c (google it).)
     */
    PCRTCRSPCSERIALIZEDOBJECTATTRIBUTE pAttrib;
    /* V2 - SHA-256: */
    pAttrib = RTCrSpcIndirectDataContent_GetPeImageObjAttrib(pInfo->pIndData,
                                                             RTCRSPCSERIALIZEDOBJECTATTRIBUTETYPE_PAGE_HASHES_V2);
    if (pAttrib)
        return rtldrPE_VerifyAllPageHashes(pModPe, pAttrib, RTDIGESTTYPE_SHA256, pvScratch, cbScratch,
                                           pInfo->iSignature + 1, pErrInfo);

    /* V1 - SHA-1: */
    pAttrib = RTCrSpcIndirectDataContent_GetPeImageObjAttrib(pInfo->pIndData,
                                                             RTCRSPCSERIALIZEDOBJECTATTRIBUTETYPE_PAGE_HASHES_V1);
    if (pAttrib)
        return rtldrPE_VerifyAllPageHashes(pModPe, pAttrib, RTDIGESTTYPE_SHA1, pvScratch, cbScratch,
                                           pInfo->iSignature + 1, pErrInfo);

    /* No page hashes: */
    return VINF_SUCCESS;
}


static int rtldrPE_VerifySignatureValidateOneImageHash(PRTLDRMODPE pModPe, PRTLDRPESIGNATURE pSignature,
                                                       PRTLDRPESIGNATUREONE pInfo, void *pvScratch, uint32_t cbScratch,
                                                       PRTERRINFO pErrInfo)
{
    /*
     * Assert sanity.
     */
    AssertReturn(pInfo->enmDigest > RTDIGESTTYPE_INVALID && pInfo->enmDigest < RTDIGESTTYPE_END, VERR_INTERNAL_ERROR_4);
    AssertPtrReturn(pInfo->pIndData, VERR_INTERNAL_ERROR_5);
    AssertReturn(RTASN1CORE_IS_PRESENT(&pInfo->pIndData->DigestInfo.Digest.Asn1Core), VERR_INTERNAL_ERROR_5);
    AssertPtrReturn(pInfo->pIndData->DigestInfo.Digest.Asn1Core.uData.pv, VERR_INTERNAL_ERROR_5);

    /* Check that the hash is supported by the code here before continuing. */
    AssertReturn(rtLdrPE_HashIsSupported(pInfo->enmDigest),
                 RTErrInfoSetF(pErrInfo, VERR_CR_DIGEST_NOT_SUPPORTED, "Unsupported digest type: %d", pInfo->enmDigest));

    /*
     * Skip it if we've already verified it.
     */
    if (pInfo->fValidatedImageHash)
        return VINF_SUCCESS;

    /*
     * Calculate it.
     */
    uint32_t const cbHash = rtLdrPE_HashGetHashSize(pInfo->enmDigest);
    AssertReturn(pInfo->pIndData->DigestInfo.Digest.Asn1Core.cb == cbHash, VERR_INTERNAL_ERROR_5);

    int rc = rtldrPE_HashImageCommon(pModPe, pvScratch, cbScratch, pInfo->enmDigest,
                                     &pSignature->HashCtx, &pInfo->HashRes, pErrInfo);
    if (RT_SUCCESS(rc))
    {
        pInfo->fValidatedImageHash = true;
        if (memcmp(&pInfo->HashRes, pInfo->pIndData->DigestInfo.Digest.Asn1Core.uData.pv, cbHash) == 0)
        {
            /*
             * Verify other signatures with the same digest type.
             */
            RTLDRPEHASHRESUNION const * const pHashRes      = &pInfo->HashRes;
            RTDIGESTTYPE const                enmDigestType = pInfo->enmDigest;
            for (uint32_t i = 0; i < pSignature->cNested; i++)
            {
                pInfo = &pSignature->paNested[i]; /* Note! pInfo changes! */
                if (   !pInfo->fValidatedImageHash
                    && pInfo->enmDigest == enmDigestType
                    /* paranoia from the top of this function: */
                    && pInfo->pIndData
                    && RTASN1CORE_IS_PRESENT(&pInfo->pIndData->DigestInfo.Digest.Asn1Core)
                    && pInfo->pIndData->DigestInfo.Digest.Asn1Core.uData.pv
                    && pInfo->pIndData->DigestInfo.Digest.Asn1Core.cb == cbHash)
                {
                    pInfo->fValidatedImageHash = true;
                    if (memcmp(pHashRes, pInfo->pIndData->DigestInfo.Digest.Asn1Core.uData.pv, cbHash) != 0)
                    {
                        rc = RTErrInfoSetF(pErrInfo, VERR_LDRVI_IMAGE_HASH_MISMATCH,
                                           "Full image signature #%u mismatch: %.*Rhxs, expected %.*Rhxs", pInfo->iSignature + 1,
                                           cbHash, pHashRes,
                                           cbHash, pInfo->pIndData->DigestInfo.Digest.Asn1Core.uData.pv);
                        break;
                    }
                }
            }
        }
        else
            rc = RTErrInfoSetF(pErrInfo, VERR_LDRVI_IMAGE_HASH_MISMATCH,
                               "Full image signature #%u mismatch: %.*Rhxs, expected %.*Rhxs", pInfo->iSignature + 1,
                               cbHash, &pInfo->HashRes,
                               cbHash, pInfo->pIndData->DigestInfo.Digest.Asn1Core.uData.pv);
    }
    return rc;
}


/**
 * Validates the image hash, including page hashes if present.
 *
 * @returns IPRT status code.
 * @param   pModPe              The PE module.
 * @param   pSignature          The decoded signature data.
 * @param   pErrInfo            Optional error info buffer.
 */
static int rtldrPE_VerifySignatureValidateHash(PRTLDRMODPE pModPe, PRTLDRPESIGNATURE pSignature, PRTERRINFO pErrInfo)
{
    /*
     * Allocate a temporary memory buffer.
     * Note! The _4K that gets subtracted is to avoid that the 16-byte heap
     *       block header in ring-0 (iprt) caused any unnecessary internal
     *       heap fragmentation.
     */
# ifdef IN_RING0
    uint32_t    cbScratch = _256K - _4K;
# else
    uint32_t    cbScratch = _1M;
# endif
    void       *pvScratch = RTMemTmpAlloc(cbScratch);
    if (!pvScratch)
    {
        cbScratch = _4K;
        pvScratch = RTMemTmpAlloc(cbScratch);
        if (!pvScratch)
            return RTErrInfoSet(pErrInfo, VERR_NO_TMP_MEMORY, "Failed to allocate 4KB of scratch space for hashing image.");
    }

    /*
     * Verify signatures.
     */
    /* Image hashes: */
    int rc = rtldrPE_VerifySignatureValidateOneImageHash(pModPe, pSignature, &pSignature->Primary,
                                                         pvScratch, cbScratch, pErrInfo);
    for (unsigned i = 0; i < pSignature->cNested && RT_SUCCESS(rc); i++)
        rc = rtldrPE_VerifySignatureValidateOneImageHash(pModPe, pSignature, &pSignature->paNested[i],
                                                         pvScratch, cbScratch, pErrInfo);

    /* Page hashes: */
    if (RT_SUCCESS(rc))
    {
        rc = rtldrPE_VerifySignatureValidateOnePageHashes(pModPe, &pSignature->Primary, pvScratch, cbScratch, pErrInfo);
        for (unsigned i = 0; i < pSignature->cNested && RT_SUCCESS(rc); i++)
            rc = rtldrPE_VerifySignatureValidateOnePageHashes(pModPe, &pSignature->paNested[i], pvScratch, cbScratch, pErrInfo);
    }

    /*
     * Ditch the scratch buffer.
     */
    RTMemTmpFree(pvScratch);
    return rc;
}

#endif /* !IPRT_WITHOUT_LDR_VERIFY */


/** @interface_method_impl{RTLDROPS,pfnVerifySignature} */
static DECLCALLBACK(int) rtldrPE_VerifySignature(PRTLDRMODINTERNAL pMod, PFNRTLDRVALIDATESIGNEDDATA pfnCallback, void *pvUser,
                                                 PRTERRINFO pErrInfo)
{
#ifndef IPRT_WITHOUT_LDR_VERIFY
    PRTLDRMODPE pModPe = (PRTLDRMODPE)pMod;

    int rc = rtldrPE_VerifySignatureImagePrecoditions(pModPe, pErrInfo);
    if (RT_SUCCESS(rc))
    {
        PRTLDRPESIGNATURE pSignature = NULL;
        rc = rtldrPE_VerifySignatureRead(pModPe, &pSignature, pErrInfo);
        if (RT_SUCCESS(rc))
        {
            rc = rtldrPE_VerifySignatureDecode(pModPe, pSignature, pErrInfo);
            if (RT_SUCCESS(rc))
                rc = rtldrPE_VerifySignatureValidateHash(pModPe, pSignature, pErrInfo);
            if (RT_SUCCESS(rc))
            {
                /*
                 * Work the callback.
                 */
                /* The primary signature: */
                RTLDRSIGNATUREINFO Info;
                Info.iSignature     = 0;
                Info.cSignatures    = (uint16_t)(1 + pSignature->cNested);
                Info.enmType        = RTLDRSIGNATURETYPE_PKCS7_SIGNED_DATA;
                Info.pvSignature    = pSignature->Primary.pContentInfo;
                Info.cbSignature    = sizeof(*pSignature->Primary.pContentInfo);
                Info.pvExternalData = NULL;
                Info.cbExternalData = 0;
                rc = pfnCallback(&pModPe->Core, &Info, pErrInfo, pvUser);

                /* The nested signatures: */
                for (uint32_t iNested = 0; iNested < pSignature->cNested && rc == VINF_SUCCESS; iNested++)
                {
                    Info.iSignature     = (uint16_t)(1 + iNested);
                    Info.cSignatures    = (uint16_t)(1 + pSignature->cNested);
                    Info.enmType        = RTLDRSIGNATURETYPE_PKCS7_SIGNED_DATA;
                    Info.pvSignature    = pSignature->paNested[iNested].pContentInfo;
                    Info.cbSignature    = sizeof(*pSignature->paNested[iNested].pContentInfo);
                    Info.pvExternalData = NULL;
                    Info.cbExternalData = 0;
                    rc = pfnCallback(&pModPe->Core, &Info, pErrInfo, pvUser);
                }
            }
            rtldrPE_VerifySignatureDestroy(pModPe, pSignature);
        }
    }
    return rc;
#else
    RT_NOREF_PV(pMod); RT_NOREF_PV(pfnCallback); RT_NOREF_PV(pvUser); RT_NOREF_PV(pErrInfo);
    return VERR_NOT_SUPPORTED;
#endif
}



/**
 * @interface_method_impl{RTLDROPS,pfnHashImage}
 */
static DECLCALLBACK(int) rtldrPE_HashImage(PRTLDRMODINTERNAL pMod, RTDIGESTTYPE enmDigest, uint8_t *pabHash, size_t cbHash)
{
    PRTLDRMODPE pModPe = (PRTLDRMODPE)pMod;

    /*
     * Allocate a temporary memory buffer.
     */
    uint32_t    cbScratch = _16K;
    void       *pvScratch = RTMemTmpAlloc(cbScratch);
    if (!pvScratch)
    {
        cbScratch = _4K;
        pvScratch = RTMemTmpAlloc(cbScratch);
        if (!pvScratch)
            return VERR_NO_TMP_MEMORY;
    }

    /*
     * Do the hashing.
     */
    RTLDRPEHASHCTXUNION HashCtx;
    RTLDRPEHASHRESUNION HashRes;
    int rc = rtldrPE_HashImageCommon(pModPe, pvScratch, cbScratch, enmDigest, &HashCtx, &HashRes, NULL);
    if (RT_SUCCESS(rc))
    {
        /*
         * Copy out the result.
         */
        RT_NOREF(cbHash); /* verified by caller */
        switch (enmDigest)
        {
            case RTDIGESTTYPE_SHA512:  memcpy(pabHash, HashRes.abSha512, sizeof(HashRes.abSha512)); break;
            case RTDIGESTTYPE_SHA256:  memcpy(pabHash, HashRes.abSha256, sizeof(HashRes.abSha256)); break;
            case RTDIGESTTYPE_SHA1:    memcpy(pabHash, HashRes.abSha1,   sizeof(HashRes.abSha1)); break;
            case RTDIGESTTYPE_MD5:     memcpy(pabHash, HashRes.abMd5,    sizeof(HashRes.abMd5)); break;
            default:                   AssertFailedReturn(VERR_INTERNAL_ERROR_3);
        }
    }
    return rc;
}


/**
 * Binary searches the lookup table.
 *
 * @returns RVA of unwind info on success, UINT32_MAX on failure.
 * @param   paFunctions     The table to lookup @a uRva in.
 * @param   iEnd            Size of the table.
 * @param   uRva            The RVA of the function we want.
 */
DECLINLINE(PCIMAGE_RUNTIME_FUNCTION_ENTRY)
rtldrPE_LookupRuntimeFunctionEntry(PCIMAGE_RUNTIME_FUNCTION_ENTRY paFunctions, size_t iEnd, uint32_t uRva)
{
    size_t iBegin = 0;
    while (iBegin < iEnd)
    {
        size_t const i = iBegin  + (iEnd - iBegin) / 2;
        PCIMAGE_RUNTIME_FUNCTION_ENTRY pEntry = &paFunctions[i];
        if (uRva < pEntry->BeginAddress)
            iEnd = i;
        else if (uRva > pEntry->EndAddress)
            iBegin = i + 1;
        else
            return pEntry;
    }
    return NULL;
}


/**
 * Processes an IRET frame.
 *
 * @returns IPRT status code.
 * @param   pState          The unwind state being worked.
 * @param   fErrCd          Non-zero if there is an error code on the stack.
 */
static int rtldrPE_UnwindFrame_Amd64_IRet(PRTDBGUNWINDSTATE pState, uint8_t fErrCd)
{
    /* POP ErrCd (optional): */
    Assert(fErrCd <= 1);
    int rcRet;
    if (fErrCd)
    {
        pState->u.x86.uErrCd = 0;
        pState->u.x86.Loaded.s.fErrCd = 1;
        rcRet = RTDbgUnwindLoadStackU64(pState, pState->u.x86.auRegs[X86_GREG_xSP], &pState->u.x86.uErrCd);
        pState->u.x86.auRegs[X86_GREG_xSP] += 8;
    }
    else
    {
        pState->u.x86.Loaded.s.fErrCd = 0;
        rcRet = VINF_SUCCESS;
    }

    /* Set return type and frame pointer. */
    pState->enmRetType          = RTDBGRETURNTYPE_IRET64;
    pState->u.x86.FrameAddr.off = pState->u.x86.auRegs[X86_GREG_xSP] - /* pretend rbp is pushed on the stack */ 8;
    pState->u.x86.FrameAddr.sel = pState->u.x86.auSegs[X86_SREG_SS];

    /* POP RIP: */
    int rc = RTDbgUnwindLoadStackU64(pState, pState->u.x86.auRegs[X86_GREG_xSP], &pState->uPc);
    if (RT_FAILURE(rc))
        rcRet = rc;
    pState->u.x86.auRegs[X86_GREG_xSP] += 8;

    /* POP CS: */
    rc = RTDbgUnwindLoadStackU16(pState, pState->u.x86.auRegs[X86_GREG_xSP], &pState->u.x86.auSegs[X86_SREG_CS]);
    if (RT_FAILURE(rc))
        rcRet = rc;
    pState->u.x86.auRegs[X86_GREG_xSP] += 8;

    /* POP RFLAGS: */
    rc = RTDbgUnwindLoadStackU64(pState, pState->u.x86.auRegs[X86_GREG_xSP], &pState->u.x86.uRFlags);
    if (RT_FAILURE(rc))
        rcRet = rc;
    pState->u.x86.auRegs[X86_GREG_xSP] += 8;

    /* POP RSP, part 1: */
    uint64_t uNewRsp = (pState->u.x86.auRegs[X86_GREG_xSP] - 8) & ~(uint64_t)15;
    rc = RTDbgUnwindLoadStackU64(pState, pState->u.x86.auRegs[X86_GREG_xSP], &uNewRsp);
    if (RT_FAILURE(rc))
        rcRet = rc;
    pState->u.x86.auRegs[X86_GREG_xSP] += 8;

    /* POP SS: */
    rc = RTDbgUnwindLoadStackU16(pState, pState->u.x86.auRegs[X86_GREG_xSP], &pState->u.x86.auSegs[X86_SREG_SS]);
    if (RT_FAILURE(rc))
        rcRet = rc;
    pState->u.x86.auRegs[X86_GREG_xSP] += 8;

    /* POP RSP, part 2: */
    pState->u.x86.auRegs[X86_GREG_xSP] = uNewRsp;

    /* Set loaded indicators: */
    pState->u.x86.Loaded.s.fRegs        |= RT_BIT(X86_GREG_xSP);
    pState->u.x86.Loaded.s.fSegs        |= RT_BIT(X86_SREG_CS) | RT_BIT(X86_SREG_SS);
    pState->u.x86.Loaded.s.fPc           = 1;
    pState->u.x86.Loaded.s.fFrameAddr    = 1;
    pState->u.x86.Loaded.s.fRFlags       = 1;
    return VINF_SUCCESS;
}


static int rtldrPE_UnwindFrame_Amd64(PRTLDRMODPE pThis, void const *pvBits, PRTDBGUNWINDSTATE pState, uint32_t uRvaPc,
                                     PCIMAGE_RUNTIME_FUNCTION_ENTRY pEntry)
{
    /* Did we find any unwind information? */
    if (!pEntry)
        return VERR_DBG_UNWIND_INFO_NOT_FOUND;

    /*
     * Do the unwinding.
     */
    IMAGE_RUNTIME_FUNCTION_ENTRY ChainedEntry;
    unsigned iFrameReg   = ~0U;
    unsigned offFrameReg = 0;

    int      fInEpilog = -1; /* -1: not-determined-assume-false;  0: false;  1: true. */
    uint8_t  cbEpilog  = 0;
    uint8_t  offEpilog = UINT8_MAX;
    int      rcRet = VINF_SUCCESS;
    int      rc;
    for (unsigned cChainLoops = 0; ; cChainLoops++)
    {
        /*
         * Get the info.
         */
        union
        {
            uint32_t uRva;
            uint8_t  ab[  RT_OFFSETOF(IMAGE_UNWIND_INFO, aOpcodes)
                        + sizeof(IMAGE_UNWIND_CODE) * 256
                        + sizeof(IMAGE_RUNTIME_FUNCTION_ENTRY)];
        } uBuf;
        rc = rtldrPEReadPartByRvaInfoBuf(pThis, pvBits, pEntry->UnwindInfoAddress, sizeof(uBuf), &uBuf);
        if (RT_FAILURE(rc))
            return rc;

        /*
         * Check the info.
         */
        ASMCompilerBarrier(); /* we're aliasing */
        PCIMAGE_UNWIND_INFO pInfo = (PCIMAGE_UNWIND_INFO)&uBuf;

        if (pInfo->Version != 1 && pInfo->Version != 2)
            return VERR_DBG_MALFORMED_UNWIND_INFO;

        /*
         * Execute the opcodes.
         */
        unsigned const cOpcodes = pInfo->CountOfCodes;
        unsigned       iOpcode  = 0;

        /*
         * Check for epilog opcodes at the start and see if we're in an epilog.
         */
        if (   pInfo->Version >= 2
            && iOpcode < cOpcodes
            && pInfo->aOpcodes[iOpcode].u.UnwindOp == IMAGE_AMD64_UWOP_EPILOG)
        {
            if (fInEpilog == -1)
            {
                cbEpilog = pInfo->aOpcodes[iOpcode].u.CodeOffset;
                Assert(cbEpilog > 0);

                uint32_t uRvaEpilog = pEntry->EndAddress - cbEpilog;
                iOpcode++;
                if (   (pInfo->aOpcodes[iOpcode - 1].u.OpInfo & 1)
                    && uRvaPc >= uRvaEpilog)
                {
                    offEpilog = uRvaPc - uRvaEpilog;
                    fInEpilog = 1;
                }
                else
                {
                    fInEpilog = 0;
                    while (iOpcode < cOpcodes && pInfo->aOpcodes[iOpcode].u.UnwindOp == IMAGE_AMD64_UWOP_EPILOG)
                    {
                        uRvaEpilog = pEntry->EndAddress
                                   - (pInfo->aOpcodes[iOpcode].u.CodeOffset + (pInfo->aOpcodes[iOpcode].u.OpInfo << 8));
                        iOpcode++;
                        if (uRvaPc - uRvaEpilog < cbEpilog)
                        {
                            offEpilog = uRvaPc - uRvaEpilog;
                            fInEpilog = 1;
                            break;
                        }
                    }
                }
            }
            while (iOpcode < cOpcodes && pInfo->aOpcodes[iOpcode].u.UnwindOp == IMAGE_AMD64_UWOP_EPILOG)
                iOpcode++;
        }
        if (fInEpilog != 1)
        {
            /*
             * Skip opcodes that doesn't apply to us if we're in the prolog.
             */
            uint32_t offPc = uRvaPc - pEntry->BeginAddress;
            if (offPc < pInfo->SizeOfProlog)
                while (iOpcode < cOpcodes && pInfo->aOpcodes[iOpcode].u.CodeOffset > offPc)
                    iOpcode++;

            /*
             * Execute the opcodes.
             */
            if (pInfo->FrameRegister != 0)
            {
                iFrameReg   = pInfo->FrameRegister;
                offFrameReg = pInfo->FrameOffset * 16;
            }
            while (iOpcode < cOpcodes)
            {
                Assert(pInfo->aOpcodes[iOpcode].u.CodeOffset <= offPc);
                uint8_t const uOpInfo   = pInfo->aOpcodes[iOpcode].u.OpInfo;
                uint8_t const uUnwindOp = pInfo->aOpcodes[iOpcode].u.UnwindOp;
                switch (uUnwindOp)
                {
                    case IMAGE_AMD64_UWOP_PUSH_NONVOL:
                        rc = RTDbgUnwindLoadStackU64(pState, pState->u.x86.auRegs[X86_GREG_xSP], &pState->u.x86.auRegs[uOpInfo]);
                        if (RT_FAILURE(rc))
                            rcRet = rc;
                        pState->u.x86.Loaded.s.fRegs |= RT_BIT(uOpInfo);
                        pState->u.x86.auRegs[X86_GREG_xSP] += 8;
                        iOpcode++;
                        break;

                    case IMAGE_AMD64_UWOP_ALLOC_LARGE:
                        if (uOpInfo == 0)
                        {
                            iOpcode += 2;
                            AssertBreak(iOpcode <= cOpcodes);
                            pState->u.x86.auRegs[X86_GREG_xSP] += pInfo->aOpcodes[iOpcode - 1].FrameOffset * 8;
                        }
                        else
                        {
                            iOpcode += 3;
                            AssertBreak(iOpcode <= cOpcodes);
                            pState->u.x86.auRegs[X86_GREG_xSP] += RT_MAKE_U32(pInfo->aOpcodes[iOpcode - 2].FrameOffset,
                                                                              pInfo->aOpcodes[iOpcode - 1].FrameOffset);
                        }
                        break;

                    case IMAGE_AMD64_UWOP_ALLOC_SMALL:
                        AssertBreak(iOpcode <= cOpcodes);
                        pState->u.x86.auRegs[X86_GREG_xSP] += uOpInfo * 8 + 8;
                        iOpcode++;
                        break;

                    case IMAGE_AMD64_UWOP_SET_FPREG:
                        iFrameReg = uOpInfo;
                        offFrameReg = pInfo->FrameOffset * 16;
                        pState->u.x86.auRegs[X86_GREG_xSP] = pState->u.x86.auRegs[iFrameReg] - offFrameReg;
                        iOpcode++;
                        break;

                    case IMAGE_AMD64_UWOP_SAVE_NONVOL:
                    case IMAGE_AMD64_UWOP_SAVE_NONVOL_FAR:
                    {
                        uint32_t off = 0;
                        iOpcode++;
                        if (iOpcode < cOpcodes)
                        {
                            off = pInfo->aOpcodes[iOpcode].FrameOffset;
                            iOpcode++;
                            if (uUnwindOp == IMAGE_AMD64_UWOP_SAVE_NONVOL_FAR && iOpcode < cOpcodes)
                            {
                                off |= (uint32_t)pInfo->aOpcodes[iOpcode].FrameOffset << 16;
                                iOpcode++;
                            }
                        }
                        off *= 8;
                        rc = RTDbgUnwindLoadStackU64(pState, pState->u.x86.auRegs[X86_GREG_xSP] + off,
                                                     &pState->u.x86.auRegs[uOpInfo]);
                        if (RT_FAILURE(rc))
                            rcRet = rc;
                        pState->u.x86.Loaded.s.fRegs |= RT_BIT(uOpInfo);
                        break;
                    }

                    case IMAGE_AMD64_UWOP_SAVE_XMM128:
                        iOpcode += 2;
                        break;

                    case IMAGE_AMD64_UWOP_SAVE_XMM128_FAR:
                        iOpcode += 3;
                        break;

                    case IMAGE_AMD64_UWOP_PUSH_MACHFRAME:
                        return rtldrPE_UnwindFrame_Amd64_IRet(pState, uOpInfo);

                    case IMAGE_AMD64_UWOP_EPILOG:
                        iOpcode += 1;
                        break;

                    case IMAGE_AMD64_UWOP_RESERVED_7:
                        AssertFailedReturn(VERR_DBG_MALFORMED_UNWIND_INFO);

                    default:
                        AssertMsgFailedReturn(("%u\n", uUnwindOp), VERR_DBG_MALFORMED_UNWIND_INFO);
                }
            }
        }
        else
        {
            /*
             * We're in the POP sequence of an epilog.  The POP sequence should
             * mirror the PUSH sequence exactly.
             *
             * Note! We should only end up here for the initial frame (just consider
             *       RSP, stack allocations, non-volatile register restores, ++).
             */
            while (iOpcode < cOpcodes)
            {
                uint8_t const uOpInfo   = pInfo->aOpcodes[iOpcode].u.OpInfo;
                uint8_t const uUnwindOp = pInfo->aOpcodes[iOpcode].u.UnwindOp;
                switch (uUnwindOp)
                {
                    case IMAGE_AMD64_UWOP_PUSH_NONVOL:
                        pState->u.x86.auRegs[X86_GREG_xSP] += 8;
                        if (offEpilog == 0)
                        {
                            rc = RTDbgUnwindLoadStackU64(pState, pState->u.x86.auRegs[X86_GREG_xSP],
                                                         &pState->u.x86.auRegs[uOpInfo]);
                            if (RT_FAILURE(rc))
                                rcRet = rc;
                            pState->u.x86.Loaded.s.fRegs |= RT_BIT(uOpInfo);
                        }
                        else
                        {
                            /* Decrement offEpilog by estimated POP instruction length. */
                            offEpilog -= 1;
                            if (offEpilog > 0 && uOpInfo >= 8)
                                offEpilog -= 1;
                        }
                        iOpcode++;
                        break;

                    case IMAGE_AMD64_UWOP_PUSH_MACHFRAME: /* Must terminate an epilog, so always execute this. */
                        return rtldrPE_UnwindFrame_Amd64_IRet(pState, uOpInfo);

                    case IMAGE_AMD64_UWOP_ALLOC_SMALL:
                    case IMAGE_AMD64_UWOP_SET_FPREG:
                    case IMAGE_AMD64_UWOP_EPILOG:
                        iOpcode++;
                        break;
                    case IMAGE_AMD64_UWOP_SAVE_NONVOL:
                    case IMAGE_AMD64_UWOP_SAVE_XMM128:
                        iOpcode += 2;
                        break;
                    case IMAGE_AMD64_UWOP_ALLOC_LARGE:
                    case IMAGE_AMD64_UWOP_SAVE_NONVOL_FAR:
                    case IMAGE_AMD64_UWOP_SAVE_XMM128_FAR:
                        iOpcode += 3;
                        break;

                    default:
                        AssertMsgFailedReturn(("%u\n", uUnwindOp), VERR_DBG_MALFORMED_UNWIND_INFO);
                }
            }
        }

        /*
         * Chained stuff?
         */
        if (!(pInfo->Flags & IMAGE_UNW_FLAGS_CHAININFO))
            break;
        ChainedEntry = *(PCIMAGE_RUNTIME_FUNCTION_ENTRY)&pInfo->aOpcodes[(cOpcodes + 1) & ~1];
        pEntry = &ChainedEntry;
        AssertReturn(cChainLoops < 32, VERR_DBG_MALFORMED_UNWIND_INFO);
    }

    /*
     * RSP should now give us the return address, so perform a RET.
     */
    pState->enmRetType = RTDBGRETURNTYPE_NEAR64;

    pState->u.x86.FrameAddr.off = pState->u.x86.auRegs[X86_GREG_xSP] - /* pretend rbp is pushed on the stack */ 8;
    pState->u.x86.FrameAddr.sel = pState->u.x86.auSegs[X86_SREG_SS];
    pState->u.x86.Loaded.s.fFrameAddr = 1;

    rc = RTDbgUnwindLoadStackU64(pState, pState->u.x86.auRegs[X86_GREG_xSP], &pState->uPc);
    if (RT_FAILURE(rc))
        rcRet = rc;
    pState->u.x86.auRegs[X86_GREG_xSP] += 8;
    pState->u.x86.Loaded.s.fPc = 1;
    return rcRet;
}


/**
 * @interface_method_impl{RTLDROPS,pfnUnwindFrame}
 */
static DECLCALLBACK(int) rtldrPE_UnwindFrame(PRTLDRMODINTERNAL pMod, void const *pvBits,
                                             uint32_t iSeg, RTUINTPTR off, PRTDBGUNWINDSTATE pState)
{
    PRTLDRMODPE pThis = (PRTLDRMODPE)pMod;

    /*
     * Translate the segment + offset into an RVA.
     */
    RTLDRADDR uRvaPc = off;
    if (iSeg != UINT32_MAX)
    {
        int rc = rtldrPE_SegOffsetToRva(pMod, iSeg, off, &uRvaPc);
        if (RT_FAILURE(rc))
            return rc;
    }

    /*
     * Check for unwind info and match the architecture.
     */
    if (   pThis->ExceptionDir.Size == 0
        || pThis->ExceptionDir.VirtualAddress < pThis->cbHeaders)
        return VERR_DBG_NO_UNWIND_INFO;
    if (pThis->Core.enmArch != pState->enmArch)
        return VERR_DBG_UNWIND_INFO_NOT_FOUND;

    /* Currently only AMD64 unwinding is implemented, so head it off right away. */
    if (pThis->Core.enmArch != RTLDRARCH_AMD64)
        return VERR_DBG_UNWIND_INFO_NOT_FOUND;

    /*
     * Make the lookup table available to us.
     */
    void const    *pvTable = NULL;
    uint32_t const cbTable = pThis->ExceptionDir.Size;
    AssertReturn(   cbTable < pThis->cbImage
                 && pThis->ExceptionDir.VirtualAddress < pThis->cbImage
                 && pThis->ExceptionDir.VirtualAddress + cbTable <= pThis->cbImage, VERR_INTERNAL_ERROR_3);
    int rc = rtldrPEReadPartByRva(pThis, pvBits, pThis->ExceptionDir.VirtualAddress, pThis->ExceptionDir.Size, &pvTable);
    if (RT_FAILURE(rc))
        return rc;

    /*
     * The rest is architecture dependent.
     *
     * Note! On windows we try catch access violations so we can safely use
     *       this code on mapped images during assertions.
     */
#if defined(_MSC_VER) && defined(IN_RING3) && !defined(IN_SUP_HARDENED_R3)
    __try
    {
#endif
        switch (pThis->Core.enmArch)
        {
            case RTLDRARCH_AMD64:
                rc = rtldrPE_UnwindFrame_Amd64(pThis, pvBits, pState, uRvaPc,
                                               rtldrPE_LookupRuntimeFunctionEntry((PCIMAGE_RUNTIME_FUNCTION_ENTRY)pvTable,
                                                                                  cbTable / sizeof(IMAGE_RUNTIME_FUNCTION_ENTRY),
                                                                                  (uint32_t)uRvaPc));
                break;

            default:
                rc = VERR_DBG_UNWIND_INFO_NOT_FOUND;
                break;
        }
#if defined(_MSC_VER) && defined(IN_RING3) && !defined(IN_SUP_HARDENED_R3)
    }
    __except (1 /*EXCEPTION_EXECUTE_HANDLER*/)
    {
        rc = VERR_DBG_UNWIND_INFO_NOT_FOUND;
    }
#endif
    rtldrPEFreePart(pThis, pvBits, pvTable);
    return rc;
}


/** @interface_method_impl{RTLDROPS,pfnDone}   */
static DECLCALLBACK(int) rtldrPEDone(PRTLDRMODINTERNAL pMod)
{
    PRTLDRMODPE pModPe = (PRTLDRMODPE)pMod;
    if (pModPe->pvBits)
    {
        RTMemFree(pModPe->pvBits);
        pModPe->pvBits = NULL;
    }
    return VINF_SUCCESS;
}


/** @interface_method_impl{RTLDROPS,pfnClose}  */
static DECLCALLBACK(int) rtldrPEClose(PRTLDRMODINTERNAL pMod)
{
    PRTLDRMODPE pModPe = (PRTLDRMODPE)pMod;
    if (pModPe->paSections)
    {
        RTMemFree(pModPe->paSections);
        pModPe->paSections = NULL;
    }
    if (pModPe->pvBits)
    {
        RTMemFree(pModPe->pvBits);
        pModPe->pvBits = NULL;
    }
    return VINF_SUCCESS;
}


/**
 * Operations for a 32-bit PE module.
 */
static const RTLDROPSPE s_rtldrPE32Ops =
{
    {
        "pe32",
        rtldrPEClose,
        NULL,
        rtldrPEDone,
        rtldrPEEnumSymbols,
        /* ext */
        rtldrPEGetImageSize,
        rtldrPEGetBits,
        rtldrPERelocate,
        rtldrPEGetSymbolEx,
        rtldrPE_QueryForwarderInfo,
        rtldrPE_EnumDbgInfo,
        rtldrPE_EnumSegments,
        rtldrPE_LinkAddressToSegOffset,
        rtldrPE_LinkAddressToRva,
        rtldrPE_SegOffsetToRva,
        rtldrPE_RvaToSegOffset,
        NULL,
        rtldrPE_QueryProp,
        rtldrPE_VerifySignature,
        rtldrPE_HashImage,
        NULL /*pfnUnwindFrame*/,
        42
    },
    rtldrPEResolveImports32,
    42
};


/**
 * Operations for a 64-bit PE module.
 */
static const RTLDROPSPE s_rtldrPE64Ops =
{
    {
        "pe64",
        rtldrPEClose,
        NULL,
        rtldrPEDone,
        rtldrPEEnumSymbols,
        /* ext */
        rtldrPEGetImageSize,
        rtldrPEGetBits,
        rtldrPERelocate,
        rtldrPEGetSymbolEx,
        rtldrPE_QueryForwarderInfo,
        rtldrPE_EnumDbgInfo,
        rtldrPE_EnumSegments,
        rtldrPE_LinkAddressToSegOffset,
        rtldrPE_LinkAddressToRva,
        rtldrPE_SegOffsetToRva,
        rtldrPE_RvaToSegOffset,
        NULL,
        rtldrPE_QueryProp,
        rtldrPE_VerifySignature,
        rtldrPE_HashImage,
        rtldrPE_UnwindFrame,
        42
    },
    rtldrPEResolveImports64,
    42
};


/**
 * Converts the optional header from 32 bit to 64 bit.
 * This is a rather simple task, if you start from the right end.
 *
 * @param   pOptHdr     On input this is a PIMAGE_OPTIONAL_HEADER32.
 *                      On output this will be a PIMAGE_OPTIONAL_HEADER64.
 */
static void rtldrPEConvert32BitOptionalHeaderTo64Bit(PIMAGE_OPTIONAL_HEADER64 pOptHdr)
{
    /*
     * volatile everywhere! Trying to prevent the compiler being a smarta$$ and reorder stuff.
     */
    IMAGE_OPTIONAL_HEADER32 volatile *pOptHdr32 = (IMAGE_OPTIONAL_HEADER32 volatile *)pOptHdr;
    IMAGE_OPTIONAL_HEADER64 volatile *pOptHdr64 = pOptHdr;

    /* from LoaderFlags and out the difference is 4 * 32-bits. */
    Assert(RT_UOFFSETOF(IMAGE_OPTIONAL_HEADER32, LoaderFlags) + 16 == RT_UOFFSETOF(IMAGE_OPTIONAL_HEADER64, LoaderFlags));
    Assert(     RT_UOFFSETOF(IMAGE_OPTIONAL_HEADER32, DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]) + 16
           ==   RT_UOFFSETOF(IMAGE_OPTIONAL_HEADER64, DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]));
    uint32_t volatile       *pu32Dst     = (uint32_t *)&pOptHdr64->DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES] - 1;
    const uint32_t volatile *pu32Src     = (uint32_t *)&pOptHdr32->DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES] - 1;
    const uint32_t volatile *pu32SrcLast = (uint32_t *)&pOptHdr32->LoaderFlags;
    while (pu32Src >= pu32SrcLast)
        *pu32Dst-- = *pu32Src--;

    /* the previous 4 fields are 32/64 and needs special attention. */
    pOptHdr64->SizeOfHeapCommit   = pOptHdr32->SizeOfHeapCommit;
    pOptHdr64->SizeOfHeapReserve  = pOptHdr32->SizeOfHeapReserve;
    pOptHdr64->SizeOfStackCommit  = pOptHdr32->SizeOfStackCommit;
    uint32_t u32SizeOfStackReserve = pOptHdr32->SizeOfStackReserve;
    pOptHdr64->SizeOfStackReserve = u32SizeOfStackReserve;

    /* The rest matches except for BaseOfData which has been merged into ImageBase in the 64-bit version..
     * Thus, ImageBase needs some special treatment. It will probably work fine assigning one to the
     * other since this is all declared volatile, but taking now chances, we'll use a temp variable.
     */
    Assert(RT_UOFFSETOF(IMAGE_OPTIONAL_HEADER32, SizeOfStackReserve) == RT_UOFFSETOF(IMAGE_OPTIONAL_HEADER64, SizeOfStackReserve));
    Assert(RT_UOFFSETOF(IMAGE_OPTIONAL_HEADER32, BaseOfData)         == RT_UOFFSETOF(IMAGE_OPTIONAL_HEADER64, ImageBase));
    Assert(RT_UOFFSETOF(IMAGE_OPTIONAL_HEADER32, SectionAlignment)   == RT_UOFFSETOF(IMAGE_OPTIONAL_HEADER64, SectionAlignment));
    uint32_t u32ImageBase = pOptHdr32->ImageBase;
    pOptHdr64->ImageBase = u32ImageBase;
}


/**
 * Converts the load config directory from 32 bit to 64 bit.
 * This is a rather simple task, if you start from the right end.
 *
 * @param   pLoadCfg    On input this is a PIMAGE_LOAD_CONFIG_DIRECTORY32.
 *                      On output this will be a PIMAGE_LOAD_CONFIG_DIRECTORY64.
 */
static void rtldrPEConvert32BitLoadConfigTo64Bit(PIMAGE_LOAD_CONFIG_DIRECTORY64 pLoadCfg)
{
    /*
     * volatile everywhere! Trying to prevent the compiler being a smarta$$ and reorder stuff.
     */
    IMAGE_LOAD_CONFIG_DIRECTORY32_V13 volatile *pLoadCfg32 = (IMAGE_LOAD_CONFIG_DIRECTORY32_V13 volatile *)pLoadCfg;
    IMAGE_LOAD_CONFIG_DIRECTORY64_V13 volatile *pLoadCfg64 = pLoadCfg;

    pLoadCfg64->CastGuardOsDeterminedFailureMode         = pLoadCfg32->CastGuardOsDeterminedFailureMode;
    pLoadCfg64->GuardXFGTableDispatchFunctionPointer     = pLoadCfg32->GuardXFGTableDispatchFunctionPointer;
    pLoadCfg64->GuardXFGDispatchFunctionPointer          = pLoadCfg32->GuardXFGDispatchFunctionPointer;
    pLoadCfg64->GuardXFGCheckFunctionPointer             = pLoadCfg32->GuardXFGCheckFunctionPointer;
    pLoadCfg64->GuardEHContinuationCount                 = pLoadCfg32->GuardEHContinuationCount;
    pLoadCfg64->GuardEHContinuationTable                 = pLoadCfg32->GuardEHContinuationTable;
    pLoadCfg64->VolatileMetadataPointer                  = pLoadCfg32->VolatileMetadataPointer;
    pLoadCfg64->EnclaveConfigurationPointer              = pLoadCfg32->EnclaveConfigurationPointer;
    pLoadCfg64->Reserved3                                = pLoadCfg32->Reserved3;
    pLoadCfg64->HotPatchTableOffset                      = pLoadCfg32->HotPatchTableOffset;
    pLoadCfg64->GuardRFVerifyStackPointerFunctionPointer = pLoadCfg32->GuardRFVerifyStackPointerFunctionPointer;
    pLoadCfg64->Reserved2                                = pLoadCfg32->Reserved2;
    pLoadCfg64->DynamicValueRelocTableSection            = pLoadCfg32->DynamicValueRelocTableSection;
    pLoadCfg64->DynamicValueRelocTableOffset             = pLoadCfg32->DynamicValueRelocTableOffset;
    pLoadCfg64->GuardRFFailureRoutineFunctionPointer     = pLoadCfg32->GuardRFFailureRoutineFunctionPointer;
    pLoadCfg64->GuardRFFailureRoutine                    = pLoadCfg32->GuardRFFailureRoutine;
    pLoadCfg64->CHPEMetadataPointer                      = pLoadCfg32->CHPEMetadataPointer;
    pLoadCfg64->DynamicValueRelocTable                   = pLoadCfg32->DynamicValueRelocTable;
    pLoadCfg64->GuardLongJumpTargetCount                 = pLoadCfg32->GuardLongJumpTargetCount;
    pLoadCfg64->GuardLongJumpTargetTable                 = pLoadCfg32->GuardLongJumpTargetTable;
    pLoadCfg64->GuardAddressTakenIatEntryCount           = pLoadCfg32->GuardAddressTakenIatEntryCount;
    pLoadCfg64->GuardAddressTakenIatEntryTable           = pLoadCfg32->GuardAddressTakenIatEntryTable;
    pLoadCfg64->CodeIntegrity.Reserved                   = pLoadCfg32->CodeIntegrity.Reserved;
    pLoadCfg64->CodeIntegrity.CatalogOffset              = pLoadCfg32->CodeIntegrity.CatalogOffset;
    pLoadCfg64->CodeIntegrity.Catalog                    = pLoadCfg32->CodeIntegrity.Catalog;
    pLoadCfg64->CodeIntegrity.Flags                      = pLoadCfg32->CodeIntegrity.Flags;
    pLoadCfg64->GuardFlags                               = pLoadCfg32->GuardFlags;
    pLoadCfg64->GuardCFFunctionCount                     = pLoadCfg32->GuardCFFunctionCount;
    pLoadCfg64->GuardCFFunctionTable                     = pLoadCfg32->GuardCFFunctionTable;
    pLoadCfg64->GuardCFDispatchFunctionPointer           = pLoadCfg32->GuardCFDispatchFunctionPointer;
    pLoadCfg64->GuardCFCCheckFunctionPointer             = pLoadCfg32->GuardCFCCheckFunctionPointer;
    pLoadCfg64->SEHandlerCount                           = pLoadCfg32->SEHandlerCount;
    pLoadCfg64->SEHandlerTable                           = pLoadCfg32->SEHandlerTable;
    pLoadCfg64->SecurityCookie                           = pLoadCfg32->SecurityCookie;
    pLoadCfg64->EditList                                 = pLoadCfg32->EditList;
    pLoadCfg64->DependentLoadFlags                       = pLoadCfg32->DependentLoadFlags;
    pLoadCfg64->CSDVersion                               = pLoadCfg32->CSDVersion;
    pLoadCfg64->ProcessHeapFlags                         = pLoadCfg32->ProcessHeapFlags; /* switched place with ProcessAffinityMask, but we're more than 16 byte off by now so it doesn't matter. */
    pLoadCfg64->ProcessAffinityMask                      = pLoadCfg32->ProcessAffinityMask;
    pLoadCfg64->VirtualMemoryThreshold                   = pLoadCfg32->VirtualMemoryThreshold;
    pLoadCfg64->MaximumAllocationSize                    = pLoadCfg32->MaximumAllocationSize;
    pLoadCfg64->LockPrefixTable                          = pLoadCfg32->LockPrefixTable;
    pLoadCfg64->DeCommitTotalFreeThreshold               = pLoadCfg32->DeCommitTotalFreeThreshold;
    uint32_t u32DeCommitFreeBlockThreshold               = pLoadCfg32->DeCommitFreeBlockThreshold;
    pLoadCfg64->DeCommitFreeBlockThreshold               = u32DeCommitFreeBlockThreshold;
    /* the rest is equal. */
    Assert(     RT_UOFFSETOF(IMAGE_LOAD_CONFIG_DIRECTORY32, DeCommitFreeBlockThreshold)
           ==   RT_UOFFSETOF(IMAGE_LOAD_CONFIG_DIRECTORY64, DeCommitFreeBlockThreshold));
}


/**
 * Translate the PE/COFF machine name to a string.
 *
 * @returns Name string (read-only).
 * @param   uMachine            The PE/COFF machine.
 */
static const char *rtldrPEGetArchName(uint16_t uMachine)
{
    switch (uMachine)
    {
        case IMAGE_FILE_MACHINE_I386:           return "X86_32";
        case IMAGE_FILE_MACHINE_AMD64:          return "AMD64";

        case IMAGE_FILE_MACHINE_UNKNOWN:        return "UNKNOWN";
        case IMAGE_FILE_MACHINE_AM33:           return "AM33";
        case IMAGE_FILE_MACHINE_ARM:            return "ARM";
        case IMAGE_FILE_MACHINE_THUMB:          return "THUMB";
        case IMAGE_FILE_MACHINE_ARMNT:          return "ARMNT";
        case IMAGE_FILE_MACHINE_ARM64:          return "ARM64";
        case IMAGE_FILE_MACHINE_EBC:            return "EBC";
        case IMAGE_FILE_MACHINE_IA64:           return "IA64";
        case IMAGE_FILE_MACHINE_M32R:           return "M32R";
        case IMAGE_FILE_MACHINE_MIPS16:         return "MIPS16";
        case IMAGE_FILE_MACHINE_MIPSFPU:        return "MIPSFPU";
        case IMAGE_FILE_MACHINE_MIPSFPU16:      return "MIPSFPU16";
        case IMAGE_FILE_MACHINE_WCEMIPSV2:      return "WCEMIPSV2";
        case IMAGE_FILE_MACHINE_POWERPC:        return "POWERPC";
        case IMAGE_FILE_MACHINE_POWERPCFP:      return "POWERPCFP";
        case IMAGE_FILE_MACHINE_R4000:          return "R4000";
        case IMAGE_FILE_MACHINE_SH3:            return "SH3";
        case IMAGE_FILE_MACHINE_SH3DSP:         return "SH3DSP";
        case IMAGE_FILE_MACHINE_SH4:            return "SH4";
        case IMAGE_FILE_MACHINE_SH5:            return "SH5";
        default:                                return "UnknownMachine";
    }
}


/**
 * Validates the file header.
 *
 * @returns iprt status code.
 * @param   pFileHdr    Pointer to the file header that needs validating.
 * @param   fFlags      Valid RTLDR_O_XXX combination.
 * @param   pszLogName  The log name to  prefix the errors with.
 * @param   penmArch    Where to store the CPU architecture.
 * @param   pErrInfo    Where to return additional error information.
 */
static int rtldrPEValidateFileHeader(PIMAGE_FILE_HEADER pFileHdr, uint32_t fFlags, const char *pszLogName,
                                     PRTLDRARCH penmArch, PRTERRINFO pErrInfo)
{
    RT_NOREF_PV(pszLogName);

    size_t cbOptionalHeader;
    switch (pFileHdr->Machine)
    {
        case IMAGE_FILE_MACHINE_I386:
            cbOptionalHeader = sizeof(IMAGE_OPTIONAL_HEADER32);
            *penmArch = RTLDRARCH_X86_32;
            break;
        case IMAGE_FILE_MACHINE_AMD64:
            cbOptionalHeader = sizeof(IMAGE_OPTIONAL_HEADER64);
            *penmArch = RTLDRARCH_AMD64;
            break;

        default:
            Log(("rtldrPEOpen: %s: Unsupported Machine=%#x\n", pszLogName, pFileHdr->Machine));
            *penmArch = RTLDRARCH_INVALID;
            return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "Unsupported Machine=%#x", pFileHdr->Machine);
    }
    if (pFileHdr->SizeOfOptionalHeader != cbOptionalHeader)
    {
        Log(("rtldrPEOpen: %s: SizeOfOptionalHeader=%#x expected %#x\n", pszLogName, pFileHdr->SizeOfOptionalHeader, cbOptionalHeader));
        return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "SizeOfOptionalHeader=%#x expected %#x",
                                   pFileHdr->SizeOfOptionalHeader, cbOptionalHeader);
    }
    /* This restriction needs to be implemented elsewhere. */
    if (   (pFileHdr->Characteristics & IMAGE_FILE_RELOCS_STRIPPED)
        && !(fFlags & (RTLDR_O_FOR_DEBUG | RTLDR_O_FOR_VALIDATION)))
    {
        Log(("rtldrPEOpen: %s: IMAGE_FILE_RELOCS_STRIPPED\n", pszLogName));
        return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "IMAGE_FILE_RELOCS_STRIPPED");
    }
    if (pFileHdr->NumberOfSections > 42)
    {
        Log(("rtldrPEOpen: %s: NumberOfSections=%d - our limit is 42, please raise it if the binary makes sense.(!!!)\n",
             pszLogName, pFileHdr->NumberOfSections));
        return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "NumberOfSections=%d, implementation max is 42", pFileHdr->NumberOfSections);
    }
    if (pFileHdr->NumberOfSections < 1)
    {
        Log(("rtldrPEOpen: %s: NumberOfSections=%d - we can't have an image without sections (!!!)\n",
             pszLogName, pFileHdr->NumberOfSections));
        return RTERRINFO_LOG_SET(pErrInfo, VERR_BAD_EXE_FORMAT, "Image has no sections");
    }
    return VINF_SUCCESS;
}


/**
 * Validates the optional header (64/32-bit)
 *
 * @returns iprt status code.
 * @param   pOptHdr     Pointer to the optional header which needs validation.
 * @param   pszLogName  The log name to  prefix the errors with.
 * @param   offNtHdrs   The offset of the NT headers from the start of the file.
 * @param   pFileHdr    Pointer to the file header (valid).
 * @param   cbRawImage  The raw image size.
 * @param   fFlags      Loader flags, RTLDR_O_XXX.
 * @param   pErrInfo    Where to return additional error information.
 */
static int rtldrPEValidateOptionalHeader(const IMAGE_OPTIONAL_HEADER64 *pOptHdr, const char *pszLogName, RTFOFF offNtHdrs,
                                         const IMAGE_FILE_HEADER *pFileHdr, uint64_t cbRawImage, uint32_t fFlags, PRTERRINFO pErrInfo)
{
    RT_NOREF_PV(pszLogName);

    const uint16_t CorrectMagic = pFileHdr->SizeOfOptionalHeader == sizeof(IMAGE_OPTIONAL_HEADER32)
                                ? IMAGE_NT_OPTIONAL_HDR32_MAGIC : IMAGE_NT_OPTIONAL_HDR64_MAGIC;
    if (pOptHdr->Magic != CorrectMagic)
    {
        Log(("rtldrPEOpen: %s: Magic=%#x - expected %#x!!!\n", pszLogName, pOptHdr->Magic, CorrectMagic));
        return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "Magic=%#x, expected %#x", pOptHdr->Magic, CorrectMagic);
    }
    const uint32_t cbImage = pOptHdr->SizeOfImage;
    if (cbImage > _1G)
    {
        Log(("rtldrPEOpen: %s: SizeOfImage=%#x - Our limit is 1GB (%#x)!!!\n", pszLogName, cbImage, _1G));
        return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "SizeOfImage=%#x - Our limit is 1GB (%#x)", cbImage, _1G);
    }
    const uint32_t cbMinImageSize = pFileHdr->SizeOfOptionalHeader + sizeof(*pFileHdr) + 4 + (uint32_t)offNtHdrs;
    if (cbImage < cbMinImageSize)
    {
        Log(("rtldrPEOpen: %s: SizeOfImage=%#x to small, minimum %#x!!!\n", pszLogName, cbImage, cbMinImageSize));
        return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "SizeOfImage=%#x to small, minimum %#x", cbImage, cbMinImageSize);
    }
    if (pOptHdr->AddressOfEntryPoint >= cbImage)
    {
        Log(("rtldrPEOpen: %s: AddressOfEntryPoint=%#x - beyond image size (%#x)!!!\n",
             pszLogName, pOptHdr->AddressOfEntryPoint, cbImage));
        return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT,
                                   "AddressOfEntryPoint=%#x - beyond image size (%#x)", pOptHdr->AddressOfEntryPoint, cbImage);
    }
    if (pOptHdr->BaseOfCode >= cbImage)
    {
        Log(("rtldrPEOpen: %s: BaseOfCode=%#x - beyond image size (%#x)!!!\n",
             pszLogName, pOptHdr->BaseOfCode, cbImage));
        return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT,
                                   "BaseOfCode=%#x - beyond image size (%#x)", pOptHdr->BaseOfCode, cbImage);
    }
#if 0/* only in 32-bit header */
    if (pOptHdr->BaseOfData >= cbImage)
    {
        Log(("rtldrPEOpen: %s: BaseOfData=%#x - beyond image size (%#x)!!!\n",
             pszLogName, pOptHdr->BaseOfData, cbImage));
        return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "BaseOfData=%#x - beyond image size (%#x)", pOptHdr->BaseOfData, cbImage);
    }
#endif
    if (!RT_IS_POWER_OF_TWO(pOptHdr->SectionAlignment))
        return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT,
                                   "SectionAlignment=%#x - not a power of two", pOptHdr->SectionAlignment);
    if (pOptHdr->SectionAlignment < 16 || pOptHdr->SectionAlignment > _128K)
        return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT,
                                   "SectionAlignment=%#x - unsupported value, not between 16 and 128KB", pOptHdr->SectionAlignment);
    if (pOptHdr->SizeOfHeaders >= cbImage)
    {
        Log(("rtldrPEOpen: %s: SizeOfHeaders=%#x - beyond image size (%#x)!!!\n",
             pszLogName, pOptHdr->SizeOfHeaders, cbImage));
        return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT,
                                   "SizeOfHeaders=%#x - beyond image size (%#x)", pOptHdr->SizeOfHeaders, cbImage);
    }
    /* don't know how to do the checksum, so ignore it. */
    if (pOptHdr->Subsystem == IMAGE_SUBSYSTEM_UNKNOWN)
    {
        Log(("rtldrPEOpen: %s: Subsystem=%#x (unknown)!!!\n", pszLogName, pOptHdr->Subsystem));
        return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "Subsystem=%#x (unknown)", pOptHdr->Subsystem);
    }
    if (pOptHdr->SizeOfHeaders < cbMinImageSize + pFileHdr->NumberOfSections * sizeof(IMAGE_SECTION_HEADER))
    {
        Log(("rtldrPEOpen: %s: SizeOfHeaders=%#x - cbMinImageSize %#x + sections %#x = %#llx!!!\n",
             pszLogName, pOptHdr->SizeOfHeaders,
             cbMinImageSize, pFileHdr->NumberOfSections * sizeof(IMAGE_SECTION_HEADER),
             cbMinImageSize + pFileHdr->NumberOfSections * sizeof(IMAGE_SECTION_HEADER)));
        return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "SizeOfHeaders=%#x - cbMinImageSize %#x + sections %#x = %#llx",
                                   pOptHdr->SizeOfHeaders, cbMinImageSize,
                                   pFileHdr->NumberOfSections * sizeof(IMAGE_SECTION_HEADER),
                                   cbMinImageSize + pFileHdr->NumberOfSections * sizeof(IMAGE_SECTION_HEADER) );
    }
    if (pOptHdr->SizeOfStackReserve < pOptHdr->SizeOfStackCommit)
    {
        Log(("rtldrPEOpen: %s: SizeOfStackReserve %#x < SizeOfStackCommit %#x!!!\n",
             pszLogName, pOptHdr->SizeOfStackReserve, pOptHdr->SizeOfStackCommit));
        return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "SizeOfStackReserve %#x < SizeOfStackCommit %#x",
                                   pOptHdr->SizeOfStackReserve, pOptHdr->SizeOfStackCommit);
    }
    if (pOptHdr->SizeOfHeapReserve < pOptHdr->SizeOfHeapCommit)
    {
        Log(("rtldrPEOpen: %s: SizeOfStackReserve %#x < SizeOfStackCommit %#x!!!\n",
             pszLogName, pOptHdr->SizeOfStackReserve, pOptHdr->SizeOfStackCommit));
        return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "SizeOfStackReserve %#x < SizeOfStackCommit %#x\n",
                                   pOptHdr->SizeOfStackReserve, pOptHdr->SizeOfStackCommit);
    }

    /* DataDirectory */
    if (pOptHdr->NumberOfRvaAndSizes != RT_ELEMENTS(pOptHdr->DataDirectory))
    {
        Log(("rtldrPEOpen: %s: NumberOfRvaAndSizes=%d!!!\n", pszLogName, pOptHdr->NumberOfRvaAndSizes));
        return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "NumberOfRvaAndSizes=%d, expected %d",
                                   pOptHdr->NumberOfRvaAndSizes, RT_ELEMENTS(pOptHdr->DataDirectory));
    }
    for (unsigned i = 0; i < RT_ELEMENTS(pOptHdr->DataDirectory); i++)
    {
        IMAGE_DATA_DIRECTORY const *pDir = &pOptHdr->DataDirectory[i];
        if (!pDir->Size)
            continue;
        size_t cb = cbImage;
        switch (i)
        {
            case IMAGE_DIRECTORY_ENTRY_EXPORT:        // 0
            case IMAGE_DIRECTORY_ENTRY_IMPORT:        // 1
            case IMAGE_DIRECTORY_ENTRY_RESOURCE:      // 2
            case IMAGE_DIRECTORY_ENTRY_EXCEPTION:     // 3
            case IMAGE_DIRECTORY_ENTRY_BASERELOC:     // 5
            case IMAGE_DIRECTORY_ENTRY_DEBUG:         // 6
            case IMAGE_DIRECTORY_ENTRY_COPYRIGHT:     // 7
            case IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT:  // 11
            case IMAGE_DIRECTORY_ENTRY_IAT:           // 12  /* Import Address Table */
                break;
            case IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG:   // 10 - need to check for lock prefixes.
                /* Delay inspection after section table is validated. */
                break;

            case IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT:  // 13
                if (fFlags & (RTLDR_O_FOR_DEBUG | RTLDR_O_FOR_VALIDATION))
                    break;
                Log(("rtldrPEOpen: %s: dir no. %d (DELAY_IMPORT) VirtualAddress=%#x Size=%#x is not supported!!!\n",
                     pszLogName, i, pDir->VirtualAddress, pDir->Size));
                return RTERRINFO_LOG_SET_F(pErrInfo, VERR_LDRPE_DELAY_IMPORT,
                                           "DELAY_IMPORT VirtualAddress=%#x Size=%#x: not supported", pDir->VirtualAddress, pDir->Size);

            case IMAGE_DIRECTORY_ENTRY_SECURITY:      // 4
                /* The VirtualAddress is a PointerToRawData. */
                cb = (size_t)cbRawImage; Assert((uint64_t)cb == cbRawImage);
                Log(("rtldrPEOpen: %s: dir no. %d (SECURITY) VirtualAddress=%#x Size=%#x\n", pszLogName, i, pDir->VirtualAddress, pDir->Size));
                if (pDir->Size < sizeof(WIN_CERTIFICATE))
                {
                    Log(("rtldrPEOpen: %s: Security directory #%u is too small: %#x bytes\n", pszLogName, i, pDir->Size));
                    return RTERRINFO_LOG_SET_F(pErrInfo, VERR_LDRPE_CERT_MALFORMED,
                                               "Security directory is too small: %#x bytes", pDir->Size);
                }
                if (pDir->Size >= RTLDRMODPE_MAX_SECURITY_DIR_SIZE)
                {
                    Log(("rtldrPEOpen: %s: Security directory #%u is too large: %#x bytes\n", pszLogName, i, pDir->Size));
                    return RTERRINFO_LOG_SET_F(pErrInfo, VERR_LDRPE_CERT_MALFORMED,
                                               "Security directory is too large: %#x bytes", pDir->Size);
                }
                if (pDir->VirtualAddress & 7)
                {
                    Log(("rtldrPEOpen: %s: Security directory #%u is misaligned: %#x\n", pszLogName, i, pDir->VirtualAddress));
                    return RTERRINFO_LOG_SET_F(pErrInfo, VERR_LDRPE_CERT_MALFORMED,
                                               "Security directory is misaligned: %#x", pDir->VirtualAddress);
                }
                /* When using the in-memory reader with a debugger, we may get
                   into trouble here since we might not have access to the whole
                   physical file.  So skip the tests below. Makes VBoxGuest.sys
                   load and check out just fine, for instance. */
                if (fFlags & RTLDR_O_FOR_DEBUG)
                    continue;
                break;

            case IMAGE_DIRECTORY_ENTRY_GLOBALPTR:     // 8   /* (MIPS GP) */
                Log(("rtldrPEOpen: %s: dir no. %d (GLOBALPTR) VirtualAddress=%#x Size=%#x is not supported!!!\n",
                     pszLogName, i, pDir->VirtualAddress, pDir->Size));
                return RTERRINFO_LOG_SET_F(pErrInfo, VERR_LDRPE_GLOBALPTR, "GLOBALPTR VirtualAddress=%#x Size=%#x: not supported",
                                           pDir->VirtualAddress, pDir->Size);

            case IMAGE_DIRECTORY_ENTRY_TLS:           // 9
                if (fFlags & (RTLDR_O_FOR_DEBUG | RTLDR_O_FOR_VALIDATION))
                    break;
                Log(("rtldrPEOpen: %s: dir no. %d (TLS) VirtualAddress=%#x Size=%#x is not supported!!!\n",
                     pszLogName, i, pDir->VirtualAddress, pDir->Size));
                return RTERRINFO_LOG_SET_F(pErrInfo, VERR_LDRPE_TLS, "TLS VirtualAddress=%#x Size=%#x: not supported",
                                           pDir->VirtualAddress, pDir->Size);

            case IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR:// 14
                if (fFlags & (RTLDR_O_FOR_DEBUG | RTLDR_O_FOR_VALIDATION))
                    break;
                Log(("rtldrPEOpen: %s: dir no. %d (COM_DESCRIPTOR) VirtualAddress=%#x Size=%#x is not supported!!!\n",
                     pszLogName, i, pDir->VirtualAddress, pDir->Size));
                return RTERRINFO_LOG_SET_F(pErrInfo, VERR_LDRPE_COM_DESCRIPTOR,
                                           "COM_DESCRIPTOR VirtualAddress=%#x Size=%#x: not supported",
                                           pDir->VirtualAddress, pDir->Size);

            default:
                Log(("rtldrPEOpen: %s: dir no. %d VirtualAddress=%#x Size=%#x is not supported!!!\n",
                     pszLogName, i, pDir->VirtualAddress, pDir->Size));
                return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "dir no. %d VirtualAddress=%#x Size=%#x is not supported",
                                           i, pDir->VirtualAddress, pDir->Size);
        }
        if (pDir->VirtualAddress >= cb)
        {
            Log(("rtldrPEOpen: %s: dir no. %d VirtualAddress=%#x is invalid (limit %#x)!!!\n",
                 pszLogName, i, pDir->VirtualAddress, cb));
            return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "dir no. %d VirtualAddress=%#x is invalid (limit %#x)",
                                       i, pDir->VirtualAddress, cb);
        }
        if (pDir->Size > cb - pDir->VirtualAddress)
        {
            Log(("rtldrPEOpen: %s: dir no. %d Size=%#x is invalid (rva=%#x, limit=%#x)!!!\n",
                 pszLogName, i, pDir->Size, pDir->VirtualAddress, cb));
            return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "dir no. %d Size=%#x is invalid (rva=%#x, limit=%#x)",
                                       i, pDir->Size, pDir->VirtualAddress, cb);
        }
    }
    return VINF_SUCCESS;
}


/**
 * Validates and touch up the section headers.
 *
 * The touching up is restricted to setting the VirtualSize field for old-style
 * linkers that sets it to zero.
 *
 * @returns iprt status code.
 * @param   paSections  Pointer to the array of sections that is to be validated.
 * @param   cSections   Number of sections in that array.
 * @param   pszLogName  The log name to  prefix the errors with.
 * @param   pOptHdr     Pointer to the optional header (valid).
 * @param   cbRawImage  The raw image size.
 * @param   fFlags      Loader flags, RTLDR_O_XXX.
 * @param   fNoCode     Verify that the image contains no code.
 */
static int rtldrPEValidateAndTouchUpSectionHeaders(IMAGE_SECTION_HEADER *paSections, unsigned cSections, const char *pszLogName,
                                                   const IMAGE_OPTIONAL_HEADER64 *pOptHdr, uint64_t cbRawImage, uint32_t fFlags,
                                                   bool fNoCode)
{
    RT_NOREF_PV(pszLogName);

    /*
     * Do a quick pass to detect linker setting VirtualSize to zero.
     */
    bool                  fFixupVirtualSize = true;
    IMAGE_SECTION_HEADER *pSH = &paSections[0];
    for (unsigned cSHdrsLeft = cSections; cSHdrsLeft > 0; cSHdrsLeft--, pSH++)
        if (    pSH->Misc.VirtualSize != 0
            && !(pSH->Characteristics & IMAGE_SCN_TYPE_NOLOAD))
        {
            fFixupVirtualSize = false;
            break;
        }

    /*
     * Actual pass.
     */
    const uint32_t              cbImage  = pOptHdr->SizeOfImage;
    uint32_t                    uRvaPrev = pOptHdr->SizeOfHeaders;
    pSH = &paSections[0];
    Log3(("RTLdrPE: Section Headers:\n"));
    for (unsigned cSHdrsLeft = cSections; cSHdrsLeft > 0; cSHdrsLeft--, pSH++)
    {
        const unsigned iSH = (unsigned)(pSH - &paSections[0]); NOREF(iSH);
        Log3(("RTLdrPE: #%d '%-8.8s'  Characteristics: %08RX32\n"
              "RTLdrPE: VirtAddr: %08RX32  VirtSize: %08RX32\n"
              "RTLdrPE:  FileOff: %08RX32  FileSize: %08RX32\n"
              "RTLdrPE: RelocOff: %08RX32   #Relocs: %08RX32\n"
              "RTLdrPE:  LineOff: %08RX32    #Lines: %08RX32\n",
              iSH, pSH->Name, pSH->Characteristics,
              pSH->VirtualAddress, pSH->Misc.VirtualSize,
              pSH->PointerToRawData, pSH->SizeOfRawData,
              pSH->PointerToRelocations, pSH->NumberOfRelocations,
              pSH->PointerToLinenumbers, pSH->NumberOfLinenumbers));

        AssertCompile(IMAGE_SCN_MEM_16BIT == IMAGE_SCN_MEM_PURGEABLE);
        if (  (  pSH->Characteristics & (IMAGE_SCN_MEM_PURGEABLE | IMAGE_SCN_MEM_PRELOAD | IMAGE_SCN_MEM_FARDATA) )
            && !(fFlags & RTLDR_O_FOR_DEBUG)) /* purgable/16-bit seen on w2ksp0 hal.dll, ignore the bunch. */
        {
            Log(("rtldrPEOpen: %s: Unsupported section flag(s) %#x section #%d '%.*s'!!!\n",
                 pszLogName, pSH->Characteristics, iSH, sizeof(pSH->Name), pSH->Name));
            return VERR_BAD_EXE_FORMAT;
        }

        if (    pSH->PointerToRawData > cbRawImage /// @todo pSH->PointerToRawData >= cbRawImage ?
            ||  pSH->SizeOfRawData > cbRawImage
            ||  pSH->PointerToRawData + pSH->SizeOfRawData > cbRawImage)
        {
            Log(("rtldrPEOpen: %s: PointerToRawData=%#x SizeOfRawData=%#x - beyond end of file (%#llx) - section #%d '%.*s'!!!\n",
                 pszLogName, pSH->PointerToRawData, pSH->SizeOfRawData, cbRawImage,
                 iSH, sizeof(pSH->Name), pSH->Name));
            return VERR_BAD_EXE_FORMAT;
        }

        if (pSH->PointerToRawData & (pOptHdr->FileAlignment - 1)) //ASSUMES power of 2 alignment.
        {
            Log(("rtldrPEOpen: %s: PointerToRawData=%#x misaligned (%#x) - section #%d '%.*s'!!!\n",
                 pszLogName, pSH->PointerToRawData, pOptHdr->FileAlignment, iSH, sizeof(pSH->Name), pSH->Name));
            return VERR_BAD_EXE_FORMAT;
        }

        if (!(pSH->Characteristics & IMAGE_SCN_TYPE_NOLOAD)) /* binutils uses this for '.stab' even if it's reserved/obsoleted by MS. */
        {
            /* Calc VirtualSize if necessary.  This is for internal reasons. */
            if (   pSH->Misc.VirtualSize == 0
                && fFixupVirtualSize)
            {
                pSH->Misc.VirtualSize = cbImage - RT_MIN(pSH->VirtualAddress, cbImage);
                for (uint32_t i = 1; i < cSHdrsLeft; i++)
                    if (   !(pSH[i].Characteristics & IMAGE_SCN_TYPE_NOLOAD)
                        && pSH[i].VirtualAddress >= pSH->VirtualAddress)
                    {
                        pSH->Misc.VirtualSize = RT_MIN(pSH[i].VirtualAddress - pSH->VirtualAddress, pSH->Misc.VirtualSize);
                        break;
                    }
            }

            if (pSH->Misc.VirtualSize > 0)
            {
                if (pSH->VirtualAddress < uRvaPrev)
                {
                    Log(("rtldrPEOpen: %s: Overlaps previous section or sections aren't in ascending order, VirtualAddress=%#x uRvaPrev=%#x - section #%d '%.*s'!!!\n",
                         pszLogName, pSH->VirtualAddress, uRvaPrev, iSH, sizeof(pSH->Name), pSH->Name));
                    return VERR_BAD_EXE_FORMAT;
                }
                if (pSH->VirtualAddress > cbImage)
                {
                    Log(("rtldrPEOpen: %s: VirtualAddress=%#x - beyond image size (%#x) - section #%d '%.*s'!!!\n",
                         pszLogName, pSH->VirtualAddress, cbImage, iSH, sizeof(pSH->Name), pSH->Name));
                    return VERR_BAD_EXE_FORMAT;
                }

                if (pSH->VirtualAddress & (pOptHdr->SectionAlignment - 1)) //ASSUMES power of 2 alignment.
                {
                    Log(("rtldrPEOpen: %s: VirtualAddress=%#x misaligned (%#x) - section #%d '%.*s'!!!\n",
                         pszLogName, pSH->VirtualAddress, pOptHdr->SectionAlignment, iSH, sizeof(pSH->Name), pSH->Name));
                    return VERR_BAD_EXE_FORMAT;
                }

#ifdef PE_FILE_OFFSET_EQUALS_RVA
                /* Our loader code assume rva matches the file offset. */
                if (    pSH->SizeOfRawData
                    &&  pSH->PointerToRawData != pSH->VirtualAddress)
                {
                    Log(("rtldrPEOpen: %s: ASSUMPTION FAILED: file offset %#x != RVA %#x - section #%d '%.*s'!!!\n",
                         pszLogName, pSH->PointerToRawData, pSH->VirtualAddress, iSH, sizeof(pSH->Name), pSH->Name));
                    return VERR_BAD_EXE_FORMAT;
                }
#endif

                uRvaPrev = pSH->VirtualAddress + pSH->Misc.VirtualSize;
            }
        }

        /* ignore the relocations and linenumbers. */
    }

    /*
     * Do a separate run if we need to validate the no-code claim from the
     * optional header.
     */
    if (fNoCode)
    {
        pSH = &paSections[0];
        for (unsigned cSHdrsLeft = cSections; cSHdrsLeft > 0; cSHdrsLeft--, pSH++)
            if (pSH->Characteristics & (IMAGE_SCN_CNT_CODE | IMAGE_SCN_MEM_EXECUTE))
                return VERR_LDR_ARCH_MISMATCH;
    }


    /** @todo r=bird: more sanity checks! */
    return VINF_SUCCESS;
}


/**
 * Reads image data by RVA using the section headers.
 *
 * @returns iprt status code.
 * @param   pModPe      The PE module instance.
 * @param   pvBuf       Where to store the bits.
 * @param   cb          Number of bytes to tread.
 * @param   RVA         Where to read from.
 */
static int rtldrPEReadRVA(PRTLDRMODPE pModPe, void *pvBuf, uint32_t cb, uint32_t RVA)
{
    const IMAGE_SECTION_HEADER *pSH = pModPe->paSections;
    PRTLDRREADER                pReader = pModPe->Core.pReader;
    uint32_t                    cbRead;
    int                         rc;

    /*
     * Is it the headers, i.e. prior to the first section.
     */
    if (RVA < pModPe->cbHeaders)
    {
        cbRead = RT_MIN(pModPe->cbHeaders - RVA, cb);
        rc = pReader->pfnRead(pReader, pvBuf, cbRead, RVA);
        if (    cbRead == cb
            ||  RT_FAILURE(rc))
            return rc;
        cb -= cbRead;
        RVA += cbRead;
        pvBuf = (uint8_t *)pvBuf + cbRead;
    }

    /* In the zero space between headers and the first section? */
    if (RVA < pSH->VirtualAddress)
    {
        cbRead = RT_MIN(pSH->VirtualAddress - RVA, cb);
        memset(pvBuf, 0, cbRead);
        if (cbRead == cb)
            return VINF_SUCCESS;
        cb -= cbRead;
        RVA += cbRead;
        pvBuf = (uint8_t *)pvBuf + cbRead;
    }

    /*
     * Iterate the sections.
     */
    for (unsigned cLeft = pModPe->cSections;
         cLeft > 0;
         cLeft--, pSH++)
    {
        uint32_t off = RVA - pSH->VirtualAddress;
        if (off < pSH->Misc.VirtualSize)
        {
            cbRead = RT_MIN(pSH->Misc.VirtualSize - off, cb);
            rc = pReader->pfnRead(pReader, pvBuf, cbRead, pSH->PointerToRawData + off);
            if (    cbRead == cb
                ||  RT_FAILURE(rc))
                return rc;
            cb -= cbRead;
            RVA += cbRead;
            pvBuf = (uint8_t *)pvBuf + cbRead;
        }
        uint32_t RVANext = cLeft ? pSH[1].VirtualAddress : pModPe->cbImage;
        if (RVA < RVANext)
        {
            cbRead = RT_MIN(RVANext - RVA, cb);
            memset(pvBuf, 0, cbRead);
            if (cbRead == cb)
                return VINF_SUCCESS;
            cb -= cbRead;
            RVA += cbRead;
            pvBuf = (uint8_t *)pvBuf + cbRead;
        }
    }

    AssertFailed();
    return VERR_INTERNAL_ERROR;
}


/**
 * Validates the data of some selected data directories entries and remember
 * important bits for later.
 *
 * This requires a valid section table and thus has to wait till after we've
 * read and validated it.
 *
 * @returns iprt status code.
 * @param   pModPe      The PE module instance.
 * @param   pOptHdr     Pointer to the optional header (valid).
 * @param   fFlags      Loader flags, RTLDR_O_XXX.
 * @param   pErrInfo    Where to return extended error information. Optional.
 */
static int rtldrPEValidateDirectoriesAndRememberStuff(PRTLDRMODPE pModPe, const IMAGE_OPTIONAL_HEADER64 *pOptHdr, uint32_t fFlags,
                                                      PRTERRINFO pErrInfo)
{
    const char *pszLogName = pModPe->Core.pReader->pfnLogName(pModPe->Core.pReader); NOREF(pszLogName);
    union /* combine stuff we're reading to help reduce stack usage. */
    {
        IMAGE_LOAD_CONFIG_DIRECTORY64   Cfg64;
        uint8_t                         abZeros[sizeof(IMAGE_LOAD_CONFIG_DIRECTORY64) * 4];
    } u;

    /*
     * The load config entry may include lock prefix tables and whatnot which we don't implement.
     * It does also include a lot of stuff which we can ignore, so we'll have to inspect the
     * actual data before we can make up our mind about it all.
     */
    IMAGE_DATA_DIRECTORY Dir = pOptHdr->DataDirectory[IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG];
    if (Dir.Size)
    {
        const size_t cbExpectV13 = !pModPe->f64Bit
                                 ? sizeof(IMAGE_LOAD_CONFIG_DIRECTORY32_V13)
                                 : sizeof(IMAGE_LOAD_CONFIG_DIRECTORY64_V13);
        const size_t cbExpectV12 = !pModPe->f64Bit
                                 ? sizeof(IMAGE_LOAD_CONFIG_DIRECTORY32_V12)
                                 : sizeof(IMAGE_LOAD_CONFIG_DIRECTORY64_V12);
        const size_t cbExpectV11 = !pModPe->f64Bit
                                 ? sizeof(IMAGE_LOAD_CONFIG_DIRECTORY32_V11)
                                 : sizeof(IMAGE_LOAD_CONFIG_DIRECTORY64_V11);
        const size_t cbExpectV10 = !pModPe->f64Bit
                                 ? sizeof(IMAGE_LOAD_CONFIG_DIRECTORY32_V10)
                                 : sizeof(IMAGE_LOAD_CONFIG_DIRECTORY64_V10);
        const size_t cbExpectV9 = !pModPe->f64Bit
                                ? sizeof(IMAGE_LOAD_CONFIG_DIRECTORY32_V9)
                                : sizeof(IMAGE_LOAD_CONFIG_DIRECTORY64_V9);
        const size_t cbExpectV8 = !pModPe->f64Bit
                                ? sizeof(IMAGE_LOAD_CONFIG_DIRECTORY32_V8)
                                : sizeof(IMAGE_LOAD_CONFIG_DIRECTORY64_V8);
        const size_t cbExpectV7 = !pModPe->f64Bit
                                ? sizeof(IMAGE_LOAD_CONFIG_DIRECTORY32_V7)
                                : sizeof(IMAGE_LOAD_CONFIG_DIRECTORY64_V7);
        const size_t cbExpectV6 = !pModPe->f64Bit
                                ? sizeof(IMAGE_LOAD_CONFIG_DIRECTORY32_V6)
                                : sizeof(IMAGE_LOAD_CONFIG_DIRECTORY64_V6);
        const size_t cbExpectV5 = !pModPe->f64Bit
                                ? sizeof(IMAGE_LOAD_CONFIG_DIRECTORY32_V5)
                                : sizeof(IMAGE_LOAD_CONFIG_DIRECTORY64_V5);
        const size_t cbExpectV4 = !pModPe->f64Bit
                                ? sizeof(IMAGE_LOAD_CONFIG_DIRECTORY32_V4)
                                : sizeof(IMAGE_LOAD_CONFIG_DIRECTORY64_V4);
        const size_t cbExpectV3 = !pModPe->f64Bit
                                ? sizeof(IMAGE_LOAD_CONFIG_DIRECTORY32_V3)
                                : sizeof(IMAGE_LOAD_CONFIG_DIRECTORY64_V3);
        const size_t cbExpectV2 = !pModPe->f64Bit
                                ? sizeof(IMAGE_LOAD_CONFIG_DIRECTORY32_V2)
                                : sizeof(IMAGE_LOAD_CONFIG_DIRECTORY64_V2);
        const size_t cbExpectV1 = !pModPe->f64Bit
                                ? sizeof(IMAGE_LOAD_CONFIG_DIRECTORY32_V1)
                                : sizeof(IMAGE_LOAD_CONFIG_DIRECTORY64_V2) /*No V1*/;
        const size_t cbNewHack  = cbExpectV5; /* Playing safe here since there might've been revisions between V5 and V6 we don't know about . */
        const size_t cbMaxKnown = cbExpectV12;

        bool fNewerStructureHack = false;
        if (   Dir.Size != cbExpectV13
            && Dir.Size != cbExpectV12
            && Dir.Size != cbExpectV11
            && Dir.Size != cbExpectV10
            && Dir.Size != cbExpectV9
            && Dir.Size != cbExpectV8
            && Dir.Size != cbExpectV7
            && Dir.Size != cbExpectV6
            && Dir.Size != cbExpectV5
            && Dir.Size != cbExpectV4
            && Dir.Size != cbExpectV3
            && Dir.Size != cbExpectV2
            && Dir.Size != cbExpectV1)
        {
            fNewerStructureHack = Dir.Size > cbNewHack /* These structure changes are slowly getting to us! More futher down. */
                               && Dir.Size <= sizeof(u);
            Log(("rtldrPEOpen: %s: load cfg dir: unexpected dir size of %u bytes, expected %zu, %zu, %zu, %zu, %zu, %zu, %zu, %zu, %zu, %zu, %zu, %zu, or %zu.%s\n",
                 pszLogName, Dir.Size, cbExpectV13, cbExpectV12, cbExpectV11, cbExpectV10, cbExpectV9, cbExpectV8, cbExpectV7, cbExpectV6, cbExpectV5, cbExpectV4, cbExpectV3, cbExpectV2, cbExpectV1,
                 fNewerStructureHack ? " Will try ignore extra bytes if all zero." : ""));
            if (!fNewerStructureHack)
                return RTErrInfoSetF(pErrInfo, VERR_LDRPE_LOAD_CONFIG_SIZE,
                                     "Unexpected load config dir size of %u bytes; supported sized: %zu, %zu, %zu, %zu, %zu, %zu, %zu, %zu, %zu, %zu, %zu, %zu, or %zu",
                                     Dir.Size, cbExpectV13, cbExpectV12, cbExpectV11, cbExpectV10, cbExpectV9, cbExpectV8, cbExpectV7, cbExpectV6, cbExpectV5, cbExpectV4, cbExpectV3, cbExpectV2, cbExpectV1);
        }

        /*
         * Read, check new stuff and convert to 64-bit.
         *
         * If we accepted a newer structures when loading for debug or validation,
         * otherwise we require the new bits to be all zero and hope that they are
         * insignificant where image loading is concerned (that's mostly been the
         * case even for non-zero bits, only hard exception is LockPrefixTable).
         */
        RT_ZERO(u.Cfg64);
        int rc = rtldrPEReadRVA(pModPe, &u.Cfg64, Dir.Size, Dir.VirtualAddress);
        if (RT_FAILURE(rc))
            return rc;
        if (   fNewerStructureHack
            && Dir.Size > cbMaxKnown
            && !(fFlags & (RTLDR_O_FOR_DEBUG | RTLDR_O_FOR_VALIDATION))
            && !ASMMemIsZero(&u.abZeros[cbMaxKnown], Dir.Size - cbMaxKnown))
        {
            Log(("rtldrPEOpen: %s: load cfg dir: Unexpected bytes are non-zero (%u bytes of which %u expected to be zero): %.*Rhxs\n",
                 pszLogName, Dir.Size, Dir.Size - cbMaxKnown, Dir.Size - cbMaxKnown, &u.abZeros[cbMaxKnown]));
            return RTErrInfoSetF(pErrInfo, VERR_LDRPE_LOAD_CONFIG_SIZE,
                                 "Grown load config (%u to %u bytes) includes non-zero bytes: %.*Rhxs",
                                 cbMaxKnown, Dir.Size, Dir.Size - cbMaxKnown, &u.abZeros[cbMaxKnown]);
        }
        rtldrPEConvert32BitLoadConfigTo64Bit(&u.Cfg64);

        if (u.Cfg64.Size != Dir.Size)
        {
            /* Kludge #1: ntdll.dll from XP seen with Dir.Size=0x40 and Cfg64.Size=0x00. */
            if (Dir.Size == 0x40 && u.Cfg64.Size == 0x00 && !pModPe->f64Bit)
            {
                Log(("rtldrPEOpen: %s: load cfg dir: Header (%d) and directory (%d) size mismatch, applying the XP kludge.\n",
                     pszLogName, u.Cfg64.Size, Dir.Size));
                u.Cfg64.Size = Dir.Size;
            }
            /* Kludge #2: This happens a lot. Structure changes, but the linker doesn't get
               updated and stores some old size in the directory.  Use the header size. */
            else if (   u.Cfg64.Size == cbExpectV13
                     || u.Cfg64.Size == cbExpectV12
                     || u.Cfg64.Size == cbExpectV11
                     || u.Cfg64.Size == cbExpectV10
                     || u.Cfg64.Size == cbExpectV9
                     || u.Cfg64.Size == cbExpectV8
                     || u.Cfg64.Size == cbExpectV7
                     || u.Cfg64.Size == cbExpectV6
                     || u.Cfg64.Size == cbExpectV5
                     || u.Cfg64.Size == cbExpectV4
                     || u.Cfg64.Size == cbExpectV3
                     || u.Cfg64.Size == cbExpectV2
                     || u.Cfg64.Size == cbExpectV1
                     || (fNewerStructureHack = (u.Cfg64.Size > cbNewHack && u.Cfg64.Size <= sizeof(u))) )
            {
                Log(("rtldrPEOpen: %s: load cfg dir: Header (%d) and directory (%d) size mismatch, applying the old linker kludge.\n",
                     pszLogName, u.Cfg64.Size, Dir.Size));

                uint32_t const uOrgDir = Dir.Size;
                Dir.Size = u.Cfg64.Size;
                RT_ZERO(u.Cfg64);
                rc = rtldrPEReadRVA(pModPe, &u.Cfg64, Dir.Size, Dir.VirtualAddress);
                if (RT_FAILURE(rc))
                    return rc;
                if (   fNewerStructureHack
                    && Dir.Size > cbMaxKnown
                    && !(fFlags & (RTLDR_O_FOR_DEBUG | RTLDR_O_FOR_VALIDATION))
                    && !ASMMemIsZero(&u.abZeros[cbMaxKnown], Dir.Size - cbMaxKnown))
                {
                    Log(("rtldrPEOpen: %s: load cfg dir: Unknown bytes are non-zero (%u bytes of which %u expected to be zero): %.*Rhxs\n",
                         pszLogName, Dir.Size, Dir.Size - cbMaxKnown, Dir.Size - cbMaxKnown, &u.abZeros[cbMaxKnown]));
                    return RTErrInfoSetF(pErrInfo, VERR_LDRPE_LOAD_CONFIG_SIZE,
                                         "Grown load config (%u to %u bytes, dir %u) includes non-zero bytes: %.*Rhxs",
                                         cbMaxKnown, Dir.Size, uOrgDir, Dir.Size - cbMaxKnown, &u.abZeros[cbMaxKnown]);
                }
                rtldrPEConvert32BitLoadConfigTo64Bit(&u.Cfg64);
                AssertReturn(u.Cfg64.Size == Dir.Size,
                             RTErrInfoSetF(pErrInfo, VERR_LDRPE_LOAD_CONFIG_SIZE, "Data changed while reading! (%d vs %d)\n",
                                           u.Cfg64.Size, Dir.Size));
            }
            else
            {
                Log(("rtldrPEOpen: %s: load cfg hdr: unexpected hdr size of %u bytes (dir %u), expected %zu, %zu, %zu, %zu, %zu, %zu, %zu, %zu, %zu, %zu, %zu, or %zu.\n",
                     pszLogName, u.Cfg64.Size, Dir.Size, cbExpectV12, cbExpectV11, cbExpectV10, cbExpectV9, cbExpectV8, cbExpectV7, cbExpectV6, cbExpectV5, cbExpectV4, cbExpectV3, cbExpectV2, cbExpectV1));
                return RTErrInfoSetF(pErrInfo, VERR_LDRPE_LOAD_CONFIG_SIZE,
                                     "Unexpected load config header size of %u bytes (dir %u); supported sized: %zu, %zu, %zu, %zu, %zu, %zu, %zu, %zu, %zu, %zu, %zu, or %zu",
                                     u.Cfg64.Size, Dir.Size, cbExpectV12, cbExpectV11, cbExpectV10, cbExpectV9, cbExpectV8, cbExpectV7, cbExpectV6, cbExpectV5, cbExpectV4, cbExpectV3, cbExpectV2, cbExpectV1);
            }
        }
        if (u.Cfg64.LockPrefixTable && !(fFlags & (RTLDR_O_FOR_DEBUG | RTLDR_O_FOR_VALIDATION)))
        {
            Log(("rtldrPEOpen: %s: load cfg dir: lock prefix table at %RX64. We don't support lock prefix tables!\n",
                 pszLogName, u.Cfg64.LockPrefixTable));
            return RTErrInfoSetF(pErrInfo, VERR_LDRPE_LOCK_PREFIX_TABLE,
                                 "Lock prefix table not supported: %RX64", u.Cfg64.LockPrefixTable);
        }
#if 0/* this seems to be safe to ignore. */
        if (    u.Cfg64.SEHandlerTable
            ||  u.Cfg64.SEHandlerCount)
        {
            Log(("rtldrPEOpen: %s: load cfg dir: SEHandlerTable=%RX64 and SEHandlerCount=%RX64 are unsupported!\n",
                 pszLogName, u.Cfg64.SEHandlerTable, u.Cfg64.SEHandlerCount));
            return VERR_BAD_EXE_FORMAT;
        }
#endif
        if (u.Cfg64.EditList && !(fFlags & (RTLDR_O_FOR_DEBUG | RTLDR_O_FOR_VALIDATION)))
        {
            Log(("rtldrPEOpen: %s: load cfg dir: EditList=%RX64 is unsupported!\n",
                 pszLogName, u.Cfg64.EditList));
            return RTErrInfoSetF(pErrInfo, VERR_BAD_EXE_FORMAT, "Load config EditList=%RX64 is not supported", u.Cfg64.EditList);
        }
        /** @todo GuardCFC? Possibly related to:
         *         http://research.microsoft.com/pubs/69217/ccs05-cfi.pdf
         * Not trusting something designed by bakas who don't know how to modify a
         * structure without messing up its natural alignment. */
        if (    (   u.Cfg64.GuardCFCCheckFunctionPointer
                 || u.Cfg64.GuardCFDispatchFunctionPointer
                 || u.Cfg64.GuardCFFunctionTable
                 || u.Cfg64.GuardCFFunctionCount
                 || u.Cfg64.GuardFlags
                 || u.Cfg64.GuardAddressTakenIatEntryTable
                 || u.Cfg64.GuardAddressTakenIatEntryCount
                 || u.Cfg64.GuardLongJumpTargetTable
                 || u.Cfg64.GuardLongJumpTargetCount)
            && !(fFlags & (RTLDR_O_FOR_DEBUG | RTLDR_O_FOR_VALIDATION)) )
        {
            Log(("rtldrPEOpen: %s: load cfg dir: Guard stuff: %RX64,%RX64,%RX64,%RX64,%RX32,%RX64,%RX64,%RX64,%RX64!\n",
                 pszLogName, u.Cfg64.GuardCFCCheckFunctionPointer, u.Cfg64.GuardCFDispatchFunctionPointer,
                 u.Cfg64.GuardCFFunctionTable, u.Cfg64.GuardCFFunctionCount, u.Cfg64.GuardFlags,
                 u.Cfg64.GuardAddressTakenIatEntryTable, u.Cfg64.GuardAddressTakenIatEntryCount,
                 u.Cfg64.GuardLongJumpTargetTable, u.Cfg64.GuardLongJumpTargetCount ));
#if 0 /* ntdll 15002 uses this. */
            return RTErrInfoSetF(pErrInfo, VERR_LDRPE_GUARD_CF_STUFF,
                                 "Guard bits in load config: %RX64,%RX64,%RX64,%RX64,%RX32,%RX64,%RX64,%RX64,%RX64!",
                                 u.Cfg64.GuardCFCCheckFunctionPointer, u.Cfg64.GuardCFDispatchFunctionPointer,
                                 u.Cfg64.GuardCFFunctionTable, u.Cfg64.GuardCFFunctionCount, u.Cfg64.GuardFlags,
                                 u.Cfg64.GuardAddressTakenIatEntryTable, u.Cfg64.GuardAddressTakenIatEntryCount,
                                 u.Cfg64.GuardLongJumpTargetTable, u.Cfg64.GuardLongJumpTargetCount);
#endif
        }
    }

    /*
     * If the image is signed and we're not doing this for debug purposes,
     * take a look at the signature.
     */
    Dir = pOptHdr->DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY];
    if (Dir.Size)
    {
        PWIN_CERTIFICATE pFirst = (PWIN_CERTIFICATE)RTMemTmpAlloc(Dir.Size);
        if (!pFirst)
            return VERR_NO_TMP_MEMORY;
        int rc = pModPe->Core.pReader->pfnRead(pModPe->Core.pReader, pFirst, Dir.Size, Dir.VirtualAddress);
        if (RT_SUCCESS(rc))
        {
            uint32_t off  = 0;
            do
            {
                PWIN_CERTIFICATE pCur = (PWIN_CERTIFICATE)((uint8_t *)pFirst + off);

                /* validate the members. */
                if (   pCur->dwLength < sizeof(WIN_CERTIFICATE)
                    || pCur->dwLength + off > Dir.Size)
                {
                    Log(("rtldrPEOpen: %s: cert at %#x/%#x: dwLength=%#x\n", pszLogName, off, Dir.Size, pCur->dwLength));
                    rc = RTErrInfoSetF(pErrInfo, VERR_LDRPE_CERT_MALFORMED,
                                       "Cert at %#x LB %#x: Bad header length value: %#x", off, Dir.Size, pCur->dwLength);
                    break;
                }
                if (    pCur->wRevision != WIN_CERT_REVISION_2_0
                    &&  pCur->wRevision != WIN_CERT_REVISION_1_0)
                {
                    Log(("rtldrPEOpen: %s: cert at %#x/%#x: wRevision=%#x\n", pszLogName, off, Dir.Size, pCur->wRevision));
                    if (pCur->wRevision >= WIN_CERT_REVISION_1_0)
                        rc = RTErrInfoSetF(pErrInfo, VERR_LDRPE_CERT_UNSUPPORTED,
                                           "Cert at %#x LB %#x: Unsupported revision: %#x", off, Dir.Size, pCur->wRevision);
                    else
                        rc = RTErrInfoSetF(pErrInfo, VERR_LDRPE_CERT_MALFORMED,
                                           "Cert at %#x LB %#x: Malformed revision: %#x", off, Dir.Size, pCur->wRevision);
                    break;
                }
                if (    pCur->wCertificateType != WIN_CERT_TYPE_PKCS_SIGNED_DATA
                    &&  pCur->wCertificateType != WIN_CERT_TYPE_X509
                    /*&&  pCur->wCertificateType != WIN_CERT_TYPE_RESERVED_1*/
                    /*&&  pCur->wCertificateType != WIN_CERT_TYPE_TS_STACK_SIGNED*/
                    &&  pCur->wCertificateType != WIN_CERT_TYPE_EFI_PKCS115
                    &&  pCur->wCertificateType != WIN_CERT_TYPE_EFI_GUID
                   )
                {
                    Log(("rtldrPEOpen: %s: cert at %#x/%#x: wCertificateType=%#x\n", pszLogName, off, Dir.Size, pCur->wCertificateType));
                    if (pCur->wCertificateType)
                        rc = RTErrInfoSetF(pErrInfo, VERR_LDRPE_CERT_UNSUPPORTED,
                                           "Cert at %#x LB %#x: Unsupported certificate type: %#x",
                                           off, Dir.Size, pCur->wCertificateType);
                    else
                        rc = RTErrInfoSetF(pErrInfo, VERR_LDRPE_CERT_MALFORMED,
                                           "Cert at %#x LB %#x: Malformed certificate type: %#x",
                                           off, Dir.Size, pCur->wCertificateType);
                    break;
                }

                /* Remember the first signed data certificate. */
                if (   pCur->wCertificateType == WIN_CERT_TYPE_PKCS_SIGNED_DATA
                    && pModPe->offPkcs7SignedData == 0)
                {
                    pModPe->offPkcs7SignedData = Dir.VirtualAddress
                                               + (uint32_t)((uintptr_t)&pCur->bCertificate[0] - (uintptr_t)pFirst);
                    pModPe->cbPkcs7SignedData  = pCur->dwLength - RT_UOFFSETOF(WIN_CERTIFICATE, bCertificate);
                }

                /* next */
                off += RT_ALIGN(pCur->dwLength, WIN_CERTIFICATE_ALIGNMENT);
            } while (off < Dir.Size);
        }
        RTMemTmpFree(pFirst);
        if (RT_FAILURE(rc) && !(fFlags & RTLDR_O_FOR_DEBUG))
            return rc;
    }

    return VINF_SUCCESS;
}


/**
 * Open a PE image.
 *
 * @returns iprt status code.
 * @param   pReader     The loader reader instance which will provide the raw image bits.
 * @param   fFlags      Loader flags, RTLDR_O_XXX.
 * @param   enmArch     Architecture specifier.
 * @param   offNtHdrs   The offset of the NT headers (where you find "PE\0\0").
 * @param   phLdrMod    Where to store the handle.
 * @param   pErrInfo    Where to return extended error information. Optional.
 */
DECLHIDDEN(int) rtldrPEOpen(PRTLDRREADER pReader, uint32_t fFlags, RTLDRARCH enmArch, RTFOFF offNtHdrs,
                            PRTLDRMOD phLdrMod, PRTERRINFO pErrInfo)
{
    /*
     * Read and validate the file header.
     */
    IMAGE_FILE_HEADER FileHdr;
    int rc = pReader->pfnRead(pReader, &FileHdr, sizeof(FileHdr), offNtHdrs + 4);
    if (RT_FAILURE(rc))
        return rc;
    RTLDRARCH enmArchImage;
    const char *pszLogName = pReader->pfnLogName(pReader);
    rc = rtldrPEValidateFileHeader(&FileHdr, fFlags, pszLogName, &enmArchImage, pErrInfo);
    if (RT_FAILURE(rc))
        return rc;

    /*
     * Match the CPU architecture.
     */
    bool fArchNoCodeCheckPending = false;
    if (    enmArch != enmArchImage
        &&  (   enmArch != RTLDRARCH_WHATEVER
             && !(fFlags & RTLDR_O_WHATEVER_ARCH)) )
    {
        if (!(fFlags & RTLDR_O_IGNORE_ARCH_IF_NO_CODE))
            return RTERRINFO_LOG_SET_F(pErrInfo, VERR_LDR_ARCH_MISMATCH, "Image is for '%s', only accepting images for '%s'.",
                                       rtldrPEGetArchName(FileHdr.Machine), RTLdrArchName(enmArch));
        fArchNoCodeCheckPending = true;
    }

    /*
     * Read and validate the "optional" header. Convert 32->64 if necessary.
     */
    IMAGE_OPTIONAL_HEADER64 OptHdr;
    rc = pReader->pfnRead(pReader, &OptHdr, FileHdr.SizeOfOptionalHeader, offNtHdrs + 4 + sizeof(IMAGE_FILE_HEADER));
    if (RT_FAILURE(rc))
        return rc;
    if (FileHdr.SizeOfOptionalHeader != sizeof(OptHdr))
        rtldrPEConvert32BitOptionalHeaderTo64Bit(&OptHdr);
    rc = rtldrPEValidateOptionalHeader(&OptHdr, pszLogName, offNtHdrs, &FileHdr, pReader->pfnSize(pReader), fFlags, pErrInfo);
    if (RT_FAILURE(rc))
        return rc;
    if (fArchNoCodeCheckPending && OptHdr.SizeOfCode != 0)
        return RTERRINFO_LOG_SET_F(pErrInfo, VERR_LDR_ARCH_MISMATCH,
                                   "Image is for '%s' and contains code (%#x), only accepting images for '%s' with code.",
                                   rtldrPEGetArchName(FileHdr.Machine), OptHdr.SizeOfCode, RTLdrArchName(enmArch));

    /*
     * Read and validate section headers.
     */
    const size_t cbSections = sizeof(IMAGE_SECTION_HEADER) * FileHdr.NumberOfSections;
    PIMAGE_SECTION_HEADER paSections = (PIMAGE_SECTION_HEADER)RTMemAlloc(cbSections);
    if (!paSections)
        return VERR_NO_MEMORY;
    rc = pReader->pfnRead(pReader, paSections, cbSections,
                          offNtHdrs + 4 + sizeof(IMAGE_FILE_HEADER) + FileHdr.SizeOfOptionalHeader);
    if (RT_SUCCESS(rc))
    {
        rc = rtldrPEValidateAndTouchUpSectionHeaders(paSections, FileHdr.NumberOfSections, pszLogName,
                                                     &OptHdr, pReader->pfnSize(pReader), fFlags, fArchNoCodeCheckPending);
        if (RT_SUCCESS(rc))
        {
            /*
             * Allocate and initialize the PE module structure.
             */
            PRTLDRMODPE pModPe = (PRTLDRMODPE)RTMemAllocZ(sizeof(*pModPe));
            if (pModPe)
            {
                pModPe->Core.u32Magic = RTLDRMOD_MAGIC;
                pModPe->Core.eState   = LDR_STATE_OPENED;
                if (FileHdr.SizeOfOptionalHeader == sizeof(OptHdr))
                    pModPe->Core.pOps = &s_rtldrPE64Ops.Core;
                else
                    pModPe->Core.pOps = &s_rtldrPE32Ops.Core;
                pModPe->Core.pReader  = pReader;
                pModPe->Core.enmFormat= RTLDRFMT_PE;
                pModPe->Core.enmType  = FileHdr.Characteristics & IMAGE_FILE_DLL
                                      ? FileHdr.Characteristics & IMAGE_FILE_RELOCS_STRIPPED
                                        ? RTLDRTYPE_EXECUTABLE_FIXED
                                        : RTLDRTYPE_EXECUTABLE_RELOCATABLE
                                      : FileHdr.Characteristics & IMAGE_FILE_RELOCS_STRIPPED
                                        ? RTLDRTYPE_SHARED_LIBRARY_FIXED
                                        : RTLDRTYPE_SHARED_LIBRARY_RELOCATABLE;
                pModPe->Core.enmEndian= RTLDRENDIAN_LITTLE;
                pModPe->Core.enmArch  = FileHdr.Machine == IMAGE_FILE_MACHINE_I386
                                      ? RTLDRARCH_X86_32
                                      : FileHdr.Machine == IMAGE_FILE_MACHINE_AMD64
                                      ? RTLDRARCH_AMD64
                                      : RTLDRARCH_WHATEVER;
                pModPe->pvBits        = NULL;
                pModPe->offNtHdrs     = offNtHdrs;
                pModPe->offEndOfHdrs  = offNtHdrs + 4 + sizeof(IMAGE_FILE_HEADER) + FileHdr.SizeOfOptionalHeader + cbSections;
                pModPe->u16Machine    = FileHdr.Machine;
                pModPe->fFile         = FileHdr.Characteristics;
                pModPe->cSections     = FileHdr.NumberOfSections;
                pModPe->paSections    = paSections;
                pModPe->uEntryPointRVA= OptHdr.AddressOfEntryPoint;
                pModPe->uImageBase    = (RTUINTPTR)OptHdr.ImageBase;
                pModPe->cbImage       = OptHdr.SizeOfImage;
                pModPe->cbHeaders     = OptHdr.SizeOfHeaders;
                pModPe->uSectionAlign = OptHdr.SectionAlignment;
                pModPe->uTimestamp    = FileHdr.TimeDateStamp;
                pModPe->cImports      = UINT32_MAX;
                pModPe->f64Bit        = FileHdr.SizeOfOptionalHeader == sizeof(OptHdr);
                pModPe->ImportDir     = OptHdr.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
                pModPe->RelocDir      = OptHdr.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];
                pModPe->ExportDir     = OptHdr.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
                pModPe->DebugDir      = OptHdr.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG];
                pModPe->SecurityDir   = OptHdr.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY];
                pModPe->ExceptionDir  = OptHdr.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXCEPTION];
                pModPe->fDllCharacteristics = OptHdr.DllCharacteristics;

                /*
                 * Perform validation of some selected data directories which requires
                 * inspection of the actual data.  This also saves some certificate
                 * information.
                 */
                rc = rtldrPEValidateDirectoriesAndRememberStuff(pModPe, &OptHdr, fFlags, pErrInfo);
                if (RT_SUCCESS(rc))
                {
                    *phLdrMod = &pModPe->Core;
                    return VINF_SUCCESS;
                }
                RTMemFree(pModPe);
            }
            else
                rc = VERR_NO_MEMORY;
        }
    }
    RTMemFree(paSections);
    return rc;
}

