AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Zurück Delphi-PRAXiS Tutorials Delphi Handle in Objektnamen konvertieren

Handle in Objektnamen konvertieren

Ein Tutorial von Fridolin Walther · begonnen am 5. Okt 2009 · letzter Beitrag vom 19. Okt 2009
Antwort Antwort
Fridolin Walther
Registriert seit: 11. Mai 2008
Hallo,

ich möchte an dieser Stelle auf ein Problem eingehen, das in dieser oder einer ähnlichen Form meiner Ansicht nach zu den mit häufigsten Fragen in diversen Foren zählt: Wie bekomme ich den Dateinamen aus einem dazugehörigen Dateihandle? Bzw. anders: Wie kann ich den Namen des Objektes ermitteln, auf das ein Handle verweist?

Obwohl die Problemstellung trivial klingt, ist sie im Usermode allein nicht sauber zu lösen. Es existiert zwar mit MSDN-Library durchsuchenNtQueryObject eine mehr oder weniger gut dokumentierte Usermode API um Informationen über ein Handle bzw. dem sich dahinter verborgenen Objekt zu erhalten, allerdings ist die Benutzung der API insbesondere mit dem zur Namensauflösung wichtigen ObjectNameInformation Parameter ausgesprochen heikel und problembehaftet. Generell gilt dieses Problem unter ausschließlicher Verwendung von Usermode Code daher als nicht sauber lösbar.

Aus diesem Grunde stelle ich hier eine Kernel Mode Lösung vor, die mit Hilfe von MSDN-Library durchsuchenObReferenceObjectByHandle, MSDN-Library durchsuchenObQueryNameString und MSDN-Library durchsuchen ObDereferenceObject eine saubere Lösung des Problems ermöglicht, ohne dabei Gefahr zu laufen in irgendwelche Locks zu laufen.

Das Herzstück stellt dabei ein kleiner Treiber dar (h2on.sys -> Handle To Object Name). Der vollständige Code des Treibers kann dem Attachment entnommen werden. Ebenfalls enthalten sind 2 vorkompilierte Treiber die mit dem XP x86 DDK (builds\x86) bzw. dem Windows 2003 x64 DDK (builds\x64) compiliert wurden. Um den x64 Treiber aber effektiv nutzen zu können, ist der Besitz eines Code Signing Zertifikats notwendig. Der Treiber ist übrigens in der Lage auf x64 Systemen sowohl Handles von 32bit als auch von 64bit Prozessen aufzulösen. Ebenfalls steht die Auflösung sowohl für 32bit als auch für 64bit Prozesse zur Verfügung. Der Treiber ist also vollständig bitdepth agnostic.

