Object Type Initializers

octobre 10th, 2007 at 08:37 admin

En lisant encore et toujours le dernier Uninformed, je me suis dit qu’on pourrait faire un truc cool avec les Object type Initializers. Dans le cas de Uninformed ils s’en servent pour exécuter un code arbitraire en ring0. Je ne propose rien de révolutionnaire, mais je voudrais exposer une autre idée peut-être plus évidente et pratique.

L’Object Type Initializer est une structure globale associé à chaque objet en fonction de son type. La structure OBJECT_TYPE est pointé par le champ Type de l’OBJECT_HEADER. Pour illustrer voici un petit exemple avec le device \device\HardDiskVolume1 qui est associé au SymbolicLink C:

kd> !object \\device\\harddiskvolume1
Object: 80ec9bb8  Type: (80ee83b0) Device
    ObjectHeader: 80ec9ba0 (old version)
    HandleCount: 0  PointerCount: 9
    Directory Object: e1007820  Name: HarddiskVolume1
   
kd> dt nt!_OBJECT_HEADER 80ec9ba0
   +0x000 PointerCount     : 9
   +0x004 HandleCount      : 0
   +0x004 NextToFree       : (null)
   +0x008 Type             : 0x80ee83b0 _OBJECT_TYPE
   +0x00c NameInfoOffset   : 0x20 ' '
   +0x00d HandleInfoOffset : 0 ''
   +0x00e QuotaInfoOffset  : 0 ''
   +0x00f Flags            : 0x16 ''
   +0x010 ObjectCreateInfo : 0x00000001 _OBJECT_CREATE_INFORMATION
   +0x010 QuotaBlockCharged : 0x00000001
   +0x014 SecurityDescriptor : (null)
   +0x018 Body             : _QUAD

Ici, l’OBJECT_TYPE est en 0x80ee83b0, hop on dump son contenu :

kd> dt nt!_OBJECT_TYPE 0x80ee83b0
   +0x000 Mutex            : _ERESOURCE
   +0x038 TypeList         : _LIST_ENTRY [ 0x80f1fb48 - 0xffbdf4c0 ]
   +0x040 Name             : _UNICODE_STRING "Device"
   +0x048 DefaultObject    : 0x8055ff40
   +0x04c Index            : 0x19
   +0x050 TotalNumberOfObjects : 0xd3
   +0x054 TotalNumberOfHandles : 0
   +0x058 HighWaterNumberOfObjects : 0xd3
   +0x05c HighWaterNumberOfHandles : 1
   +0x060 TypeInfo         : _OBJECT_TYPE_INITIALIZER
   +0x0ac Key              : 0x69766544
   +0x0b0 ObjectLocks      : [4] _ERESOURCE

Le champ Name de la structure OBJECT_TYPE confirme que notre objet est bien un Device. Ce qui nous intéresse est le champ TypeInfo qui est de type OBJECT_TYPE_INITIALIZER :

  
kd> dt nt!_OBJECT_TYPE_INITIALIZER 0x80ee83b0+60
   +0x000 Length           : 0x4c
   +0x002 UseDefaultObject : 0x1 ''
   +0x003 CaseInsensitive  : 0x1 ''
   +0x004 InvalidAttributes : 0x100
   +0x008 GenericMapping   : _GENERIC_MAPPING
   +0x018 ValidAccessMask  : 0x1f01ff
   +0x01c SecurityRequired : 0 ''
   +0x01d MaintainHandleCount : 0 ''
   +0x01e MaintainTypeList : 0x1 ''
   +0x020 PoolType         : 0 ( NonPagedPool )
   +0x024 DefaultPagedPoolCharge : 0
   +0x028 DefaultNonPagedPoolCharge : 0xe8
   +0x02c DumpProcedure    : (null)
   +0x030 OpenProcedure    : (null)
   +0x034 CloseProcedure   : (null)
   +0x038 DeleteProcedure  : 0x8059f6fd     void  nt!IopDeleteDevice+0
   +0x03c ParseProcedure   : 0x805704ed     long  nt!IopParseDevice+0
   +0x040 SecurityProcedure : 0x8059b35e     long  nt!IopGetSetSecurityObject+0
   +0x044 QueryNameProcedure : (null)
   +0x048 OkayToCloseProcedure : (null)

