1044 lines
33 KiB
C++
1044 lines
33 KiB
C++
|
/*++
|
||
|
|
||
|
Copyright (c) 1995 Microsoft Corporation
|
||
|
|
||
|
Module Name:
|
||
|
|
||
|
dice.cxx
|
||
|
|
||
|
Abstract:
|
||
|
|
||
|
This file implements the Image Integrity API's.
|
||
|
|
||
|
Author:
|
||
|
|
||
|
Bryan Tuttle (bryant) 7-Dec-1995
|
||
|
|
||
|
Environment:
|
||
|
|
||
|
User Mode
|
||
|
|
||
|
--*/
|
||
|
|
||
|
#include <private.h>
|
||
|
|
||
|
BOOL
|
||
|
FindCertificate(
|
||
|
IN PLOADED_IMAGE LoadedImage,
|
||
|
IN DWORD Index,
|
||
|
LPWIN_CERTIFICATE * Certificate
|
||
|
)
|
||
|
{
|
||
|
PIMAGE_DATA_DIRECTORY pDataDir;
|
||
|
DWORD_PTR CurrentCert;
|
||
|
BOOL rc;
|
||
|
|
||
|
if (LoadedImage->fDOSImage) {
|
||
|
// No way this could have a certificate;
|
||
|
return(FALSE);
|
||
|
}
|
||
|
|
||
|
rc = FALSE;
|
||
|
|
||
|
__try {
|
||
|
if (LoadedImage->FileHeader->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) {
|
||
|
pDataDir = &((PIMAGE_NT_HEADERS32)(LoadedImage->FileHeader))->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY];
|
||
|
} else if (LoadedImage->FileHeader->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) {
|
||
|
pDataDir = &((PIMAGE_NT_HEADERS64)(LoadedImage->FileHeader))->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY];
|
||
|
} else {
|
||
|
__leave; // Not an interesting file type.
|
||
|
}
|
||
|
|
||
|
// Check if the cert pointer is reasonable.
|
||
|
if (!pDataDir->VirtualAddress ||
|
||
|
!pDataDir->Size ||
|
||
|
(pDataDir->VirtualAddress + pDataDir->Size > LoadedImage->SizeOfImage) ||
|
||
|
(pDataDir->VirtualAddress + pDataDir->Size < pDataDir->VirtualAddress))
|
||
|
{
|
||
|
__leave;
|
||
|
}
|
||
|
|
||
|
// The data should not be part of any in-memory data.
|
||
|
|
||
|
{
|
||
|
DWORD_PTR CertStart = pDataDir->VirtualAddress;
|
||
|
DWORD_PTR CertEnd = CertStart + pDataDir->Size;
|
||
|
ULONG i;
|
||
|
for (i = 0; i < LoadedImage->NumberOfSections; i++) {
|
||
|
DWORD_PTR SectionStart = LoadedImage->Sections[i].PointerToRawData;
|
||
|
DWORD_PTR SectionEnd = SectionStart + LoadedImage->Sections[i].SizeOfRawData;
|
||
|
|
||
|
if (SectionStart && (CertStart < SectionEnd || CertEnd < SectionEnd)) {
|
||
|
__leave; // The cert preceeds this section's data must be bogus
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// We're not looking at an empty security slot or an invalid (past the image boundary) value.
|
||
|
// Let's see if we can find it.
|
||
|
|
||
|
DWORD CurrentIdx = 0;
|
||
|
DWORD_PTR LastCert;
|
||
|
|
||
|
CurrentCert = (DWORD_PTR)(LoadedImage->MappedAddress) + pDataDir->VirtualAddress;
|
||
|
LastCert = CurrentCert + pDataDir->Size;
|
||
|
|
||
|
while (CurrentCert < LastCert ) {
|
||
|
if (CurrentIdx == Index) {
|
||
|
rc = TRUE;
|
||
|
__leave;
|
||
|
}
|
||
|
CurrentIdx++;
|
||
|
CurrentCert += ((LPWIN_CERTIFICATE)CurrentCert)->dwLength;
|
||
|
CurrentCert = (CurrentCert + 7) & ~7; // align it.
|
||
|
}
|
||
|
} __except(EXCEPTION_EXECUTE_HANDLER) { }
|
||
|
|
||
|
if (rc == TRUE) {
|
||
|
*Certificate = (LPWIN_CERTIFICATE)CurrentCert;
|
||
|
}
|
||
|
|
||
|
return(rc);
|
||
|
}
|
||
|
|
||
|
typedef struct _EXCLUDE_RANGE {
|
||
|
PBYTE Offset;
|
||
|
DWORD Size;
|
||
|
struct _EXCLUDE_RANGE *Next;
|
||
|
} EXCLUDE_RANGE;
|
||
|
|
||
|
typedef enum {
|
||
|
Raw,
|
||
|
Virtual
|
||
|
} ADDRTYPE;
|
||
|
|
||
|
class EXCLUDE_LIST
|
||
|
{
|
||
|
public:
|
||
|
EXCLUDE_LIST() {
|
||
|
m_Image = NULL;
|
||
|
m_ExRange = (EXCLUDE_RANGE *)MemAlloc(sizeof(EXCLUDE_RANGE));
|
||
|
}
|
||
|
|
||
|
~EXCLUDE_LIST() {
|
||
|
EXCLUDE_RANGE *pTmp;
|
||
|
pTmp = m_ExRange->Next;
|
||
|
while (pTmp) {
|
||
|
MemFree(m_ExRange);
|
||
|
m_ExRange = pTmp;
|
||
|
pTmp = m_ExRange->Next;
|
||
|
}
|
||
|
MemFree(m_ExRange);
|
||
|
}
|
||
|
|
||
|
void Init(LOADED_IMAGE * Image, DIGEST_FUNCTION pFunc, DIGEST_HANDLE dh) {
|
||
|
m_Image = Image;
|
||
|
m_ExRange->Offset = NULL;
|
||
|
m_ExRange->Size = 0;
|
||
|
m_pFunc = pFunc;
|
||
|
m_dh = dh;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
void Add(DWORD_PTR Offset, DWORD Size, ADDRTYPE AddrType);
|
||
|
|
||
|
BOOL Emit(PBYTE Offset, DWORD Size);
|
||
|
|
||
|
private:
|
||
|
LOADED_IMAGE * m_Image;
|
||
|
EXCLUDE_RANGE * m_ExRange;
|
||
|
DIGEST_FUNCTION m_pFunc;
|
||
|
DIGEST_HANDLE m_dh;
|
||
|
};
|
||
|
|
||
|
void
|
||
|
EXCLUDE_LIST::Add(
|
||
|
DWORD_PTR Offset,
|
||
|
DWORD Size,
|
||
|
ADDRTYPE AddrType
|
||
|
)
|
||
|
{
|
||
|
if (AddrType == Virtual) {
|
||
|
// Always save raw offsets
|
||
|
DWORD_PTR RawOffset;
|
||
|
|
||
|
// Note: it's O.K. to cast down to a dword here. Offset is really a Rva from the start
|
||
|
// of the image (always limited to 4G).
|
||
|
|
||
|
RawOffset = (DWORD_PTR)ImageRvaToVa((PIMAGE_NT_HEADERS)m_Image->FileHeader, m_Image->MappedAddress, (DWORD)Offset, NULL);
|
||
|
Offset = RawOffset;
|
||
|
}
|
||
|
|
||
|
EXCLUDE_RANGE *pTmp, *pExRange;
|
||
|
|
||
|
pExRange = m_ExRange;
|
||
|
|
||
|
while (pExRange->Next && (pExRange->Next->Offset < (PBYTE)Offset)) {
|
||
|
pExRange = pExRange->Next;
|
||
|
}
|
||
|
|
||
|
pTmp = (EXCLUDE_RANGE *) MemAlloc(sizeof(EXCLUDE_RANGE));
|
||
|
pTmp->Next = pExRange->Next;
|
||
|
pTmp->Offset = (PBYTE)Offset;
|
||
|
pTmp->Size = Size;
|
||
|
pExRange->Next = pTmp;
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
|
||
|
BOOL
|
||
|
EXCLUDE_LIST::Emit(
|
||
|
PBYTE Offset,
|
||
|
DWORD Size
|
||
|
)
|
||
|
{
|
||
|
BOOL rc;
|
||
|
|
||
|
EXCLUDE_RANGE *pExRange;
|
||
|
DWORD EmitSize, ExcludeSize;
|
||
|
|
||
|
pExRange = m_ExRange->Next;
|
||
|
rc = TRUE;
|
||
|
|
||
|
while (pExRange && (Size > 0)) {
|
||
|
if (pExRange->Offset >= Offset) {
|
||
|
// Emit what's before the exclude list.
|
||
|
EmitSize = __min((DWORD)(pExRange->Offset - Offset), Size);
|
||
|
if (EmitSize) {
|
||
|
rc = (*m_pFunc)(m_dh, Offset, EmitSize);
|
||
|
if (rc == FALSE) {
|
||
|
return rc;
|
||
|
}
|
||
|
Size -= EmitSize;
|
||
|
Offset += EmitSize;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (Size) {
|
||
|
if (pExRange->Offset + pExRange->Size >= Offset) {
|
||
|
// Skip over what's in the exclude list.
|
||
|
ExcludeSize = __min(Size, (DWORD)(pExRange->Offset + pExRange->Size - Offset));
|
||
|
Size -= ExcludeSize;
|
||
|
Offset += ExcludeSize;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pExRange = pExRange->Next;
|
||
|
}
|
||
|
|
||
|
// Emit what's left.
|
||
|
if (Size) {
|
||
|
rc = (*m_pFunc)(m_dh, Offset, Size);
|
||
|
}
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
|
||
|
BOOL
|
||
|
IMAGEAPI
|
||
|
ImageGetDigestStream(
|
||
|
IN HANDLE FileHandle,
|
||
|
IN DWORD DigestLevel,
|
||
|
IN DIGEST_FUNCTION DigestFunction,
|
||
|
IN DIGEST_HANDLE DigestHandle
|
||
|
)
|
||
|
|
||
|
/*++
|
||
|
|
||
|
Routine Description:
|
||
|
|
||
|
Given an image, return the bytes necessary to construct a certificate.
|
||
|
Only PE images are supported at this time.
|
||
|
|
||
|
Arguments:
|
||
|
|
||
|
FileHandle - Handle to the file in question. The file should be opened
|
||
|
with at least GENERIC_READ access.
|
||
|
|
||
|
DigestLevel - Indicates what data will be included in the returned buffer.
|
||
|
Valid values are:
|
||
|
|
||
|
CERT_PE_IMAGE_DIGEST_DEBUG_INFO - Include Debug symbolic (if mapped)
|
||
|
CERT_PE_IMAGE_DIGEST_RESOURCES - Include Resource info
|
||
|
CERT_PE_IMAGE_DIGEST_ALL_IMPORT_INFO - Include ALL the import information
|
||
|
|
||
|
By default, neither Debug Symbolic, Resources, nor import information affected
|
||
|
by binding are returned.
|
||
|
|
||
|
DigestFunction - User supplied routine that will process the data.
|
||
|
|
||
|
DigestHandle - User supplied handle to identify the digest. Passed as the first
|
||
|
argument to the DigestFunction.
|
||
|
|
||
|
Return Value:
|
||
|
|
||
|
TRUE - Success.
|
||
|
|
||
|
FALSE - There was some error. Call GetLastError for more information. Possible
|
||
|
values are ERROR_INVALID_PARAMETER or ERROR_OPERATION_ABORTED.
|
||
|
|
||
|
--*/
|
||
|
|
||
|
{
|
||
|
LOADED_IMAGE LoadedImage;
|
||
|
BOOL rc, fAddThisSection, fDebugAdded;
|
||
|
DWORD i;
|
||
|
EXCLUDE_LIST ExList;
|
||
|
PIMAGE_SECTION_HEADER SectionHeaders;
|
||
|
ULONG ResourceOffset, ResourceSize, DebugOffset, DebugSize, RelocOffset, RelocSize, SectionHeaderSize;
|
||
|
PIMAGE_FILE_HEADER FileHeader;
|
||
|
PIMAGE_DATA_DIRECTORY pDataDir;
|
||
|
INT RelocHdr;
|
||
|
union {
|
||
|
IMAGE_NT_HEADERS32 PE32;
|
||
|
IMAGE_NT_HEADERS64 PE64;
|
||
|
}Hdr;
|
||
|
BOOL f32;
|
||
|
|
||
|
if (MapIt(FileHandle, &LoadedImage, MAP_READONLY) == FALSE) {
|
||
|
// Unable to map the image.
|
||
|
SetLastError(ERROR_INVALID_PARAMETER);
|
||
|
return(FALSE);
|
||
|
}
|
||
|
|
||
|
rc = ERROR_INVALID_PARAMETER;
|
||
|
|
||
|
__try {
|
||
|
|
||
|
if (LoadedImage.fDOSImage) {
|
||
|
__leave;
|
||
|
}
|
||
|
|
||
|
if (LoadedImage.FileHeader->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) {
|
||
|
f32 = TRUE;
|
||
|
} else if (LoadedImage.FileHeader->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) {
|
||
|
f32 = FALSE;
|
||
|
} else {
|
||
|
__leave;
|
||
|
}
|
||
|
|
||
|
ExList.Init(&LoadedImage, DigestFunction, DigestHandle);
|
||
|
|
||
|
// Return all the interesting stuff from the image. First, the common stuff.
|
||
|
|
||
|
// 1. Add the DOS stub (if it exists).
|
||
|
|
||
|
if ((ULONG_PTR)LoadedImage.FileHeader - (ULONG_PTR) LoadedImage.MappedAddress) {
|
||
|
if (!ExList.Emit((PBYTE) LoadedImage.MappedAddress,
|
||
|
(DWORD)((ULONG_PTR) LoadedImage.FileHeader - (ULONG_PTR) LoadedImage.MappedAddress)))
|
||
|
{
|
||
|
rc = ERROR_OPERATION_ABORTED;
|
||
|
__leave;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Add the headers, but not the checksum and not the security Data directory entry.
|
||
|
|
||
|
if (f32) {
|
||
|
Hdr.PE32 = *((PIMAGE_NT_HEADERS32)LoadedImage.FileHeader);
|
||
|
pDataDir = &Hdr.PE32.OptionalHeader.DataDirectory[0];
|
||
|
Hdr.PE32.OptionalHeader.CheckSum = 0;
|
||
|
} else {
|
||
|
Hdr.PE64 = *((PIMAGE_NT_HEADERS64)LoadedImage.FileHeader);
|
||
|
pDataDir = &Hdr.PE64.OptionalHeader.DataDirectory[0];
|
||
|
Hdr.PE64.OptionalHeader.CheckSum = 0;
|
||
|
}
|
||
|
|
||
|
pDataDir[IMAGE_DIRECTORY_ENTRY_SECURITY].Size = 0;
|
||
|
pDataDir[IMAGE_DIRECTORY_ENTRY_SECURITY].VirtualAddress = 0;
|
||
|
|
||
|
SectionHeaderSize = sizeof(IMAGE_SECTION_HEADER) * LoadedImage.NumberOfSections;
|
||
|
SectionHeaders = (PIMAGE_SECTION_HEADER) MemAlloc(SectionHeaderSize);
|
||
|
if (SectionHeaders == NULL)
|
||
|
{
|
||
|
rc = ERROR_OPERATION_ABORTED;
|
||
|
__leave;
|
||
|
}
|
||
|
ResourceOffset = pDataDir[IMAGE_DIRECTORY_ENTRY_RESOURCE].VirtualAddress;
|
||
|
ResourceSize = pDataDir[IMAGE_DIRECTORY_ENTRY_RESOURCE].Size;
|
||
|
RelocOffset = pDataDir[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress;
|
||
|
RelocSize = pDataDir[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size;
|
||
|
|
||
|
fDebugAdded = TRUE;
|
||
|
DebugOffset = 0xFFFFFFFF;
|
||
|
RelocHdr = -1;
|
||
|
|
||
|
for (i = 0; i < LoadedImage.NumberOfSections; i++) {
|
||
|
SectionHeaders[i] = LoadedImage.Sections[i];
|
||
|
|
||
|
// Keep track of the reloc section header. We may need to adjust it later.
|
||
|
|
||
|
if (RelocSize &&
|
||
|
((LoadedImage.Sections[i].VirtualAddress <= RelocOffset) &&
|
||
|
(LoadedImage.Sections[i].VirtualAddress +
|
||
|
LoadedImage.Sections[i].Misc.VirtualSize >= RelocOffset + RelocSize))
|
||
|
)
|
||
|
{
|
||
|
RelocHdr = i;
|
||
|
}
|
||
|
|
||
|
// If resources aren't in the digest, we need to clear the resource section header
|
||
|
|
||
|
if (ResourceSize && !(DigestLevel & CERT_PE_IMAGE_DIGEST_RESOURCES)) {
|
||
|
|
||
|
if (((LoadedImage.Sections[i].VirtualAddress <= ResourceOffset) &&
|
||
|
(LoadedImage.Sections[i].VirtualAddress +
|
||
|
LoadedImage.Sections[i].Misc.VirtualSize >= ResourceOffset + ResourceSize))
|
||
|
)
|
||
|
{
|
||
|
// Found the resource section header. Zero it out.
|
||
|
SectionHeaders[i].Misc.VirtualSize = 0;
|
||
|
SectionHeaders[i].VirtualAddress = 0;
|
||
|
SectionHeaders[i].SizeOfRawData = 0;
|
||
|
SectionHeaders[i].PointerToRawData = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!(DigestLevel & CERT_PE_IMAGE_DIGEST_DEBUG_INFO)) {
|
||
|
// Same with mapped debug info.
|
||
|
if (!strncmp((char *)LoadedImage.Sections[i].Name, ".debug", sizeof(".debug"))) {
|
||
|
|
||
|
DebugOffset = SectionHeaders[i].VirtualAddress;
|
||
|
DebugSize = SectionHeaders[i].SizeOfRawData;
|
||
|
ExList.Add(SectionHeaders[i].PointerToRawData + (DWORD_PTR) LoadedImage.MappedAddress, DebugSize, Raw);
|
||
|
|
||
|
SectionHeaders[i].Misc.VirtualSize = 0;
|
||
|
SectionHeaders[i].VirtualAddress = 0;
|
||
|
SectionHeaders[i].SizeOfRawData = 0;
|
||
|
SectionHeaders[i].PointerToRawData = 0;
|
||
|
fDebugAdded = FALSE;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// The first pass on the section headers is finished. See it we need to adjust the
|
||
|
// reloc dir or the image headers.
|
||
|
|
||
|
if (!(DigestLevel & CERT_PE_IMAGE_DIGEST_RESOURCES)) {
|
||
|
// If the resources aren't in the digest, don't add the base reloc address or the
|
||
|
// resource address/size to the digest. This allows subsequent tools to add/subtract
|
||
|
// resource info w/o effecting the digest.
|
||
|
|
||
|
if ((ResourceOffset < RelocOffset) && (RelocHdr != -1))
|
||
|
{
|
||
|
pDataDir[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress = 0;
|
||
|
SectionHeaders[RelocHdr].PointerToRawData = 0;
|
||
|
SectionHeaders[RelocHdr].VirtualAddress = 0;
|
||
|
}
|
||
|
pDataDir[IMAGE_DIRECTORY_ENTRY_RESOURCE].Size = 0;
|
||
|
pDataDir[IMAGE_DIRECTORY_ENTRY_RESOURCE].VirtualAddress = 0;
|
||
|
if (f32) {
|
||
|
Hdr.PE32.OptionalHeader.SizeOfImage = 0;
|
||
|
Hdr.PE32.OptionalHeader.SizeOfInitializedData = 0;
|
||
|
} else {
|
||
|
Hdr.PE64.OptionalHeader.SizeOfImage = 0;
|
||
|
Hdr.PE64.OptionalHeader.SizeOfInitializedData = 0;
|
||
|
}
|
||
|
ExList.Add(ResourceOffset, ResourceSize, Virtual);
|
||
|
}
|
||
|
|
||
|
if (!(DigestLevel & CERT_PE_IMAGE_DIGEST_DEBUG_INFO) &&
|
||
|
(fDebugAdded == FALSE))
|
||
|
{
|
||
|
// Debug wasn't added to the image and IS mapped in. Allow these to grow also.
|
||
|
if (f32) {
|
||
|
Hdr.PE32.OptionalHeader.SizeOfImage = 0;
|
||
|
Hdr.PE32.OptionalHeader.SizeOfInitializedData = 0;
|
||
|
} else {
|
||
|
Hdr.PE64.OptionalHeader.SizeOfImage = 0;
|
||
|
Hdr.PE64.OptionalHeader.SizeOfInitializedData = 0;
|
||
|
}
|
||
|
if ((DebugOffset < RelocOffset) && (RelocHdr != -1))
|
||
|
{
|
||
|
pDataDir[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress = 0;
|
||
|
SectionHeaders[RelocHdr].PointerToRawData = 0;
|
||
|
SectionHeaders[RelocHdr].VirtualAddress = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Looks good. Send the headers to the digest function.
|
||
|
|
||
|
if (f32) {
|
||
|
if (!ExList.Emit((PBYTE) &Hdr.PE32, sizeof(Hdr.PE32))) {
|
||
|
rc = ERROR_OPERATION_ABORTED;
|
||
|
__leave;
|
||
|
}
|
||
|
} else {
|
||
|
if (!ExList.Emit((PBYTE) &Hdr.PE64, sizeof(Hdr.PE64))) {
|
||
|
rc = ERROR_OPERATION_ABORTED;
|
||
|
__leave;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Then the section headers.
|
||
|
|
||
|
if (!ExList.Emit((PBYTE) SectionHeaders, SectionHeaderSize)) {
|
||
|
rc = ERROR_OPERATION_ABORTED;
|
||
|
__leave;
|
||
|
}
|
||
|
|
||
|
MemFree(SectionHeaders);
|
||
|
|
||
|
// The headers are done. Now let's see what we need to do with the import information.
|
||
|
|
||
|
if (!(DigestLevel & CERT_PE_IMAGE_DIGEST_ALL_IMPORT_INFO)) {
|
||
|
// The user didn't explicitly ask for all import info.
|
||
|
// Add the info modified by bind to the exclude list.
|
||
|
|
||
|
PIMAGE_IMPORT_DESCRIPTOR ImportDesc;
|
||
|
DWORD ImportDescSize, IATSize;
|
||
|
PVOID IAT;
|
||
|
|
||
|
ImportDesc = (PIMAGE_IMPORT_DESCRIPTOR) ImageDirectoryEntryToData(
|
||
|
LoadedImage.MappedAddress,
|
||
|
FALSE,
|
||
|
IMAGE_DIRECTORY_ENTRY_IMPORT,
|
||
|
&ImportDescSize);
|
||
|
if (ImportDescSize) {
|
||
|
|
||
|
IAT = ImageDirectoryEntryToData(LoadedImage.MappedAddress,
|
||
|
FALSE,
|
||
|
IMAGE_DIRECTORY_ENTRY_IAT,
|
||
|
&IATSize);
|
||
|
|
||
|
if (IAT) {
|
||
|
// Easy case. All the IATs are grouped together.
|
||
|
ExList.Add((DWORD_PTR) IAT, IATSize, Raw);
|
||
|
|
||
|
// Add the TimeDateStamp and ForwarderChain fields in the Import Descriptors
|
||
|
|
||
|
while (ImportDesc->Characteristics) {
|
||
|
ExList.Add((DWORD_PTR) &ImportDesc->TimeDateStamp, 8, Raw);
|
||
|
ImportDesc++;
|
||
|
}
|
||
|
|
||
|
} else {
|
||
|
// Not so easy. Need to walk each Import descriptor to find the bounds of the IAT
|
||
|
// (note, there's no requirement that all the IAT's for all descriptors be contiguous).
|
||
|
|
||
|
|
||
|
while (ImportDesc->Characteristics) {
|
||
|
PIMAGE_THUNK_DATA ThunkStart;
|
||
|
ExList.Add((DWORD_PTR)&ImportDesc->TimeDateStamp, 8, Raw);
|
||
|
ThunkStart = (PIMAGE_THUNK_DATA) ImageRvaToVa((PIMAGE_NT_HEADERS)LoadedImage.FileHeader,
|
||
|
LoadedImage.MappedAddress,
|
||
|
(ULONG) ImportDesc->OriginalFirstThunk,
|
||
|
NULL);
|
||
|
if (f32) {
|
||
|
PIMAGE_THUNK_DATA32 Thunk = (PIMAGE_THUNK_DATA32)ThunkStart;
|
||
|
while (Thunk->u1.AddressOfData) {
|
||
|
Thunk++;
|
||
|
}
|
||
|
ExList.Add( (DWORD)ImportDesc->FirstThunk,
|
||
|
(DWORD)((DWORD_PTR)Thunk - (DWORD_PTR) ThunkStart + sizeof(IMAGE_THUNK_DATA32)), Virtual);
|
||
|
} else {
|
||
|
PIMAGE_THUNK_DATA64 Thunk = (PIMAGE_THUNK_DATA64)ThunkStart;
|
||
|
while (Thunk->u1.AddressOfData) {
|
||
|
Thunk++;
|
||
|
}
|
||
|
ExList.Add( (DWORD)ImportDesc->FirstThunk,
|
||
|
(DWORD)((DWORD_PTR)Thunk - (DWORD_PTR) ThunkStart + sizeof(IMAGE_THUNK_DATA64)), Virtual);
|
||
|
}
|
||
|
ImportDesc++;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Add each section header followed by the data from that section.
|
||
|
|
||
|
for (i = 0; i < LoadedImage.NumberOfSections; i++) {
|
||
|
if (!ExList.Emit((PBYTE) (LoadedImage.MappedAddress + LoadedImage.Sections[i].PointerToRawData),
|
||
|
LoadedImage.Sections[i].SizeOfRawData))
|
||
|
{
|
||
|
rc = ERROR_OPERATION_ABORTED;
|
||
|
__leave;
|
||
|
}
|
||
|
}
|
||
|
rc = ERROR_SUCCESS;
|
||
|
|
||
|
} __except(EXCEPTION_EXECUTE_HANDLER) { }
|
||
|
|
||
|
UnMapIt(&LoadedImage);
|
||
|
|
||
|
SetLastError(rc);
|
||
|
|
||
|
return(rc == ERROR_SUCCESS ? TRUE : FALSE);
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
BOOL
|
||
|
IMAGEAPI
|
||
|
ImageAddCertificate(
|
||
|
IN HANDLE FileHandle,
|
||
|
IN LPWIN_CERTIFICATE Certificate,
|
||
|
OUT PDWORD Index
|
||
|
)
|
||
|
|
||
|
/*++
|
||
|
|
||
|
Routine Description:
|
||
|
Add a certificate to the image. There is no checking to ensure there are no
|
||
|
duplicate types.
|
||
|
|
||
|
Arguments:
|
||
|
|
||
|
FileHandle - Handle to the file in question. The file should be opened
|
||
|
with at least GENERIC_WRITE access.
|
||
|
|
||
|
Certificate - Pointer to a WIN_CERTIFICATE structure.
|
||
|
|
||
|
Index - After adding the Certificate to the image, this is the index
|
||
|
you can use for later references to that certificate.
|
||
|
|
||
|
Return Value:
|
||
|
|
||
|
TRUE - Success
|
||
|
FALSE - There was some error. Call GetLastError() for more information.
|
||
|
|
||
|
--*/
|
||
|
|
||
|
{
|
||
|
LOADED_IMAGE LoadedImage;
|
||
|
DWORD rc;
|
||
|
LPWIN_CERTIFICATE pCert;
|
||
|
DWORD OnDiskCertLength;
|
||
|
DWORD_PTR NewCertLocation;
|
||
|
DWORD OriginalImageSize;
|
||
|
PIMAGE_DATA_DIRECTORY pDataDir;
|
||
|
BOOL f32, fSkipUnMap;
|
||
|
|
||
|
if (MapIt(FileHandle, &LoadedImage, MAP_READWRITE) == FALSE) {
|
||
|
// Unable to map the image.
|
||
|
SetLastError(ERROR_INVALID_PARAMETER);
|
||
|
return(FALSE);
|
||
|
}
|
||
|
|
||
|
rc = ERROR_INVALID_PARAMETER;
|
||
|
fSkipUnMap = FALSE;
|
||
|
|
||
|
__try {
|
||
|
|
||
|
if (LoadedImage.fDOSImage) {
|
||
|
__leave;
|
||
|
}
|
||
|
|
||
|
if (LoadedImage.FileHeader->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) {
|
||
|
f32 = TRUE;
|
||
|
pDataDir = &((PIMAGE_NT_HEADERS32)(LoadedImage.FileHeader))->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY];
|
||
|
} else if (LoadedImage.FileHeader->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) {
|
||
|
f32 = FALSE;
|
||
|
pDataDir = &((PIMAGE_NT_HEADERS64)(LoadedImage.FileHeader))->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY];
|
||
|
} else {
|
||
|
__leave;
|
||
|
}
|
||
|
|
||
|
pCert = (LPWIN_CERTIFICATE) Certificate;
|
||
|
|
||
|
// Test the output parameter and the the cert.
|
||
|
|
||
|
*Index = (DWORD) -1;
|
||
|
OnDiskCertLength = pCert->dwLength;
|
||
|
OnDiskCertLength = (OnDiskCertLength + 7) & ~7; // Round the size of cert.
|
||
|
|
||
|
// Grow the image.
|
||
|
|
||
|
OriginalImageSize = LoadedImage.SizeOfImage;
|
||
|
OriginalImageSize = (OriginalImageSize + 7) & ~7; // Round the size of Image.
|
||
|
|
||
|
// Check if the cert pointer is at least reasonable.
|
||
|
if (pDataDir->VirtualAddress &&
|
||
|
(pDataDir->VirtualAddress + pDataDir->Size) > LoadedImage.SizeOfImage)
|
||
|
{
|
||
|
__leave;
|
||
|
}
|
||
|
|
||
|
// Looks good now.
|
||
|
|
||
|
*Index = 0;
|
||
|
if (pDataDir->VirtualAddress == 0) {
|
||
|
pDataDir->VirtualAddress = OriginalImageSize;
|
||
|
pDataDir->Size = 0;
|
||
|
NewCertLocation = OriginalImageSize;
|
||
|
} else {
|
||
|
LPWIN_CERTIFICATE CurrentCert;
|
||
|
|
||
|
NewCertLocation = pDataDir->VirtualAddress + pDataDir->Size + (DWORD_PTR) LoadedImage.MappedAddress;
|
||
|
CurrentCert = (LPWIN_CERTIFICATE) (LoadedImage.MappedAddress + pDataDir->VirtualAddress);
|
||
|
while (((DWORD_PTR)CurrentCert) < NewCertLocation) {
|
||
|
if (CurrentCert->dwLength == 0) {
|
||
|
__leave;
|
||
|
}
|
||
|
CurrentCert = (LPWIN_CERTIFICATE)(((DWORD_PTR)CurrentCert + CurrentCert->dwLength + 7) & ~7);
|
||
|
(*Index)++;
|
||
|
}
|
||
|
NewCertLocation -= (DWORD_PTR) LoadedImage.MappedAddress;
|
||
|
}
|
||
|
|
||
|
if (!GrowMap (&LoadedImage, OnDiskCertLength + (OriginalImageSize - LoadedImage.SizeOfImage))) {
|
||
|
fSkipUnMap = TRUE;
|
||
|
__leave;
|
||
|
}
|
||
|
|
||
|
if (NewCertLocation < OriginalImageSize) {
|
||
|
// There's data after the current security data. Move it down.
|
||
|
memmove(LoadedImage.MappedAddress + NewCertLocation + pCert->dwLength,
|
||
|
LoadedImage.MappedAddress + NewCertLocation,
|
||
|
(unsigned) (OriginalImageSize - NewCertLocation));
|
||
|
}
|
||
|
|
||
|
memmove(LoadedImage.MappedAddress + NewCertLocation,
|
||
|
pCert,
|
||
|
pCert->dwLength);
|
||
|
|
||
|
// GrowMap may have moved the dirs.
|
||
|
if (f32) {
|
||
|
pDataDir = &((PIMAGE_NT_HEADERS32)(LoadedImage.FileHeader))->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY];
|
||
|
} else {
|
||
|
pDataDir = &((PIMAGE_NT_HEADERS64)(LoadedImage.FileHeader))->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY];
|
||
|
}
|
||
|
|
||
|
pDataDir->Size += OnDiskCertLength;
|
||
|
rc = ERROR_SUCCESS;
|
||
|
|
||
|
} __except(EXCEPTION_EXECUTE_HANDLER) { }
|
||
|
|
||
|
if (!fSkipUnMap)
|
||
|
UnMapIt(&LoadedImage);
|
||
|
|
||
|
SetLastError(rc);
|
||
|
|
||
|
return(rc == ERROR_SUCCESS ? TRUE : FALSE);
|
||
|
}
|
||
|
|
||
|
|
||
|
BOOL
|
||
|
IMAGEAPI
|
||
|
ImageRemoveCertificate(
|
||
|
IN HANDLE FileHandle,
|
||
|
IN DWORD Index
|
||
|
)
|
||
|
|
||
|
/*++
|
||
|
|
||
|
Routine Description:
|
||
|
|
||
|
Remove a certificate from an image.
|
||
|
|
||
|
Arguments:
|
||
|
|
||
|
FileHandle - Handle to the file in question. The file should be opened
|
||
|
with at least GENERIC_WRITE access.
|
||
|
|
||
|
Index - The index to remove from the image.
|
||
|
|
||
|
Return Value:
|
||
|
|
||
|
TRUE - Successful
|
||
|
|
||
|
FALSE - There was some error. Call GetLastError() for more information.
|
||
|
|
||
|
--*/
|
||
|
|
||
|
{
|
||
|
LOADED_IMAGE LoadedImage;
|
||
|
LPWIN_CERTIFICATE CurrentCert;
|
||
|
DWORD rc;
|
||
|
DWORD OldCertLength;
|
||
|
|
||
|
if (MapIt(FileHandle, &LoadedImage, MAP_READWRITE) == FALSE) {
|
||
|
// Unable to map the image.
|
||
|
SetLastError(ERROR_INVALID_PARAMETER);
|
||
|
return(FALSE);
|
||
|
}
|
||
|
|
||
|
rc = ERROR_INVALID_PARAMETER;
|
||
|
|
||
|
__try {
|
||
|
|
||
|
if (FindCertificate(&LoadedImage, Index, &CurrentCert) == FALSE) {
|
||
|
__leave;
|
||
|
}
|
||
|
|
||
|
OldCertLength = CurrentCert->dwLength;
|
||
|
OldCertLength = (OldCertLength + 7) & ~7; // The disk size is actually a multiple of 8
|
||
|
|
||
|
memmove(CurrentCert,
|
||
|
((PCHAR)CurrentCert) + OldCertLength,
|
||
|
(size_t)(LoadedImage.SizeOfImage - (((DWORD_PTR)CurrentCert) - (DWORD_PTR)LoadedImage.MappedAddress) - OldCertLength));
|
||
|
|
||
|
if (LoadedImage.FileHeader->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) {
|
||
|
((PIMAGE_NT_HEADERS32)LoadedImage.FileHeader)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY].Size -= OldCertLength;
|
||
|
if (!((PIMAGE_NT_HEADERS32)LoadedImage.FileHeader)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY].Size) {
|
||
|
// Last one removed. Clear the pointer
|
||
|
((PIMAGE_NT_HEADERS32)LoadedImage.FileHeader)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY].VirtualAddress = 0;
|
||
|
}
|
||
|
} else {
|
||
|
((PIMAGE_NT_HEADERS64)LoadedImage.FileHeader)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY].Size -= OldCertLength;
|
||
|
if (!((PIMAGE_NT_HEADERS64)LoadedImage.FileHeader)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY].Size) {
|
||
|
// Last one removed. Clear the pointer
|
||
|
((PIMAGE_NT_HEADERS64)LoadedImage.FileHeader)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY].VirtualAddress = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
LoadedImage.SizeOfImage -= OldCertLength;
|
||
|
|
||
|
rc = ERROR_SUCCESS;
|
||
|
|
||
|
} __except(EXCEPTION_EXECUTE_HANDLER) { }
|
||
|
|
||
|
UnMapIt(&LoadedImage);
|
||
|
|
||
|
SetLastError(rc);
|
||
|
|
||
|
return(rc == ERROR_SUCCESS ? TRUE : FALSE);
|
||
|
}
|
||
|
|
||
|
|
||
|
BOOL
|
||
|
IMAGEAPI
|
||
|
ImageEnumerateCertificates(
|
||
|
IN HANDLE FileHandle,
|
||
|
IN WORD TypeFilter,
|
||
|
OUT PDWORD CertificateCount,
|
||
|
IN OUT PDWORD Indices OPTIONAL,
|
||
|
IN DWORD IndexCount OPTIONAL
|
||
|
)
|
||
|
|
||
|
/*++
|
||
|
|
||
|
Routine Description:
|
||
|
|
||
|
Enumerate the certificates in an image.
|
||
|
|
||
|
Arguments:
|
||
|
|
||
|
FileHandle - Handle to the file in question. The file should be opened
|
||
|
with at least GENERIC_READ access.
|
||
|
|
||
|
TypeFilter - The filter to apply when enumertating the certificates.
|
||
|
Valid values are:
|
||
|
|
||
|
CERT_SECTION_TYPE_ANY - Enumerate all certificate types
|
||
|
in the image.
|
||
|
|
||
|
CertificateCount - How many certificates are in the image.
|
||
|
|
||
|
Indices - An array of indexes that match the filter type.
|
||
|
|
||
|
IndexCount - The number of indexes in the indices array.
|
||
|
|
||
|
Return Value:
|
||
|
|
||
|
TRUE - Successful
|
||
|
|
||
|
FALSE - There was some error. Call GetLastError() for more information.
|
||
|
|
||
|
--*/
|
||
|
|
||
|
{
|
||
|
LOADED_IMAGE LoadedImage;
|
||
|
BOOL rc;
|
||
|
PIMAGE_DATA_DIRECTORY pDataDir;
|
||
|
LPWIN_CERTIFICATE CurrentCert, LastCert;
|
||
|
PIMAGE_OPTIONAL_HEADER32 OptionalHeader32 = NULL;
|
||
|
PIMAGE_OPTIONAL_HEADER64 OptionalHeader64 = NULL;
|
||
|
|
||
|
if (MapIt(FileHandle, &LoadedImage, MAP_READONLY) == FALSE) {
|
||
|
// Unable to map the image.
|
||
|
SetLastError(ERROR_INVALID_PARAMETER);
|
||
|
return(FALSE);
|
||
|
}
|
||
|
|
||
|
rc = ERROR_INVALID_PARAMETER;
|
||
|
__try {
|
||
|
if (LoadedImage.fDOSImage) {
|
||
|
__leave;
|
||
|
}
|
||
|
|
||
|
if (LoadedImage.FileHeader->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) {
|
||
|
pDataDir = &((PIMAGE_NT_HEADERS32)(LoadedImage.FileHeader))->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY];
|
||
|
} else if (LoadedImage.FileHeader->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) {
|
||
|
pDataDir = &((PIMAGE_NT_HEADERS64)(LoadedImage.FileHeader))->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY];
|
||
|
} else {
|
||
|
__leave;
|
||
|
}
|
||
|
|
||
|
if ((pDataDir->VirtualAddress + pDataDir->Size) > LoadedImage.SizeOfImage) {
|
||
|
*CertificateCount = 0;
|
||
|
__leave;
|
||
|
}
|
||
|
|
||
|
if (!pDataDir->VirtualAddress || !pDataDir->Size) {
|
||
|
*CertificateCount = 0;
|
||
|
} else {
|
||
|
|
||
|
DWORD MatchedIndex = 0;
|
||
|
DWORD ActualIndex = 0;
|
||
|
|
||
|
CurrentCert = (LPWIN_CERTIFICATE)((DWORD_PTR)LoadedImage.MappedAddress + pDataDir->VirtualAddress);
|
||
|
LastCert = (LPWIN_CERTIFICATE)((DWORD_PTR)CurrentCert + pDataDir->Size);
|
||
|
|
||
|
while (CurrentCert < LastCert ) {
|
||
|
if ((TypeFilter == CERT_SECTION_TYPE_ANY) || (TypeFilter == CurrentCert->wCertificateType)) {
|
||
|
if (Indices && (MatchedIndex < IndexCount)) {
|
||
|
Indices[MatchedIndex] = ActualIndex;
|
||
|
}
|
||
|
MatchedIndex++;
|
||
|
}
|
||
|
|
||
|
ActualIndex++;
|
||
|
CurrentCert = (LPWIN_CERTIFICATE)((((DWORD_PTR)CurrentCert + CurrentCert->dwLength) +7) & ~7);
|
||
|
}
|
||
|
|
||
|
*CertificateCount = MatchedIndex;
|
||
|
}
|
||
|
|
||
|
rc = ERROR_SUCCESS;
|
||
|
|
||
|
} __except(EXCEPTION_EXECUTE_HANDLER) { }
|
||
|
|
||
|
UnMapIt(&LoadedImage);
|
||
|
|
||
|
SetLastError(rc);
|
||
|
|
||
|
return(rc == ERROR_SUCCESS ? TRUE : FALSE);
|
||
|
}
|
||
|
|
||
|
|
||
|
BOOL
|
||
|
IMAGEAPI
|
||
|
ImageGetCertificateData(
|
||
|
IN HANDLE FileHandle,
|
||
|
IN DWORD CertificateIndex,
|
||
|
OUT LPWIN_CERTIFICATE Certificate,
|
||
|
IN OUT PDWORD RequiredLength
|
||
|
)
|
||
|
|
||
|
/*++
|
||
|
|
||
|
Routine Description:
|
||
|
|
||
|
Given a specific certificate index, retrieve the certificate data.
|
||
|
|
||
|
Arguments:
|
||
|
|
||
|
FileHandle - Handle to the file in question. The file should be opened
|
||
|
with at least GENERIC_READ access.
|
||
|
|
||
|
CertificateIndex - Index to retrieve
|
||
|
|
||
|
Certificate - Output buffer where the certificate is to be stored.
|
||
|
|
||
|
RequiredLength - Size of the certificate buffer (input). On return, is
|
||
|
set to the actual certificate length. NULL can be used
|
||
|
to determine the size of a certificate.
|
||
|
|
||
|
Return Value:
|
||
|
|
||
|
TRUE - Successful
|
||
|
|
||
|
FALSE - There was some error. Call GetLastError() for more information.
|
||
|
|
||
|
--*/
|
||
|
|
||
|
{
|
||
|
LOADED_IMAGE LoadedImage;
|
||
|
DWORD ErrorCode;
|
||
|
|
||
|
LPWIN_CERTIFICATE ImageCert;
|
||
|
|
||
|
if (MapIt(FileHandle, &LoadedImage, MAP_READONLY) == FALSE) {
|
||
|
// Unable to map the image.
|
||
|
SetLastError(ERROR_INVALID_PARAMETER);
|
||
|
return(FALSE);
|
||
|
}
|
||
|
|
||
|
ErrorCode = ERROR_INVALID_PARAMETER;
|
||
|
__try {
|
||
|
if (FindCertificate(&LoadedImage, CertificateIndex, &ImageCert) == FALSE) {
|
||
|
__leave;
|
||
|
}
|
||
|
|
||
|
if (*RequiredLength < ImageCert->dwLength) {
|
||
|
*RequiredLength = ImageCert->dwLength;
|
||
|
ErrorCode = ERROR_INSUFFICIENT_BUFFER;
|
||
|
} else {
|
||
|
memcpy(Certificate, (PUCHAR)ImageCert, ImageCert->dwLength);
|
||
|
ErrorCode = ERROR_SUCCESS;
|
||
|
}
|
||
|
} __except(EXCEPTION_EXECUTE_HANDLER) { }
|
||
|
|
||
|
UnMapIt(&LoadedImage);
|
||
|
|
||
|
SetLastError(ErrorCode);
|
||
|
return(ErrorCode == ERROR_SUCCESS ? TRUE: FALSE);
|
||
|
}
|
||
|
|
||
|
|
||
|
BOOL
|
||
|
IMAGEAPI
|
||
|
ImageGetCertificateHeader(
|
||
|
IN HANDLE FileHandle,
|
||
|
IN DWORD CertificateIndex,
|
||
|
IN OUT LPWIN_CERTIFICATE CertificateHeader
|
||
|
)
|
||
|
|
||
|
/*++
|
||
|
|
||
|
Routine Description:
|
||
|
|
||
|
Given a specific certificate index, retrieve the certificate data.
|
||
|
|
||
|
Arguments:
|
||
|
|
||
|
FileHandle - Handle to the file in question. The file should be opened
|
||
|
with at least GENERIC_READ access.
|
||
|
|
||
|
CertificateIndex - Index to retrieve.
|
||
|
|
||
|
CertificateHeader - Pointer to a WIN_CERTIFICATE to fill in.
|
||
|
|
||
|
Return Value:
|
||
|
|
||
|
TRUE - Success
|
||
|
|
||
|
FALSE - There was some error. Call GetLastError() for more information.
|
||
|
|
||
|
--*/
|
||
|
|
||
|
{
|
||
|
LOADED_IMAGE LoadedImage;
|
||
|
LPWIN_CERTIFICATE ImageCert;
|
||
|
BOOL rc;
|
||
|
|
||
|
if (MapIt(FileHandle, &LoadedImage, MAP_READONLY) == FALSE) {
|
||
|
// Unable to map the image.
|
||
|
SetLastError(ERROR_INVALID_PARAMETER);
|
||
|
return(FALSE);
|
||
|
}
|
||
|
|
||
|
if (FindCertificate(&LoadedImage, CertificateIndex, &ImageCert) == FALSE) {
|
||
|
rc = FALSE;
|
||
|
goto Exit;
|
||
|
}
|
||
|
|
||
|
__try {
|
||
|
memcpy(CertificateHeader, ImageCert, sizeof(WIN_CERTIFICATE));
|
||
|
rc = TRUE;
|
||
|
} __except(EXCEPTION_EXECUTE_HANDLER) {
|
||
|
rc = FALSE;
|
||
|
}
|
||
|
Exit:
|
||
|
|
||
|
UnMapIt(&LoadedImage);
|
||
|
|
||
|
if (rc == FALSE) {
|
||
|
SetLastError(ERROR_INVALID_PARAMETER);
|
||
|
}
|
||
|
|
||
|
return(rc);
|
||
|
}
|