Click here to Skip to main content
15,879,348 members
Articles / Programming Languages / C++
Tip/Trick

Checking for exported symbols/functions in a DLL without loading it

Rate me:
Please Sign up or sign in to vote.
4.86/5 (22 votes)
19 Dec 2010CPOL 55.6K   22   19
You can find out if a DLL exports some symbols/functions without calling LoadLibrary() on it
The tip/trick title speaks for itself. I give you a simple function that takes a file name and a list of symbols/function names, and it returns true if the specified file is a DLL that exports all of the specified symbols. Detecting exports by ordinal numbers are not supported because I wasn't in need of that, but if someone needs it and I have the time, then I can put it in easily because it's easier to check than the exports by names. All this is done without loading the DLL, thus avoiding execution of a possibly dangerous DllMain() of an unwanted DLL in your process. It works with both 32 and 64 bit binaries. I used it in a plugin system to safely detect some plugins of older versions in a program. Use this code only when reasonable because checking all DLLs with this before all LoadLibrary() calls can slow down your plugin initialization code in a system with lot of plugins (eg.: GIMP)!!!
That's all folks! :D

stdafx.h:
C++
#pragma once

#include <windows.h>
#include <winnt.h> // must be after windows.h
#include <vector>
#include <set>
#include <string>
#include <cassert>


stdafx.cpp:
C++
#include "stdafx.h"


CheckForDllExportedSymbols.h:
C++
#ifndef __CHECK_FOR_DLL_EXPORTED_SYMBOLS_H__
#define __CHECK_FOR_DLL_EXPORTED_SYMBOLS_H__
#pragma once

enum ECFESResult
{
    eCFES_OK,
    eCFES_MissingFunctions,
    eCFES_ErrorOpeningFile,
    eCFES_ErrorReadingFile,
    eCFES_InvalidDosHeader,
    eCFES_InvalidNTHeader,
    eCFES_NotDLL,
    eCFES_DLLStructureError,
};

// Checks if the specified file is a real DLL that exports all 
// the specified functions.
ECFESResult CheckForExportedSymbols(LPCTSTR dll_path, 
    const char* symbols[], int symbol_count);

#endif


CheckForDllExportedSymbols.cpp:
C++
#include "stdafx.h"
#include "CheckForDllExportedSymbols.h"

struct SRVAToFileOffset : public std::vector<IMAGE_SECTION_HEADER>
{
    DWORD operator()(DWORD RVA) const
    {
        for (const_iterator it=begin(), eit = end(); it != eit; ++it)
        {
            if (RVA >= it->VirtualAddress && RVA < it->VirtualAddress + 
    	        it->Misc.VirtualSize)
            return RVA - it->VirtualAddress + it->PointerToRawData;
        }

        return RVA;
    }
};

//-----------------------------------------------------------------------------

class CCFESFile
{
    public:
    CCFESFile() : m_hFile(INVALID_HANDLE_VALUE) {}

    ~CCFESFile()
    {
        Close();
    }

    bool OpenForRead(LPCTSTR file_path)
    {
        Close();
        m_hFile = CreateFile(file_path, GENERIC_READ, FILE_SHARE_READ, NULL, 
            OPEN_EXISTING, 0, NULL);
        return m_hFile != INVALID_HANDLE_VALUE;
    }

    void Close()
    {
        if (m_hFile != INVALID_HANDLE_VALUE)
        {
            ::CloseHandle(m_hFile);
            m_hFile = INVALID_HANDLE_VALUE;
        }
    }

    bool Read(void* buffer, DWORD bytes_to_read, 
        DWORD file_offset = 0xFFFFFFFF, DWORD* bytes_read = NULL)
    {
        assert(m_hFile != INVALID_HANDLE_VALUE);

        if (m_hFile == INVALID_HANDLE_VALUE)
            return false;

        if (file_offset < 0xFFFFFFFF)
        {
            DWORD new_file_offset = SetFilePointer(m_hFile, (LONG)file_offset, 
                NULL, FILE_BEGIN);

            if (new_file_offset != file_offset)
                return false;
        }

        DWORD read;

        if (!ReadFile(m_hFile, buffer, bytes_to_read, &read, NULL))
            return false;

        if (!bytes_read)
            return read == bytes_to_read;

        *bytes_read = read;

        return true;
    }

