Получение порядкового с имени функции программно

Каков самый легкий путь в C++ для получения ординала экспортируемой функции dll, данной ее имя? (Поиск пути, который не делает invlove парсинг IATs самостоятельно...),

8
задан Cœur 21 January 2019 в 09:35
поделиться

2 ответа

Я не могу придумать никакого ужасно простого способа сделать то, что вы хотите. У вас есть по крайней мере пара вариантов, которые я вижу:

  1. Пойти по пути, указанному Марком, хотя он кажется немного неуклюжим и может иметь некоторые недостатки.
  2. Использовать таблицу указателей имен (NPT) и таблицу ординалов экспорта (EOT) для поиска ординалов экспорта.

Основная проблема первого варианта в том, что вы не знаете, сколько ординалов нужно перебрать (в порядковых номерах могут быть пробелы, поэтому рассчитывать на то, что GetProcAddress вернет NULL, чтобы сообщить о конце, не получится). Это также несколько неэффективно, поскольку требует многократного выполнения множества вызовов Win32 и, по сути, сводится к линейному поиску в таблице адресов экспорта. Довольно неэлегантно, действительно.

В качестве альтернативы можно выполнить поиск в NPT и использовать полученный индекс в EOT для получения порядкового номера. Это более элегантный подход, поскольку он позволяет получить порядковый номер самым прямым способом (фактически это тот же метод, который использует динамический компоновщик для преобразования экспортных имен в их адреса). Кроме того, поскольку NPT лексически отсортирован, можно выполнять бинарный поиск, который, очевидно, предпочтительнее линейного поиска другого метода. Фактически, один вызов GetProcOrdinal, реализованный с помощью этого метода, должен быть немного быстрее, чем один вызов GetProcAddress. Возможно, более важно то, что этот метод не зависит от каких-либо неизвестных (т.е. количества ординалов). Недостатком этого метода является то, что он не так прост, как другой метод.

Вы можете использовать библиотеку Debug Help Library, чтобы избежать разбора образа PE-файла (так я и поступил вначале), но оказалось, что разобрать необходимые части PE-образа не так уж и сложно. Я думаю, что отсутствие зависимости от библиотеки Debug Help Library стоит минимальных дополнительных усилий, необходимых для разбора заголовков PE-образа.

Приступая к делу, вот пример реализации на C:

#include <stdio.h>

#include "windows.h"

/// Efficiently searches a module's name pointer table (NPT) for the named
/// procedure.
///
/// @param[in] npt     Address of the NPT to search.
///
/// @param[in] size    Number of entries in the NPT.
///
/// @param[in] base    Base address of the module containing the NPT. This is
///                    used to resolve addresses in the NPT (which are relative
///                    to the module's base address).
///
/// @param[in] proc    String containing the name of the procedure to search
///                    for.
///
/// @return    Returns the index into the NPT of the entry matching the named
///            procedure. If no such matching entry exists, the function returns
///            -1.
///
DWORD FindNptProc (PDWORD npt, DWORD size, PBYTE base, LPCSTR proc)
{
    INT   cmp;
    DWORD max;
    DWORD mid;
    DWORD min;

    min = 0;
    max = size - 1;

    while (min <= max) {
        mid = (min + max) >> 1;
        cmp = strcmp((LPCSTR)(npt[mid] + base), proc);
        if (cmp < 0) {
            min = mid + 1;
        } else if (cmp > 0) {
            max = mid - 1;
        } else {
            return mid;
        }
    }

    return -1;
}

/// Gets a pointer to a module's export directory table (EDT).
///
/// @param[in] module    Handle to the module (as returned by GetModuleHandle).
///
/// @return    Returns a pointer to the module's EDT. If there is an error (e.g.
///            if the module handle is invalid or the module has no EDT) the
///            function will return NULL.
///
PIMAGE_EXPORT_DIRECTORY GetExportDirectoryTable (HMODULE module)
{
    PBYTE                   base; // base address of module
    PIMAGE_FILE_HEADER      cfh;  // COFF file header
    PIMAGE_EXPORT_DIRECTORY edt;  // export directory table (EDT)
    DWORD                   rva;  // relative virtual address of EDT
    PIMAGE_DOS_HEADER       mds;  // MS-DOS stub
    PIMAGE_OPTIONAL_HEADER  oh;   // so-called "optional" header
    PDWORD                  sig;  // PE signature

    // Start at the base of the module. The MS-DOS stub begins there.
    base = (PBYTE)module;
    mds = (PIMAGE_DOS_HEADER)module;

    // Get the PE signature and verify it.
    sig = (DWORD *)(base + mds->e_lfanew);
    if (IMAGE_NT_SIGNATURE != *sig) {
        // Bad signature -- invalid image or module handle
        return NULL;
    }

    // Get the COFF file header.
    cfh = (PIMAGE_FILE_HEADER)(sig + 1);

    // Get the "optional" header (it's not actually optional for executables).
    oh = (PIMAGE_OPTIONAL_HEADER)(cfh + 1);

    // Finally, get the export directory table.
    if (IMAGE_DIRECTORY_ENTRY_EXPORT >= oh->NumberOfRvaAndSizes) {
        // This image doesn't have an export directory table.
        return NULL;
    }
    rva = oh->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
    edt = (PIMAGE_EXPORT_DIRECTORY)(base + rva);

    return edt;
}

