Programming User Context

[Previous] [Next]

We will be learning a number of powerful and interesting techniques that can be performed by manipulating tokens in Windows 2000. The first step down the path to all the fun is to acquire a handle to a token. So let's begin our journey by obtaining a handle to a process's token:

 BOOL OpenProcessToken(    HANDLE  hProcessHandle,    DWORD   dwDesiredAccess,    PHANDLE phTokenHandle); 

The OpenProcessToken function retrieves a handle to a process's token. The hProcessHandle parameter is the handle to the process for which we wish to open the token. Unfortunately the system does not allow you to pass NULL to indicate your desire to retrieve a handle to your own process's token. But a quick call to GetCurrentProcess returns a pseudo-handle that can be passed as the hProcessHandle parameter. The dwDesiredAccess parameter indicates how the token is to be used. You should request only the rights that your code needs. Table 11-2 describes the available token access rights. Many of the functions listed in the table will be discussed later in this chapter.

The variable whose address you pass as phTokenHandle receives a handle to the requested token. If the return value of OpenProcessToken is TRUE, the function succeeded; otherwise the function failed, in which case you should call GetLastError for more information.

Table 11-2. Token access rights

Value Description
TOKEN_ADJUST_DEFAULT Required when calling SetTokenInformtion (discussed shortly) to change features of the token, such as the default owner, primary group, or default DACL.
TOKEN_ADJUST_GROUPS Required in a call to AdjustTokenGroups.
TOKEN_ADJUST_PRIVILEGES Required in a call to AdjustTokenPrivileges.
TOKEN_ADJUST_SESSIONID Required to adjust the session ID of token and requires the SE_TCB_NAME privilege.
TOKEN_ASSIGN_PRIMARY Required when using the token in calls to CreateProcessAsUser.
TOKEN_DUPLICATE Required to duplicate the token.
TOKEN_EXECUTE Equals STANDARD_RIGHTS_EXECUTE. See Chapter 10 for further discussion of standard rights.
TOKEN_IMPERSONATE Required to use this token with ImpersonateLoggedOnUser.
TOKEN_QUERY Required to read any token information other than its source using GetTokenInformation.
TOKEN_QUERY_SOURCE Required to read the token's source using GetTokenInformation.
TOKEN_READ Combines STANDARD_RIGHTS_READ and TOKEN_QUERY. See Chapter 10 for further discussion of standard rights.
TOKEN_WRITE Combines STANDARD_RIGHTS_WRITE, TOKEN_ADJUST_PRIVILEGES, TOKEN_ADJUST_GROUPS, and TOKEN_ADJUST_DEFAULT. See Chapter 10 for further discussion of standard rights.
TOKEN_ALL_ACCESS Complete access to the token, combining all rights.

The most likely reason for OpenProcessToken to fail is insufficient access rights; however, if your service is running in the LocalSystem user context, it will probably have sufficient access to any process's token. (For more information on access control, see Chapter 10.)

Token objects are kernel objects, and as with most kernel objects, you should pass the token object's handle to CloseHandle when you are finished using the resource.

NOTE
The handle you receive when calling OpenProcessToken is the handle of the token, which affects the user context of that process. Anything you do to adjust the token can have dramatic and immediate effects on the secure behavior of the process from which you retrieved the token. I will be discussing what you can do to a token shortly. For information on how to obtain the handle to a process (other than the current process), see the discussion of OpenProcess in Chapter 22 of Programming Applications for Microsoft Windows, Fourth Edition (Jeffrey Richter, Microsoft Press, 1999).

If you guessed that there must also be a way to obtain a thread's token (assuming the thread is impersonating at the time), you are correct. You can do this using OpenThreadToken:

 BOOL OpenThreadToken(    HANDLE  hThreadHandle,    DWORD   dwDesiredAccess,    BOOL    fOpenAsSelf,    PHANDLE phTokenHandle); 

Notice that OpenThreadToken is very similar to OpenProcessToken except that the first parameter is a handle to a thread rather than a process, and it has the additional parameter fOpenAsSelf. This parameter indicates to the system who you want to open the token as. Let me explain.

