目录
VC++常用功能开发汇总(专栏文章列表,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/124272585C++软件异常排查从入门到精通系列教程(专栏文章列表,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/125529931 今天同事要给某exe主程序设置以管理员权限运行,结果在工程属性中设置对应选项后,并没有起作用。于是找到我,希望我帮他排查一下,看看为啥设置选项后没有起作用。今天把当时的完整排查分享出来,给大家提供一个借鉴和参考。
1、问题描述
因为程序需要读U盾中设备信息,需要有管理员权限,即程序以管理员权限运行。程序是用Visual Studio开发的,在Visual Studio中,在程序的工程属性中设置以管理员权限运行,如下所示:
设置后重新编译程序,启动运行时并没有弹出如下的UAC提权的提示框:
应该是设置程序以管理员权限运行失败了。于是找到我,让我帮他排查一下。
2、UAC权限控制与系统登录用户类型
Windows从Vista系统开始就引入了UAC权限控制机制,强化了管理员权限的概念,做了更严格的权限限制与安全控制。比如对一些权限敏感的路径,比如C:\Program Files、C:\Windows\system32,如果要在这些路径下创建文件、向文件中写数据,都是需要管理员权限的。再比如,在Windows系统的注册表中,如果要向HKEY_LOCAL_MACHINE节点写入或修改内容时,也需要管理员权限的。这些操作在之前的Windows XP系统都没有限制的。
程序有没有以管理员权限运行,主要和两个因素有关,一个是当前系统登录的用户类型(是否是个超级管理员Administrator),一个是程序有没有申请管理员权限。
2.1、超级管理员登录
超级管理员是Windows系统内置的管理员账户Administrator,该超级管理员用户默认是不启用的,如果需要使用该账户则需要手动开启。在超级管理员登录的情况下,系统设定该用户拥有最高的权限,所有由该用户启动的程序,都是以管理员权限运行的,不管程序是否设置了以管理员权限运行的属性。也不会有如下的UAC提示框:
所以,首先和同事确认了他测试时,系统登录的是否是超级管理员Administrator,因为该用户登录时所有程序都是以管理员权限运行的,是不会弹出UAC提示框的。
同事反馈说,他当前登录的是普通管理员用户,不是超级管理员Adminstrator,所以没弹出UAC提示框,和超级管理员Administrator没关系的。
其实也简单,可以启动一个安装包程序对比,一般安装包程序都要设置管理员权限的,看看安装包程序在启动时有没有弹出UAC提示框。
2.2、将程序设置成以管理员权限运行
Windows系统的登录用户主要有三大类:超级管理员Adminstrator、普通管理员用户和标准用户。对于超级管理员Administrator,其权限最大,上面讲过,在该用户下启动的程序都是以管理员权限运行的,不会弹出UAC提示框。
对于普通管理员用户,隶属管理员组,但权限比超级管理员小一些。在普通管理员登录的情况下,所有程序默认是标准用户权限运行。如果程序要以管理员权限运行,则需要给程序设置管理员权限的属性。对于Visual Studio 2010及以上版本,主需要在程序的工程属性中设置required Administrator属性,即将程序设置以管理员权限运行。
设置以管理员权限运行的程序,在普通管理员登录的场景下,会弹出UAC提示框:
提示用户当前程序申请以管理员权限运行,可能会修改敏感位置的数据(比如向敏感位置C:\Program Files中写入数据、向HKEY_LOCAL_MACHINE注册表节点下写入数据)。点击是,就是同意以管理员权限运行;点击否,就是不同意一管理员权限运行,程序就不会运行。
对于安装包程序,一般默认的安装路径就是C:\Program Files下的,也需要向HKEY_LOCAL_MACHINE下写入一些注册表信息的,而这些操作都需要管理员权限才能执行的,所以安装包程序需要设置以管理员权限运行的。
在普通管理员用户和标准用户登录的场景下,一般看到需要管理员权限的exe程序,都会在程序图标右下角显示一个小盾牌,如下所示:
看到这些小盾牌,表示这些程序都需要以管理员权限。注意,这些小盾牌图标,在超级管理员登录时,是不会显示的,因为超级管理员登录时所有的程序都是以管理员权限运行的,不管程序有没有申请管理员权限。同事在其电脑上是以普通管理员登录的,如果程序设置了以管理员权限运行,则编译出来的exe程序的图标右下角应该有的盾牌标识的。但同事反馈,并没有看到盾牌显示。
按讲这不科学啊,就是在程序的工程属性中设置required Administrator就可以了,为啥无效呢?
3、新建一个管理员账户进行验证
上面说到,同事已经在Visual Studio工程属性中设置了requiredAdministrator,但实测下来好像并没有生效。按讲就是这么处理,为啥不生效呢?这个有点奇怪了。我机器上当前使用的是Administrator超级管理员,于是想手动创建一个普通的管理员用户,去在普通管理员用户下去实地验证下。
3.1、创建普通管理员账户
创建管理员账户步骤有点绕,这个地方详细说一下。
首先打开控制面板,选择“小图标”查看方式,然后找到用户账户这个按钮并点击:
在打开的页面中,点击“管理其他账户”按钮弹出如下的窗口:
在窗口中点击“在电脑设置中添加新用户”:
在弹出的窗口点击“将其他人添加到电脑中”按钮,弹出如下的窗口:
然后,先点击左边的用户节点,然后右边切换到用户列表页面,在空白的地方点击右键,弹出如上图的右键菜单,点击“新用户”菜单项,在弹出的新用户窗口中输入用户名admin2,点击确定,就创建了一个用户admin2。但这个用户还不是管理员,需要点击左边的组节点,右边会弹出组列表,找到“Administrators”组,右键点击:
在弹出的右键菜单中点击“添加到组”菜单项,弹出如下的窗口:
在窗口中点击“添加”按钮,弹出选择用户界面:
在图中的输入框中输入刚才添加的用户名admin2,然后点击“检查名称”按钮,找到刚才添加的用户,添加进来即可,如下:
这样admin2添加到管理员组中了,至此才完成管理员用户的添加。
3.2、使用新增的普通管理员用户,进行测试验证
点击Win10系统的开始菜单中的头像按钮,看到其他用户,找到刚才新增的用户,点击之,就用新增的用户登录到系统中去。新增的用户是普通管理员用户,和同事的普通管理员账户的登录环境就一致了。
于是启动Visual Studio 2010,创建一个MFC测试工程,在工程属性中配置requiredAdministrator选项,即将程序设置成以管理员权限启动,然后编译工程生成exe文件。到目录中查看exe文件,文件图标的右下角有个盾牌的标记,这个标记表示程序启动时会申请以管理员权限启动。于是双击exe文件启动之,确实会弹出UAC提示框的,如下所示:
这说明在工程属性中配置requiredAdministrator属性是可以实现程序以管理员权限运行的。所以,遇到不确定的问题时,可以写测试代码去验证,或者到类似的环境中去验证。
4、到同事的机器上去排查
在我电脑上新建了一个普通管理员账户进行测试,在工程属性中配置requiredAdministrator属性是可以实现程序以管理员权限运行的,为啥同事的环境中就有问题呢?看来还是得到现场去看一下。到同事那边,查看了一下,确实是有问题的,并没有弹出UAC提示框,程序图标的右下角也是没有小盾牌标识的。
于是在他机器上使用Visual Studio 2010创建了一个测试工程,在工程属性中设置了一下requiredAdministrator属性,编译出来的exe程序是可以以管理员运行的。奇怪了,那为啥同事开发的软件就有问题呢?他们写的软件比较复杂,程序中调用了一些函数导致设置管理员权限失效了?按讲不对啊,这个requiredAdministrator属性设置和程序代码时没关系的。
于是想到去将创建的测试工程属性与同事开发的软件工程属性比对一下,看看有什么不一样的配置。果然找到了问题,同事开发的软件工程属性中生成清单那一项选择了“否”,如下:
即不产生manifest清单文件,而测试工程中默认选的是“是”!
于是将同事的软件工程的这个属性改成“是”,重新编译一下就好了,程序图标中有小盾牌了,程序启动时也有弹出UAC提示窗口了。所以,我们在遇到问题时需要多测试多对比,找出有差异的地方,可能就是问题所在了。对比差异也是我们日常排查问题时一个常用手段了。
5、通过代码判断程序是否以管理员权限运行
有时我们需要在代码中判断目标进程是否以管理员权限运行,所以此处我们顺便说一下如何通过代码去判断进程是否以管理员权限运行的。
Widnows系统提供了API函数GetTokenInformation可以检测到程序是否以管理员权限运行的。调用GetTokenInformation函数时,第一个参数传入与待检测进程的token值,第二个参数传入TokenElevation标记(检测进程有没有提权成功),第三个参数传入TOKEN_ELEVATION结构体对象地址,TOKEN_ELEVATION结构体定义如下:
typedef struct _TOKEN_ELEVATION {
DWORD TokenIsElevated;
} TOKEN_ELEVATION, *PTOKEN_ELEVATION;
当GetTokenInformation函数返回时,看TOKEN_ELEVATION结构体中的TokenIsElevated是否为非0,非0则表示提权成功,即目标进程是以管理员权限运行的。
判断目标进程是否以管理员权限运行的相关代码如下所示:
CString strTip;
DWORD dwPid = 14060; // 目标进程的进程id
HANDLE hProcess = ::OpenProcess( 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;
}
我们可以先到Windows任务管理器中查看目标进程的进程id,然后调用调用API函数OpenProcess通过进程id获取进程句柄,然后将进程句柄传到上面封装的函数IsRunasAdmin中即可。
6、最后
文中详细讲述了问题的完整排查过程,并阐述了Windows系统中与管理员权限相关的内容,希望能给大家带来一定的借鉴和参考。