summaryrefslogtreecommitdiffstats
path: root/src/bldprogs/VBoxCheckImports.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/bldprogs/VBoxCheckImports.cpp')
-rw-r--r--src/bldprogs/VBoxCheckImports.cpp381
1 files changed, 381 insertions, 0 deletions
diff --git a/src/bldprogs/VBoxCheckImports.cpp b/src/bldprogs/VBoxCheckImports.cpp
new file mode 100644
index 00000000..ab9c988e
--- /dev/null
+++ b/src/bldprogs/VBoxCheckImports.cpp
@@ -0,0 +1,381 @@
+/* $Id: VBoxCheckImports.cpp $ */
+/** @file
+ * IPRT - Checks that a windows image only imports from a given set of DLLs.
+ */
+
+/*
+ * Copyright (C) 2012-2022 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>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/formats/mz.h>
+#include <iprt/formats/pecoff.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+typedef struct
+{
+ const char *pszImage;
+ FILE *pFile;
+ bool f32Bit;
+ union
+ {
+ IMAGE_NT_HEADERS32 Nt32;
+ IMAGE_NT_HEADERS64 Nt64;
+ } Hdrs;
+
+ uint32_t cSections;
+ PIMAGE_SECTION_HEADER paSections;
+} MYIMAGE;
+
+
+static bool Failed(MYIMAGE *pThis, const char *pszFmt, ...)
+{
+ va_list va;
+ fprintf(stderr, "error '%s': ", pThis->pszImage);
+ va_start(va, pszFmt);
+ vfprintf(stderr, pszFmt, va);
+ va_end(va);
+ fprintf(stderr, "\n");
+ return false;
+}
+
+
+static bool ReadPeHeaders(MYIMAGE *pThis)
+{
+ /*
+ * MZ header.
+ */
+ IMAGE_DOS_HEADER MzHdr;
+ if (fread(&MzHdr, sizeof(MzHdr), 1, pThis->pFile) != 1)
+ return Failed(pThis, "Reading DOS header");
+
+ if (MzHdr.e_magic != IMAGE_DOS_SIGNATURE)
+ return Failed(pThis, "No MZ magic (found %#x)", MzHdr.e_magic);
+
+ if (fseek(pThis->pFile, MzHdr.e_lfanew, SEEK_SET) != 0)
+ return Failed(pThis, "Seeking to %#lx", (unsigned long)MzHdr.e_lfanew);
+
+ /*
+ * NT signature + file header.
+ */
+ if (fread(&pThis->Hdrs.Nt32, offsetof(IMAGE_NT_HEADERS32, OptionalHeader), 1, pThis->pFile) != 1)
+ return Failed(pThis, "Reading NT file header");
+
+ if (pThis->Hdrs.Nt32.Signature != IMAGE_NT_SIGNATURE)
+ return Failed(pThis, "No PE magic (found %#x)", pThis->Hdrs.Nt32.Signature);
+
+ if (pThis->Hdrs.Nt32.FileHeader.SizeOfOptionalHeader == sizeof(pThis->Hdrs.Nt32.OptionalHeader))
+ pThis->f32Bit = true;
+ else if (pThis->Hdrs.Nt32.FileHeader.SizeOfOptionalHeader == sizeof(pThis->Hdrs.Nt64.OptionalHeader))
+ pThis->f32Bit = false;
+ else
+ return Failed(pThis, "Unsupported SizeOfOptionalHeaders value: %#x",
+ pThis->Hdrs.Nt32.FileHeader.SizeOfOptionalHeader);
+
+ /*
+ * NT optional header.
+ */
+ if (fread(&pThis->Hdrs.Nt32.OptionalHeader, pThis->Hdrs.Nt32.FileHeader.SizeOfOptionalHeader, 1, pThis->pFile) != 1)
+ return Failed(pThis, "Reading NT optional header");
+
+ if ( pThis->Hdrs.Nt32.OptionalHeader.Magic
+ != (pThis->f32Bit ? IMAGE_NT_OPTIONAL_HDR32_MAGIC : IMAGE_NT_OPTIONAL_HDR64_MAGIC) )
+ return Failed(pThis, "Bad optional header magic: %#x", pThis->Hdrs.Nt32.OptionalHeader.Magic);
+
+ uint32_t NumberOfRvaAndSizes = pThis->f32Bit
+ ? pThis->Hdrs.Nt32.OptionalHeader.NumberOfRvaAndSizes
+ : pThis->Hdrs.Nt64.OptionalHeader.NumberOfRvaAndSizes;
+ if (NumberOfRvaAndSizes != IMAGE_NUMBEROF_DIRECTORY_ENTRIES)
+ return Failed(pThis, "Unsupported NumberOfRvaAndSizes value: %#x", NumberOfRvaAndSizes);
+
+ /*
+ * Read the section table.
+ */
+ pThis->cSections = pThis->Hdrs.Nt32.FileHeader.NumberOfSections;
+ if (!pThis->cSections)
+ return Failed(pThis, "No sections in image!");
+ pThis->paSections = (PIMAGE_SECTION_HEADER)calloc(sizeof(pThis->paSections[0]), pThis->cSections);
+ if (!pThis->paSections)
+ return Failed(pThis, "Out of memory!");
+ if (fread(pThis->paSections, sizeof(pThis->paSections[0]), pThis->cSections, pThis->pFile) != pThis->cSections)
+ return Failed(pThis, "Reading NT section headers");
+
+
+ return true;
+}
+
+
+static bool ReadAtRva(MYIMAGE *pThis, uint32_t uRva, void *pvBuf, size_t cbToRead)
+{
+ unsigned const uRvaOrg = uRva;
+ size_t const cbToReadOrg = cbToRead;
+
+ /*
+ * Header section.
+ */
+ int iSh = -1;
+ uint32_t uSectRva = 0;
+ uint32_t offSectRaw = 0;
+ uint32_t cbSectRaw = pThis->f32Bit
+ ? pThis->Hdrs.Nt32.OptionalHeader.SizeOfHeaders
+ : pThis->Hdrs.Nt64.OptionalHeader.SizeOfHeaders;
+ uint32_t cbSectMax = pThis->paSections[0].VirtualAddress;
+
+ for (;;)
+ {
+ /* Read if we've got a match. */
+ uint32_t off = uRva - uSectRva;
+ if (off < cbSectMax)
+ {
+ uint32_t cbThis = cbSectMax - off;
+ if (cbThis > cbToRead)
+ cbThis = (uint32_t)cbToRead;
+
+ memset(pvBuf, 0, cbThis);
+
+ if (off < cbSectRaw)
+ {
+ if (fseek(pThis->pFile, offSectRaw + off, SEEK_SET) != 0)
+ return Failed(pThis, "Seeking to %#x", offSectRaw + off);
+ if (fread(pvBuf, RT_MIN(cbThis, cbSectRaw - off), 1, pThis->pFile) != 1)
+ return Failed(pThis, "Reading %u bytes at %#x", RT_MIN(cbThis, cbSectRaw - off), offSectRaw + off);
+ }
+
+ cbToRead -= cbThis;
+ if (!cbToRead)
+ return true;
+ uRva += cbThis;
+ pvBuf = (uint8_t *)pvBuf + cbThis;
+ }
+
+ /* next section */
+ iSh++;
+ if ((unsigned)iSh >= pThis->cSections)
+ return Failed(pThis, "RVA %#x LB %u is outside the image", uRvaOrg, cbToReadOrg);
+ uSectRva = pThis->paSections[iSh].VirtualAddress;
+ offSectRaw = pThis->paSections[iSh].PointerToRawData;
+ cbSectRaw = pThis->paSections[iSh].SizeOfRawData;
+ if ((unsigned)iSh + 1 < pThis->cSections)
+ cbSectMax = pThis->paSections[iSh + 1].VirtualAddress - uSectRva;
+ else
+ cbSectMax = pThis->paSections[iSh].Misc.VirtualSize;
+ }
+}
+
+
+static bool ReadStringAtRva(MYIMAGE *pThis, uint32_t uRva, char *pszBuf, size_t cbMax)
+{
+ uint32_t const uRvaOrg = uRva;
+
+ /*
+ * Try read the whole string at once.
+ */
+ uint32_t cbImage = pThis->f32Bit
+ ? pThis->Hdrs.Nt32.OptionalHeader.SizeOfImage
+ : pThis->Hdrs.Nt64.OptionalHeader.SizeOfImage;
+ uint32_t cbThis = uRva < cbImage ? cbImage - uRva : 1;
+ if (cbThis > cbMax)
+ cbThis = (uint32_t)cbMax;
+ if (!ReadAtRva(pThis, uRva, pszBuf, cbThis))
+ return false;
+ if (memchr(pszBuf, 0, cbThis) != NULL)
+ return true;
+
+ /*
+ * Read more, byte-by-byte.
+ */
+ for (;;)
+ {
+ cbMax -= cbThis;
+ if (!cbMax)
+ return Failed(pThis, "String to long at %#x", uRvaOrg);
+ pszBuf += cbThis;
+ uRva += cbThis;
+
+ cbThis = 1;
+ if (!ReadAtRva(pThis, uRva, pszBuf, cbThis))
+ return false;
+ if (!*pszBuf)
+ return true;
+ }
+}
+
+
+static void *ReadAtRvaAlloc(MYIMAGE *pThis, uint32_t uRva, size_t cbToRead)
+{
+ void *pvBuf = malloc(cbToRead);
+ if (pvBuf)
+ {
+ if (ReadAtRva(pThis, uRva, pvBuf, cbToRead))
+ return pvBuf;
+ free(pvBuf);
+ }
+ else
+ Failed(pThis, "Out of memory!");
+ return NULL;
+}
+
+
+static bool ParseAndCheckImports(MYIMAGE *pThis, const char **papszAllowed, unsigned cAllowed)
+{
+ /*
+ * Do we have an import directory? If so, read it.
+ */
+ IMAGE_DATA_DIRECTORY ImpDir = pThis->f32Bit
+ ? pThis->Hdrs.Nt32.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]
+ : pThis->Hdrs.Nt64.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
+ if (ImpDir.Size == 0)
+ return true;
+
+ uint32_t cImps = ImpDir.Size / sizeof(IMAGE_IMPORT_DESCRIPTOR);
+ if (cImps * sizeof(IMAGE_IMPORT_DESCRIPTOR) != ImpDir.Size)
+ return Failed(pThis, "Import directory size is not a multiple of IMAGE_IMPORT_DESCRIPTOR: %#x", ImpDir.Size);
+
+ PIMAGE_IMPORT_DESCRIPTOR paImps = (PIMAGE_IMPORT_DESCRIPTOR)ReadAtRvaAlloc(pThis, ImpDir.VirtualAddress, ImpDir.Size);
+ if (!paImps)
+ return false;
+
+ /* There is usually an empty entry at the end. */
+ if ( paImps[cImps - 1].Name == 0
+ || paImps[cImps - 1].FirstThunk == 0)
+ cImps--;
+
+ /*
+ * Do the processing.
+ */
+ bool fRc = true;
+ for (uint32_t i = 0; i < cImps; i++)
+ {
+ /* Read the import name string. */
+ char szName[128];
+ if (!ReadStringAtRva(pThis, paImps[i].Name, szName, sizeof(szName)))
+ {
+ fRc = false;
+ break;
+ }
+
+ /* Check it against the list of allowed DLLs. */
+ bool fFound = false;
+ unsigned j = cAllowed;
+ while (j-- > 0)
+ if (stricmp(papszAllowed[j], szName) == 0)
+ {
+ fFound = true;
+ break;
+ }
+ if (!fFound)
+ fRc = Failed(pThis, "Illegal import: '%s'", szName);
+ }
+
+ free(paImps);
+ return fRc;
+}
+
+
+static int usage(const char *argv0)
+{
+ printf("usage: %s --image <image> [allowed-dll [..]]\n", argv0);
+ return RTEXITCODE_SUCCESS;
+}
+
+
+int main(int argc, char **argv)
+{
+ /*
+ * Parse arguments.
+ */
+ const char *pszImage = NULL;
+ const char **papszAllowed = (const char **)calloc(argc, sizeof(const char *));
+ unsigned cAllowed = 0;
+
+ for (int i = 1; i < argc; i++)
+ {
+ const char *psz = argv[i];
+ if (*psz == '-')
+ {
+ if (!strcmp(psz, "--image") || !strcmp(psz, "-i"))
+ {
+ if (++i >= argc)
+ {
+ fprintf(stderr, "syntax error: File name expected after '%s'.\n", psz);
+ return RTEXITCODE_SYNTAX;
+ }
+ pszImage = argv[i];
+ }
+ else if ( !strcmp(psz, "--help")
+ || !strcmp(psz, "-help")
+ || !strcmp(psz, "-h")
+ || !strcmp(psz, "-?") )
+ return usage(argv[0]);
+ else if ( !strcmp(psz, "--version")
+ || !strcmp(psz, "-V"))
+ {
+ printf("$Revision: 153224 $\n");
+ return RTEXITCODE_SUCCESS;
+ }
+ else
+ {
+ fprintf(stderr, "syntax error: Unknown option '%s'.\n", psz);
+ return RTEXITCODE_SYNTAX;
+ }
+ }
+ else
+ papszAllowed[cAllowed++] = argv[i];
+ }
+
+ if (!pszImage)
+ {
+ fprintf(stderr, "syntax error: No input file specified.\n");
+ return RTEXITCODE_SYNTAX;
+ }
+
+ /*
+ * Open the image and process headers.
+ */
+ RTEXITCODE rcExit = RTEXITCODE_FAILURE;
+ MYIMAGE MyImage;
+ memset(&MyImage, 0, sizeof(MyImage));
+ MyImage.pszImage = pszImage;
+ MyImage.pFile = fopen(pszImage, "rb");
+ if (MyImage.pFile)
+ {
+ if ( ReadPeHeaders(&MyImage)
+ && ParseAndCheckImports(&MyImage, papszAllowed, cAllowed))
+ rcExit = RTEXITCODE_SUCCESS;
+
+ fclose(MyImage.pFile);
+ free(MyImage.paSections);
+ }
+ else
+ Failed(&MyImage, "Failed to open image for binary reading.");
+
+ return rcExit;
+}
+