On remarque que cette structure contient un ensemble de handlers qui sont appelés en fonction des opérations réalisées sur l’objet. Par exemple IopParseDevice permet à l’I/O Manager de parser le nom de l’objet lors d’une demande d’ouverture de handle sur un device. L’I/O Manager va regarder avec cette routine si le device existe bien dans l’Object NameSpace \device et retrouver le pointeur pour sur le device. Par exemple si vous ouvrer un handle sur C:\tapz.txt, le SymbolicLink vous envoie sur \Device\HardDiskVolume1 à travers ObpParseSymbolicLink et IopParseDevice va retrouver le pointeur sur l’objet device dans l’ObjectDirectory \device.

Juste pour info voici le prototype de IopParseDevice.

NTSTATUS
IopParseDevice(
    IN PVOID ParseObject,
    IN PVOID ObjectType,
    IN PACCESS_STATE AccessState,
    IN KPROCESSOR_MODE AccessMode,
    IN ULONG Attributes,
    IN OUT PUNICODE_STRING CompleteName,
    IN OUT PUNICODE_STRING RemainingName,
    IN OUT PVOID Context OPTIONAL,
    IN PSECURITY_QUALITY_OF_SERVICE SecurityQos OPTIONAL,
    OUT PVOID *Object
    )

/*++

Routine Description:

    This routine interfaces to the NT Object Manager.  It is invoked when
    the object system is given the name of an entity to create or open and the
    name translates to a device object.  This routine is specified as the parse
    routine for all device objects.

    In the normal case of an NtCreateFile, the user specifies either the name
    of a device or of a file.  In the former situation, this routine is invoked
    with a pointer to the device and a null ("") string.  For this case, the
    routine simply allocates an IRP, fills it in, and passes it to the driver
    for the device.  The driver will then perform whatever rudimentary functions
    are necessary and will return a status code indicating whether an error was
    incurred.  This status code is remembered in the Open Packet (OP).

    In the latter situation, the name string to be opened/created is non-null.
    That is, it contains the remainder of the pathname to the file that is to
    be opened or created.  For this case, the routine allocates an IRP, fills
    it in, and passes it to the driver for the device.  The driver may then
    need to take further action or it may complete the request immediately.  If
    it needs to perform some work asynchronously, then it can queue the request
    and return a status of STATUS_PENDING.  This allows this routine and its
    caller to return to the user so that he can continue.  Otherwise, the open/
    create is basically finished.

    If the driver supports symbolic links, then it is also possible for the
    driver to return a new name.  This name will be returned to the Object
    Manager as a new name to look up.  The parsing will then begin again from
    the start.

    It is also the responsibility of this routine to create a file object for
    the file, if the name specifies a file.  The file object's address is
    returned to the NtCreateFile service through the OP.

Arguments:

    ParseObject - Pointer to the device object the name translated into.

    ObjectType - Type of the object being opened.

    AccessState - Running security access state information for operation.

    AccessMode - Access mode of the original caller.

    Attributes - Attributes to be applied to the object.

    CompleteName - Complete name of the object.

    RemainingName - Remaining name of the object.

    Context - Pointer to an Open Packet (OP) from NtCreateFile service.

    SecurityQos - Optional security quality of service indicator.

    Object - The address of a variable to receive the created file object, if
        any.

Return Value:

    The function return value is one of the following:

        a)  Success - This indicates that the function succeeded and the object
            parameter contains the address of the created file object.

        b)  Error - This indicates that the file was not found or created and
            no file object was created.

        c)  Reparse - This indicates that the remaining name string has been
            replaced by a new name that is to be parsed.

--*/

En général voici le cheminement de l’appel à partir d’un CreateFileW sur n’importe quel fichier.

