在现代的Windows操作系统中,为了提高安全性,微软引入了用户访问控制(User Account Control,简称UAC)机制。UAC是一种安全特性,它通过限制应用程序执行敏感操作,除非这些应用程序获得了管理员权限。例如,如果应用程序需要修改注册表,那么它就需要以管理员权限运行。然而,如果应用程序在大多数情况下不需要管理员权限,只有在特定情况下(如首次运行时)才需要,那么可能希望构建一个不需要以管理员模式运行的应用程序,但当它需要进行注册表更改时,它能够提升到管理员模式。
UAC机制的设计理念是防止具有有限访问权限的用户执行需要更高访问权限的任务。因此,当一个应用程序以“用户”模式运行时,它不能将自己改变为“管理员”模式。解决方案是在需要时,在运行时提升权限,通过重启应用程序,并以“管理员”身份运行。
UAC是什么?
用户访问控制(UAC)是微软为较新的Windows版本(从Vista开始)开发的机制。它通过限制应用程序执行敏感和危险任务而不获取管理员权限,从而提供更高级别的安全性。
在运行时真的可以提升权限吗?
确切地说,不可以。UAC背后的整个理念是防止具有有限访问权限的用户执行需要更高访问权限的任务。因此,当一个应用程序以“用户”模式执行时,它不能改变自己,就好像它最初是以“管理员”模式运行的。诀窍是在需要时,在运行时提升自己,通过重启它,这次以“管理员”身份运行。
是否以管理员模式运行?
这是需要做的第一件事,当需要做需要管理员权限的事情时。如果应用程序已经以“管理员”权限启动,那么就不需要提升。只有在即将执行的特定任务需要“管理员”权限,而应用程序以“用户”模式启动时,才需要提升。那么,首先,如何确定应用程序是否已经以提升的权限运行。
首先,需要分配并初始化管理员组的SID。根据MSDN,安全标识符(SID)是用于在Windows操作系统中标识安全主体或安全组的唯一值。众所周知的SID是一组SID,用于标识通用用户或通用组。它们的值在所有操作系统中保持不变。
在继续之前,让熟悉另一个函数,CheckTokenMembership。CheckTokenMembership函数确定指定的安全标识符(SID)是否在访问令牌中启用。为了确定应用程序令牌的组成员身份,使用CheckTokenMembershipEx。
对于目的,可以调用CheckTokenMembership,并通过这样做,询问SID是否在进程的主要访问令牌中启用。
BOOL IsAppRunningAsAdminMode() {
BOOL fIsRunAsAdmin = FALSE;
DWORD dwError = ERROR_SUCCESS;
PSID pAdministratorsGroup = NULL;
SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY;
if (!AllocateAndInitializeSid(
&NtAuthority,
2,
SECURITY_BUILTIN_DOMAIN_RID,
DOMAIN_ALIAS_RID_ADMINS,
0,
0,
0,
0,
0,
&pAdministratorsGroup)) {
dwError = GetLastError();
goto Cleanup;
}
if (!CheckTokenMembership(NULL, pAdministratorsGroup, &fIsRunAsAdmin)) {
dwError = GetLastError();
goto Cleanup;
}
Cleanup:
if (pAdministratorsGroup) {
FreeSid(pAdministratorsGroup);
pAdministratorsGroup = NULL;
}
if (ERROR_SUCCESS != dwError) {
throw dwError;
}
return fIsRunAsAdmin;
}
如果IsAppRunningAsAdminMode返回TRUE,那么没有什么需要做的,一切都可以继续进行任何任务。如果它返回FALSE,那么需要提升。
如何提升
在运行时提升的方式是获取应用程序的名称和路径,并将其以“管理员”身份执行,而当前运行的实例当然必须终止。还需要解决最终用户拒绝确认这种提升的情况,正如在以下代码中看到的:
wchar_t szPath[MAX_PATH];
if (GetModuleFileName(NULL, szPath, ARRAYSIZE(szPath))) {
SHELLEXECUTEINFO sei = {sizeof(sei)};
sei.lpVerb = L"runas";
sei.lpFile = szPath;
sei.hwnd = NULL;
sei.nShow = SW_NORMAL;
if (!ShellExecuteEx(&sei)) {
DWORD dwError = GetLastError();
if (dwError == ERROR_CANCELLED) {
std::cout << "User did not allow elevation" << std::endl;
} else {
_exit(1);
// Quit itself
}
}
}
运行POC
写了一个小型的POC来演示这个想法。按下“Y”后,应用程序将尝试提升自己,只有在它没有以管理员权限运行时才会这样做。