0

我正在尝试检查当前用户是否在我的 C 应用程序中的 Windows 管理员组中。我已经尝试过 WinAPI 函数“ IsUserAnAdmin ”,但如果当前进程也具有管理员权限,该函数似乎只返回 True 。但仅仅因为当前进程以中等完整性运行并不意味着用户不在管理员组中。在寻找替代方案时,我发现metasploit是如何检测到这一点的:它只是运行命令“cmd.exe /c whoami /groups”并检查输出是否包含“S-1-5-32-544”,即管理员 SID。

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

4

3 回答 3

4

如果BUILTIN\Administrators ( S-1-5-32-544 ) 组(别名)的用户成员,则此 sid 存在于它的令牌组中。并且通常仅在这种情况下(当然可以为具有S-1-5-32-544的非管理员用户和没有它的管理员用户创建令牌)。如此简单而有效的检查 - 列出令牌组并查看 -此处存在S-1-5-32-544,具有任何属性。IsUserAdmin不只是检查这个 sid,而是

即使令牌中存在 SID ,系统也可能不会在访问检查中使用SID 。SID可能被禁用或具有SE_GROUP_USE_FOR_DENY_ONLY属性。执行访问检查时,系统仅使用启用的 SID 来授予访问权限。

当管理员用户(S-1-5-32-544 Alias 的成员)交互式登录系统和UAC活动时 - 系统过滤它的令牌,并为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);
}
于 2020-11-27T20:39:50.663 回答
0

正如我所看到的,其他人已经发布了他们自己的解决方案。我真的很感激这一点!与此同时,我发现了一种类似于@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;
}

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

于 2020-11-28T23:01:04.667 回答
0

使用以下 win32 api 的示例:

获取用户名

NetUserGetLocalGroups

ConvertStringSidToSidW(具有组 S-1-5-32-544 的 SID)

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;
}
于 2020-11-27T16:52:32.543 回答