Checking if the current user is in the administrator group with WinAPI

1.5k views Asked by At

I'm trying to check if the current user is in the administrator group on Windows in my C application. I already tried the WinAPI function "IsUserAnAdmin", but it seems that this function only returns True if the current process also has admin privileges. But only because the current process is running with medium integrity doesn't mean that the user isn't in the administrator group. While I was searching for alternatives I discovered how metasploit does detect this: It simply runs the command "cmd.exe /c whoami /groups" and checks if the output contains "S-1-5-32-544", which is the administrators SID.

I was wondering if I could do the same thing in a more efficient way using the WinAPI without the cmd command?

3

There are 3 answers

2
RbMm On BEST ANSWER

if user member of BUILTIN\Administrators (S-1-5-32-544) group (alias) this sid present in it token groups. and usually only in this case (of course possible create token for not admin user with S-1-5-32-544 and for admin user without it). so simply and effective check - list token groups and look - are S-1-5-32-544 present here, with any attributes. IsUserAdmin not simply check for this sid, but

Even if a SID is present in the token, the system may not use the SID in an access check. The SID may be disabled or have the SE_GROUP_USE_FOR_DENY_ONLY attribute. The system uses only enabled SIDs to grant access when performing an access check.

when admin user (member of S-1-5-32-544 Alias) interactive login to system and UAC active - system filter it token, and set SE_GROUP_USE_FOR_DENY_ONLY attribute for S-1-5-32-544 (except built-in Administrator - S-1-5-32-500)

so code can be next:

inline ULONG BOOL_TO_ERROR(BOOL f)
{
    return f ? NOERROR : GetLastError();
}
 
ULONG IsUserInAdminGroup(BOOLEAN* pb)
{
    *pb = FALSE;
 
    HANDLE hToken;
    ULONG dwError = BOOL_TO_ERROR(OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken));
 
    if (dwError == NOERROR)
    {
        // /RTCs must be disabled !
        static volatile UCHAR guz = 0;
        PVOID stack = alloca(guz);
        ULONG cb = 0, rcb = 0x100;
 
        union {
            PVOID buf;
            PTOKEN_GROUPS ptg;
        };
 
        do 
        {
            if (cb < rcb)
            {
                cb = RtlPointerToOffset(buf = alloca(rcb - cb), stack);
            }
 
            dwError = BOOL_TO_ERROR(GetTokenInformation(hToken, ::TokenGroups, buf, cb, &rcb));
 
        } while (dwError == ERROR_INSUFFICIENT_BUFFER);
 
        CloseHandle(hToken);
 
        if (dwError == NOERROR)
        {
            if (ULONG GroupCount = ptg->GroupCount)
            {
                static const SID_IDENTIFIER_AUTHORITY NT_AUTHORITY = SECURITY_NT_AUTHORITY;
                PSID_AND_ATTRIBUTES Groups = ptg->Groups;
                do 
                {
                    PSID Sid = Groups++->Sid;
                    if (*GetSidSubAuthorityCount(Sid) == 2 &&
                        *GetSidSubAuthority(Sid, 0) == SECURITY_BUILTIN_DOMAIN_RID &&
                        *GetSidSubAuthority(Sid, 1) == DOMAIN_ALIAS_RID_ADMINS &&
                        !memcmp(&NT_AUTHORITY, GetSidIdentifierAuthority(Sid), sizeof(SID_IDENTIFIER_AUTHORITY)))
                    {
                        *pb = TRUE;
                        break;
                    }
                } while (--GroupCount);
            }
 
            return NOERROR;
        }
    }
 
    return dwError;
}

also possible do direct check of user sid from token - are it member of DOMAIN_ALIAS_RID_ADMINS alias. here question - how exactly is the task, why this necessary at all. example of code (used ntsam.h and linked with samlib.lib - part of standard windows SDK)