kd> kv
ChildEBP RetAddr  Args to Child              
f744eb3c 8056356c 80df2cf8 00000000 80ebc4b8 nt!IopParseDevice (FPO: [Non-Fpo])
f744ebc4 8056769a 00000000 f744ec04 00000040 nt!ObpLookupObjectName+0x56a (FPO: [Non-Fpo])
f744ec18 80570f03 00000000 00000000 44ed3001 nt!ObOpenObjectByName+0xeb (FPO: [Non-Fpo])
f744ec94 80570fd2 0120f85c c0100080 0120f7fc nt!IopCreateFile+0x407 (FPO: [Non-Fpo])
f744ecf0 80571108 0120f85c c0100080 0120f7fc nt!IoCreateFile+0x8e (FPO: [Non-Fpo])
f744ed30 804de7ec 0120f85c c0100080 0120f7fc nt!NtCreateFile+0x30 (FPO: [Non-Fpo])
f744ed30 7c91eb94 0120f85c c0100080 0120f7fc nt!KiFastCallEntry+0xf8 (FPO: [0,0] TrapFrame @ f744ed64)
0120f7b8 7c91d68e 7c810916 0120f85c c0100080 ntdll!KiFastSystemCallRet (FPO: [0,0,0])
0120f7bc 7c810916 0120f85c c0100080 0120f7fc ntdll!NtCreateFile+0xc (FPO: [11,0,0])
0120f854 77e64f42 00000000 c0000000 00000003 kernel32!CreateFileW+0x35f (FPO: [Non-Fpo])
[...]

Le fonctionement des Object Type Initializers est assez basique avec les objets de type device (IoDeviceObjectType). Tout se passe avec le handler ParseProcedure. En fait IopParseDevice va crée un FILE_OBJECT sur le device demandé (ici \Device\HardDiskVolume1) avec ObCreateObject si il n’existe pas. Grâce à cela, la prochaine fois qu’on aura besoin d’accéder au device le l’I/O Manager pourra directement le retrouver. IopParseDevice renvoie donc un FILE_OBJECT sur le device et ObOpenObjectByName crée un handle dessus avec ObpCreateHandle. Pour résumer je me suis amusé à mettre des breakpints sur les fonctions ObpParseSymbolicLink, IopParseDevice et ObpCreateHandle. Puis j’ai lancé la commande « echo tapz > NUL ». Ce qui veut dire, envoyé la sortie « tapz » sur le device NUL.

kd> bl
 0 e 805704ed     0001 (0001) nt!IopParseDevice ".printf \\"IopParseDevice\\n\\" ; !ustr poi(esp+18);g"
 2 e 80564f52     0001 (0001) nt!ObpParseSymbolicLink ".printf \"ObpParseSymbolicLink\n\" ; !ustr poi(esp+18);g"
 3 e 8056443f     0001 (0001) nt!ObpCreateHandle ".printf \\"ObpCreateHandle\\n\\" ; !thread ; dd poi(esp+C) l 1; dt nt!_OBJECT_TYPE poi(esp+C) -a Name"

kd> g
ObpParseSymbolicLink
String(14,248) at f6ec2c04: \\??\\NUL

IopParseDevice
String(24,248) at f6ec2c04: \\Device\\Null