Nachfolgend die Kernel Mode Routine, die das übergebene Handle hObject des Prozesses ulProcessID in den dazugehörigen Objektnamen auflöst inkl. einiger Erklärungen:
Code:
NTSTATUS
H2ONGetObjectNameFromHandle (
    __in_opt       ULONG  ulProcessId,
    __in           HANDLE hObject,
    __inout_opt    PWCHAR objName,
    __in           ULONG  ulBuffSize,
    __out          PULONG pulBuffSizeRequired
    )
{
    NTSTATUS                   ntStatus           = STATUS_SUCCESS;
    PEPROCESS                  pProcess           = NULL;
    KAPC_STATE                 apcState           = {0,};
    BOOLEAN                    bAttached          = FALSE;
    HANDLE                     hProcessId         = 0;
    PVOID                      pObject            = NULL;
    ULONG                      ulInfoBuffSize     = 0;
    ULONG                      ulInfoBuffSizeReq  = 0;
    POBJECT_NAME_INFORMATION   pNameInfo          = NULL;

    if (!hObject)
    {
        return STATUS_INVALID_PARAMETER_2;
    }
    if (!pulBuffSizeRequired)
    {
        return STATUS_INVALID_PARAMETER_5;
    }

    *pulBuffSizeRequired = 0;

    if (ulProcessId)
    {
        /* Der Caller hat eine Process ID angegeben. Aus diesem Grunde müssen wir uns den entsprechenden EPROCESS Pointer besorgen. */

        /*
         * Microsoft hat einer etwas merkwürdige Art mit Process IDs umzugehen. Sie möchten sie zwar grundsätzlich als HANDLE übergeben
         * haben, liefern sie aber stets als ULONG zurück. Um uns hässliche Casts zu ersparen, erstellen wir lieber eine temporäre Variable
         * die die Process ID als HANDLE speichert.
         */

        *((PULONG)(&hProcessId)) = ulProcessId;
       
        ntStatus = PsLookupProcessByProcessId (hProcessId, &pProcess);
        if (!NT_SUCCESS (ntStatus))
        {
            goto cleanup;
        }
    }
    else
    {
        pProcess = PsGetCurrentProcess ();
    }

    /* Wir müssen uns nur mit der übergebenen Prozess ID verbinden, wenn dies nicht bereits unser aktueller Prozess ist. */

    if (PsGetCurrentProcess () != pProcess)
    {
        /*
         * Und da wir in diesem Fall kein Handle unseres eigenen Prozesses auflösen wollen, verbinden wir uns mit dem
         * gewünschten Prozess um unsere Magie wirken zu lassen :).
         */

        KeStackAttachProcess (pProcess, &apcState);
        bAttached = TRUE;
    }

    /* 
     * Jetzt, da wir uns im korrekten Prozesskontext befinden, können wir das Handle endlich auflösen und holen uns eine
     * Referenz zum gewünschten Objekt.
     */
    ntStatus = ObReferenceObjectByHandle (hObject,
                                          0,
                                          NULL,
                                          KernelMode,
                                          &pObject,
                                          NULL);
    if (!NT_SUCCESS (ntStatus))
    {
        goto cleanup;
    }

    /* 
     * Da wir nicht genau wissen wie lang der ObjectName sein wird, raten wir einfach und erstellen vorerst einen Buffer
     * unserer Standardgröße.
     */

    ulInfoBuffSize = H2ON_DEFAULT_BUFF_SIZE;

    do
    {
        pNameInfo = ExAllocatePoolWithTag (PagedPool,
                                           ulInfoBuffSize,
                                           H2ON_TAG);
       
        if (!pNameInfo)
        {
            /* Speicher ist aus wie es scheint, in dem Fall gibts einen entsprechenden Fehlercode zurück und es wird aufgeräumt. */
            ntStatus = STATUS_INSUFFICIENT_RESOURCES;
            goto cleanup;
        }

        /* Jetzt versuchen wir uns den Namen des referenzierten Objects zu holen */
        ntStatus = ObQueryNameString (pObject,
                                      pNameInfo,
                                      ulInfoBuffSize,
                                      &ulInfoBuffSizeReq);

        if (ntStatus == STATUS_INFO_LENGTH_MISMATCH)
        {
            /* Falls unser Buffer zu klein war, geben wir den alten Buffer frei und setzen die korrekte Größe für den Buffer. */
            ExFreePoolWithTag (pNameInfo, H2ON_TAG);
            pNameInfo = NULL;
            ulInfoBuffSize = ulInfoBuffSizeReq;
        }
    /* Und das Ganze wird wiederholt, bis die Größe passt :) */
    } while (STATUS_INFO_LENGTH_MISMATCH == ntStatus);

    /* Eine kleine Überprüfung des Rückgabewerts von ObQueryNameString bringt Gewissheit, das wir den korrekten Namen jetzt haben. */
    if (!NT_SUCCESS (ntStatus))
    {
        goto cleanup;
    }

    /* Also gehts los damit den Namen in den Usermode zu schaffen ... zuerst mal schauen ob da genug Platz ist. */
    if ((pNameInfo->Name.Length + sizeof (WCHAR)) > ulBuffSize)
    {
        /* Was in diesem Falle leider nicht der Fall ist. Fehler ausspucken und aufräumen. */
        ntStatus = STATUS_BUFFER_TOO_SMALL;
        *pulBuffSizeRequired = pNameInfo->Name.Length;
        goto cleanup;
    }
    else
    {
        /* Ist in diesem Falle vorhanden, sehr schön. Also wird rüberkopiert. */
        if (objName)
        {
            RtlCopyMemory (objName, pNameInfo->Name.Buffer, pNameInfo->Name.Length);
            RtlCopyMemory (((PUCHAR)objName) + pNameInfo->Name.Length, L"", sizeof (WCHAR));
        }
    }

    /* Und zu guter letzt wird aufgeräumt ... */
cleanup:
    if (pNameInfo)
    {
        ExFreePoolWithTag (pNameInfo, H2ON_TAG);
    }
    if (pObject)
    {
        ObDereferenceObject (pObject);
    }
    if (bAttached)
    {
        KeUnstackDetachProcess (&apcState);
    }
   
    if (pProcess && ulProcessId)
    {
        ObDereferenceObject (pProcess);
    }
    return ntStatus;
}
Nun ist die Kernel Mode Implementierung natürlich nur eine Hälfte der Medallie. Irgendwie möchte man als Programmierer jetzt natürlich auch auf den Treiber zugreifen um seine Handles aufzulösen. Entsprechend kommt der Treiber kombiniert mit einer kleinen Interface Unit (include\h2on.pas), die die Treiber Funktionalität in eine simple API kapselt:
Delphi-Quellcode:
function GetObjectNameFromHandle(dwProcessId: DWORD; hObject: THandle;
  objName: PWideChar; dwBuffSize: DWORD; pdwBuffSizeRequired: PDWORD): DWORD;

