2

I'm not even sure if this is possible for Windows; I haven't seen a single person asking for something this general and finding a solution. It probably is possible, but there might not be APIs for handling it.

I have an automated testing module I'm working on for Windows that consumes a module to handle detected EXEs in a generalized fashion unless it detects that the binary is from a specific testing framework. So far, I've only been able to do this by querying the help and handling responses/string parsing. This can cause problems if I trigger a long test someone wrote outside of a framework that accepts command-line parameters for help but doesn't actually process those command-line parameters and just auto-runs. So instead of doing a lightning fast query, sometimes I can get stuck waiting for the test to finish. That's what I'm trying to avoid with this fancy new module. :)

The crux of this issue is that this consumed module for getting the list of DLLs will be distributed to non-development Windows systems, and that I can't say what it was built with(.NET, C++, etc). That prevents me from using dumpbin and link as Microsoft does not allow them to be distributed. For my own licensing requirements, this module of mine will not be sold; freeware forever.

I was advised to look into dumpbin before I realized I couldn't distribute it. When I use that, this is what I'm getting:

c:\test_dir>dumpbin /dependents .\qt_unit_test.exe
Microsoft (R) COFF/PE Dumper Version 14.00.24215.1
Copyright (C) Microsoft Corporation.  All rights reserved.


Dump of file .\qt_test_unit_test.exe

File Type: EXECUTABLE IMAGE

  Image has the following dependencies:

    Qt5Test.dll
    Qt5Core.dll
    KERNEL32.dll
    VCRUNTIME140.dll
    api-ms-win-crt-utility-l1-1-0.dll
    api-ms-win-crt-runtime-l1-1-0.dll
    api-ms-win-crt-math-l1-1-0.dll
    api-ms-win-crt-stdio-l1-1-0.dll
    api-ms-win-crt-locale-l1-1-0.dll
    api-ms-win-crt-heap-l1-1-0.dll

  Summary

        1000 .data
        1000 .gfids
        1000 .pdata
        2000 .rdata
        1000 .reloc
        1000 .rsrc
        1000 .text

The only needed information is the list of DLL dependencies. Getting both dynamic and static libraries used is not required. Minimum needed is the DLLs, but if someone I can get static libraries used too, that would be cool; totally optional.

I was basically doing this to verify if it was a Qt Test binary:

c:\test_dir>dumpbin /dependents .\qt_unit_test.exe | call findstr /i qt5test 1>nul 2>nul
c:\test_dir>if "%errorlevel%"=="0" echo is Qt Test

Coding-wise, what I've tried so far is C#'s Assembly.GetReferencedAssemblies, but that of course only gets assembly information, so regular old STL C++ apps generate exceptions.

Next I diff'd one of Ed Bayiates's answers to try to get something meaningful, but honestly I'm a fish out of water in WinAPI-land. There's whole concepts I'm probably misunderstanding... Anyway, you can check the current code here, but I don't understand how to translate from the returned IntPtr to a string or list of strings that tell you what DLLs were used for it. It seems like it's working otherwise, but yeah, classic voodoo programming scenario here...

Btw, I looked into the possibility of just using the Visual C++ Build Tools standalone, but that thing ends up being over a gig after you get all the tools which defeats my purpose of a light-weight generalized automated testing module.

I'm willing to use any language to accomplish this; I started with C++ and C# as I figured having WinAPI access would probably get me out of the woods for something this OS-specific.

Update:

6.4. The .idata Section All image files that import symbols, including virtually all executable (EXE) files, have an .idata section. A typical file layout for the import information follows...

While working on getting the .idata table to handle this, I ran across a nasty little gotcha. The "virtually all executables..." clause of the idata section isn't so all-encompassing as the documentation would lead you to believe. Check out Windows 10's built in Calculator.exe app:

C:\WINDOWS\system32>dumpbin /summary "C:\Program Files\WindowsApps\Microsoft.WindowsCalculator_10.1703.601.0_x64__8wekyb3d8bbwe\Calculator.exe"

