Kernelobjekte

Microsoft Windows ist ein objektorientierter Kernel. Dateien, Prozesse, Threads - alles ist ein Objekt. All diese Objekte haben eine gemeinsame Datenstruktur und Schnittstelle. Dieser Beitrag zeigt, wie der Kernel Objekte erzeugt und im Arbeitsspeicher ablegt.

Für jede Klasse von Objekten gibt es eine spezialisierte Routine wie IoCreateDriver oder PspCreateProcess, die diese Objekte erzeugt. Alle diese Routinen rufen jedoch früher oder später ObCreateObject auf und verweisen dabei auf eine geeignete _OBJECT_TYPE Struktur.

Wie ein Bauplan beschreibt die Struktur _OBJECT_TYPE eine Klasse von Objekten:

kd> dt _OBJECT_TYPE
struct _OBJECT_TYPE, 12 elements, 0x190 bytes
   +0x000 Mutex            : _ERESOURCE
   +0x038 TypeList         : _LIST_ENTRY
   +0x040 Name             : _UNICODE_STRING
   +0x048 DefaultObject    : Ptr32 Void
   +0x04c Index            : Uint4B
   +0x050 TotalNumberOfObjects : Uint4B
   +0x054 TotalNumberOfHandles : Uint4B
   +0x058 HighWaterNumberOfObjects : Uint4B
   +0x05c HighWaterNumberOfHandles : Uint4B
   +0x060 TypeInfo         : _OBJECT_TYPE_INITIALIZER
   +0x0ac Key              : Uint4B
   +0x0b0 ObjectLocks      : [4] _ERESOURCE

_OBJECT_TYPE_INITIALIZER legt weitere Einzelheiten über die Klasse fest und enthält Details ihrer Implementierung, zum Beispiel Behandlungsroutinen für Standardereignisse:

kd> dt _OBJECT_TYPE_INITIALIZER
struct _OBJECT_TYPE_INITIALIZER, 20 elements, 0x4c bytes
   +0x000 Length           : Uint2B
   +0x002 UseDefaultObject : UChar
   +0x003 CaseInsensitive  : UChar
   +0x004 InvalidAttributes : Uint4B
   +0x008 GenericMapping   : _GENERIC_MAPPING
   +0x018 ValidAccessMask  : Uint4B
   +0x01c SecurityRequired : UChar
   +0x01d MaintainHandleCount : UChar
   +0x01e MaintainTypeList : UChar
   +0x020 PoolType         : _POOL_TYPE
   +0x024 DefaultPagedPoolCharge : Uint4B
   +0x028 DefaultNonPagedPoolCharge : Uint4B
   +0x02c DumpProcedure    : Ptr32     void 
   +0x030 OpenProcedure    : Ptr32     long 
   +0x034 CloseProcedure   : Ptr32     void 
   +0x038 DeleteProcedure  : Ptr32     void 
   +0x03c ParseProcedure   : Ptr32     long 
   +0x040 SecurityProcedure : Ptr32     long 
   +0x044 QueryNameProcedure : Ptr32     long 
   +0x048 OkayToCloseProcedure : Ptr32     unsigned char

Objekte werden gewöhnlich mit ihre Startadresse identifiziert. Als Beispiel soll der System-Prozess dienen. Er hat unter Windows XP immer die PID 4, so dass man ihn hierüber im Debugger auswählen kann:

kd> !process 4 0
Searching for Process with Cid == 4
PROCESS 812927c0  SessionId: none  Cid: 0004    Peb: 00000000  ParentCid: 0000
    DirBase: 00039000  ObjectTable: e1000a80  HandleCount: 216.
    Image: System
