如何在C语言中检查Windows用户是否具有管理员权限?

5

有没有办法在C语言中严格地条件检查Windows用户是否具有管理员权限?

我希望以编程方式实现此操作(而不仅仅是告诉用户“以管理员身份运行”)。

4个回答

7

有几种根本不同的方法。最常见的方法,不幸的是,相当乏味。它涉及查找当前用户的SID,然后找到他所属的组,然后找到其中是否有管理员组:

#include <windows.h> 
#include <vector>

bool is_administrator() { 
    HANDLE access_token; 
    DWORD buffer_size = 0; 
    PSID admin_SID; 
    TOKEN_GROUPS *group_token = NULL; 
    SID_IDENTIFIER_AUTHORITY NT_authority = SECURITY_NT_AUTHORITY; 

    if (!OpenProcessToken(GetCurrentProcess(),TOKEN_READ,&access_token)) 
        return false; 

    GetTokenInformation( 
        access_token, 
        TokenGroups, 
        group_token, 
        0, 
        &buffer_size 
        ); 

    std::vector<char> buffer(buffer_size);

    group_token = 
        reinterpret_cast<TOKEN_GROUPS*>(&buffer[0]); 

    bool succeeded = GetTokenInformation( 
        access_token, 
        TokenGroups, 
        group_token, 
        buffer_size, 
        &buffer_size 
        ); 

    CloseHandle(access_token); 
    if (!succeeded) 
        return false;

    if (!AllocateAndInitializeSid( 
        &NT_authority, 
        2, 
        SECURITY_BUILTIN_DOMAIN_RID, 
        DOMAIN_ALIAS_RID_ADMINS, 
        0,0,0,0,0,0, 
        &admin_SID 
        )) 
    {
        return false; 
    }

    bool found=false; 
    for(int i=0; !found && i < group_token->GroupCount; i++) 
        found = EqualSid(admin_SID,group_token->Groups[i].Sid); 
    FreeSid(admin_SID); 
    return found; 
} 

有另一种更简单的方法:
bool is_administrator() 
{ 
        bool result; 
        DWORD rc; 
        wchar_t user_name[256]; 
        USER_INFO_1 *info; 
        DWORD size = sizeof( user_name ); 
        GetUserNameW( user_name, &size); 
        rc = NetUserGetInfo( NULL, user_name, 1, (byte **) &info ); 
        if ( rc != NERR_Success ) 
                return false; 
        result = info->usri1_priv == USER_PRIV_ADMIN; 
        NetApiBufferFree( info ); 
        return result; 
} 

无论哪种情况,如果您拥有一个域名,事情可能会有些棘手,因为特定用户可能是本地计算机上的管理员,但不是域上的管理员,反之亦然。查找信息并没有什么实质性的变化,但您可能需要思考一下您真正想要的是什么。
编辑:正如@Benj指出的那样,第一种方法确实需要进行更新。虽然我已经修复了它明显的漏洞,但它仍然是一个庞大的、单一的函数,没有异常安全性,而且通常使用了过时的编码风格。也许需要进行小幅更新:
#include <windows.h> 
#include <vector>
#include <algorithm>

class sid {
    PSID s;
public:
    sid(SID_IDENTIFIER_AUTHORITY auth, std::vector<DWORD> sub_auths) {
        DWORD count = sub_auths.size();
        sub_auths.resize(7, DWORD());

        if (!AllocateAndInitializeSid( 
            &auth,
            count, 
            sub_auths[0], sub_auths[1], sub_auths[2], sub_auths[3],
            sub_auths[4], sub_auths[5], sub_auths[6], sub_auths[7],
            &s 
            )) 
        {
            throw std::runtime_error("Unable to allocate Admin SID");
        }
    }

    sid(PSID const &p=NULL) : s(p) {}
    bool operator==(sid const &r) const { return EqualSid(s, r.s); }
};

class access_token {
    HANDLE token;
public:
    access_token(HANDLE PID=GetCurrentProcess(), DWORD access=TOKEN_READ) {
        if (!OpenProcessToken(PID, access, &token))
            throw std::runtime_error("Unable to open process token");
    }
    operator HANDLE() { return token; }
    ~access_token() { CloseHandle(token); }
};

