因为工作需要,windows服务进程需要能够创建出带界面的进程,并且此界面进程需要管理员权限,之前找到两种方法解决这个问题。
1.服务进程里面复用winlogon.exe的令牌,并且加入窗口站,用CreateProcessAsUser创建界面进程。
winlogon.exe是个很神奇的进程,一方面其session id不为0(服务用户所在session id为0),并且跟登录用 户属于同一个session。这样利用其进程token,并且加入窗口站,就能创建出具有管理员权限的界面程序,但是由于winlogon.exe是属于SYSTEM用户的,所以利用其token创建出的界面进程也是属于SYSTEM用户的。
下面是大致的代码,其中dwWinlogon是winlogon.exe进程的id。
dwCreationFlags = NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE;
si.cb = sizeof(STARTUPINFO);
si.lpDesktop = "winsta0\\default";///加入窗口站
hProcess = OpenProcess(MAXIMUM_ALLOWED, FALSE, dwWinlogon);
bResult = ::OpenProcessToken(hProcess, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY
| TOKEN_DUPLICATE | TOKEN_ASSIGN_PRIMARY | TOKEN_ADJUST_SESSIONID
| TOKEN_READ | TOKEN_WRITE, &hPToken);
if (FALSE == bResult)
{
dwErrorCode = ::GetLastError();
LOG_DEBUG("WTSQueryUserToken failed, dwErrorCode is %u", dwErrorCode);
break;
}
// 创建一个新的访问令牌来复制一个已经存在的标记
bResult = DuplicateTokenEx(hPToken, MAXIMUM_ALLOWED, NULL,
SecurityIdentification, TokenPrimary, &hUserTokenDup);
if (FALSE == bResult)
{
dwErrorCode = ::GetLastError();
LOG_DEBUG("DuplicateTokenEx failed, dwErrorCode is %u", dwErrorCode);
break;
}
// 创建环境信息
LPVOID pEnv = NULL;
bResult = CreateEnvironmentBlock(&pEnv, hUserTokenDup, TRUE);
dwErrorCode = ::GetLastError();
if (bResult)
{
dwCreationFlags |= CREATE_UNICODE_ENVIRONMENT;
}
else
{
pEnv = NULL;
}
CString command = L"\"" + processPath_ + L"\"";
if (arguments_.GetLength() != 0)
{
command += L" " + arguments_;
}
// 通过winlogon创建一个进程
bResult = CreateProcessAsUser(
hUserTokenDup, // 令牌
NULL, // 程序全路径
(LPSTR)(LPCSTR)(command), // 程序命令行(和前者二选一)
NULL, // 进程安全属性
NULL, // 线程安全属性
FALSE, // 句柄不可继承
dwCreationFlags, // 创建标识
pEnv, // 环境信息
NULL, // 当前路径
&si, // 新进程的主窗口特性
&pi // 新创建的进程相关信息
);
2.以当前服务进程的token为基准,创建界面程序
用当前服务进程的token,加入窗口站,用CreateProcessAsUser创建,结果出现了交互式服务检测弹框,读者可以看我的另外一篇博客,代码层面剖析交互式服务检测由来
此时修改token的session为当前登录用户的session id即可。代码大概如下所示:
DWORD dwSessionId = WTSGetActiveConsoleSessionId();
BOOL bRet = SetTokenInformation(hTokenDup, TokenSessionId, &dwSessionId, sizeof(DWORD));
上面两种创建出的界面进程都是SYSTEM用户下,不是当前登录用户下的,能否创建当前登录用户下,具有管理员权限的界面进程呢,答案是肯定的。
UAC开启时,当前用户拥有两个token,分别是受限的token和不受限的token。explorer.exe进程的token就属于受限的token。
在服务程序中,可以用下面代码获取到受限的token。
HANDLE GetCurrentUserToken()
{
PWTS_SESSION_INFO pSessionInfo = 0;
DWORD dwCount = 0;
::WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, 0, 1, &pSessionInfo, &dwCount);
int session_id = 0;
for (DWORD i = 0; i < dwCount; ++i)
{
WTS_SESSION_INFO si = pSessionInfo[i];
if (WTSActive == si.State)
{
session_id = si.SessionId;
break;
}
}
::WTSFreeMemory(pSessionInfo);
HANDLE current_token = 0;
BOOL bRet = ::WTSQueryUserToken(session_id, ¤t_token);
int errorcode = GetLastError();
if (bRet == FALSE)
{
LOG_ERROR("WTSQueryUserToken errorcode: %d ", errorcode);
return 0;
}
HANDLE primaryToken = 0;
bRet = ::DuplicateTokenEx(current_token, TOKEN_ASSIGN_PRIMARY | TOKEN_ALL_ACCESS, 0, SecurityImpersonation, TokenPrimary, &primaryToken);
errorcode = GetLastError();
::CloseHandle(current_token);
if (bRet == FALSE)
{
LOG_ERROR("DuplicateTokenEx errorcode: %d ", errorcode);
return 0;
}
return primaryToken;
}
然后由此token可以得到不受限的token,代码如下所示。
HANDLE primaryToken = GetCurrentUserToken();
HANDLE hUnfilteredToken = NULL;
DWORD dwSize = 0;
BOOL bRet = GetTokenInformation(primaryToken, TokenLinkedToken,(VOID*)&hUnfilteredToken, sizeof(HANDLE), &dwSize);
获取到的hUnfilteredToken就是不受限的token,以token作为CreateProcessAsUser的第一个参数,就可以创建出具有管理员权限,并且属于当前用户的界面程序了,并且这种情况下,不需要加入窗口站。
相关代码如下:
BOOL RunAdminPrivilege()
{
HANDLE primaryToken = GetCurrentUserToken();
if (primaryToken == 0)
{
LOG_ERROR("GetCurrentUserToken fail : ");
return FALSE;
}
HANDLE hUnfilteredToken = NULL;
DWORD dwSize = 0;
BOOL bRet = GetTokenInformation(primaryToken, TokenLinkedToken, (VOID*)&hUnfilteredToken, sizeof(HANDLE), &dwSize);
STARTUPINFO StartupInfo = {0};
PROCESS_INFORMATION processInfo;
StartupInfo.cb = sizeof(STARTUPINFO);
CString command = L"\"" + processPath_ + L"\"";
if (arguments_.GetLength() != 0)
{
command += L" " + arguments_;
}
void* lpEnvironment = NULL;
BOOL resultEnv = ::CreateEnvironmentBlock(&lpEnvironment, hUnfilteredToken, FALSE);
if (resultEnv == 0)
{
long nError = GetLastError();
LOG_ERROR("CreateEnvironmentBlock errorcode: %d ", nError);
}
LOG_DEBUG("CreateProcessAsUser command: %s ", command);
BOOL result = ::CreateProcessAsUser(hUnfilteredToken, 0, (LPSTR)(LPCSTR)(command), NULL, NULL, FALSE, CREATE_NEW_CONSOLE | NORMAL_PRIORITY_CLASS | CREATE_UNICODE_ENVIRONMENT, lpEnvironment, 0, &StartupInfo, &processInfo);
if(!result)
{
long nError = GetLastError();
LOG_DEBUG("CreateProcessAsUser error : %d ", nError);
}
if(lpEnvironment != NULL)
{
::DestroyEnvironmentBlock(lpEnvironment);
}
::CloseHandle(primaryToken);
LOG_DEBUG("CreateProcessAsUser result: %d ", result);
return result;
}