The NT Insider

Keeping Secrets - Windows NT Security (Part II)
(By: The NT Insider, Vol 6, Issue 5, Sep-Oct 1999 | Published: 15-Oct-99| Modified: 16-Aug-02)

 

This article is part of a continuing series on Windows NT Security.  In the previous article, we covered terminology...below, we continue with a brief segment on basic construction.

 

Now that we have reviewed the basic terms, we will discuss how to construct these various data structures within your own kernel mode code.  Many of these routines are based upon the NT Native API, and several of these functions are present only in the IFS Kit.

 

Security Identifier

 

Because the Security Descriptor is used to identify security entities (users, groups, computers, etc.) it is essential that we start by describing how to build security descriptors.  In the remainder of this section we will discuss how to obtain an SID for use in your driver.

 

Using Standard SID Values

 

One possibility here is to use the set of pre-defined SIDs exported by the operating system.  This is part of the SE_EXPORTS structure (from ntifs.h) as shown in Figure 1.

 

typedef struct _SE_EXPORTS {

 

    // Privilege values

    //

 

    LUID    SeCreateTokenPrivilege;

    LUID    SeAssignPrimaryTokenPrivilege;

    LUID    SeLockMemoryPrivilege;

    LUID    SeIncreaseQuotaPrivilege;

    LUID    SeUnsolicitedInputPrivilege;

    LUID    SeTcbPrivilege;

    LUID    SeSecurityPrivilege;

    LUID    SeTakeOwnershipPrivilege;

    LUID    SeLoadDriverPrivilege;

    LUID    SeCreatePagefilePrivilege;

    LUID    SeIncreaseBasePriorityPrivilege;

    LUID    SeSystemProfilePrivilege;

    LUID    SeSystemtimePrivilege;

    LUID    SeProfileSingleProcessPrivilege;

    LUID    SeCreatePermanentPrivilege;

    LUID    SeBackupPrivilege;

    LUID    SeRestorePrivilege;

    LUID    SeShutdownPrivilege;

    LUID    SeDebugPrivilege;

    LUID    SeAuditPrivilege;

    LUID    SeSystemEnvironmentPrivilege;

    LUID    SeChangeNotifyPrivilege;

    LUID    SeRemoteShutdownPrivilege;

 

    // Universally defined Sids

    //

 

    PSID  SeNullSid;

    PSID  SeWorldSid;

    PSID  SeLocalSid;

    PSID  SeCreatorOwnerSid;

    PSID  SeCreatorGroupSid;

 

    // Nt defined Sids

    //

 

    PSID  SeNtAuthoritySid;

    PSID  SeDialupSid;

    PSID  SeNetworkSid;

    PSID  SeBatchSid;

    PSID  SeInteractiveSid;

    PSID  SeLocalSystemSid;

    PSID  SeAliasAdminsSid;

    PSID  SeAliasUsersSid;

    PSID  SeAliasGuestsSid;

    PSID  SeAliasPowerUsersSid;

    PSID  SeAliasAccountOpsSid;

    PSID  SeAliasSystemOpsSid;

    PSID  SeAliasPrintOpsSid;

    PSID  SeAliasBackupOpsSid;

 

} SE_EXPORTS, *PSE_EXPORTS;

 

Figure 1 ? Pre-defined SIDs from SE_EXPORTS

 

Access to these must be ?activated? by ensuring that your driver calls the SeEnableAccessToExports macro because a failure to call this function will yield incorrect results.  Once enabled, you can use the SeExports variable to access these well-known values.

 

Constructing an SID

 

An alternative to using the standard SID values is to construct an SID directly.  Typically, this would only be done for the pre-defined values as an alternative to using the exported types.  For example, the code snippet in Figure 2 demonstrates how to construct an SID for ?local system?.

 

{

  //

  // Temporary stack based storage for an SID.

  //

  UCHAR sidBuffer[64];

  PISID localSid = (PISID) sidBuffer;

  SID_IDENTIFIER_AUTHORITY localSidAuthority = SECURITY_NT_AUTHORITY;

 

  //

  // Build the local system SID

  //

  RtlZeroMemory(sidBuffer, sizeof(sidBuffer));

       

  localSid->Revision = SID_REVISION;

  localSid->SubAuthorityCount = 1;

  localSid->IdentifierAuthority = localSidAuthority;

  localSid->SubAuthority[0] = SECURITY_LOCAL_SYSTEM_RID;

       

  //

  // Make sure it is valid

  //

  if (!RtlValidSid(localSid)) {

           

      DbgPrint("no dice - SID is invalid\n");

           

      return(1);

           

  }

       

}

 

 

Figure 2 ? Constructing a SID for a local system

 

 

In this code sample, we used the well-known ?local system? authority value (SECURITY_NT_AUTHORITY) and then associated with it a single ?Relative Identifier? or RID.  This RID value (SECURITY_LOCAL_SYSTEM_RID) is used to indicate that the calling process is part of the operating system.

 

