Creating an Object Manager Browser Part 2–Viewing Object Information

February 9, 2014

no comments

In the previous post, I’ve shown how to use Native API functions to access information not available through the normal, documented, Windows API. In this post, I’d like to show how to take a look at specific objects, such as mutexes, events and semaphores. But first, a bug fix.

In the code that was doing the directory object enumeration was a bug, manifested when the list of objects was too long – or rather, the buffer required to hold all object names and type names was insufficient. The code checked the returned number of bytes needs and compared with the buffer size allocated beforehand, but that was the wrong approach. The number returned may have been smaller simply because the next object name/type name would not fit. The correct way to handle this is by looking at the return status value of NtQueryDirectoryObject: if it’s STATUS_SUCCESS (0) , then there’s no more data. But if it’s STATUS_NEED_MORE_ENTRIES (0x105), then another call needs to be made to get more entries. Here’s the code snippet:

    ULONG index = 0, bytes;
    BOOLEAN firstEntry = TRUE;
    int start = 0;
    do {
        status = NtQueryDirectoryObject(hDirectory, _buffer, _size, FALSE, firstEntry, &index, &bytes);
        if(status < 0)
            break;
        for(ULONG i = 0; i < index - start; i++)
            list.push_back(make_pair(_buffer[i].Name.Buffer, _buffer[i].TypeName.Buffer));
        if(status == 0)
            break;
        // more entries (STATUS_NEED_MORE_ENTRIES)
        start = index;
        firstEntry = FALSE;
    } while(true);

Now, on to the main event.

Getting general object information

To get general object information, such as the number of open handles, a call to NtQueryObject is required. Its prototyped is the same as ZwQueryObject, documented in the WDK. NtQueryObject itself is also “documented” in that it points driver writers to use ZwQueryObject instead, confirming the two prototypes are the same. Of course we’re using the NtDll (user mode) function, and generally there’s no equivalence guarantee, but we know that NtDll reflects a lot of kernel system services. Its prototype is as follows:

        NTSTATUS NTAPI
            NtQueryObject(
            IN HANDLE hObject,
            IN OBJECT_INFORMATION_CLASS ObjectInfoClass,
            OUT PVOID Buffer,
            IN ULONG BufferSize,
            OUT PULONG BytesReturned
            );

The idea is to give a handle and type type of information required (ObjectInfoClass) and get back the result in the Buffer argument, that should be of an appropriate type. For the most basic information, the enumeration ObjectBasicInformation (=0) can be used, which requires the buffer to of type OBJECT_BASIC_INFORMATION defined like so:

        typedef struct _OBJECT_BASIC_INFORMATION {
            ULONG Attributes;
            ACCESS_MASK DesiredAccess;
            ULONG HandleCount;
            ULONG ReferenceCount;
            ULONG PagedPoolUsage;
            ULONG NonPagedPoolUsage;
            ULONG Reserved[3];
            ULONG NameInformationLength;
            ULONG TypeInformationLength;
            ULONG SecurityDescriptorLength;
            LARGE_INTEGER CreationTime;
        } OBJECT_BASIC_INFORMATION, *POBJECT_BASIC_INFORMATION;

For our purposes, I’ve used the members HandleCount, ReferenceCount (number of pointers held by kernel entities), PagedPoolUsage and NonPagePoolUsage (both refer to the memory consumed by the object).

Before we can call NtQueryObject we need to obtain a handle to the object. There’s no general function that can provide this. Instead, we need to use specific native function for specific object types (and not all types are supported). Examples include NtOpenEvent, NtOpenMutant, NtOpenSemaphore, NtOpenSection, etc.

You may wander why we’re not using the regular Win32 APIs for this, such as OpenEvent, OpenMutex, OpenSemaphore, etc. The reason is that the Win32 API functions prepend the string “\Sessions\x\BaseNamedObjects” to the actual name provides (where x is the session id of the caller), while the native functions can reference any object in the object manager’s namespace.

Most of the NtOpen* functions have the same format. Here’s an example for NtOpenEvent:

        NTSTATUS NTAPI NtOpenEvent(
            _Out_  PHANDLE EventHandle,
            _In_   ACCESS_MASK DesiredAccess,
            _In_   POBJECT_ATTRIBUTES ObjectAttributes
            );

The Object Attributes are initialized as described in the previous post with the object’s name.

With the OBJECT_BASIC_INFORMATION object in hand, it’s now an easy matter to show the results in a dialog.

Getting Detailed Object Information

The next step is to get the detailed information for a particular object type. For example, for an event object, we may want to know if it’s a notification event (manual reset) or a synchronization event (auto reset), and its current signaled state.

For these kinds of queries, we need specific functions for specific objects: NtQueryEvent, NtQueryMutant, NtQuerySemaphore, etc. Here’s an example for NtQueryEvent:

        NTSTATUS NTAPI NtQueryEvent(
            IN HANDLE               EventHandle,
            IN EVENT_INFORMATION_CLASS EventInformationClass,
            OUT PVOID               EventInformation,
            IN ULONG                EventInformationLength,
            OUT PULONG              ReturnLength OPTIONAL);

Where EVENT_INFORMATION_CLASS and the resulting data are defined like so:

        typedef enum _EVENT_INFORMATION_CLASS {
            EventBasicInformation
        } EVENT_INFORMATION_CLASS, *PEVENT_INFORMATION_CLASS;

        typedef enum _EVENT_TYPE {
            NotificationEvent,
            SynchronizationEvent
        } EVENT_TYPE, *PEVENT_TYPE;

        typedef struct _EVENT_BASIC_INFORMATION {
            EVENT_TYPE              EventType;
            LONG                    EventState;
        } EVENT_BASIC_INFORMATION, *PEVENT_BASIC_INFORMATION;

The same style of querying is done for the other objects. Here’s the complete code for an event:

    ULONG size;
    if(m_TypeName == L"Event") {
        EVENT_BASIC_INFORMATION info;
        auto status = NtQueryEvent(m_hObject, EventBasicInformation, &info, sizeof(info), &size);
        if(status == 0) {
            m_Name1 = L"Type:"; m_Name2 = "Signaled:";
            m_Value1 = info.EventType == NotificationEvent ? L"Notification (Manual Reset)" : L"Synchronization (Auto Reset)";
            m_Value2 = info.EventState ? L"True" : L"False";
        }
    }

m_Name1 and m_Name2 are the titles of the two lines of specific info, and m_Value1 and m_Value2 are the corresponding values; all these are bound to static text in an MFC dialog. Here’s an example of event information:

image

So there you have it. We’re getting closer to the original WinObj, and even got some extra features, such as showing detailed info for Section objects and allowing copying of object names. I’m planning to add a global search feature, which would find an object with a specific name.

The only major feature missing right now is the second tab of an object’s properties – the Security tab. This may seem easy at first glance – just call the EditSecurity function or CreateSecurityPage; but the real deal here is implementing the ISercurityInformation interface required by these two functions. Hopefully, I can make it work and show how it’s done in the next (and probably last) part of this WinObj-like tool series.

For now, you can enjoy the result by downloading it here.

Add comment
facebook linkedin twitter email

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*