Microsoft (R) COFF/PE Dumper Version 14.00.24215.1
Copyright (C) Microsoft Corporation.  All rights reserved.


Dump of file C:\Program Files\WindowsApps\Microsoft.WindowsCalculator_10.1703.601.0_x64__8wekyb3d8bbwe\Calculator.exe

File Type: EXECUTABLE IMAGE

  Summary

       4C000 .data
        1000 .gfids
        1000 .giats
       21000 .pdata
      135000 .rdata
        A000 .reloc
        1000 .rsrc
      20D000 .text
        1000 .tls
        1000 minATL

No .idata section. However, that doesn't stop dumpbin from finding those dependencies:

C:\WINDOWS\system32>dumpbin /dependents "C:\Program Files\WindowsApps\Microsoft.WindowsCalculator_10.1703.601.0_x64__8wekyb3d8bbwe\Calculator.exe"
Microsoft (R) COFF/PE Dumper Version 14.00.24215.1
Copyright (C) Microsoft Corporation.  All rights reserved.


Dump of file C:\Program Files\WindowsApps\Microsoft.WindowsCalculator_10.1703.601.0_x64__8wekyb3d8bbwe\Calculator.exe

File Type: EXECUTABLE IMAGE

  Image has the following dependencies:

    api-ms-win-core-localization-l1-2-1.dll
    api-ms-win-eventing-provider-l1-1-0.dll
    api-ms-win-core-com-l1-1-1.dll
    api-ms-win-core-sysinfo-l1-2-1.dll
    api-ms-win-core-processthreads-l1-1-2.dll
    api-ms-win-core-sysinfo-l1-2-3.dll
    vccorlib140_app.DLL
    MSVCP140_APP.dll
    CONCRT140_APP.dll
    VCRUNTIME140_APP.dll
    api-ms-win-crt-runtime-l1-1-0.dll
    api-ms-win-crt-convert-l1-1-0.dll
    api-ms-win-crt-string-l1-1-0.dll
    api-ms-win-crt-heap-l1-1-0.dll
    api-ms-win-crt-stdio-l1-1-0.dll
    api-ms-win-crt-math-l1-1-0.dll
    api-ms-win-crt-locale-l1-1-0.dll
    api-ms-win-core-util-l1-1-0.dll
    api-ms-win-core-synch-l1-2-0.dll
    api-ms-win-core-winrt-error-l1-1-1.dll
    api-ms-win-core-winrt-string-l1-1-0.dll
    api-ms-win-core-handle-l1-1-0.dll
    api-ms-win-core-winrt-l1-1-0.dll
    api-ms-win-core-profile-l1-1-0.dll
    api-ms-win-core-libraryloader-l1-2-0.dll
    api-ms-win-core-interlocked-l1-2-0.dll

  Summary

       4C000 .data
        1000 .gfids
        1000 .giats
       21000 .pdata
      135000 .rdata
        A000 .reloc
        1000 .rsrc
      20D000 .text
        1000 .tls
        1000 minATL

Update #2:

Getting closer to accomplishing this one after getting some great advice and input from one of my mentors. I managed to remove all the windows internals that required debug library usage, or libs outside the GAC when using Visual C++ Runtime redists.

Current problem is going from taking the RVA of the import table to getting all the names in the import table. My first queried import gives a null name even though the rest of the data isn't null.

//******************************************************************************
// HEADERS
#include "Windows.h"

#include <iostream>
#include <sstream>
#include <fstream>
#include <string>
#include <vector>

#include <experimental\filesystem>

//******************************************************************************
// NAMESPACES
namespace fs = std::experimental::filesystem;

//******************************************************************************
//FUNCTION DECLARATIONS
bool verify_image_file(std::string);
std::vector<char> read_all_bytes(const char* file);
std::vector<std::string> parse_file(std::string file);

