判断进程是否以管理员权限运行(***)

时间:2022-08-23 08:38:12

        有时我们需要判断进程是否以管理员权限运行,比如在运行安装包时需要安装包进程以管理员权限运行,因为安装包将执行写注册表、注册组件等需要管理员权限的操作。如果没有申请到管理员权限,这些需要管理员权限的操作都会执行失败,则会导致安装失败。以QQ7.1安装包为例,如果当前以标准用户登录到系统中,并且UAC关闭,双击运行时将申请不到管理员权限,QQ会弹出如下的提示框:

判断进程是否以管理员权限运行(***)

    我们的TL安装程序可以参考QQ的做法,避免出现没有管理员权限导致安装失败的问题,即如果没有申请到管理员权限,则直接弹出如上类似的提示。要弹出提示,则要判断当前安装程序的进程是否以管理员权限运行。那应该如何判断呢?

一、判断函数的实现

    经查阅相关资料得知,调用GetTokenInformation函数,获取TOKEN_ELEVATION结构体信息,通过结构体中的TokenIsElevated字段就能判断出来,相关的代码如下:

BOOL IsRunasAdmin() 
{
BOOL bElevated = FALSE;
HANDLE hToken = NULL;

// Get current process token
if ( !OpenProcessToken( GetCurrentProcess(), TOKEN_QUERY, &hToken ) )
return FALSE;

TOKEN_ELEVATION tokenEle;
DWORD dwRetLen = 0;

// Retrieve token elevation information
if ( GetTokenInformation( hToken, TokenElevation, &tokenEle, sizeof(tokenEle), &dwRetLen ) )
{
if ( dwRetLen == sizeof(tokenEle) )
{
bElevated = tokenEle.TokenIsElevated;
}
}

CloseHandle( hToken );
return bElevated;
}

二、管理员权限与当前用户的类型、UAC开关的关系

    对于需要管理员权限的程序,可以通过内嵌manifest文件的方式,设置requireAdministrator(需要管理员权限),这样在程序启动时会申请管理员权限。在VS2010中,可以在工程的属性中进行配置,如下图所示:

判断进程是否以管理员权限运行(***)

    程序在什么情况下可以申请到管理员权限,在什么情况下申请不到,在这里就各种场景简单的说明一下。考虑当前的登入的用户类型、UAC的打开与关闭、程序本身有没有设置requireAdministrator属性。

   1、UAC打开

    此种情况只讨论设置requireAdministrator属性如何申请到管理员权限的情形。对于没有设置requireAdministrator属性的程序,肯定是以非管理员权限运行的。

    (1)登录的是超级管理员Administrator

    默认情况下,超级管理员Administrator是禁用的,可以通过这样的途径来开启:右键计算机 ->管理 ->系统工具 ->本地用户和组 ->用户 ->右键Administrator ->属性 ->取消账户禁用 ->注销(不行就重启)->登陆Administrator即可。超级管理员在用户管理中是可以重命名的。

    对于设置了requireAdministrator属性的程序,启动时能申请到管理员权限。由于Administrator是超级管理员,权限最高的用户,所以在提权的时候不会弹出UAC提示窗口。

    (2)登录的是管理员(非超级管理员Administrator)

    在启动设置了requireAdministrator属性的程序时,会弹出UAC提示框,如下所示:

判断进程是否以管理员权限运行(***)

     (3)登录的是标准用户(非管理员)

     在启动设置了requireAdministrator属性的程序时,会弹出输入管理员密码的UAC提示框,如下:

判断进程是否以管理员权限运行(***)

    输入管理员密码后,才能正常的启动程序。

   2、UAC关闭

    UAC关闭,应该只是关闭了UAC提示。此种情况设置了requireAdministrator属性的程序和没有设置requireAdministrator属性的程序都要嫁衣说明。

    (1)登录的是超级管理员Administrator或者管理员(普通管理员)

    在启动设置了requireAdministrator属性的程序,会申请到管理员权限,且不会弹出UAC提示框。对于没有设置requireAdministrator属性的程序,启动时会以管理员权限运行还是标准权限运行呢?经研究,发现也会以管理员权限运行。比如在运行中输入cmd,打开cmd窗口,窗口的标题中会加上“管理员”的字样,如下所示:

判断进程是否以管理员权限运行(***)

当然还有很多示例可以拿过来加以验证。

    (2)登录的是标准用户

    在启动设置了requireAdministrator属性的程序,还会弹出提示输入管理员密码的提示框吗?UAC已经关闭,不会再弹出提示要输入管理员密码的提示框,那程序权限是如何处理的呢?查看系统关于UAC关闭时的说明如下:

判断进程是否以管理员权限运行(***)

    由上可知,程序是申请不到管理员权限的,程序是可以启动的,但是所有执行需要管理员权限的操作都会返回失败这就是QQ在申请不到管理员权限时弹出无法安装的提示的原因所在了。