ObpCreateHandle
THREAD 80d698e0  Cid 0154.0184  Teb: 7ffdf000 Win32Thread: e10d7798 RUNNING on processor 0
Not impersonating
DeviceMap                 e1686fd0
Owning Process            80d66c10       Image:         cmd.exe
Wait Start TickCount      427719         Ticks: 2 (0:00:00:00.020)
Context Switch Count      1034                 LargeStack
UserTime                  00:00:00.010
KernelTime                00:00:06.879
Win32 Start Address 0x4ad05056
Start Address 0x7c810665
Stack Init f6ec3000 Current f6ec2aa8 Base f6ec3000 Limit f6ec0000 Call 0
Priority 8 BasePriority 8 PriorityDecrement 0 DecrementCount 16
ChildEBP RetAddr  Args to Child              
f6ec2bc8 80567727 00000000 80d57e00 00000000 nt!ObpCreateHandle (FPO: [Non-Fpo])
f6ec2c18 80570f03 00000000 00000000 ec2c8401 nt!ObOpenObjectByName+0x28c (FPO: [Non-Fpo])
f6ec2c94 80570fd2 0012fc14 40100080 0012fbb4 nt!IopCreateFile+0x407 (FPO: [Non-Fpo])
f6ec2cf0 80571108 0012fc14 40100080 0012fbb4 nt!IoCreateFile+0x8e (FPO: [Non-Fpo])
f6ec2d30 804de7ec 0012fc14 40100080 0012fbb4 nt!NtCreateFile+0x30 (FPO: [Non-Fpo])
f6ec2d30 7c91eb94 0012fc14 40100080 0012fbb4 nt!KiFastCallEntry+0xf8 (FPO: [0,0] TrapFrame @ f6ec2d64)
WARNING: Frame IP not in any known module. Following frames may be wrong.
0012fc0c 4ad02f2a 00000000 40000000 00000001 0x7c91eb94
0012fc58 4ad02e91 00144378 00000301 00000001 0x4ad02f2a
0012fc6c 4ad03efe 00144378 00000301 00147e88 0x4ad02e91
0012fe98 4ad013d7 00147e88 00000000 00147e88 0x4ad03efe
0012ffa8 80584053 0012ffe0 4ad1f18a 4ad05178 0x4ad013d7
0012ff44 4ad05164 00000001 003237c8 003229a8 nt!CcPfBeginAppLaunch+0x19f (FPO: [Non-Fpo])
0012ffd0 8054ad38 0012ffc8 80d698e0 ffffffff 0x4ad05164
0012fff0 00000000 4ad05056 00000000 78746341 nt!ExFreePoolWithTag+0x676 (FPO: [Non-Fpo])

00000000  ???????? <- POBJECT_TYPE==NULL
   +0x040 Name : _UNICODE_STRING
nt!ObpCreateHandle:
8056443f 8bff            mov     edi,edi

kd> !object 80d57e00
Object: 80d57e00  Type: (80f1fca0) File
    ObjectHeader: 80d57de8 (old version)
    HandleCount: 0  PointerCount: 2

kd> !fileobj 80d57e00

Device Object: 0xffb66ca8   \\Driver\\Null
Vpb is NULL
Event signalled

Flags:  0x2
	Synchronous IO

Private Cache Map: 0x00000001
CurrentByteOffset: 0

Au départ ObpParseSymbolicLink prend le SymbolicLink \??\NUL puis renvoie le device \Device\Null. Après l’I/O Manager va crée un FILE_OBJECT dessus avec la routine IopParseDevice et le renvoyé à ObOpenObjectByName qui par la suite va crée un handle dessus avec ObpCreateHandle. Lors de l’appel à ObpCreateHandle on peut voir que le second argument passé est bien un PFILE_OBJECT sur le DEVICE_OBJECT NULL. Après la fonction NtWriteFile va envoyer le contenu de la commande « echo tapz » avec un IRP_MJ_WRITE au driver null.sys grâce au handle référençant le FILE_OBJECT associé au device NULL.

Revenons-en aux Object Type Initializers, au début, j’avais juste parlé des handlers associé aux objets de type IoDeviceObjectType. Il existe en fait des structures OBJECT_TYPE_INITIALIZER pour chaque type d’objet du kernel. Dans le cas d’un objet de type IoFileObjectType les handlers sont :

kd> dt nt!_OBJECT_TYPE_INITIALIZER  poi(nt!IoFileObjectType)+60
   +0x000 Length           : 0x4c
   +0x002 UseDefaultObject : 0 ''
   +0x003 CaseInsensitive  : 0x1 ''
   +0x004 InvalidAttributes : 0x130
   +0x008 GenericMapping   : _GENERIC_MAPPING
   +0x018 ValidAccessMask  : 0x1f01ff
   +0x01c SecurityRequired : 0 ''
   +0x01d MaintainHandleCount : 0x1 ''
   +0x01e MaintainTypeList : 0x1 ''
   +0x020 PoolType         : 0 ( NonPagedPool )
   +0x024 DefaultPagedPoolCharge : 0x400
   +0x028 DefaultNonPagedPoolCharge : 0xe8
   +0x02c DumpProcedure    : (null) 
   +0x030 OpenProcedure    : (null) 
   +0x034 CloseProcedure   : 0x8056aa31     void  nt!IopCloseFile+0
   +0x038 DeleteProcedure  : 0x8056a83b     void  nt!IopDeleteFile+0
   +0x03c ParseProcedure   : 0x805720ae     long  nt!IopParseFile+0
   +0x040 SecurityProcedure : 0x8059b35e     long  nt!IopGetSetSecurityObject+0
   +0x044 QueryNameProcedure : 0x80581825     long  nt!IopQueryName+0
   +0x048 OkayToCloseProcedure : (null) 

