There are two equivalent ways trustees can be assigned a specific set of permissions on Windows: via generic rights and via a combination of standard and specific rights.
Generic rights are identical for all object types. There are only four of them:
GENERIC_READ (0x80000000)
GENERIC_WRITE (0x40000000)
GENERIC_EXECUTE (0x20000000)
GENERIC_ALL (0x10000000)
As the name implies, generic rights are not object-specific and can thus easily be used by programmers to assign standard permission sets to any kind of object without having to worry about the peculiarities of files, registry keys, printers, and so on.
The exact meaning of each generic right depends on the object type, however. Each object type has a set of specific rights like FILE_LIST_DIRECTORY
or PRINTER_ACCESS_USE
. The exact names, their meanings and even the number of available specific rights depend on the capabilities of the object type. This is necessary - or what would you make of the right to add sub-objects to a printer? In other words, the generic rights provide a level of abstraction to the real capabilities of the differing object types.
Obviously, there needs to be a mapping table somewhere that stores the association between each generic right and the set of standard plus specific rights it is equivalent to. Unfortunately that table is well hidden in the bowels of the operating system. Nicolas Sylvain was kind enough to publish an undocumented routine that programmatically determines the mappings. Interestingly, some mappings change with the operating system version.
Without further ado, here is Nicolas' code:
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0501
#endif
#include <windows.h>
#include <winternl.h>
#include <stdio.h>
#define STATUS_SUCCESS 0
typedef struct _OBJECT_TYPE_INFORMATION {
UNICODE_STRING TypeName;
ULONG TotalNumberOfObjects; ULONG TotalNumberOfHandles;
ULONG TotalPagedPoolUsage;
ULONG TotalNonPagedPoolUsage;
ULONG TotalNamePoolUsage;
ULONG TotalHandleTableUsage;
ULONG HighWaterNumberOfObjects;
ULONG HighWaterNumberOfHandles;
ULONG HighWaterPagedPoolUsage;
ULONG HighWaterNonPagedPoolUsage;
ULONG HighWaterNamePoolUsage;
ULONG HighWaterHandleTableUsage;
ULONG InvalidAttributes;
GENERIC_MAPPING GenericMapping;
ULONG ValidAccessMask;
BOOLEAN SecurityRequired;
BOOLEAN MaintainHandleCount;
ULONG PoolType;
ULONG DefaultPagedPoolCharge;
ULONG DefaultNonPagedPoolCharge;
} OBJECT_TYPE_INFORMATION, *POBJECT_TYPE_INFORMATION;
typedef struct _OBJECT_ALL_TYPES_INFORMATION {
ULONG NumberOfTypes;
OBJECT_TYPE_INFORMATION TypeInformation[1];
} OBJECT_ALL_TYPES_INFORMATION, *POBJECT_ALL_TYPES_INFORMATION;
typedef enum _OBJECT_INFORMATION_CLASS {
ObjectAllInformation = 3
} OBJECT_INFORMATION_CLASS, *POBJECT_INFORMATION_CLASS;
typedef NTSTATUS (WINAPI* NtQueryObjectFunction)(
HANDLE Handle,
OBJECT_INFORMATION_CLASS ObjectInformationClass,
PVOID ObjectInformation,
ULONG ObjectInformationLength,
PULONG ReturnLength);
DWORD DumpGenericMapping() {
HMODULE ntdll = ::LoadLibraryA("ntdll.dll");
FARPROC ntqueryobject_ptr = ::GetProcAddress(ntdll, "NtQueryObject");
NtQueryObjectFunction NtQueryObject =
reinterpret_cast<ntqueryobjectfunction>(ntqueryobject_ptr);
if (!NtQueryObject)
return ERROR_UNIDENTIFIED_ERROR;
DWORD dummy = 0;
DWORD size = sizeof(dummy);
NTSTATUS status = NtQueryObject(NULL, ObjectAllInformation, &dummy, size, &size);
if (0 == size)
return ::GetLastError();
BYTE* buffer = new BYTE[size];
status = NtQueryObject(NULL, ObjectAllInformation, buffer, size, &size);
if (status != STATUS_SUCCESS) {
delete[] buffer;
return ::GetLastError();
}
OBJECT_ALL_TYPES_INFORMATION* all_types =
reinterpret_cast<object_all_types_information*>(buffer);
wprintf(L" Type Name All Execute Read Write \n");
wprintf(L"-----------------------+----------+----------+----------+----------\n");
OBJECT_TYPE_INFORMATION* current_type = all_types->TypeInformation;
for (ULONG i = 0; i < all_types->NumberOfTypes; i++) {
wprintf(L"%23.23wZ 0x%8.8X 0x%8.8X 0x%8.8X 0x%8.8X\n",
¤t_type->TypeName,
current_type->GenericMapping.GenericAll,
current_type->GenericMapping.GenericExecute,
current_type->GenericMapping.GenericRead,
current_type->GenericMapping.GenericWrite);
USHORT offset = (current_type->TypeName.MaximumLength+3) & ~3;
BYTE* next_type = reinterpret_cast<byte*>(current_type->TypeName.Buffer) +
offset;
current_type = reinterpret_cast<object_type_information*>(next_type);
}
delete[] buffer;
return ERROR_SUCCESS;
}
</object_type_information*></byte*></object_all_types_information*>