Remember that when you call OpenThreadToken you are requesting a token, which is a secured object in Windows. This means that you might or might not have access to the object. Remember also that you are running in a thread that could be impersonating a security context other than your process's security context. Because it is very common for a thread to retrieve a handle to its own token when it is impersonating, the system allows you to indicate that you want all access checks that are necessary to open a thread's token to be performed against your process's token. You should pass TRUE for the fOpenAsSelf parameter of OpenThreadToken if you want access checks to be performed against your process's token. You should pass FALSE if you want access checks to be performed against your thread's impersonation token. This parameter has no meaning if your thread is not currently impersonating. (I will discuss impersonation in more detail later in this chapter.)

Like OpenProcessToken, the phTokenHandle parameter of OpenThreadToken returns a handle to the requested token. If the return value of OpenThreadToken is TRUE, the function succeeded; otherwise the function failed, in which case you should call GetLastError for more information.

If the thread is not impersonating, the OpenThreadToken function will fail and GetLastError will return ERROR_NO_TOKEN.

So now you have a handle to a token. What can you do with it? you ask. Read on.

Reading Token Information

Most of the token information listed in Table 11-1 can also be read from a token, assuming that the calling code has TOKEN_QUERY (or TOKEN_QUERY_SOURCE) access to the object. You should use the GetTokenInformation function to find the contents of a token:

 BOOL GetTokenInformation(    HANDLE hTokenHandle,    TOKEN_INFORMATION_CLASS tokenInformationClass,    PVOID  pTokenInformation,    DWORD  dwTokenInformationLength,    PDWORD pdwReturnLength); 

You should pass a handle to a token as the hTokenHandle parameter. The tokenInformationClass parameter indicates what information you wish to obtain from the token, and the pTokenInformation parameter is a pointer to a buffer that the system fills with the requested information. The dwTokenInformationLength parameter indicates the length of the buffer you passed, and the pdwReturnLength parameter points to a variable that receives the size of the buffer necessary to retrieve the information.