HRESULT IsUserInAdminGroup(PSID UserSid, BOOLEAN* pb)
{
    SAM_HANDLE ServerHandle, DomainHandle;

    NTSTATUS status = SamConnect(0, &ServerHandle, SAM_SERVER_LOOKUP_DOMAIN, 0);

    if (0 <= status)
    {
        ULONG len = GetSidLengthRequired(1);

        PSID BuiltIn = (PSID)alloca(len);
        static const SID_IDENTIFIER_AUTHORITY NT_AUTHORITY = SECURITY_NT_AUTHORITY;

        InitializeSid(BuiltIn, const_cast<SID_IDENTIFIER_AUTHORITY*>(&NT_AUTHORITY), 1);
        *GetSidSubAuthority(BuiltIn, 0) = SECURITY_BUILTIN_DOMAIN_RID;

        status = SamOpenDomain(ServerHandle, DOMAIN_READ, BuiltIn, &DomainHandle);
        
        SamCloseHandle(ServerHandle);

        if (0 <= status)
        {
            ULONG MembershipCount, *Aliases;
            
            status = SamGetAliasMembership(DomainHandle, 1, &UserSid, &MembershipCount, &Aliases);
            
            SamCloseHandle(DomainHandle);

            if (0 <= status)
            {
                PVOID buf = Aliases;
                if (MembershipCount)
                {
                    do 
                    {
                        if (*Aliases++ == DOMAIN_ALIAS_RID_ADMINS)
                        {
                            *pb = TRUE;
                            break;
                        }
                    } while (--MembershipCount);
                }
                SamFreeMemory(buf);
            }
        }
    }

    return HRESULT_FROM_NT(status);
}

HRESULT IsUserInAdminGroup(BOOLEAN* pb)
{
    *pb = FALSE;

    HANDLE hToken;
    ULONG dwError = BOOL_TO_ERROR(OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken));

    if (dwError == NOERROR)
    {
        // /RTCs must be disabled !
        static volatile UCHAR guz = 0;
        PVOID stack = alloca(guz);
        ULONG cb = 0, rcb = 0x80;

        union {
            PVOID buf;
            PTOKEN_USER ptu;
        };

        do 
        {
            if (cb < rcb)
            {
                cb = RtlPointerToOffset(buf = alloca(rcb - cb), stack);
            }

            dwError = BOOL_TO_ERROR(GetTokenInformation(hToken, ::TokenUser, buf, cb, &rcb));

        } while (dwError == ERROR_INSUFFICIENT_BUFFER);

        CloseHandle(hToken);

        if (dwError == NOERROR)
        {
            return IsUserInAdminGroup(ptu->User.Sid, pb);
        }
    }

    return HRESULT_FROM_WIN32(dwError);
}
0
Louis Bernard On

As I've seen, others have posted their own solutions already. I really appreciate this! In the meanwhile I've found a way similar to @Florent's approach.

First I get the admin group name with "LookupAccountSid" and the SID "S-1-5-32-544", then I enumerate the local groups with "NetUserGetLocalGroups" and compare every group name with the one I aquired from the first function.

My code looks like this:

BOOL isUserAdmin() {

    BOOL retVal = FALSE;

    PSID psid;
    wchar_t buffName[256];
    DWORD buffNameSize = 256;
    wchar_t buffDomain[256];
    DWORD buffDomainSize = 256;
    SID_NAME_USE SidType = SidTypeGroup;
    LPBYTE buffer;
    DWORD entries, total_entries;

    convert_string_sid_to_sid(L"S-1-5-32-544", &psid);
    lookup_account_sid(NULL, psid, buffName, &buffNameSize, buffDomain, &buffDomainSize, &SidType);

    wchar_t user[256];
    DWORD size = sizeof(user) / sizeof(user[0]);
    get_user_name(user, &size);

    net_user_get_local_groups(NULL, user, 0, LG_INCLUDE_INDIRECT, &buffer, MAX_PREFERRED_LENGTH, &entries, &total_entries);
    LOCALGROUP_USERS_INFO_0* groups = (LOCALGROUP_USERS_INFO_0*)buffer;
    for (size_t i = 0; i < entries; i++) {
        if (issame(groups[i].lgrui0_name, buffName)) {
            retVal = TRUE;
        }
    }
    net_api_buffer_free(buffer);
    GlobalFree(psid);
    return retVal;
}