//******************************************************************************
// CONSTANTS
//        LABEL                                                HEX            DEC
//
const WORD MAGIC_NUM_32BIT             = static_cast<const WORD>(0x10b);      // 267
const WORD MAGIC_NUM_64BIT             = static_cast<const WORD>(0x20b);      // 523
const int IMG_SIGNATURE_OFFSET         = static_cast<const int>(0x3c);        // 60
const int IMPORT_TABLE_OFFSET_32       = static_cast<const int>(0x68);        // 104
const int IMPORT_TABLE_OFFSET_64       = static_cast<const int>(0x78);        // 120
const int IMG_SIGNATURE_SIZE           = static_cast<const int>(0x4);         // 4
const int OPT_HEADER_OFFSET_32         = static_cast<const int>(0x1c);        // 28
const int OPT_HEADER_OFFSET_64         = static_cast<const int>(0x18);        // 24
const int DATA_DIR_OFFSET_32           = static_cast<const int>(0x60);        // 96
const int DATA_DIR_OFFSET_64           = static_cast<const int>(0x70);        // 112
const int DATA_IAT_OFFSET_64           = static_cast<const int>(0xD0);        // 208
const int DATA_IAT_OFFSET_32           = static_cast<const int>(0xC0);        // 192
const int SZ_OPT_HEADER_OFFSET         = static_cast<const int>(0x10);        // 16
const int RVA_AMOUNT_OFFSET_64         = static_cast<const int>(0x6c);        // 108
const int RVA_AMOUNT_OFFSET_32         = static_cast<const int>(0x5c);        // 92
const char * KNOWN_IMG_SIGNATURE       = static_cast<const char*>("PE\0\0");

//******************************************************************************
// Globals
bool is64Bit = false;
bool is32Bit = false;

//******************************************************************************
// PLACEHOLDERS
const char* ph_file("C:\\Windows\\System32\\notepad.exe");

//******************************************************************************
// ENTRY
int main(int argc, char* argv[])
{
    if (!verify_image_file(ph_file))return -1;

    if (parse_file(ph_file).size() > 1)return 0;
    else return -1;

    return -1;
}