The buffer pointed to by the pTokenInformation parameter varies in type, depending on the tokenInformationClass parameter. The following list describes the information that you can retrieve from a token: the information class value and the data type used. (The TOKEN_INFORMATION_CLASS is also used with the SetTokenInformation function, discussed later in this chapter.)

  • TokenUser Returns the SID of the token user. This is the user whose account name was used to create the token during logon. (This value is not used with SetTokenInformation.)
  • typedef struct _TOKEN_USER {    SID_AND_ATTRIBUTES User; } TOKEN_USER;

  • TokenDefaultDacl Used to read or set a token's default DACL. See Chapter 10 for further discussion of default DACLs as well as detailed discussion on building a DACL. The TOKEN_ADJUST_DEFAULT access right is required to set information in the DACL.
  •  typedef struct _TOKEN_DEFAULT_DACL {    PACL DefaultDacl; } TOKEN_DEFAULT_DACL;

  • TokenOwner Used to read or set the default owner of the token object. (See Chapter 10 for discussion of object ownership.) The TOKEN_ADJUST_DEFAULT access right is necessary to set the owner of a token.
  • typedef struct _TOKEN_OWNER {    PSID Owner; } TOKEN_OWNER;

  • TokenPrimaryGroup Reads or sets the token's primary group. The TOKEN_ADJUST_DEFAULT access right is required in calls to SetTokenInformation.
  • typedef struct _TOKEN_PRIMARY_GROUP {    PSID PrimaryGroup; } TOKEN_PRIMARY_GROUP;

  • TokenGroups Returns the SIDs of the groups associated with the token. (This value is not used with SetTokenInformation.)
  • typedef struct _TOKEN_GROUPS {    DWORD GroupCount;    SID_AND_ATTRIBUTES Groups[ANYSIZE_ARRAY]; } TOKEN_GROUPS;

  • TokenPrivileges Returns the privileges associated with the token. (This value is not used with SetTokenInformation.)
  • typedef struct _TOKEN_PRIVILEGES {    DWORD PrivilegeCount;    LUID_AND_ATTRIBUTES Privileges[ANYSIZE_ARRAY]; } TOKEN_PRIVILEGES;

  • TokenSource Returns the token's source. This is a textual string representing the creating entity of the token. The TOKEN_QUERY_SOURCE access right is necessary to retrieve this information. (This value is not used with SetTokenInformation.)
  • typedef struct _TOKEN_SOURCE {    CHAR SourceName[8];    LUID SourceIdentifier; } TOKEN_SOURCE;

  • TokenType Returns the token's type. The possible values are TokenPrimary and TokenImpersonation. The pTokenInformation parameter of GetTokenInformation will return a single TOKEN_TYPE indicating the token's type. (This value is not used with SetTokenInformation.)
  • typedef enum _TOKEN_TYPE {    TokenPrimary = 1,    TokenImpersonation } TOKEN_TYPE;

  • TokenImpersonationLevel Returns impersonation level. See Table 11-3 for more information. (This value is not used with SetTokenInformation.)
  • typedef enum _SECURITY_IMPERSONATION_LEVEL {    SecurityAnonymous,    SecurityIdentification,    SecurityImpersonation,    SecurityDelegation } SECURITY_IMPERSONATION_LEVEL;

  • TokenStatistics Returns general information about the token. Members of note include GroupCount, PrivilegeCount, and ModifiedId. The ModifiedId member is a locally unique identifier (LUID) that changes each time the token is modified. Your code can use this value to detect whether or not a token has changed since the last time you checked. This detection ability can be very useful in writing fault-tolerant code that calls into third party DLLs or libraries. (This value is not used with SetTokenInformation.)
  • typedef struct _TOKEN_STATISTICS {    LUID TokenId;    LUID AuthenticationId;    LARGE_INTEGER ExpirationTime;    TOKEN_TYPE TokenType;    SECURITY_IMPERSONATION_LEVEL ImpersonationLevel;    DWORD DynamicCharged;    DWORD DynamicAvailable;    DWORD GroupCount;    DWORD PrivilegeCount;    LUID ModifiedId; } TOKEN_STATISTICS;

  • TokenRestrictedSids Returns the token's restricted SIDs. Restricted tokens are covered in detail later in this chapter. (This value is not used with SetTokenInformation.)
  • typedef struct _TOKEN_GROUPS {    DWORD GroupCount;    SID_AND_ATTRIBUTES Groups[ANYSIZE_ARRAY]; } TOKEN_GROUPS;

  • TokenSessionId Indicates a Terminal Server session ID for the token. The pTokenInformation parameter of GetTokenInformation will return a DWORD. The value of the DWORD is 0 if Terminal Server is not installed on the local machine or if the token is associated with the Terminal Server console. Otherwise the token is associated with a Terminal Server client, and the value of the DWORD is a session ID for the client. (This value is not used with SetTokenInformation.)
  • DWORD

As the previous list shows, you can read a great deal of information from a token. Here are the most common items to retrieve from a token:

  • The token user SID The user account that the token represents. This information is commonly received from a token to find out who is executing the code. You can pass this SID to LookupAccountSid (discussed in Chapter 9) to get the textual name of the user account.
  • The logon SID This SID is buried in with the token groups, so it takes a little digging to retrieve (which I will discuss in a moment). However, the logon SID can be very convenient for uniquely identifying the session. If a user logs on to a machine running Windows 2000 more than once (either interactively or through other means), the system creates a unique logon SID for each session, regardless of whether the token user is the same from session to session.
  • Token groups Useful for finding the groups associated with a token. However, if you want to find out whether a token has a single group, you can use the CheckTokenMembership function.
  • Token default DACL Useful for finding out exactly what the DACL of a newly created object will be if you pass NULL for the security attributes when creating the object.

When retrieving information from a token, you will typically have to call GetTokenInformation to find out the required length of the buffer, and then call it again to actually retrieve the information. To create truly fault-tolerant code, you should be prepared to call GetTokenInformation repeatedly until you have successfully retrieved the desired information. This is because the size of token information (such as the default DACL) can change between the time you call GetTokenInformation to retrieve the buffer size and the time you call GetTokenInformation to retrieve the actual data. This condition in multithreaded programming is known as a race condition and can cause those hard-to-find bugs that appear only once every several months or so.

