Fun things to do with SIDs

As I mentioned in my previous article, SIDs are fascinating beasts.

Consider domain SIDs.  As I mentioned yesterday, domain SIDs have the form S-1-5-5-X-Y.  But where do X and Y come from?

S-1-5-5-X is the “domain” SID (X is the domain RID).  It turns out that in current versions of NT, X is always a 96bit random number (3 RIDs).  It’s calculated when the domain is created. And when you establish trust relationships between domains, the SIDs in the foreign domains are filtered out to prevent a foreign domain that has the same value of X (a highly unlikely occurrence, but possible).

Now let’s consider Y.  Before Windows 2000, the allocation of Y was easy.  There was a strict domain hierarchy consisting of a primary domain controller that handled all account modifications, and backup domain controllers that handled authentication requests.  Since all account creation occurred on the primary domain controller, all the PDC did was to look in the SAM database for the highest previously allocated user RID, increment that value, assign the new value to the new user account, then write the incremented value back into the database.

For Windows 2000, the system went to a multi-master replication scheme – every domain controller could create user accounts.  To handle that case, the RID FSMO (Flexible Single Master Operations) DC (essentially a broker for RID allocations) for divided the user RID space into a set of ranges.  As each DC needed more RIDs, it asked the RID FSMO DC for a new allocation, and the RID FSMO DC would assign a range to the DC.

Now for the fun stuff.

If you are a user on a domain, it’s relatively trivial to determine the well known groups in the domain.  For example, if you have your current processes SID (which you get by calling OpenProcessToken() and calling GetTokenInformation() asking for the TokenUser information).

To get the domain administrators group for a given SID, you can do the following:

      PSID GetWellKnownDomainSid(PSID UserSid, WELL_KNOWN_SID_TYPE DomainSidType)
{
SID_IDENTIFIER_AUTHORITY sia = SECURITY_NT_AUTHORITY;
PSID domainSid;
PSID sidToReturn;
DWORD sidSize = SECURITY_MAX_SID_SIZE;
//
// Check to ensure that the calling SID is an NT SID
//

if (memcmp(GetSidIdentifierAuthority(UserSid), &sia, sizeof(SID_IDENTIFIER_AUTHORITY)) != 0) {
return NULL;
}

            //
// Now allocate the domain SID for the specified user SID
//
if (!AllocateAndInitializeSid(&sia, 3, GetSidSubAuthority(UserSid, 0), GetSidSubAuthority(UserSid, 1), GetSidSubAuthority(UserSid, 2), 0, 0, 0, 0, 0, &domainSid))
{
return NULL;
}
sidToReturn = LocalAlloc(LMEM_FIXED, sidSize);
if (sidToReturn == NULL)
{
FreeSid(domainSid);
return NULL;
}
if (!CreateWellKnownSid(DomainSidType, domainSid, sidToReturn, &sidSize))
{
FreeSid(domainSid);
return NULL;
}
FreeSid(domainSid);
return sidToReturn;
}

So to get the domain admin SID for a user SID, you simply call GetWellKnownDomainSid(psid, WinBuiltinAdministratorsSid);

Caveat Lector: I’ve not tested this code, it’s likely it may have bugs in it, but as a proof of concept, it’s accurate.

Comment: If you’re willing to modify the callers SID, the code to AllocateAndInitializeSid() can be replaced with *GetSidSubAuthorityCount(UserSid) = 3.

Edit: domain SID->domain RID