降权启动进程
一 背景
在有些情况下,windows通过计划任务(通过服务也是如此)调起的进程是system权限的。而在system权限下进程可能会遇到某些问题:
(1)通过注册表或expand 环境变量等方法得到的系统目录并不是我们想要的,例如通过SHGetSpecialFolderPath获取CSIDL_LOCAL_APPDATA的路径为C:\windows\system32\config\systemprofile\appdata\local;通过GetEnvironmentVariable获取TMP的路径为C:\Windows\TEMP等;这一类的目录包括且不限于:desktop, paograms, appdata, etc..
(2)具有system权限的进程创建的子进程也是具有system权限的,这样子进程也会遇到上面第1点的问题
(3)GetEnvironmentVariable函数获取到的环境变量都是SYSTEM用户的
(4)对HKEY_CURRENT_USER的部分注册表的写操作将会被重定向到HKEY_USERS.DEFAULT
二 解决方案
1. 解决目录问题
(1)如何判断是在system下
正常的方法当然是通过通过权限相关的API来判断,当然也可以有一些小技巧来判断。例如上面说到的通过SHGetSpecialFolderPath获取CSIDL_LOCAL_APPDATA的路径,非system权限下为C:\Users\username\AppData\Local,而在system下为C:\windows\system32\config\systemprofile\appdata\local。因此,代码如下:
bool IsSystemPrivilegeImp()
{
static bool isSystemPrivilege = false;
if (isSystemPrivilege)
{
return isSystemPrivilege;
}
char szPath[MAX_PATH] = {0};
if (::SHGetSpecialFolderPathA(NULL, szPath, CSIDL_APPDATA, TRUE))
{
std::string flag("config\\systemprofile");
std::string path(szPath);
if (path.find(flag) != std::string::npos)
{
isSystemPrivilege = true;
}
}
return isSystemPrivilege;
}
(2)模拟当前登陆用户
想要获取的正确的目录,需要模拟当前登陆用户,获取登录用户的token,再调用SHGetSpecialFolderPath传入此token来获取。另外通过此token,还可以通过CreateProcessAsUser来创建登录用户权限的进程。
// 若执行成功,传出获取的token
bool ImpersonateLoggedOnUserWrapper(HANDLE& hToken)
{
DWORD dwConsoleSessionId = WTSGetActiveConsoleSessionId();
if (WTSQueryUserToken(dwConsoleSessionId, &hToken))
{
if (ImpersonateLoggedOnUser(hToken))
{
return true;
}
}
return false;
}
(3)获取正确的目录
通过上面获取的token传入SHGetSpecialFolderPath来获取CSIDL_LOCAL_APPDATA等正确的路径。网上资料说这个函数有时会失败,GetLastError返回5。而Windows提供的另外一个API叫做SHGetFolderPath,是可以正常获取路径的,因此我们使用后面这个API。
BOOL WINAPI SHGetSpecialFolderPathWrapper(
HWND hwnd,
LPWSTR lpszPath,
int csidl,
BOOL fCreate
)
{
BOOL ret = FALSE;
do
{
if (false == IsSystemPrivilegeImp())
{
if (SHGetSpecialFolderPath(hwnd, lpszPath, csidl, fCreate))
{
ret = TRUE;
break;
}
}
HANDLE hToken = NULL;
if (false == ImpersonateLoggedOnUserWrapper(hToken))
{
break;
}
if (S_OK == SHGetFolderPath(NULL, csidl, hToken, SHGFP_TYPE_DEFAULT, lpszPath))
{
ret = TRUE;
//使用完毕之后通过调用RevertToSelf取消模拟
RevertToSelf();
break;
}
} while (0);
return ret;
}
2. 降权启动进程
判断当前是否是system权限,如果是system权限,则以普通管理员用户的权限重新把自己调起。
#define TokenLinkedToken 19
DWORD GetActiveSessionID()
{
DWORD dwSessionId = 0;
PWTS_SESSION_INFO pSessionInfo = NULL;
DWORD dwCount = 0;
WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, 0, 1, &pSessionInfo, &dwCount);
for(DWORD i = 0; i < dwCount; i++)
{
WTS_SESSION_INFO si = pSessionInfo[i];
if(WTSActive == si.State)
{
dwSessionId = si.SessionId;
break;
}
}
WTSFreeMemory(pSessionInfo);
return dwSessionId;
}
BOOL TriggerAppExecute(std::wstring wstrCmdLine/*, INT32& n32ExitResult*/)
{
DWORD dwProcesses = 0;
BOOL bResult = FALSE;
DWORD dwSid = GetActiveSessionID();
DWORD dwRet = 0;
PROCESS_INFORMATION pi;
STARTUPINFO si;
HANDLE hProcess = NULL, hPToken = NULL, hUserTokenDup = NULL;
if (!WTSQueryUserToken(dwSid, &hPToken))
{
PROCESSENTRY32 procEntry;
DWORD dwPid = 0;
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnap == INVALID_HANDLE_VALUE)
{
return FALSE;
}
procEntry.dwSize = sizeof(PROCESSENTRY32);
if (Process32First(hSnap, &procEntry))
{
do
{
if (_tcsicmp(procEntry.szExeFile, _T("explorer.exe")) == 0)
{
DWORD exeSessionId = 0;
if (ProcessIdToSessionId(procEntry.th32ProcessID, &exeSessionId) && exeSessionId == dwSid)
{
dwPid = procEntry.th32ProcessID;
break;
}
}
} while (Process32Next(hSnap, &procEntry));
}
CloseHandle(hSnap);
// explorer进程不存在
if (dwPid == 0)
{
return FALSE;
}
hProcess = ::OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, dwPid);
if (hProcess == NULL)
{
return FALSE;
}
if(!::OpenProcessToken(hProcess, TOKEN_ALL_ACCESS_P,&hPToken))
{
CloseHandle(hProcess);
return FALSE;
}
}
if (hPToken == NULL)
return FALSE;
TOKEN_LINKED_TOKEN admin;
bResult = GetTokenInformation(hPToken, (TOKEN_INFORMATION_CLASS)TokenLinkedToken, &admin, sizeof(TOKEN_LINKED_TOKEN), &dwRet);
if (!bResult) // vista 以前版本不支持TokenLinkedToken
{
TOKEN_PRIVILEGES tp;
LUID luid;
if (LookupPrivilegeValue(NULL,SE_DEBUG_NAME,&luid))
{
tp.PrivilegeCount =1;
tp.Privileges[0].Luid =luid;
tp.Privileges[0].Attributes =SE_PRIVILEGE_ENABLED;
}
//复制token
DuplicateTokenEx(hPToken,MAXIMUM_ALLOWED,NULL,SecurityIdentification,TokenPrimary,&hUserTokenDup);
}
else
{
hUserTokenDup = admin.LinkedToken;
}
LPVOID pEnv =NULL;
DWORD dwCreationFlags = CREATE_PRESERVE_CODE_AUTHZ_LEVEL;
// hUserTokenDup为当前登陆用户的令牌
if(CreateEnvironmentBlock(&pEnv,hUserTokenDup,TRUE))
{
//如果传递了环境变量参数,CreateProcessAsUser的
//dwCreationFlags参数需要加上CREATE_UNICODE_ENVIRONMENT,
//这是MSDN明确说明的
dwCreationFlags|=CREATE_UNICODE_ENVIRONMENT;
}
else
{
//环境变量创建失败仍然可以创建进程,
//但会影响到后面的进程获取环境变量内容
pEnv = NULL;
}
ZeroMemory( &si, sizeof(si) );
si.cb = sizeof(si);
si.dwFlags = STARTF_USESHOWWINDOW;
si.wShowWindow = SW_HIDE;
ZeroMemory( &pi, sizeof(pi) );
bResult = CreateProcessAsUser(
hUserTokenDup, // client's access token
NULL, // file to execute
(LPTSTR) wstrCmdLine.c_str(), // command line
NULL, // pointer to process SECURITY_ATTRIBUTES
NULL, // pointer to thread SECURITY_ATTRIBUTES
FALSE, // handles are not inheritable
dwCreationFlags, // creation flags
pEnv, // pointer to new environment block
NULL, // name of current directory
&si, // pointer to STARTUPINFO structure
&pi // receives information about new process
);
if(pi.hProcess)
{
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
}
if (hUserTokenDup != NULL)
CloseHandle(hUserTokenDup);
if (hProcess != NULL)
CloseHandle(hProcess);
if (hPToken != NULL)
CloseHandle(hPToken);
if (pEnv != NULL)
DestroyEnvironmentBlock(pEnv);
return TRUE;
}