std::vector<sid> get_group_sids() {
    DWORD buffer_size = 0; 
    TOKEN_GROUPS *group_token = NULL; 
    std::vector<sid> groups;
    access_token token;

    GetTokenInformation(token, TokenGroups, group_token, 0, &buffer_size);

    std::vector<char> buffer(buffer_size);

    group_token = reinterpret_cast<TOKEN_GROUPS*>(&buffer[0]);

    if (GetTokenInformation(token, TokenGroups, group_token, buffer_size, &buffer_size))
        for (int i=0; i<group_token->GroupCount; i++)
            groups.push_back(group_token->Groups[i].Sid);
    return groups;
}

bool is_administrator() {
    std::vector<sid> groups = get_group_sids();

    SID_IDENTIFIER_AUTHORITY NT_authority = SECURITY_NT_AUTHORITY;
    std::vector<DWORD> sub_auths;

    sub_auths.push_back(SECURITY_BUILTIN_DOMAIN_RID);
    sub_auths.push_back(DOMAIN_ALIAS_RID_ADMINS);

    sid admin_SID(NT_authority, sub_auths);

    return std::find(groups.begin(), groups.end(), admin_SID) != groups.end();
}

#ifdef TEST
#include <iostream>
#include <iomanip>

int main() {
    std::cout << std::boolalpha << is_administrator() << "\n";
}

#endif

在第一个例子中,new char[buffer_size] 在哪里被删除了? - Benj
总是可以将其快速修改为一个 vector<char> - Benj
@Benj:你应该等第三个版本! :-) - Jerry Coffin
抱歉,Jerry,+1 是我的极限;-) - Benj
1
值得注意的是,不同的方法可能会有不同的结果。例如,如果foobar已登录,并通过net localgroup Administrators foobar /Delete从管理员组中删除了foobar,则第一种方法(OpenProcessToken)将声称foobar是管理员,而第二种方法则不会。我认为第一种方法更可取,因为它与事实一致,即foobar在注销之前仍具有管理员权限。 - raymai97

4

以下是一种稍微不同(并且更短?更容易?)的方法,部分内容改编自MSDN上的一些提示:

PSID administrators_group = NULL;
SID_IDENTIFIER_AUTHORITY nt_authority = SECURITY_NT_AUTHORITY;
BOOL result = AllocateAndInitializeSid( &nt_authority, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &administrators_group);
BOOL is_user_admin = FALSE;
if (result)
{
    CheckTokenMembership(NULL, administrators_group, &is_user_admin);
    FreeSid(administrators_group);
}

if (is_user_admin)
{
    // do something here for admin users...
}

我还没有测试过,但想知道您是否知道这是否适用于启用了UAC的机器,以确定UAC提升,而不一定是管理员组成员身份? - turkinator

3

当然,您需要调用:

OpenThreadToken()

获取用户的令牌。
GetTokenInformation(hToken, TokenGroups, NULL, 0, &dwNeeded)

获取令牌组信息的大小(并分配足够的空间)

GetTokenInformation(hToken, TokenGroups, pTokenGroups, dwSize, &dwNeeded)

获取本地组
AllocateAndInitializeSid(&SIDAuthNT, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &psidAdministrators);

获取管理员的SID
EqualSid()

将该SID与您本地组中的SIDs进行比较。


谢谢,这对我很有帮助,但一开始我没有意识到第一次调用GetTokenInformation返回错误是可以的。我建议在答案中添加(确保GetLastError() == ERROR_INSUFFICIENT_MEMORY)。 - TBD
1
这实际上是Win32编程中非常常见的模式。每当您看到一个API被调用两次,第一次使用NULL时,通常情况下第一次调用获取的是大小,以便您可以为第二次调用分配正确的内存。 - Benj

-2

你应该使用来自SHELL32的IsUserAnAdmin函数


在启用UAC的Win8/10上,IsUserAnAdmin无法正确识别属于管理员组的用户。 - jeffm

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接