//******************************************************************************
// FILE PARSER
std::vector<std::string> parse_file(std::string file)
{
    std::vector<char> bytes = read_all_bytes(file.c_str());
    std::vector<std::string> dependencies;

    DWORD * signature_offset_location = (DWORD*)&bytes[IMG_SIGNATURE_OFFSET];
    char * signature = (char*)&bytes[*signature_offset_location];

    if (*signature != *KNOWN_IMG_SIGNATURE)return dependencies;

    DWORD coff_file_header_offset = *signature_offset_location + IMG_SIGNATURE_SIZE;
    IMAGE_FILE_HEADER* coff_file_header = (IMAGE_FILE_HEADER*)&bytes[coff_file_header_offset];
    DWORD optional_file_header_offset = coff_file_header_offset + sizeof(IMAGE_FILE_HEADER);

    WORD size_of_optional_header_offset = coff_file_header_offset + SZ_OPT_HEADER_OFFSET;
    WORD* size_of_optional_header = (WORD*)&bytes[size_of_optional_header_offset];  

    //Magic is a 2-Byte value at offset-zero of the optional file header regardless of 32/64 bit
    WORD* magic_number = (WORD*)&bytes[optional_file_header_offset];

    if (*magic_number == MAGIC_NUM_32BIT)is32Bit = true;
    else if (*magic_number == MAGIC_NUM_64BIT)is64Bit = true;
    else
    {
        std::cerr << "Could not parse magic number for 32 or 64-bit PE-format Image File." << std::endl;
        return dependencies;
    }

    if (is64Bit)
    {
        IMAGE_OPTIONAL_HEADER64 * img_opt_header_64 = (IMAGE_OPTIONAL_HEADER64*)&bytes[optional_file_header_offset];
        IMAGE_DATA_DIRECTORY* import_table_data_dir = (IMAGE_DATA_DIRECTORY*)&bytes[optional_file_header_offset + IMPORT_TABLE_OFFSET_64];
        DWORD* import_table_address = (DWORD*)import_table_data_dir;



        // To Get the import table, you need to check all the IMAGE_SECTION_HEADERs for the section that matches size of the direct-query.
        // TO get those you can use normal offsets. To go further, we need to start using the RVA
        // IMAGE_SECTION_HEADERS starts directly after the end of the optional file header for file_header->NumberOfSections
        // Then, your RVA is if (ptr_to_raw_data >= va && ptr_to_raw_data < va + SizeOfData){//isSection}
        // DWORD FileOffset = Ptr_To_Raw_Data - VA + PointerToRawData


        DWORD image_section_header_offset = optional_file_header_offset;

        for (int i = 0; i < coff_file_header->NumberOfSections; i++)
        {
            IMAGE_SECTION_HEADER* queried_section_header = (IMAGE_SECTION_HEADER*)&bytes[image_section_header_offset];
            if  (queried_section_header->PointerToRawData >= import_table_data_dir->VirtualAddress && (queried_section_header->PointerToRawData < (import_table_data_dir->VirtualAddress + queried_section_header->SizeOfRawData)))
            {
                DWORD import_table_offset = queried_section_header->PointerToRawData - import_table_data_dir->VirtualAddress + queried_section_header->PointerToRawData;
                IMAGE_IMPORT_DESCRIPTOR* import_table_descriptor = (IMAGE_IMPORT_DESCRIPTOR*)&bytes[import_table_offset];
                if (import_table_descriptor->Name==NULL && 
                    import_table_descriptor->Characteristics==NULL &&
                    import_table_descriptor->FirstThunk==NULL &&
                    import_table_descriptor->ForwarderChain==NULL &&
                    import_table_descriptor->OriginalFirstThunk==NULL &&
                    import_table_descriptor->TimeDateStamp==NULL)
                {
                    break;//Signifies end of IMAGE_IMPORT_DESCRIPTORs
                }

                DWORD* dependency_name_address = (DWORD*)import_table_descriptor->Name;
                char * dependency_name = (char *)&bytes[import_table_descriptor->Name];
                dependencies.push_back((std::string)dependency_name);
                int breakpoint = 0;
            }
            image_section_header_offset = image_section_header_offset + sizeof(IMAGE_SECTION_HEADER);
        }
    }
    else//32-bit behavior
    {
        //todo
    }

    return dependencies;
}

//******************************************************************************
// FILE READER
std::vector<char> read_all_bytes(const char* filename)
{
    std::ifstream ifs(filename, std::ios::binary | std::ios::ate);
    std::ifstream::pos_type pos = ifs.tellg();

    std::vector<char> result(pos);
    ifs.seekg(0, std::ios::beg);
    ifs.read(&result[0], pos);

    return result;
}