[Seine Startadresse ist in deisem Beispiel 0x812927c0. Hierüber können wir jetzt die für alle Klassen gemeinsamen Daten des Objekts einsehen:

kd> !object 812927c0
Object: 812927c0  Type: (81292e38) Process
    ObjectHeader: 812927a8 (old version)
    HandleCount: 2  PointerCount: 57

Das Objekt gehört zur Klasse der Prozesse ("Process") und beginnt an der Adresse 0x812927c0. Beides war in diesem Fall natürlich bereits bekannt, aber dies ist die Bestätigung, dass wir die Daten richtig interpretieren. Wenn Sie die Ausgabe des Debuggers genau betrachten, dann werden Sie feststellen, dass der Objekt-Header an einer niedrigeren Adresse als das Objekt selbst steht, nämlich an 0x812927a8.

Andere Objekte enthalten möglicherweise auch andere Informationen, wie zum Beispiel einen Verweis auf ein Directory, welches dann wiederum auf das Objekt verweist, einen Namen und im Fall der hier dargestellten symbolischen Verknüpfung auch den Namen des Ziels der Verknüpfung:

Object: e13ad800  Type: (812be0a8) SymbolicLink
    ObjectHeader: e13ad7e8 (old version)
    HandleCount: 0  PointerCount: 1
    Directory Object: e10076a0  Name: ScsiPort1
    Target String is '\Device\Ide\IdePort1'

Der Name des Ziels gehört zu den spezifischen Informationen dieser Klasse, er wird deshalb im Rumpf des Objekts gespeichert. Der Name des Objektes selbst und das übergeordnete Verzeichnis sind jedoch Eigenschaften, die auch viele andere Klassen aufweisen. Deshalb werden diese Informationen in dem allgemeinen Objekt-Header und einer seiner Erweiterungen abgelegt.

Zurück zur Routine ObCreateObject. Bevor sie Speicher für das Objekt belegt, trägt sie einige Meta-Daten über das zu erstellende Objekt zusammen und ruft dazu ObpCaptureObjectCreateInformation auf. Diese wiederum ruft weitere Funktionen auf. SeCaptureSecurityDescriptor überprüft zunächst gründlich die Eingabedaten, bevor sie einen ausreichend großen Speicherbereich im Paged Pool des Kernels reserviert und mit der Marke "SeSc" versieht. Schließlich kopiert die Routine den Security Descriptor des neuen Objektes aus den ihr übergeben Objekt-Attributen in diesen Speicherbereich.

Die genannten Marken ("pool tags") wurden eingeführt, um den Speicherverbrauch von Routinen zu überwachen und Softwareentwickler auf mögliche Fehler hinzuweisen. Bei der forensischen Untersuchung eines Arbeitssppeicherabbildes sind diese Marken sehr hilfreich, um Objekte bestimmter Klassen zu lokalisieren.

Später ruft ObpCaptureObjectCreateInformation dann ObpCaptureObjectName auf, um in ähnlicher Weise den Namen des Objekts zu speichern. Den hierfür benötigten Speicherbereich reserviert eine weitere Routine, ObpAllocateObjectNameBuffer. Als Markierung vergibt sie die Zeichenfolge "ObNm". Neben dem Namen wird hier auch ein Verweis auf das übergeordnete Verzeichnis abgelegt.

Schließlich benötigen wir auch noch Speicher für den Rumpf des Objektes mit seinen klassen-spezifischen Daten. Hierum kümmert sich ObpAllocateObject. Die Funktion ermittelt den gewünschten Pool (einer wird stets im Speicher gehalten, Teile des anderen können bei Bedarf auf die Festplatte ausgelagert werden) aus der Struktur _OBJECT_TYPE. Auch das Pool Tag entnimmt sie dieser Struktur. Dabei setzt die Funktion das höchstwertigste Bit des Pool Tags, um eine Verwendung durch das Betriebssystem anzuzeigen. Der Microsoft Debugger markiert diese Speicherblöcke dann als geschützt ("protected"). Allerdings schützt dieses Vorgehen nur gegen eine versehentliche Verwendung eines Tags durch Software anderer Hersteller, zum Beispiel eines Gerätetreibers. Microsofts Dokumentation verlangt zwar, dass nur reine ASCII-Zeichen (7 Bit) in Tags verwendet werden. Allerdings kann man die entsprechende Routine ExAllocatePoolWithTag auch erfolgreich mit einem "unsauberen" Tag aufrufen. Entsprechende Beispiele findet man unter anderem im Windows XP SP2 MP Kernel, zum Beispiel in der Routine CmpAllocateKeyControlBlock.

Sobald ObCreateObject seine Arbeit beendet hat, enthält der Arbeitsspeicher die folgenden Strukturen (von niedrigeren zu höheren Adressen):

  1. _POOL_HEADER, 0x08 Bytes
  2. _OBJECT_QUOTA_CHARGES, 0x10 Bytes, optional
  3. _OBJECT_HANDLE_DB, 0x08 Bytes, optional
  4. _OBJECT_NAME, 0x10 Bytes, optional, verweist auf Speicherblock mit "ObNm" Pool Tag
  5. _OBJECT_CREATOR_INFO, 0x10 Bytes, optional
  6. _OBJECT_HEADER, 0x18 bytes, kann auf einen Speicherblock mit "SeSc" Pool Tag verweisen
  7. Rumpf des Objekts, das Objekt wird durch diese Adresse identifiziert!

Archiv

Impressum

Dieses Blog ist ein Projekt von:
Andreas Schuster
Im Äuelchen 45
D-53177 Bonn
impressum@forensikblog.de

Copyright © 2005-2012 by
Andreas Schuster
Alle Rechte vorbehalten.
Powered by Movable Type 5.12