I think this is the shortest solution here, but I'm not sure if it's the most effective and reliable one.

2
Florent On

Exemple using the following win32 api:

GetUserName

NetUserGetLocalGroups

ConvertStringSidToSidW (with SID of group S-1-5-32-544)

LookupAccountSidW

#ifndef UNICODE
#define UNICODE
#endif 

#include <stdio.h>
#include <winbase.h>
#include <lmaccess.h>
#include <lmapibuf.h>
#include <lmerr.h>
#include <sddl.h>

#pragma comment(lib, "netapi32.lib")

int main() {
    // 1) ---- Get the user name --
    wchar_t myBuffer1[5001];
    DWORD myBufferSize; 
    myBufferSize = sizeof myBuffer1;
    
    wprintf(L"\nTaille:%d",myBufferSize);   
    GetUserNameW(myBuffer1, &myBufferSize);
    wprintf(L"\nUSER:%ls",myBuffer1);
    
    // 2)---- Get group name of administrators : S-1-5-32-544 (multi lang compatibility)
    PSID  adminSid = NULL;
    if (!ConvertStringSidToSidW(L"S-1-5-32-544", &adminSid)) 
        wprintf(L"\nFail ConvertStringSidToSidW");
    else {
        wchar_t adminName[1000];
        DWORD adminNameSize = sizeof adminName;
        wchar_t domainName[1000];
        DWORD domainNameSize = sizeof domainName;
        SID_NAME_USE sidType = SidTypeGroup;

        if (LookupAccountSidW(NULL, adminSid, adminName, &adminNameSize, domainName, &domainNameSize, &sidType))        {
            wprintf(L"\nAdministrators group name:%ws\n", adminName);
        } else  printf("LookupAccountSidW Error code: %d", GetLastError());
    }
    if (adminSid) LocalFree(adminSid);


    // 3) ---- Get groups of the users
    int err = 0;
    LPLOCALGROUP_USERS_INFO_0 pBufGrp = NULL; // allocated by fct call NetUserGetLocalGroups()
    DWORD dwEntriesRead = 0;    // to get the number readed entries
    DWORD dwTotalEntries = 0;   // to get the number of EXISTING entries
    NET_API_STATUS nStatus;
    switch (nStatus = NetUserGetLocalGroups(
            NULL,       //(LPCWSTR servername) NULL -> LOCAL COMPUTER
            (LPCWSTR)  myBuffer1,  //LPCWSTR username,
            0,          // DWORD   level, the only choice in the doc.
            LG_INCLUDE_INDIRECT,   //DWORD   flags,
            (LPBYTE *) &pBufGrp,  //LPBYTE  *bufptr, Buffer that will contain groups
            MAX_PREFERRED_LENGTH,  // DWORD   prefmaxlen, here no restriction on amount of memory
            &dwEntriesRead,
            &dwTotalEntries))   {
        case NERR_Success: break;
        case ERROR_ACCESS_DENIED: err++;        
        case ERROR_INVALID_LEVEL: err++;
        case ERROR_INVALID_PARAMETER: err++;
        case ERROR_MORE_DATA: err++;
        case ERROR_NOT_ENOUGH_MEMORY: err++;
        case NERR_DCNotFound: err++;
        case NERR_UserNotFound: err++;      
        case RPC_S_SERVER_UNAVAILABLE: err++;       
        default: 
                if (!err) printf("\nunknown error %d",nStatus);
                else printf("\nechec %d",err); 
                if (pBufGrp != NULL) NetApiBufferFree(pBufGrp); 
                return 2;   
            
    }   
    printf("\nNumber of groups read %d/%d",dwEntriesRead , dwTotalEntries); 
    
    for (DWORD i = 0 ; i < dwEntriesRead ; i++) {
        wprintf(L"\nGroupe Nun:%d%ls",i,pBufGrp[i].lgrui0_name);
    }
    
    if (pBufGrp != NULL) NetApiBufferFree(pBufGrp);     
    return 0;
}