OSRLogo
OSRLogoOSRLogoOSRLogo x Subscribe to The NT Insider
OSRLogo
x

Everything Windows Driver Development

x
x
x
GoToHomePage xLoginx
 
 

    Fri, 20 Oct 2017     115100 members

   Login
   Join


 
 
Contents
  Online Dump Analyzer
OSR Dev Blog
The NT Insider
Downloads
ListServer / Forum
Driver Jobs
  Express Links
  · The NT Insider Digital Edition - May-June 2016 Now Available!
  · Windows 8.1 Update: VS Express Now Supported
  · HCK Client install on Windows N versions
  · There's a WDFSTRING?
  · When CAN You Call WdfIoQueueP...ously

Keeping Secrets - Windows NT Security (Part II)

 

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().

 

 

 

 

 

 

 

 

 

Related Articles
Keeping Secrets - Windows Security (Part III)
Keeping Secrets - Windows NT Security (Part I)
You've Gotta Use Protection -- Inside Driver & Device Security
Still Feeling Insecure? - IoCreateDeviceSecure( ) for Windows 2K/XP/.NET
Securing Device Interfaces - A Better Approach than Sending an SD
Locking Down Drivers - A Survey of Techniques
What is Coming with Vista - Limited User Access

User Comments
Rate this article and give us feedback. Do you find anything missing? Share your opinion with the community!
Post Your Comment

"Use Ex version"
Microsoft has added Ex versions for ZwOpenThreadToken and ZwOpenProcessToken - the only difference is that you can specify the OBJ_KERNEL_HANDLE flag (which is done to ensure the correct behavior of the code. Otherwise there is a potential attack.)

Rating:
24-Jan-10, Tony Mason


Post Your Comments.
Print this article.
Email this article.

Writing WDF Drivers I: Core Concepts
LAB

Nashua (Amherst), NH
15-19 May 2017

Writing WDF Drivers II: Advanced Implementation Techniques
LAB

Nashua (Amherst), NH
23-26 May 2017

Kernel Debugging and Crash Analysis
LAB

Dulles (Sterling), VA
26-30 Jun 2017

Windows Internals and Software Driver Development
LAB

Nashua (Amherst), NH
24-28 Jul 2017

 
 
 
 
x
LetUsHelp
 

Need to develop a Windows file system solution?

We've got a kit for that.

Need Windows internals or kernel driver expertise?

Bring us your most challenging project - we can help!

System hangs/crashes?

We've got a special diagnostic team that's standing by.

Visit the OSR Corporate Web site for more information about how OSR can help!

 
bottom nav links