{
dwProcessId:
Die Prozess ID des Prozesses dem das aufzulösende Handle gehört. Kann Null sein, wenn ein Handle des eigenen Prozesses aufgelöst werden soll.

hObject:
Das eigentliche Handle.

objName:
Ein Pointer auf einen Buffer, der den aufgelösten Objektnamen erhält.

dwBuffSize:
Die Größe des mit objName übergebenen Buffers in Bytes.

pdwBuffSizeRequired:
Pointer auf ein DWORD, daß die Größe des Objektnamen zurückliefert, falls der mit objName übergebene Buffer zu klein sein sollte.

Rückgabewert:
Falls die Operation erfolgreich gewesen sein sollte, wird ERROR_SUCCESS zurück geliefert. Ansonsten ein entsprechender Fehlercode.
}
Dazu gesellen sich 2 Helferfunktionen um den Treiber im System zu installieren bzw. zu deinstallieren:
Delphi-Quellcode:
function InstallAndStartDriver(): Boolean;
function UninstallAndStopDriver(): Boolean;
Beide Funktionen liefern einen Boolean Wert zurück, der angibt ob der Treiber korrekt installiert und gestartet bzw. gestoppt und deinstalliert wurde.
Angehängte Dateien
Dateityp: rar h2on_740.rar (119,6 KB, 34x aufgerufen)
"While Mr. Kim, by virtue of youth and naiveté, has fallen prey to the inexplicable need for human contact, let me step in and assure you that my research will go on uninterrupted, and that social relationships will continue to baffle and repulse me."
 
19. Okt 2009, 08:51
Dieses Thema wurde von "Daniel G" von "Neuen Beitrag zur Code-Library hinzufügen" nach "Tutorials und Kurse" verschoben.
Aufgrund des Umfangs eher für die Tutorial-Sparte geeignet als für die Code-Lib.
Apollonius

 
Turbo Delphi für Win32
 
#3
  Alt 19. Okt 2009, 14:18
Zugegeben: Ich kenne mich nicht mit Treiber-Entwicklung aus. Mir kommt dieser Code jedoch wie eine einzige Sicherheitslücke vor.
  Mit Zitat antworten Zitat
Benutzerbild von himitsu
himitsu

 
Delphi 10.4 Sydney
 
#4
  Alt 19. Okt 2009, 15:32
Zitat:
Wie bekomme ich den Dateinamen aus einem dazugehörigen Dateihandle?
- Dateihandle nehmen und damit eine MMF erstellen
- dieses dann (teilweise) in den Arbeitsspeicher mappen
- MSDN-Library durchsuchenGetModuleFileName nutzen, um damit den Dateinamen auszulesen

einzige Mankos:
- man braucht die nötigen Rechte um eine MMF damit erstellen zu können (sollten aber meißtens vorhanden sein)
- die Datei muß mindestens 1 Byte groß sein, da man ja etwas benötigt, um es in den Speicher mappen zu können
  Mit Zitat antworten Zitat
Fridolin Walther

 
Delphi 2009 Professional
 
#5
  Alt 19. Okt 2009, 15:53
Zitat von himitsu:
Zitat:
Wie bekomme ich den Dateinamen aus einem dazugehörigen Dateihandle?
- MSDN-Library durchsuchenGetModuleFileName nutzen, um damit den Dateinamen auszulesen
Sicher das Du nicht eher MSDN-Library durchsuchenGetMappedFileName meinst?