Lastly, we used RtlValidSid to confirm that we built the SID correctly.  While we might not use this in production code, such crosschecks are valuable in debugging new security code.

 

Extracting the SID from the Token

 

Another technique, and one that can prove to be quite valuable, is to extract the SID from the token of the current thread or process (See Figure 3).  This is often used to determine the caller performing a particular I/O operation.  For example, when the CIFS File Server (srv.sys) is performing an operation that requires authentication it impersonates the client.  A file system (or filter) can determine the SID of the remote client and act appropriately based upon that information, rather than using the local system SID, which is normally used by SRV because it runs as a kernel mode driver.

 

NTSTATUS GetCallerSid(PUCHAR SidBuffer, PULONG SidBufferLength)

{

   

    UCHAR       buffer[256];

    PISID       sid = (PISID)&buffer[sizeof(TOKEN_USER)];

    NTSTATUS    status;

    HANDLE      handle;

    ULONG       tokenInfoLength;

    LONG        length;

   

    // sanity check

    //

    ASSERT(SidBuffer);

    ASSERT(SidBufferLength);

 

    if (*SidBufferLength == 0) {

 

        return STATUS_INSUFFICIENT_RESOURCES;

 

    }

 

    // open the thread token

    //

    status = ZwOpenThreadToken(NtCurrentThread(), TOKEN_READ, TRUE, &handle);

   

    if (status == STATUS_NO_TOKEN) {

 

        // No thread level token, so use the process

        // level token.  This is the common case since the only

        // time a thread has a token is when it is impersonating.

        //

        status = ZwOpenProcessToken(NtCurrentProcess(), TOKEN_READ, &handle);

 

    }

 

    //

    // This should have succeeded.  In this example, we

    // crash if it didn't work.

    //

    if (!NT_SUCCESS(status)) {

 

        return status;

 

    }

 

    //  Retrieve the user information from the token. 

    //

    status = ZwQueryInformationToken( handle,

                                      TokenUser,

                                      buffer,

                                      sizeof(buffer),

                                      &tokenInfoLength);

 

    // This call should always work.

    //

    if (!NT_SUCCESS(status)) {

 

        DbgPrint("ZwQueryInformationToken failure - status %x\n",status);

 

        return status;

 

    }

 

    length = tokenInfoLength - sizeof(TOKEN_USER);

 

    ASSERT(length > 0);

   

    if ((ULONG)length > *SidBufferLength) {

 

        DbgPrint("SidBufferLength too small - expected %d. got %d.\n", length,

                 *SidBufferLength);

 

        return STATUS_INSUFFICIENT_RESOURCES;

 

    }

 

    // copy the sid

    //

    *SidBufferLength = (ULONG)length;

 

    RtlCopyMemory(SidBuffer, sid, *SidBufferLength);

 

    //

    // for debug, let's see it

    //

    DbgPrint("SID (Revision %u, SubAuthorityCount %u):\n",

             sid->Revision,

             sid->SubAuthorityCount);

   

    DbgPrint("PsclUtilsGetSid:\tIdentifierAuthority = %u-%u-%u-%u-%u-%u\n",

             sid->IdentifierAuthority.Value[0],

             sid->IdentifierAuthority.Value[1],

             sid->IdentifierAuthority.Value[2],

             sid->IdentifierAuthority.Value[3],

             sid->IdentifierAuthority.Value[4],

             sid->IdentifierAuthority.Value[5]);

   

    if (sid->SubAuthorityCount) {

        ULONG   index;

       

        for (index = 0; index < sid->SubAuthorityCount;index++) {

           

            DbgPrint("PsclUtilsGetSid:\tSubAuthority = index %d value %u\n",

                     index,

                     sid->SubAuthority[index]);

           

        }

       

    }

        

    return STATUS_SUCCESS;

 

}

Figure 3 ? Technique For Extracting SID From Token

 

 

Note the technique used here was specifically designed to work, even in the face of impersonation.   This is accomplished by first attempting to open the token for the calling thread.  If that fails, the process token is used.  This works because normally threads rely upon their process credentials, rather than specific thread credentials.  In the case where the thread has credentials, it is probably because the thread is impersonating.  A process must have a token.

 

The routine ZwOpenThreadToken() is not presently included in ntddk.h or ntifs.h but has been discussed and published in a number of support forums.  The prototype for this function is shown in Figure 4.

 

NTSYSAPI

NTSTATUS

NTAPI

ZwOpenThreadToken(HANDLE ThreadHandle,

                                     ACCESS_MASK AccessMask,

                                     BOOLEAN OpenAsSelf,

                                     PHANDLE TokenHandle);

 

 

Figure 4 ? ZwOpenThreadToken()

 

 

The prototypes for  NtProcessToken() is included in ntifs.h.  Note that ZwProcessToken() has the same prototype, although it uses the normal system call mechanism to invoke NtProcessToken().

 

 

 

 

 

 

 

 

 

This article was printed from OSR Online http://www.osronline.com

Copyright 2017 OSR Open Systems Resources, Inc.