使用 WinAPI 检查当前用户是否在管理员组中

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

我正在尝试检查当前用户是否在我的 C 应用程序 Windows 上的管理员组中。我已经尝试过 WinAPI 函数“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 确实检测到这一点:它只是运行命令“cmd.exe /c whoami /groups”并检查输出是否包含“S-1-5-32-544” ,这是管理员 SID。

我想知道是否可以在不使用 cmd 命令的情况下使用 WinAPI 以更有效的方式做同样的事情?

示例使用以下 win32 api:

获取用户名

NetUserGetLocalGroups

ConvertStringSidToSidW(SID 为 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;
}

if BUILTIN\Administrators (S-1-5-32-544) 组(别名)的用户成员sid 出现在它的令牌组中。通常仅在这种情况下(当然可以为非管理员用户使用 S-1-5-32-544 创建令牌,而对于没有它的管理员用户)。如此简单有效的检查 - 列出令牌组并查看 - 此处显示 S-1-5-32-544,具有 any 属性。 IsUserAdmin 不是简单地检查这个 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.

当管理员用户(S-1-5-32-544 别名的成员)交互式登录系统并且 UAC 活动时 -系统过滤它的token,并为S-1-5-32-544设置SE_GROUP_USE_FOR_DENY_ONLY属性(内置管理员 - S-1-5-32-500)

所以代码可以是下一个:

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;
}

也可以通过令牌直接检查用户 sid - 它是否是 DOMAIN_ALIAS_RID_ADMINS 别名的成员。这里的问题 - 任务究竟是什么,为什么这是必要的。代码示例(使用 ntsam.h 并与 samlib.lib 链接 - 标准 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);
}

正如我所见,其他人已经发布了他们自己的解决方案。我真的很感激! 与此同时,我发现了一种类似于@Florent 的方法。

首先,我使用“LookupAccountSid”和 SID“S-1-5-32-544”获取管理组名称,然后使用“NetUserGetLocalGroups”枚举本地组,并将每个组名与我的组名进行比较从第一个函数获取。

我的代码如下所示:

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;
}

我认为这是最短的解决方案,但我不确定它是否是最有效和最可靠的解决方案。