Zitat von himitsu:
einzige Mankos:
- man braucht die nötigen Rechte um eine MMF damit erstellen zu können (sollten aber meißtens vorhanden sein)
- die Datei muß mindestens 1 Byte groß sein, da man ja etwas benötigt, um es in den Speicher mappen zu können
- du benötigst Rechte um das Handle duplizieren zu können, wenn Du das Handle eines fremden Prozesses auflösen möchtest

Davon Abgesehen funktioniert Deine Methode explizit nur mit Dateihandles. Die hier vorgestellte Methode allerdings grundsätzlich mit allen Handles. Das Beispiel mit den Dateihandles ist nur die häufigste Anwendung. Prinzipiell kannst Du auch Handles zu Mutexes oder Events zu Namen auflösen oder aber Registry Handles z.B..

Zitat von Apollonius:
Zugegeben: Ich kenne mich nicht mit Treiber-Entwicklung aus. Mir kommt dieser Code jedoch wie eine einzige Sicherheitslücke vor.
Nunja, es geht prinzipiell um die Demonstration des Vorgehens. Es wird z.B. nicht explizit überprüft ob der übergebene Buffer wirklich beschreibbar ist. Ein ProbeForWrite wäre also Minimum noch notwendig.
Fridolin Walther
  Mit Zitat antworten Zitat
Benutzerbild von himitsu
himitsu

 
Delphi 10.4 Sydney
 
#6
  Alt 19. Okt 2009, 16:01
Zitat von Fridolin Walther:
Sicher das Du nicht eher MSDN-Library durchsuchenGetMappedFileName meinst?
OK, GetMappedFileName klingt da wohl wirklich besser,
aber ich bin mir sicher, daß es auch mit GetModuleFileName ging ... nja, wäre wohl auch irgendwie etwas logisch, da Beide irgendwie die Adresse, bzw. den Namen, von in den Speicher gemappten Dateien liefern.

GetModulaName will zwar 'nen Handle, aber dieses stimmt rein zufällig mit der Speicheradresse überein

Zitat von Fridolin Walther:
- du benötigst Rechte um das Handle duplizieren zu können, wenn Du das Handle eines fremden Prozesses auflösen möchtest
OK, ich bin jetzt einfach mal davon ausgegangen, daß man das entsprechende Handle schon in seinem eigenem Prozess drinnen hat

Zitat von Fridolin Walther:
Davon Abgesehen funktioniert Deine Methode explizit nur mit Dateihandles.
drum hatte ich auch explizit diese Stelle zitiert


Nja, es war aber auch nur als Beispiel dafür, daß man nicht immer gleich mit Kanonen (Treibern) auf Spatzen (Dateihandles) schießen muß und es auch manchmal ganz einfach geht.
  Mit Zitat antworten Zitat
Fridolin Walther

 
Delphi 2009 Professional
 
#7
  Alt 19. Okt 2009, 16:08
Zitat von himitsu:
OK, GetMappedFileName klingt da wohl wirklich besser, aber ich bin mir sicher, daß es auch mit GetModuleFileName ging ... nja, wäre wohl auch irgendwie etwas logisch, da Beide irgendwie die Adresse, bzw. den Namen, von in den Speicher gemappten Dateien liefern.
Der Mechanismus ist imho unterschiedlich. GetMappedFileName benutzt NtQueryVirtualMemory um den Namen aufzulösen. GetModuleFileName benutzt dagegen imho eine Linked List innerhalb des PEB. Wäre mir neu, daß MMFs dort mit eingetragen sind, aber ausschließen mag ich es nicht. Ich werds einfach mal ausprobieren nachher .

Zitat von himitsu:
Nja, es war aber auch nur als Beispiel dafür, daß man nicht immer gleich mit Kanonen (Treibern) auf Spatzen (Dateihandles) schießen muß und es auch manchmal ganz einfach geht.
Auch mit NtQueryObject kannst Du einen Teil der Dateihandles auflösen ... aber eben nur einen Teil .
Fridolin Walther
  Mit Zitat antworten Zitat
Themen-Optionen Tutorial durchsuchen
Tutorial durchsuchen:

Erweiterte Suche
Ansicht

Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +2. Es ist jetzt 13:18 Uhr.
Powered by vBulletin® Copyright ©2000 - 2021, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2021 by Daniel R. Wolf