Frage Ordnungszahl vom Funktionsnamen programmatisch erhalten


Was ist der einfachste Weg in C ++, eine Ordinalzahl einer exportierten Dll-Funktion zu erhalten, wenn man ihren Namen angibt? (Auf der Suche nach einem Weg, der die IATs selbst nicht analysiert ...)

Vielen Dank, Dan


8
2018-02-16 14:36


Ursprung


Antworten:


Ich kann mir keine schrecklich einfache Art vorstellen, zu tun, was Sie wollen. Sie haben mindestens ein paar Optionen, die ich sehen kann:

  1. Nehmen Sie den Weg von Mark, obwohl es ein bisschen klatschig scheint und einige Mängel haben kann.
  2. Verwenden Sie die Name Pointer Table (NPT) und Export Ordinal Table (EOT), um Export-Ordinalzahlen zu finden.

Das Hauptproblem, das ich bei der ersten Option sehe, ist, dass Sie nicht wissen, wie viele Ordinalzahlen Sie versuchen sollen (es kann Lücken in den Ordnungszahlen geben, also rechnen Sie weiter GetProcAddress NULL zurückgeben, um das Ende zu signalisieren, wird nicht funktionieren). Es ist auch etwas ineffizient, weil es erfordert, ein Menge von Win32-Aufrufe wiederholt und es ergibt sich grundsätzlich eine lineare Suche der Exportadressentabelle. Ziemlich unelegant, in der Tat.

Als Alternative können Sie den NPT durchsuchen und den resultierenden Index in EOT verwenden, um eine Ordinalzahl zu erhalten. Dies ist ein eleganterer Ansatz, da es auf möglichst direkte Weise bei der Ordinalzahl ankommt (es ist tatsächlich die gleiche Methode, die der dynamische Linker verwendet, um Exportnamen in ihre Adressen aufzulösen). Da der NPT lexikalisch sortiert ist, ist es auch möglich, eine binäre Suche durchzuführen, die offensichtlich der linearen Suche der anderen Methode vorzuziehen ist. In der Tat, ein einziger Anruf an GetProcOrdinal implementiert mit dieser Methode sollte etwas schneller als nur sein ein Aufruf GetProcAddress. Noch wichtiger ist, dass dieses Verfahren nicht von irgendwelchen Unbekannten abhängt (d. H. Anzahl von Ordnungszahlen). Der Nachteil dieser Methode ist, dass sie nicht so einfach ist wie die andere Methode.

Sie könnten die Debug-Hilfebibliothek verwenden, um zu vermeiden, dass das Parsing des PE-Dateiabbilds durchgeführt wird (das ist, was ich ursprünglich getan habe), aber es stellt sich heraus, dass das Parsen der erforderlichen Teile des PE-Abbilds nicht so schwierig ist. Ich denke, die Vermeidung der Abhängigkeit von der Debug-Hilfebibliothek ist den minimalen zusätzlichen Aufwand wert, der zum Parsen der PE-Abbild-Header benötigt wird.

Im Folgenden finden Sie eine Beispielimplementierung in 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 ist, wo die interessanten Bits passieren. Der Code ist hoffentlich ziemlich selbsterklärend; jedoch, um völlig Ich verstehe, dass es ein wenig Wissen über das PE-Dateiformat erfordert, auf das ich hier nicht eingehen werde (es gibt viele Informationen darüber im Web). FindNptProc ist einfach eine Komfortfunktion, die die binäre Suche des NPT durchführt. GetExportDirectoryTable ist eine weitere Komfortfunktion, die die PE-Header analysiert, um die Exportverzeichnistabelle zu finden.

Der Code oben kompiliert sauber für mich unter Visual Studio 2008 und Windows XP (SP3), aber YMMV. Ich bin nicht wirklich ein Windows-Typ *, also ist dies möglicherweise nicht der sauberste Code Portabilität (in Bezug auf verschiedene Versionen von Windows). Wie üblich wird dieser Code "wie besehen" ohne jegliche Gewährleistung zur Verfügung gestellt;)

* Ja, falls Sie sich fragen, ich machen fühle mich immer noch irgendwie schmutzig, nachdem ich den Windows-Code im Microsoft-Stil geschrieben habe.


13
2018-02-26 03:43



Ein hässlicher Weg wäre, einen Systemaufruf mit einem dumpbin-Befehl auszuführen und die Ausgabe zu parsen. Aber das hat ungefähr die gleiche Eleganz wie ein Bulle im sprichwörtlichen Porzellanladen.

dumpbin / exportiert c: \ windows \ system32 \ user32.dll | grep FunktionOfInterest

Sonst könnten Sie eine einfache Anrufschleife schreiben GetProcAddress mit ordinals (übergeben in den unteren zwei Bytes des name-Parameters). Wenn der Funktionszeiger mit dem Zeiger übereinstimmt, der zurückgegeben wird, wenn der tatsächliche Name übergeben wird, sind Sie fertig.

Hier ist die Grundidee ohne Fehlerprüfung:

  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
2018-02-16 15:02