/// Gets the ordinal of an exported procedure.
///
/// @param[in] module    Handle (as returned by GetModuleHandle) of the module
///                      that exports the procedure.
///
/// @param[in] proc      String containing the name of the procedure.
///
/// @return    Returns the procedure's ordinal. If an ordinal for the procedure
///            could not be located (e.g. if the named procedure is not exported
///            by the specified module) then the function will return -1.
///
DWORD GetProcOrdinal (HMODULE module, LPCSTR proc)
{
    PBYTE                   base; // module base address
    PIMAGE_EXPORT_DIRECTORY edt;  // export directory table (EDT)
    PWORD                   eot;  // export ordinal table (EOT)
    DWORD                   i;    // index into NPT and/or EOT
    PDWORD                  npt;  // name pointer table (NPT)

    base = (PBYTE)module;

    // Get the export directory table, from which we can find the name pointer
    // table and export ordinal table.
    edt = GetExportDirectoryTable(module);

    // Get the name pointer table and search it for the named procedure.
    npt = (DWORD *)(base + edt->AddressOfNames);
    i = FindNptProc(npt, edt->NumberOfNames, base, proc);
    if (-1 == i) {
        // The procedure was not found in the module's name pointer table.
        return -1;
    }

    // Get the export ordinal table.
    eot = (WORD *)(base + edt->AddressOfNameOrdinals);

    // Actual ordinal is ordinal from EOT plus "ordinal base" from EDT.
    return eot[i] + edt->Base;
}

int main (int argc, char *argv [])
{
    LPCSTR  procName;
    HMODULE module = NULL;
    LPCSTR  moduleName;
    DWORD   ordinal;

    if (argc != 3) {
        printf("A DLL name and procedure name must be specified\n");
        return EXIT_FAILURE;
    }

    moduleName = argv[1];
    procName   = argv[2];

    if (NULL == LoadLibrary(moduleName)) {
        printf("Could not load library %s\n", moduleName);
        return EXIT_FAILURE;
    }

    module = GetModuleHandle(moduleName);
    if (NULL == module) {
        printf("Couldn't get a handle to %s\n", moduleName);
        return EXIT_FAILURE;
    }

    ordinal = GetProcOrdinal(module, procName);
    if (-1 == ordinal) {
        printf("Could not find ordinal for %s in %s\n", procName, moduleName);
    } else {
        printf("Found %s at ordinal %d\n", procName, ordinal);
    }

    return EXIT_SUCCESS;
}

GetProcOrdinal - это то место, где происходят самые интересные моменты. Код, надеюсь, достаточно понятен; однако, чтобы полностью понять его, может потребоваться немного знаний о формате PE-файлов, в которые я не собираюсь вдаваться здесь (в Интернете есть много информации об этом). FindNptProc - это просто удобная функция, которая выполняет бинарный поиск NPT. GetExportDirectoryTable - еще одна удобная функция, которая анализирует PE-заголовки для поиска таблицы экспортных каталогов.

Приведенный выше код компилируется чисто для меня под Visual Studio 2008 и Windows XP (SP3), но YMMV. Я не очень разбираюсь в Windows*, так что это может быть не самый чистый код с точки зрения переносимости (в плане различных версий Windows). Как обычно, этот код предоставляется "как есть" без каких-либо гарантий ;)

* Да, если вам интересно, я действительно все еще чувствую себя грязным после написания всего этого кода Windows в стиле Microsoft.

14
ответ дан 5 December 2019 в 09:25
поделиться

Уродливым способом было бы запустить системный вызов с командой dumpbin и проанализировать вывод. Но он имеет такую ​​же элегантность, как бык в пресловутой посудной лавке.

dumpbin / export c: \ windows \ system32 \ user32.dll | grep FunctionOfInterest

В противном случае вы могли бы написать простой цикл, вызывающий GetProcAddress с порядковыми номерами (переданными в младших двух байтах параметра имени). Когда указатель функции совпадает с указателем, возвращаемым при передаче фактического имени, все готово.

Вот основная идея без проверки ошибок:

  HANDLE hMod;
  HANDLE byname, byord;
  int ord;

  hMod = LoadLibrary( "user32.dll" );
  byname = GetProcAddress( hMod, "GetWindow" );
  byord = 0;
  ord = 1;
  while ( 1 ) {
     byord = GetProcAddress( hMod, (LPCSTR)ord );
     if ( byord == byname ) {
        printf( "ord = %d\n", ord );
        break;
        }
     ord++;
     }
4
ответ дан 5 December 2019 в 09:25
поделиться
Другие вопросы по тегам:

Похожие вопросы: