Programmieren mit C++

Visual C++

Systemnahe Lösungen mit VC++

Fremde Datei-I/O-Operationen protokollieren

Wie kann man Datei-Ein- und Ausgaben einer fremden Anwendung beobachten und protokollieren?

Frage

Die Dateioperationen beliebiger Programme zu protokollieren, ist unter Windows nicht mehr so einfach möglich wie früher unter einem Singletasking-Betriebssystem wie DOS, das keine Sicherheitsmechanismen kannte. Unter Windows kann ein Datei-Spy lediglich auf VxD-Ebene realisiert werden – und auch dieser Ansatz funktioniert so nur unter Windows 9x, nicht jedoch unter Windows NT.

Die Grundlage für die Implementation des VxD-Treibers ist die Tatsache, daß Windows 9x einen Hook implementiert, über den sämtliche Dateioperationen ablaufen. Alle Zugriffe auf die Registry, das Swapfile des virtuellen Speichers, Dateien, DLLs und sogar das Öffnen von VxDs erfolgt über diesen Hook. Klinkt man sich durch einen VxD-Treiber in diesen Hook ein, kann man alle Operationen in eine Protokolldatei schreiben, die hier der Einfachheit halber fest mit

C:\FileCall.Spy

vorgegeben ist. Da jedoch der komplette Quellcode des VxD-Treibers auf der Begleit-CD enthalten ist, kann dieser Name beliebig geändert werden.

Das Laden des Spy-VxD-Treibers erfolgt durch dynamisches Laden über eine simple Anwendung. Dies ist vergleichbar mit dem Laden einer DLL, jedoch unterscheiden sich DLLs und VxD-Treiber in einem wichtigen Punkt: VxD-Treiber operieren im Sicherheitsring 0, in dem auch das Kernel operiert, während DLLs im niedriger privilegierten Ring 3 laufen und somit keine so umfangreichen Zugriffsrechte besitzen wie ein VxD-Treiber.

Das Programm SpyExec lädt den Treiber SPY.VXD, der sich im gleichen oder im Windows-System-Verzeichnis befinden muß. Nachdem der Treiber geladen ist, bleibt der Hook aktiv, bis der OK-Button zum Beenden gedrückt wird. Erst dann stoppt das Protokollieren. Bis zu diesem Zeitpunkt fallen jedoch unzählige Events an, so daß die Protokolldatei schon nach kurzer Laufzeit recht umfangreich werden kann.

Implementation des Treibers

Der VxD-Treiber muß neben dem allgemeinen Device-IO-Handling, das durch

DWORD _stdcall SPY_W32_DeviceIOControl(
    DWORD  dwService,
    DWORD  dwDDB,
    DWORD  hDevice,
    LPDIOC lpDIOCParms)

erfolgt, vor allem drei Servicefunktionen bereitstellen, die

  • den Hook installieren
  • die Protokolldatei öffnen und
  • die Datei wieder schließen sowie den Hook wieder deinstallieren

Die zugehörigen Funktionen haben das Format

DWORD _stdcall VxDProc(DWORD  dwDDB, 
                       DWORD  hDevice, 
                       LPDIOC lpDIOCParms)

Aufgerufen werden diese Funktionen über DeviceIoControl(), die sowohl die Standard-Calls wie auch die speziellen Services behandelt. Die Nachricht DIOC_OPEN wird einmal direkt nach SYS_DYNAMIC_INIT gesendet, wenn der VxD-Treiber über CreateFile() geladen wird. Als Reaktion darauf muß die Fuktion 0 returnieren, um Win32 mitzuteilen, daß der VxD-Treiber DEVIOCTL unterstützt.

DWORD _stdcall SPY_W32_DeviceIOControl(
     DWORD  dwService,
     DWORD  dwDDB,
     DWORD  hDevice,
        LPDIOC lpDIOCParms)
{
  DWORD dwRetVal = 0;
  if ( dwService == DIOC_OPEN )
  {
    Out_Debug_String("SPY: WIN32 DEVIOCTL supported here!\n\r");
    dwRetVal = 0;
  }

Analog dazu trifft direkt vor SYS_DYNAMIC_EXIT die Nachricht DIOC_CLOSEHANDLE ein, wenn der VxD-Treiber über CloseFile() wieder freigegeben wird. Der Treiber kann diesen Call benutzen, um eigene Aufräumarbeiten wie z.B. das Schließen der Protokolldatei durchzuführen.

  else if ( dwService == DIOC_CLOSEHANDLE )
  {
    Out_Debug_String("SPY: Closing!\n\r");
    dwRetVal = SPY_CleanUp();
  }

Ansonsten bearbeitet der VxD-Treiber nur noch die eigens bereitgestellten Services, die durch die nachfolgend beschriebenen Funktionen implementiert werden. Die Identifikation der Services erfolgt über den numerischen Parameter dwService, also insbesondere nicht über eine Referenz auf die Funktion, die den Service implementiert. Vielmehr kommt hier eine eigene Sprungtabelle zum Einsatz.

  else if(dwService==1)
  {
    dwRetVal = VxDProc(dwDDB, hDevice, lpDIOCParms);
    return(dwRetVal);
  }
  else if(dwService==2)
       {
         VxDProc1(dwDDB, hDevice, lpDIOCParms);
         return(NO_ERROR);
       }
       else if(dwService==3)
            {
              VxDProc2(dwDDB, hDevice, lpDIOCParms);
            }
return(dwRetVal);
}

Der Service 1 installiert die Hook-Funktion, die weiter unten beschrieben wird, und speichert eine Referenz auf die bisher gültige Hook-Funktion, die als Ergebnis der Installations-Funktion geliefert wird.

DWORD _stdcall VxDProc(DWORD  dwDDB, 
                       DWORD  hDevice, 
                       LPDIOC lpDIOCParms)
{
  ppPrevHook=IFSMgr_InstallFileSystemApiHook(OurFileHook);
  return(NO_ERROR);
}

Das Öffnen der Protokolldatei leistet der Service 3 über spezielle Funktionen, deren Bezeichner mit „R0_“ beginnen. Diese arbeiten privilegiert im Ring 0, was mit den Standard-Funktionen für das Dateihandling nicht vorgesehen ist. In der Konstanten FileNm ist der Name der Protokolldatei fest abgelegt. Bei Bedarf kann dieser Name geändert oder die Vergabe des Namens parametrisiert werden, was hier vom Aufwand her betrachtet wenig sinnvoll erschien.

DWORD _stdcall VxDProc2(DWORD  dwDDB,
                        DWORD  hDevice, 
                        LPDIOC lpDIOCParms)
{
  char FileNm[]="c:\\FileCall.Spy";
  char buff[50];
  int pAction=0x12;         
  Init=1;
  if(R0_OpenCreateFile(R0_OPENCREATFILE, 0x2, 0, pAction, 
                       R0_NO_CACHE, FileNm, &pHandle, 
                       &pAction)!=0)
    FileFail=1;
  else Init=FileFail=0;
return(FileFail);
}

Der Service 2 deinstalliert die Hook-Funktion und schließt die Protokolldatei.

DWORD _stdcall VxDProc1(DWORD  dwDDB,
                        DWORD  hDevice, 
                        LPDIOC lpDIOCParms)
{
  PDWORD pdw;
  pdw = (PDWORD)lpDIOCParms->lpvOutBuffer;
  pdw[0]=IFSMgr_RemoveFileSystemApiHook(OurFileHook);
R0_CloseFile(pHandle);
return(NO_ERROR);
}

Die Hauptanwendung SpyExec ist eine reine Konsolen-Applikation, die lediglich den VxD-Treiber dynamisch lädt und den Aufruf CVXDSAMP vorbereitet.

int main()
{
  HANDLE      hCVxD = 0;
  DWORD       cbBytesReturned;
  DWORD       dwErrorCode;
  DWORD       RetInfo[1];
  DWORD       ErrorCode;
hCVxD = CreateFile("\\\\.\\SPY.VXD", 0,0,0,
                     CREATE_NEW, FILE_FLAG_DELETE_ON_CLOSE, 0);

Konnte der Treiber geladen werden, öffnet Service 3 zunächst die Protokolldatei.

    if (!(hCVxD == INVALID_HANDLE_VALUE ))
    {
      if(!DeviceIoControl(hCVxD, SPY_APIFUNC_3,
                          (LPVOID)NULL, 0,
                          (LPVOID)RetInfo, sizeof(RetInfo),
                          &cbBytesReturned, NULL))
      {
        printf("Unable to create C:\\FileCall.Spy file!");
        CloseHandle(hCVxD);
        exit(1);
      }

Erst danach kann die Hook-Funktion installiert werden.

      DeviceIoControl(hCVxD, SPY_APIFUNC_1,
                      (LPVOID)NULL, 0,
                      (LPVOID)RetInfo, 
                      sizeof(RetInfo),
                      &cbBytesReturned,    
                      NULL);

Sobald die Hook-Funktion installiert ist, zeigt SpyExec eine einfache Messagebox an, bis zu deren Betätigung die Anwendung im Wartezustand verbleibt, während die Hook-Funktion Multitasking-bedingt permanent durch Windows für Dateioperationen genutzt wird, Als Elternfenster wird der Messagebox NULL übergeben.

      MessageBox(NULL, 
            "Press OK to unhook spy\nLogging to C:\\FileCall.Spy", 
            "SPY HOOKED", MB_OK);

Nach dem Schließen der Messagebox deinstalliert Service 2 die Hook-Funktion...

      DeviceIoControl(hCVxD, SPY_APIFUNC_2,
                      (LPVOID)NULL, 0,
                      (LPVOID)RetInfo, 
                      sizeof(RetInfo),
                      &cbBytesReturned, 
                      NULL);

...und CloseHandle() schließt den VxD-Treiber.

      if(!CloseHandle(hCVxD))
        printf("Unable to close\n");
    }
 
  return(0);
}

Bedingt durch diese Arbeitsweise kann SpyExec alle Dateioperationen analysieren und protokollieren. Damit läßt sich auch die Frage beantworten, welche DLLs eine Anwendung öffnen, indem einfach das Protokoll ausgewertet wird. Wie das Protokoll aussieht, legt letztendlich die benutzerdefinierte Hook-Funktion fest. Hierzu gibt es kein Patentrezept, so daß nachfolgend lediglich das Grundprinzip aufgezeigt wird.

Die von der Analyse erfaßten Dateioperationen werden über eine CASE-Schleife abgegriffen. Derzeit werden folgende Operationen protokolliert:

  • IFSFN_OPEN
  • IFSFN_CLOSE
  • IFSFN_FINDOPEN
  • IFSFN_FINDNEXT
  • IFSFN_FINDCLOSE
  • IFSFN_RENAME
  • IFSFN_DELETE
  • IFSFN_READ
  • IFSFN_WRITE

Nicht bearbeitete Events werden durch den Default-Handler abgefangen. Für alle protokollierten Events gilt die generelle Vorgehensweise, die nachfolgend am Beispiel von IFSFN_FINDCLOSE gezeigt wird. Als erstes ist das Reentrance-Problem zu lösen, indem ein Flag verwaltet wird.

case IFSFN_FINDCLOSE:
   if(AlreadyInside==1 || Init==1)
     break;

Danach ruft der Handler einfach die zuvor installierte Hook-Funktion auf, auf die ein Zeiger beim Installieren des Hooks gespeichert wurde.

   AlreadyInside=1;
   iRet=(*(*ppPrevHook))(pfn,nFunction,nDrive,nResources,Cp, pir);

Damit muß der eigene Handler keinerlei Funktionalität bereitstellen, außer der Protokollierung, die nach dem Aufruf des vorherigen Hooks erfolgt.

Lösung





Sachgebiet


© 2009-2012 by Alojado Publishing. Alle Rechte vorbehalten. Ausgewiesene Marken gehören ihren jeweiligen Eigentümern.
Mit der Benutzung dieser Seite erkennen Sie die Nutzungsbedingungen und die Datenschutzerklärung an. Der Betreiber übernimmt keine Haftung für den Inhalt verlinkter externer Internetseiten.
Seite erzeugt 2012-05-20 02:08:16 von textarchiv.alojado.de