Si on regarde le handler OpenProcedure celui ci est NULL. Ce qui veut dire que lors d’un demande de création de handle sur un objet de type IoFileObjectType, la fonction ObpCreateHandle n’a que faire de ce handler. Maintenant si on pense un peu, sachant que la plupart des *ObjectType sont exportées par le kernel. Il est possible d’installer nous même notre propre OpenProcedure dans la structure OBJECT_TYPE_INITIALIZER. On pourrait donc filtrer toutes les ouvertures de handle sur les fichiers (et devices) de notre système. Imaginez ce qu’un rootkit pourrait faire de cela. Lors de l’ouverture d’un handle sur un fichier il n’aurait qu’a vérifier le nom se trouvant dans le FILE_OBJECT pour voir si oui ou non il laisse la fonction ObpCreateHandle continuer. On pourrait donc avoir un filtrage assez simple pour contrôler l’ouverture des fichiers. La fonction qui sera dans le champ OpenProcedure devra avoir le prototype suivant :

typedef NTSTATUS (*OB_OPEN_METHOD)(
    IN OB_OPEN_REASON OpenReason,
    IN PEPROCESS Process OPTIONAL,
    IN PVOID Object,
    IN ACCESS_MASK GrantedAccess,
    IN ULONG HandleCount
    );

Par contre, dans le cas ou l’utilisateur demande d’afficher le contenu d’un dossier, nous ne verrons passer que l’ouverture de handle sur le dossier. On ne pourra donc pas contrôler ce qui sera renvoyé.

Maintenant intéressons nous à des objets plus « abstrait » que des fichiers ou devices, comme des processes ou des threads.
La structure OBJECT_TYPE_INITIALIZER associé aux objets de type process (nt!PsProcessType) contient :

kd> dt nt!_OBJECT_TYPE_INITIALIZER poi(nt!PsProcessType)+60
   +0x000 Length           : 0x4c
   +0x002 UseDefaultObject : 0 ''
   +0x003 CaseInsensitive  : 0 ''
   +0x004 InvalidAttributes : 0xb0
   +0x008 GenericMapping   : _GENERIC_MAPPING
   +0x018 ValidAccessMask  : 0x1f0fff
   +0x01c SecurityRequired : 0x1 ''
   +0x01d MaintainHandleCount : 0 ''
   +0x01e MaintainTypeList : 0x1 ''
   +0x020 PoolType         : 0 ( NonPagedPool )
   +0x024 DefaultPagedPoolCharge : 0x1000
   +0x028 DefaultNonPagedPoolCharge : 0x290
   +0x02c DumpProcedure    : (null)
   +0x030 OpenProcedure    : (null)
   +0x034 CloseProcedure   : (null)
   +0x038 DeleteProcedure  : 0x805841e0     void  nt!PspProcessDelete+0
   +0x03c ParseProcedure   : (null)
   +0x040 SecurityProcedure : 0x8056a0b7     long  nt!SeDefaultObjectMethod+0
   +0x044 QueryNameProcedure : (null)
   +0x048 OkayToCloseProcedure : (null)

Ici juste un handler pour la DeleteProcedure. Il est aussi possible que nous rajoutions notre propre handler pour gérer l’ouverture de handle sur les objets associés au process, les EPROCESS. Dans le cas ou un user demande l’ouverture d’un handle sur un process, on pourrait facilement le contrôler et lui refuser l’accès, pour par exemple, rendre un process immortel :] On peut exactement faire la même chose pour les threads (PsThreadType).

Voici un liste non-exaustive des type d’objets auquels peut être appliqué cette méthode.