    private:
    HANDLE m_hFile;
};

//-----------------------------------------------------------------------------

class CCheckForExportedSymbols
{
public:
    CCheckForExportedSymbols();
    ECFESResult CheckForExportedSymbols(LPCTSTR dll_path, 
        const char* symbols[], int symbol_count);
    
private:
    bool ReadHeaders(int symbol_count);
    bool CheckSymbols(const char* symbols[], int symbol_count);
    bool Error(ECFESResult res);
    bool Read(void* buffer, DWORD bytes_to_read, 
        DWORD file_offset = 0xFFFFFFFF);
        
private:
    ECFESResult m_Result;
    CCFESFile m_File;
    IMAGE_DATA_DIRECTORY m_ExportDirLocation; // RVA and size
    SRVAToFileOffset m_RVAToFileOffset;
};

CCheckForExportedSymbols::CCheckForExportedSymbols()
: m_Result(eCFES_OK)
{
    memset(&m_ExportDirLocation, 0, sizeof(m_ExportDirLocation));
}

bool CCheckForExportedSymbols::ReadHeaders(int symbol_count)
{
    IMAGE_DOS_HEADER dos_hdr;

    if (!Read(&dos_hdr, sizeof(dos_hdr)))
        return false;

    if (dos_hdr.e_magic != IMAGE_DOS_SIGNATURE)
        return Error(eCFES_InvalidDosHeader);

    IMAGE_NT_HEADERS64 hdr;

    if (!Read(&hdr, sizeof(hdr) 
        - sizeof(hdr.OptionalHeader) + sizeof(WORD), dos_hdr.e_lfanew))
        return false;

    if (hdr.Signature != IMAGE_NT_SIGNATURE)
        return Error(eCFES_InvalidNTHeader);

    if (hdr.OptionalHeader.Magic != 
        IMAGE_NT_OPTIONAL_HDR32_MAGIC && 
        hdr.OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR64_MAGIC)
        return Error(eCFES_InvalidNTHeader);

    if ((hdr.FileHeader.Characteristics & IMAGE_FILE_DLL) == 0)
        return Error(eCFES_NotDLL);

    // In case of zero function names we only check for the validity of the DLL
    if (symbol_count <= 0)
        return Error(eCFES_OK);

    DWORD opt_hdr_size = min((DWORD)sizeof(hdr.OptionalHeader), 
        (DWORD)hdr.FileHeader.SizeOfOptionalHeader);

    if (hdr.OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC)
    {
        // 32 bit binary
        if (opt_hdr_size < offsetof(IMAGE_OPTIONAL_HEADER32, 
            DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT+1]))
            return Error(eCFES_MissingFunctions);

        if (!Read((WORD*)&hdr.OptionalHeader.Magic + 1, 
            opt_hdr_size - sizeof(WORD)))
            return false;

        IMAGE_NT_HEADERS32 &hdr32 = *(IMAGE_NT_HEADERS32*) & hdr;

        if (hdr32.OptionalHeader.NumberOfRvaAndSizes 
            < IMAGE_DIRECTORY_ENTRY_EXPORT + 1)
            return Error(eCFES_MissingFunctions);

        m_ExportDirLocation = 
            hdr32.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
    }
    else
    {
        // 64 bit binary
        if (opt_hdr_size < offsetof(IMAGE_OPTIONAL_HEADER64, 
            DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT+1]))
            return Error(eCFES_MissingFunctions);

        if (!Read((WORD*)&hdr.OptionalHeader.Magic + 1, 
            opt_hdr_size - sizeof(WORD)))
            return false;

        if (hdr.OptionalHeader.NumberOfRvaAndSizes 
            < IMAGE_DIRECTORY_ENTRY_EXPORT + 1)
            return Error(eCFES_MissingFunctions);

        m_ExportDirLocation = 
            hdr.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
    }

    // reading the section headers in order to be able to calculate file 
    // offsets from RVAs
    if (hdr.FileHeader.NumberOfSections)
    {
        m_RVAToFileOffset.resize(hdr.FileHeader.NumberOfSections);
        DWORD section_header_file_offset = dos_hdr.e_lfanew + sizeof(
            hdr.Signature) + sizeof(hdr.FileHeader) 
            + hdr.FileHeader.SizeOfOptionalHeader;

        if (!Read(&m_RVAToFileOffset[0], hdr.FileHeader.NumberOfSections 
            * sizeof(IMAGE_SECTION_HEADER), section_header_file_offset))
            return false;
    }

    return true;
}

