diff options
Diffstat (limited to 'src/bldprogs/VBoxCheckImports.cpp')
-rw-r--r-- | src/bldprogs/VBoxCheckImports.cpp | 381 |
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; +} + |