//******************************************************************************
// IMAGE-TYPE FILE VERIFIER
bool verify_image_file(std::string file_to_verify)
{
    if (fs::exists(file_to_verify))
    {
        size_t extension_query = file_to_verify.find(".dll", 0);
        if (extension_query == std::string::npos)
        {
            extension_query = file_to_verify.find(".DLL", 0);
            if (extension_query == std::string::npos)
            {
                extension_query = file_to_verify.find(".exe", 0);
                if (extension_query == std::string::npos)
                {
                    extension_query = file_to_verify.find(".EXE", 0);
                }
                else { return true; }

                if (extension_query != std::string::npos) { return true; }
            }
            else { return true; }
        }
        else { return true; }
    }
    return false;
}
kayleeFrye_onDeck
  • 6,648
  • 5
  • 69
  • 80
  • 2
    Well ... the format of a PE executable is well-known, so failing all else you could parse it yourself. – Harry Johnston Apr 28 '17 at 02:35
  • 3
    Per comment by @HarryJohnston - this may be useful and informative: [Peering Inside the PE: A Tour of the Win32 Portable Executable File Format](https://msdn.microsoft.com/en-us/library/ms809762.aspx) – Phil Brubaker Apr 28 '17 at 03:10
  • 1
    Note, using native tools like **dumpbin**; **depends.exe** on .NET assemblies does not provide the full picture. Both will show only a dependency to `mscoree.dll` and not the multitude of .NET assemblies that make up the full dependency tree that .NET-aware tools such as _DotPeek_ or _Reflector_ are capable of discerning –  Apr 28 '17 at 03:33
  • Thanks, @MickyD. I will have to keep that in mind and test against some .NET testing frameworks to see if any of the more popular ones have their assembly info unavailable via `Assembly.LoadFile(@fileAbsoPath).GetReferencedAssemblies`. @Harry Johnston and @Phil Brubaker, tyvm! I'm reading up on it now. Would you happen to know the legality of using sample apps from these old articles? It looks like `pedump` is still holding strong. I've seen some forks invoke the GPL, but I think that's their license requirement, not Matt Pietrek's. – kayleeFrye_onDeck Apr 28 '17 at 04:35
  • 2
    "Static libraries" are a part of executable already. Also applications can load dynamic libraries on the fly with arbitrary names using `LoadLibrary` function so tools such as dumpbin are not sufficient. To get list of dependencies you need to monitor running process. – user7860670 Apr 28 '17 at 05:39
  • @VTT will that be a problem for this specific use case? It's asking about DLLs used to build with, not something for runtime. – kayleeFrye_onDeck Apr 28 '17 at 06:21
  • I found this on the C boards ( https://cboard.cprogramming.com/windows-programming/82906-problem-while-reading-inport-table.html ). The snippet at the bottom that one of the members helped fix seems to work well enough. I can't get back cool things like .NET Assembly info, but if a common enough .NET DLL is detected in that initial query, I can follow up with a slim .NET app query like this: https://stackoverflow.com/a/43669672/3543437 – kayleeFrye_onDeck Apr 28 '17 at 06:27
  • 1
    *"It's asking about DLLs used to build with, not something for runtime."* - That's a fairly arbitrary limitation. From your question it seems like you have to care about Qt. Qt uses run-time dynamic linking. You won't see those dependencies when looking at the import tables, and yet, those modules are required for any Qt application to get off the ground (e.g. `platforms\qwindows.dll`). – IInspectable Apr 28 '17 at 21:15
  • @IInspectable I must have mistakenly assumed `dumpbin` was getting this info from the Import Address Table. How does `dumpbin` get the info as shown from the output above? That's whatever it is that I need to do -- find whatever way dumpbin gets this info, and either do queries via known relative addresses, or regexes for DLL name patterns. – kayleeFrye_onDeck Apr 28 '17 at 22:38
  • 1
    `dumpbin` parses the import tables. That's the easy part. Those are dependencies used for [load-time dynamic linking](https://msdn.microsoft.com/en-us/library/windows/desktop/ms684184.aspx). Applications can also implement [run-time dynamic linking](https://msdn.microsoft.com/en-us/library/windows/desktop/ms685090.aspx), i.e. by calling `LoadLibrary`/`GetProcAddress`. Those imports aren't reflected in the static import table, but often also required for execution. You would need to monitor a process to find those. And ensure 100% code coverage in your monitoring for reliable results. – IInspectable Apr 28 '17 at 23:10
  • Aha! So that's where the special sauce is :) The _static_ import table... I find voodoo programming to be completely hateful and prone to regret later, so I'll read up more on this. The original PE RE article from 94' is solid enough, but relies on me knowing some axioms and concepts mentioned that I'm not formally acquainted with. I'll see if I can spot something more visual and modern in the meantime, but you've given me a great place to dig in, @IInspectable! – kayleeFrye_onDeck Apr 29 '17 at 00:38
  • It looks like some terminology changes have happened since the '94 article. `.idata` section is no longer equivalent to the import table, as referred to in the PECOFF PDF. You can have an import table and no `.idata` section. Check `Calculator.exe`'s true EXE with `dumpbin` for an example. Still working on this when I can. Been able to traverse a lot of the PECOFF so far when just loading the file as a char array and following offsets returned from the STRUCT pointers of various parts. These DLL names are still on my hit-list. The no-idata section threw me off when I thought I was done. – kayleeFrye_onDeck May 22 '17 at 00:09

1 Answers1

2

Non-optimized POC code, tested against x86 and x64

Uses mostly pointer-math instead of accessors, and uses very little from Windows.h and winnt.h-- everything should be included with the Visual C++ Redists installed for 2017. If you want an earlier version, you'll have to remove the Visual C++ 2017 features, which are not much to speak of beyond file-system helpers.

//******************************************************************************
// Headers
#include "Windows.h"

#include <iostream>
#include <sstream>
#include <fstream>
#include <string>
#include <vector>

#include <experimental\filesystem>

//******************************************************************************
// Namespaces
namespace fs = std::experimental::filesystem;

//******************************************************************************
//FUNCTION DECLARATIONS
bool verify_image_file(std::string);
std::vector<char> read_all_bytes(const char* file);
std::vector<std::string> parse_pe_import_table_names(std::string file);

//******************************************************************************
// Constants
//        LABEL                                                HEX            DEC
//
const WORD MAGIC_NUM_32BIT          = static_cast<const WORD>(0x10b);     // 267
const WORD MAGIC_NUM_64BIT          = static_cast<const WORD>(0x20b);     // 523
const int IMG_SIGNATURE_OFFSET      = static_cast<const int>(0x3c);       // 60
const int IMPORT_TABLE_OFFSET_32    = static_cast<const int>(0x68);       // 104
const int IMPORT_TABLE_OFFSET_64    = static_cast<const int>(0x78);       // 120
const int IMG_SIGNATURE_SIZE        = static_cast<const int>(0x4);        // 4
const int OPT_HEADER_OFFSET_32      = static_cast<const int>(0x1c);       // 28
const int OPT_HEADER_OFFSET_64      = static_cast<const int>(0x18);       // 24
const int DATA_DIR_OFFSET_32        = static_cast<const int>(0x60);       // 96
const int DATA_DIR_OFFSET_64        = static_cast<const int>(0x70);       // 112
const int DATA_IAT_OFFSET_64        = static_cast<const int>(0xD0);       // 208
const int DATA_IAT_OFFSET_32        = static_cast<const int>(0xC0);       // 192
const int SZ_OPT_HEADER_OFFSET      = static_cast<const int>(0x10);       // 16
const int RVA_AMOUNT_OFFSET_64      = static_cast<const int>(0x6c);       // 108
const int RVA_AMOUNT_OFFSET_32      = static_cast<const int>(0x5c);       // 92
const char * KNOWN_IMG_SIGNATURE    = static_cast<const char*>("PE\0\0");

//******************************************************************************
// Globals
bool is64Bit = false;
bool is32Bit = false;

//******************************************************************************
// Exceptions
class invalid_parameters        : public std::exception { const char* what() const throw()
{ return "You did not provide the solitary command-line parameter of the EXE or DLL to check.\n"; } };

class invalid_image_file        : public std::exception { const char* what() const throw()
{ return "The file detected was not determined to be an image file based off its extension.\n"; } };

class unexpected_rva_offset     : public std::exception { const char* what() const throw()
{ return "An unexpected value was returned for the RVA to File Offset.\n"; } };

class non_image_magic_number    : public std::exception { const char* what() const throw()
{ return "The PE Optional Header's Magic Number did not indicate the file was an image.\n"; } };

class invalid_pe_signature      : public std::exception { const char* what() const throw()
{ return "The PE Signature was not detected.\n"; } };

//******************************************************************************
// Entry
int main(int argc, char* argv[])
{
    if (argc != 2)throw invalid_parameters();

    const char* param_one = (const char*)argv[1];



    if (!verify_image_file(argv[1])) { throw invalid_image_file(); }

    std::vector<std::string> static_import_dependencies = parse_pe_import_table_names(argv[1]);
    if (static_import_dependencies.size() > 1)
    {
        for (int i = 0; i < (static_import_dependencies.size()-1);i++)
        {
            std::cout << static_import_dependencies[i] << std::endl;
        }
        return 0;
    }
    else return -1;

    return -1;
}

//******************************************************************************
// PE Parser
std::vector<std::string> parse_pe_import_table_names(std::string file)
{
    std::vector<char> bytes = read_all_bytes(file.c_str());
    std::vector<std::string> dependencies;

    DWORD * signature_offset_location = (DWORD*)&bytes[IMG_SIGNATURE_OFFSET];
    char * signature = (char*)&bytes[*signature_offset_location];

    if (*signature != *KNOWN_IMG_SIGNATURE)return dependencies;

    DWORD coff_file_header_offset = *signature_offset_location + IMG_SIGNATURE_SIZE;
    IMAGE_FILE_HEADER* coff_file_header = (IMAGE_FILE_HEADER*)&bytes[coff_file_header_offset];
    DWORD optional_file_header_offset = coff_file_header_offset + sizeof(IMAGE_FILE_HEADER);

    WORD size_of_optional_header_offset = coff_file_header_offset + SZ_OPT_HEADER_OFFSET;
    WORD* size_of_optional_header = (WORD*)&bytes[size_of_optional_header_offset];

    //Magic is a 2-Byte value at offset-zero of the optional file header regardless of 32/64 bit
    WORD* magic_number = (WORD*)&bytes[optional_file_header_offset];

    if (*magic_number == MAGIC_NUM_32BIT)is32Bit = true;
    else if (*magic_number == MAGIC_NUM_64BIT)is64Bit = true;
    else
    {
        std::cerr << "Could not parse magic number for 32 or 64-bit PE-format Image File." << std::endl;
        return dependencies;
    }

    if (is64Bit)
    {
        IMAGE_OPTIONAL_HEADER64 * img_opt_header_64 = (IMAGE_OPTIONAL_HEADER64*)&bytes[optional_file_header_offset];
        IMAGE_DATA_DIRECTORY* import_table_data_dir = (IMAGE_DATA_DIRECTORY*)&bytes[optional_file_header_offset + IMPORT_TABLE_OFFSET_64];
        DWORD* import_table_address = (DWORD*)import_table_data_dir;

        DWORD image_section_header_offset = optional_file_header_offset + coff_file_header->SizeOfOptionalHeader;

        for (int i = 0; i < coff_file_header->NumberOfSections; i++)
        {
            IMAGE_SECTION_HEADER* queried_section_header = (IMAGE_SECTION_HEADER*)&bytes[image_section_header_offset];
            if (*import_table_address >= queried_section_header->VirtualAddress &&
                (*import_table_address < (queried_section_header->VirtualAddress + queried_section_header->SizeOfRawData)))
            {
                DWORD import_table_offset = *import_table_address - queried_section_header->VirtualAddress + queried_section_header->PointerToRawData;
                while(true)
                {
                    IMAGE_IMPORT_DESCRIPTOR* import_table_descriptor = (IMAGE_IMPORT_DESCRIPTOR*)&bytes[import_table_offset];
                    if (import_table_descriptor->OriginalFirstThunk == 0)
                    {
                        break;//Signifies end of IMAGE_IMPORT_DESCRIPTORs
                    }
                    // (VA from data directory _entry_ to Image Import Descriptor's element you want) - VA from section header + section header's PointerToRawData
                    DWORD dependency_name_address = import_table_descriptor->Name;//VA not RVA; ABSOLUTE
                    DWORD name_offset = dependency_name_address - queried_section_header->VirtualAddress + queried_section_header->PointerToRawData;
                    char * dependency_name = (char *)&bytes[name_offset];
                    dependencies.push_back((std::string)dependency_name);
                    import_table_offset = import_table_offset + sizeof(IMAGE_IMPORT_DESCRIPTOR);
                }
            }
            image_section_header_offset = image_section_header_offset + sizeof(IMAGE_SECTION_HEADER);
        }
    }
    else//32-bit behavior
    {
        IMAGE_OPTIONAL_HEADER32 * img_opt_header_32 = (IMAGE_OPTIONAL_HEADER32*)&bytes[optional_file_header_offset];
        IMAGE_DATA_DIRECTORY* import_table_data_dir = (IMAGE_DATA_DIRECTORY*)&bytes[optional_file_header_offset + IMPORT_TABLE_OFFSET_32];
        DWORD* import_table_address = (DWORD*)import_table_data_dir;

        DWORD image_section_header_offset = optional_file_header_offset + coff_file_header->SizeOfOptionalHeader;

        for (int i = 0; i < coff_file_header->NumberOfSections; i++)
        {
            IMAGE_SECTION_HEADER* queried_section_header = (IMAGE_SECTION_HEADER*)&bytes[image_section_header_offset];
            if (*import_table_address >= queried_section_header->VirtualAddress &&
                (*import_table_address < (queried_section_header->VirtualAddress + queried_section_header->SizeOfRawData)))
            {
                DWORD import_table_offset = *import_table_address - queried_section_header->VirtualAddress + queried_section_header->PointerToRawData;
                while (true)
                {
                    IMAGE_IMPORT_DESCRIPTOR* import_table_descriptor = (IMAGE_IMPORT_DESCRIPTOR*)&bytes[import_table_offset];
                    if (import_table_descriptor->OriginalFirstThunk == 0)
                    {
                        break;//Signifies end of IMAGE_IMPORT_DESCRIPTORs
                    }
                    // (VA from data directory _entry_ to Image Import Descriptor's element you want) - VA from section header + section header's PointerToRawData
                    DWORD dependency_name_address = import_table_descriptor->Name;//VA not RVA; ABSOLUTE
                    DWORD name_offset = dependency_name_address - queried_section_header->VirtualAddress + queried_section_header->PointerToRawData;
                    char * dependency_name = (char *)&bytes[name_offset];
                    dependencies.push_back((std::string)dependency_name);
                    import_table_offset = import_table_offset + sizeof(IMAGE_IMPORT_DESCRIPTOR);
                }
            }
            image_section_header_offset = image_section_header_offset + sizeof(IMAGE_SECTION_HEADER);
        }
    }

    return dependencies;
}

//******************************************************************************
// File Reader
std::vector<char> read_all_bytes(const char* filename)
{
    std::ifstream ifs(filename, std::ios::binary | std::ios::ate);
    std::ifstream::pos_type pos = ifs.tellg();

    std::vector<char> result(pos);
    ifs.seekg(0, std::ios::beg);
    ifs.read(&result[0], pos);

    return result;
}

//******************************************************************************
// IMAGE-TYPE FILE VERIFIER
bool verify_image_file(std::string file_to_verify)
{
    if (fs::exists(file_to_verify))
    {
        size_t extension_query = file_to_verify.find(".dll", 0);
        if (extension_query == std::string::npos)
        {
            extension_query = file_to_verify.find(".DLL", 0);
            if (extension_query == std::string::npos)
            {
                extension_query = file_to_verify.find(".exe", 0);
                if (extension_query == std::string::npos)
                {
                    extension_query = file_to_verify.find(".EXE", 0);
                }
                else { return true; }

                if (extension_query != std::string::npos) { return true; }
            }
            else { return true; }
        }
        else { return true; }
    }
    return false;
}
kayleeFrye_onDeck
  • 6,648
  • 5
  • 69
  • 80