bool CCheckForExportedSymbols::CheckSymbols(const char* symbols[], 
    int symbol_count)
{
    // finding out the length of the longest function name that we are 
    // looking for

    std::set
        <DWORD> symbol_lengths;

    DWORD max_funcname_len = 0;

    for (int i = 0; i < symbol_count; ++i)
    {
        DWORD len = (DWORD)lstrlenA(symbols[i]);
        symbol_lengths.insert(len);
        max_funcname_len = max(max_funcname_len, len);
    }

    // reading the export directory
    assert(m_ExportDirLocation.Size >= sizeof(IMAGE_EXPORT_DIRECTORY));

    IMAGE_EXPORT_DIRECTORY export_dir;

    if (!Read(&export_dir, sizeof(export_dir), m_RVAToFileOffset(
        m_ExportDirLocation.VirtualAddress)))
        return false;

    if (export_dir.NumberOfNames < (DWORD)symbol_count)
        return Error(eCFES_MissingFunctions);

    // Collecting the function names exported by the DLL. We omit function 
    // names whose length doesn't match
    // the length of any of the function names that we are searching for.
    std::vector<DWORD> exported_symbol_rvas;

    exported_symbol_rvas.resize(export_dir.NumberOfNames);

    if (!Read(&exported_symbol_rvas[0], sizeof(DWORD)*export_dir.NumberOfNames,
        m_RVAToFileOffset(export_dir.AddressOfNames)))
        return false;

    std::set
        <std::string> exported_symbols;

    std::vector<char> buf(max_funcname_len + 1, 0);

    for (std::vector<DWORD>::const_iterator it = exported_symbol_rvas.begin(),
        eit = exported_symbol_rvas.end(); it != eit; ++it)
    {
        DWORD read;

        if (!m_File.Read(&buf[0], max_funcname_len + 1, m_RVAToFileOffset(*it),
            &read))
            return Error(eCFES_ErrorReadingFile);

        DWORD buf_size = min(max_funcname_len + 1, read);

        DWORD len = 0;

        for (DWORD i = 0; i < buf_size; ++i)
        {
            if (!buf[i])
                break;

            ++len;
        }

        if (len >= buf_size)
            continue;

        if (!symbol_lengths.count(len))
            continue;

        exported_symbols.insert(&buf[0]);
    }

    if ((int)exported_symbols.size() < symbol_count)
        return Error(eCFES_MissingFunctions);

    for (int i = 0; i < symbol_count; ++i)
    {
        if (!exported_symbols.count(symbols[i]))
            return Error(eCFES_MissingFunctions);
    }

    return Error(eCFES_OK);
}

bool CCheckForExportedSymbols::Error(ECFESResult res)
{
    m_Result = res;
    return false;
}

bool CCheckForExportedSymbols::Read(void* buffer, DWORD bytes_to_read, 
    DWORD file_offset)
{
    DWORD read;

    if (!m_File.Read(buffer, bytes_to_read, file_offset, &read))
        return Error(eCFES_ErrorReadingFile);

    if (read != bytes_to_read)
        return Error(eCFES_DLLStructureError);

    return true;
}

ECFESResult CCheckForExportedSymbols::CheckForExportedSymbols(LPCTSTR dll_path,
    const char* symbols[], int symbol_count)
{
    if (!m_File.OpenForRead(dll_path))
        return eCFES_ErrorOpeningFile;

    if (!ReadHeaders(symbol_count))
        return m_Result;

    if (!CheckSymbols(symbols, symbol_count))
        return m_Result;

    return eCFES_OK;
}