kd> x /v  nt!*type
pub global 8055ffb4    0 nt!ObpSymbolicLinkObjectType = 
pub global 805609c0    0 nt!PsJobType = 
pub global 8055a6f8    0 nt!MmProductType = 
pub global 80561800    0 nt!ExSemaphoreObjectType = 
pub global 805580ec    0 nt!IoControllerObjectType = 
pub global 8059b864    0 nt!RtlBaseAceType = 
pub global 8068e268    0 nt!CmpKeyObjectType = 
pub global 806aab6c    0 nt!CmInstallUILanguageIdType = 
pub global 80561768    0 nt!ExEventPairObjectType = 
pub global 805568bc    0 nt!DbgkDebugObjectType = 
pub global 8068e280    0 nt!CmRegistrySizeLimitType = 
pub global 806b0c60    0 nt!CmSuiteBufferType = 
pub global 8056a534    0 nt!SeTokenType = 
pub global 805595b8    0 nt!KeI386CpuType = 
pub global 80561c9c    0 nt!ExDesktopObjectType = 
pub global 8056176c    0 nt!ExTimerObjectType = 
pub global 806a6130    0 nt!MmVerifyDriverBufferType = 
pub global 80561cd8    0 nt!ExCallbackObjectType = 
pub global 80561760    0 nt!ExProfileObjectType = 
pub global 80561764    0 nt!ExMutantObjectType = 
pub global 805580e0    0 nt!IoDriverObjectType = 
pub global 805580d8    0 nt!IoFileObjectType = 
pub global 805612b8    0 nt!WmipGuidObjectType = 
pub global 805580dc    0 nt!IoDeviceHandlerObjectType = 
pub global 8055efa0    0 nt!MmSectionObjectType = 
pub global 80552e04    0 nt!KeI386MachineType = 
pub global 80560a38    0 nt!PsProcessType = 
pub global 8055ff50    0 nt!ObpTypeObjectType = 
pub global 8055ff80    0 nt!ObpDirectoryObjectType = 
pub global 80557634    0 nt!CmpBootType = 
pub global 80558808    0 nt!PnpDefaultInterfaceType = 
pub global 8068dde0    0 nt!SeTokenObjectType = 
pub global 80561c20    0 nt!ExEventObjectType = 
pub global 80561734    0 nt!ExpKeyedEventObjectType = 
pub global 80628d45    0 nt!ObEnumerateObjectsByType = 
pub global 805580e4    0 nt!IoDeviceObjectType = 
pub global 805580f0    0 nt!IoAdapterObjectType = 
pub global 8055a268    0 nt!LpcPortObjectType = 
pub global 80552818    0 nt!InitWinPEModeType = 
pub global 805580e8    0 nt!IoCompletionObjectType = 
pub global 8055a264    0 nt!LpcWaitablePortObjectType = 
pub global 80561ca0    0 nt!ExWindowStationObjectType = 
pub global 80560a3c    0 nt!PsThreadType = 

NB : j'ai trié qques valeurs, certaines sont certainement fausses.

Les Object Type Initializers, sont donc un moyen simple et puissant pour contrôler la manipulation des objets kernel depuis le userland. Cela peut très bien servir dans le cadre d’un rootkit ou bien même pour un HIDS. Cela dépend de quel coté de la force vous êtes :] Evidemment leurs utilisation reste assez limité, je n’ai pas évoqué toutes les possibilités que les Object Type Initializers offraient, si vous en voyez d’autres n’hésitez pas me le signaler.

Ref :
Object Type Initializers
http://uninformed.org/index.cgi?v=8&a=2&p=19

Windows Internals -> Device Drivers -> Opening Devices

The kernel object namespace and Win32
http://www.nynaeve.net/?p=61
http://www.nynaeve.net/?p=86
http://www.nynaeve.net/?p=92

Debugger tricks: API call logging, the quick’n’dirty way (part 2)
http://www.nynaeve.net/?p=144

Entry Filed under: Non classé

6 Comments

Trackback this post


Calendar

octobre 2021
L Ma Me J V S D
« fév    
 123
45678910
11121314151617
18192021222324
25262728293031

Most Recent Posts