三、IsRunasAdmin函数的说明

    IsRunasAdmin已经在上面提到的所有的场景中验证通过。之前在《Windows核心编程》第5版的第4章第5节“管理员以标准用户权限运行时”看到,书中也给出了一个方法,如下所示:
BOOL GetProcessElevation(TOKEN_ELEVATION_TYPE* pElevationType, BOOL* pIsAdmin) 
{
HANDLE hToken = NULL;
DWORD dwSize;

// Get current process token
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken))
return(FALSE);

BOOL bResult = FALSE;

// Retrieve elevation type information
if (GetTokenInformation(hToken, TokenElevationType,
pElevationType, sizeof(TOKEN_ELEVATION_TYPE), &dwSize)) {
// Create the SID corresponding to the Administrators group
byte adminSID[SECURITY_MAX_SID_SIZE];
dwSize = sizeof(adminSID);
CreateWellKnownSid(WinBuiltinAdministratorsSid, NULL, &adminSID,
&dwSize);

if (*pElevationType == TokenElevationTypeLimited) {
// Get handle to linked token (will have one if we are lua)
HANDLE hUnfilteredToken = NULL;
GetTokenInformation(hToken, TokenLinkedToken, (VOID*)
&hUnfilteredToken, sizeof(HANDLE), &dwSize);

// Check if this original token contains admin SID
if (CheckTokenMembership(hUnfilteredToken, &adminSID, pIsAdmin)) {
bResult = TRUE;
}

// Don't forget to close the unfiltered token
CloseHandle(hUnfilteredToken);
} else {
*pIsAdmin = IsUserAnAdmin();
bResult = TRUE;
}
}

// Don't forget to close the process token
CloseHandle(hToken);

return(bResult);
}
根据文中的意思,通过传出参数pIsAdmin可以得知进程是否以管理员权限运行。经测试,在标准用户登录且UAC打开时是有问题的,经调试,每次都会走到如下的分支:
*pIsAdmin = IsUserAnAdmin();
这样就有问题,IsUserAnAdmin是判断当前登录用户是否是管理员用户。而在标准用户登录且UAC打开时,启动requireAdministrator的程序,会弹出输入管理员密码的提示框,也可以以管理员权限运行的,即使当前登录的用户不是管理员用户。

四、判断其他的进程是否以管理员权限运行

    上面的代码稍加改动一下,就可以实现判断其他进程是否以管理员权限运行了。假设我们知道目标进程的进程id(在测试时,我们可以到任务管理器中查看目标进程的进程id),则可以调用OpenProcess得到进程句柄,然后再将这个句柄传递给OpenProcessToken。相关代码如下所示:

        DWORD dwPid = 2337; // 目标进程的进程id
HANDLE hProcess = ::OpenProcess( /*PROCESS_ALL_ACCESS*/PROCESS_QUERY_INFORMATION, FALSE, dwPid );
if ( hProcess == NULL )
{
strTip.Format( _T("OpenProcess to get the process handle failed, possible reason: the process id doesn't exsit, GetLastError: %d"), GetLastError() );
AfxMessageBox( strTip );
return;
}

BOOL bRunAsAdmin = IsRunasAdmin( hProcess );
if ( bRunAsAdmin )
{
strTip.Format( _T("Pid(%d) run as admin!"), dwPid );
}
else
{
strTip.Format( _T("Pid(%d) don't run as admin!"), dwPid );
}
AfxMessageBox( strTip );

BOOL IsRunasAdmin( HANDLE hProcess )
{
BOOL bElevated = FALSE;
HANDLE hToken = NULL;

CString strTip;

// Get target process token
if ( !OpenProcessToken( hProcess/*GetCurrentProcess()*/, TOKEN_QUERY, &hToken ) )
{
strTip.Format( _T("OpenProcessToken failed, GetLastError: %d"), GetLastError() );
AfxMessageBox( strTip );
return FALSE;
}

TOKEN_ELEVATION tokenEle;
DWORD dwRetLen = 0;

// Retrieve token elevation information
if ( GetTokenInformation( hToken, TokenElevation, &tokenEle, sizeof(tokenEle), &dwRetLen ) )
{
if ( dwRetLen == sizeof(tokenEle) )
{
bElevated = tokenEle.TokenIsElevated;
}
}
else
{
strTip.Format( _T("GetTokenInformation failed, GetLastError: %d"), GetLastError() );
AfxMessageBox( strTip );
}

CloseHandle( hToken );
return bElevated;
}
    注意,在调用OpenProcess获取进程句柄时,不要传递全权限的参数PROCESS_ALL_ACCESS,传递较低权限的参数PROCESS_QUERY_INFORMATION,避免因为程序的权限不够导致OpenProcess函数执行失败。至于为什么要使用PROCESS_QUERY_INFORMATION参数,可以参见MSDN中说明:

                                                       判断进程是否以管理员权限运行(***)
因为获取的进程句柄是给OpenProcessToken函数使用的,所以根据MSDN中的说明,使用PROCESS_QUERY_INFORMATION参数。