//-----------------------------------------------------------------------------

ECFESResult CheckForExportedSymbols(LPCTSTR dll_path, const char* symbols[], 
    int symbol_count)
{
    CCheckForExportedSymbols CFES;
    return CFES.CheckForExportedSymbols(dll_path, symbols, symbol_count);
}


Test.cpp:
C++
#include "stdafx.h"
#include "CheckForDllExportedSymbols.h"
#include <cstdio>
int main()
{
    static const TCHAR DLL[] = TEXT("e:\\PluginDLL.dll");
    static const char* EXPORTED_SYMBOLS[] = { "fnPluginDLL" };

    printf("%d\n", CheckForExportedSymbols(DLL,
        EXPORTED_SYMBOLS,
        sizeof(EXPORTED_SYMBOLS) / sizeof(EXPORTED_SYMBOLS[0])));

    return 0;
}

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United Kingdom United Kingdom
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralMy vote of 5 Pin
gndnet8-Aug-12 1:33
gndnet8-Aug-12 1:33 
GeneralReason for my vote of 5 Very nicely done! I using the same t... Pin
nv313-Dec-11 3:40
nv313-Dec-11 3:40 
GeneralReason for my vote of 5 nice article Pin
erewrwerq20-Dec-10 16:28
erewrwerq20-Dec-10 16:28 
GeneralReason for my vote of 5 new information for me and its usefu... Pin
mutpan13-Dec-10 18:56
mutpan13-Dec-10 18:56 
General@SledgeHammer01: Actually you pointed out something that was... Pin
pasztorpisti6-Dec-10 22:37
pasztorpisti6-Dec-10 22:37 
GeneralReason for my vote of 5 It will be great if this peace of co... Pin
Member 43208446-Dec-10 19:16
Member 43208446-Dec-10 19:16 
General@pasztorpisti: Fair enough. That is something I did not thin... Pin
SledgeHammer016-Dec-10 19:02
SledgeHammer016-Dec-10 19:02 
GeneralReason for my vote of 5 5 for taking the time to write an ar... Pin
DRLaverdure6-Dec-10 14:50
DRLaverdure6-Dec-10 14:50 
GeneralI used this to detect older versions of some plugins in a sy... Pin
pasztorpisti6-Dec-10 14:33
pasztorpisti6-Dec-10 14:33 
GeneralReason for my vote of 2 Wouldn't LoadLibrary() and GetProces... Pin
SledgeHammer016-Dec-10 13:38
SledgeHammer016-Dec-10 13:38 
GeneralLoadLibraryEx Pin
waleri11-Feb-11 1:31
waleri11-Feb-11 1:31 
GeneralRe: LoadLibraryEx Pin
pasztorpisti11-Feb-11 8:30
pasztorpisti11-Feb-11 8:30 
GeneralAutomatic Finder for DLL contents(methods) Pin
Member 43208446-Dec-10 19:11
Member 43208446-Dec-10 19:11 
GeneralRe: Automatic Finder for DLL contents(methods) Pin
pasztorpisti6-Dec-10 22:58
pasztorpisti6-Dec-10 22:58 
GeneralRe: Automatic Finder for DLL contents(methods) Pin
Member 43208447-Dec-10 2:49
Member 43208447-Dec-10 2:49 
GeneralRe: Automatic Finder for DLL contents(methods) Pin
JackDingler15-Mar-12 6:23
JackDingler15-Mar-12 6:23 
QuestionRe: Automatic Finder for DLL contents(methods) Pin
Member 432084415-Mar-12 9:30
Member 432084415-Mar-12 9:30 
AnswerRe: Automatic Finder for DLL contents(methods) Pin
JackDingler15-Mar-12 9:39
JackDingler15-Mar-12 9:39 
GeneralThanks for the code! Pin
T800G6-Dec-10 6:51
T800G6-Dec-10 6:51 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.