The following function shows how to call GetTokenInformation properly to receive information about a token. It also returns the token information in a buffer allocated using LocalAlloc. If you use this function in your code, you should free the returned buffer using LocalFree when you are finished with the buffer.

 LPVOID AllocateTokenInformation(HANDLE hToken,     TOKEN_INFORMATION_CLASS tokenClass ){    PVOID    pvBuffer = NULL;    __try{       BOOL fSuccess;       // Initial buffer size       ULONG    lSize = 0 ;       do       {          // Do we have a size yet?          if (lSize != 0)          {             // Do we already have a buffer?             if (pvBuffer != NULL)                LocalFree(pvBuffer);// Then free it             // Allocate a new buffer              pvBuffer = LocalAlloc(LPTR, lSize) ;             if(pvBuffer == NULL)                __leave;          }          // Try again          fSuccess = GetTokenInformation( hToken, tokenClass,              pvBuffer, lSize, &lSize ) ;       // Still not enough buffer?       }while( !fSuccess && (GetLastError() ==          ERROR_INSUFFICIENT_BUFFER)) ;              // If we failed for some other reason, back out       if(!fSuccess)       {          if(pvBuffer)             LocalFree(pvBuffer) ;          pvBuffer = NULL;       }    }__finally{}    // Return locally allocated buffer    return (pvBuffer) ; } 

The following code fragment shows the use of this function to retrieve the token's user SID and default DACL for the current process.

 HANDLE hToken; if(!OpenProcessToken( GetCurrentProcess(), TOKEN_QUERY, &hToken)){    // Error } TOKEN_USER* ptUser = (TOKEN_USER*)AllocateTokenInformation(hToken,    TokenUser);  if ( ptUser != NULL){    // Do something with the SID pointed to by ptUser->User } TOKEN_DEFAULT_DACL* ptDACL =     (TOKEN_DEFAULT_DACL*)AllocateTokenInformation(hToken,    TokenDefaultDacl);  if ( ptDACL != NULL){    // Do something with the DACL pointed to by ptDACL->DefaultDacl } 

Notice that a number of the structures returned by GetTokenInformation use the SID_AND_ATTRIBUTES structure, which is defined as follows:

 typedef struct _SID_AND_ATTRIBUTES {     PSID  Sid;     DWORD Attributes;  } SID_AND_ATTRIBUTES ; 

This structure includes a SID for a trustee account and an Attributes member, which includes information about the SID. See the Platform SDK documentation for the meaning of the Attributes member for specific structures that include a SID_AND_ATTRIBUTES structure.

The following function, AllocateTokenLogonSID, uses the AllocateTokenInformation sample function to retrieve the token's group SIDs, and then iterates through the SIDs to find the logon SID discussed earlier in this section. The function finds the logon SID by checking the Attributes member for the SE_GROUP_LOGON_ID flag. The returned PSID should be freed using LocalFree.

 PSID AllocateTokenLogonSID(HANDLE hToken){    PSID psidLogon = NULL;    TOKEN_GROUPS* ptGroups = NULL;    __try{       // Get the token groups       ptGroups = (TOKEN_GROUPS*)          AllocateTokenInformation(hToken, TokenGroups);       if (ptGroups == NULL)          __leave;       // Find the logon SID       int nCount = ptGroups->GroupCount;       while (nCount--){          if ((ptGroups->Groups[nCount].Attributes & SE_GROUP_LOGON_ID)              != 0)             break;       }       if (nCount == -1)          __leave; // No logon SID found       // Get memory for the returned SID       ULONG lLen = GetLengthSid(ptGroups->Groups[nCount].Sid);       psidLogon = (PSID)LocalAlloc(LPTR, lLen);       if (psidLogon == NULL)          __leave;       // Copy the logon SID       if(!CopySid(lLen, psidLogon, ptGroups->Groups[nCount].Sid)){          LocalFree(psidLogon);          psidLogon = NULL;          __leave;       }    }__finally{       if (ptGroups != NULL)          LocalFree(ptGroups);    }    return (psidLogon); } 

The TokenMaster Sample Application

The TokenMaster sample application ("11 TokenMaster.exe") demonstrates the use of the token-related system functions, including GetTokenInformation and SetTokenInformation. The source code and resource files for the sample application are in the 11-TokenMaster directory on the companion CD. The program allows you to acquire a token from one of four sources: a process, a thread, a user's credentials, or via duplication. The program also allows you to view and modify token information. When the user executes TokenMaster, the dialog box in Figure 11-1 appears.

click to view at full size.

Figure 11-1. User interface for the TokenMaster sample application

The source code for this sample will help you understand how to apply the concepts discussed in this chapter. As a user of the TokenMaster sample application, you can explore the many capabilities of user context in Windows 2000. I will describe TokenMaster before I discuss some of the coding techniques so that you can use the sample to test some of the concepts in the chapter.

When TokenMaster is executed, its first task is to check its own user identity. If it is running under any account other than the LocalSystem account, it does the following in an attempt to upgrade itself:

  1. Enumerates the processes running in the system, and locates the System process.
  2. Uses OpenProcessToken to acquire a handle to the System process's token.
  3. Uses CreateProcessAsUser (which is discussed later in this chapter) to re-execute itself under the LocalSystem user context.
  4. If all three steps succeed, TokenMaster exits knowing that a new and more powerful instance of itself has been launched.

The features of TokenMaster are far more educational for you if you run the sample application under the LocalSystem account, but special privileges are required to take the first three steps. If you are interested in executing TokenMaster to its full potential, you must do the following:

  1. Log on as an administrator of the system.
  2. Add the "Increase Quotas" and "Replace a Process Level Token" privileges to your user account using the TrusteeMan sample application from Chapter 9 or the Microsoft Management Console (MMC) Group Policy snap-in.
  3. Log out, and then log on again so that the new privileges take effect.
  4. Launch TokenMaster.

If you are successful, when you execute TokenMaster, the Status window in the application should contain the following message: "Token Master, Status - Token Master running as SYSTEM". Otherwise, the window will report that TokenMaster is running as the account you used to launch the application.

The primary function of the application is to view information about a token and modify the information that can be changed. The following are ways to acquire a token:

  • Open a token from an existing process or thread You can select a process and additionally a thread (if you are interested in opening an impersonation token) by using the Processes and Threads combo boxes. Click the button labeled OpenProcessToken or OpenThreadToken to grab the token.
  • Retrieve a process from the system by using a user's credentials You can type in a username and password and select a logon type and provider. TokenMaster uses LogonUser (discussed later in this chapter) to retrieve a token from the system.
  • Duplicate an existing token If you have already acquired a token for use by TokenMaster, you can make a duplicate token by clicking the DuplicateTokenEx button. This duplication option allows you to set the impersonation level and the token type of the new token. If TokenMaster has a token that can be duplicated, the Token Information window will display the token information.

After you have acquired a token, you will see a listing of information about the token in the Token Information window. You can view all retrievable information for a token by using the GetTokenInformation function.

Using TokenMaster, you can modify and adjust the token in a variety of ways:

  • Adjust token groups and privileges, including the ability to enable or disable individual items.
  • Modify a token's default DACL. See Chapter 10 for more information on default DACLs.
  • Create a restricted token.
  • Launch executables using a token. This is a particularly useful feature of TokenMaster. You can create a new process by using a token that you have modified using TokenMaster. This allows you to change a token or restrict it in some way, and then launch code to see how the process is affected.

I strongly suggest that you spend some time using the TokenMaster sample application to become familiar with the user context-related features of Windows.

You will also find that TokenMaster can be used to ease the process of debugging. For example, I often launch the application and "steal" a token from the LocalSystem process. I then use this token to execute the Microsoft Visual Studio development environment, with which I can compile and test code from the LocalSystem user account. In terms of security, using the LocalSystem token when testing approximates the behavior of code running in a service.

The source code for this sample application demonstrates a number of useful programming tactics. First, it calls nearly every system function covered in this chapter. It also offers some tips that might be useful to you if you are new to security programming for Windows.

Windows security functions often require the allocation and reallocation of small buffers. This requirement can often be viewed as troublesome by developers, including myself. As a result, it is often tempting to hard-code buffer sizes or make other concessions that can lead to code that isn't airtight. The TokenMaster sample application addresses this requirement by making use of a simple template class known as CAutoBuf. You will see that this class greatly simplifies code where variable-sized buffers are required for system functions.



Programming Server-Side Applications for Microsoft Windows 2000
Programming Server-Side Applications for Microsoft Windows 2000 (Microsoft Programming)
ISBN: 0735607532
EAN: 2147483647
Year: 2000
Pages: 126

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net