概述:降权操作,Windows服务进程中以低权限创建进程

[toc]

背景说明

在服务程序中调用了某一个程序的安装程序,由于权限的问题,这个安装程序也继承了服务的 SYSTEM 权限,导致安装程序与预期不符合。

解决方案

最终实现的目标就是在服务中以普通用户的权限去启动安装程序,要以普通用户去启动,就涉及到降权的问题,需要获取用户的信息。在任务管理器详细信息中可以看到,资源管理器是以普通用户的身份启动的,因此可以在服务中以 Explore.exe 的权限去调用安装程序。

实现逻辑

此逻辑也可以用于解决 UAC 弹窗的问题。

  1. 获取token
  2. 通过token获取用户的会话ID
  3. 通过token和ID启动进程

代码

接口

最终调用的接口为: CreateProcessAsUser

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int ExecutePackage(LPCSTR pszPath, LPCSTR pszParams)
{
if (nullptr == pszPath)
return -1;

wstring szPath = stringToWstring(pszPath);

wstring szParams;
if (pszParams)
{
szParams = stringToWstring(pszParams);
}

CreateProcessWithAdmin(szPath.c_str(), szParams.c_str());
return 0;
}

实现

实现如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
/*
* @fn
* @brief CreateProcessWithAdmin
* @param[in]
* strpath: 程序路径
* strParams: 程序执行命令
* @param[out]
* @return
*
* @detail
*/
bool CreateProcessWithAdmin(LPCWSTR lpExePath, LPCWSTR lpParam)
{
HANDLE hExplorerToken = GetExplorerToken();
HANDLE hTokenDup = NULL;
LPVOID pEnvironment = nullptr;
bool res{ false };

char szErr[256] = { 0 };
int iErrCode = 0;
do
{
if (hExplorerToken == NULL)
{
iErrCode = GetLastError();
break;
}
// 复制令牌,把调用方有效的所有访问权限给复制后的令牌.
DWORD dwReturnBytes = 0;
DWORD dwReturnLen = 0;
DWORD dwTokenSessionId = 0;

// 通过token获取sessionId
if (::GetTokenInformation(hExplorerToken, TokenSessionId, &dwTokenSessionId, sizeof(DWORD), &dwReturnLen) == FALSE)
{
break;
}

// 通过 SessionId 和 Token运行程序
- res = _CreateProcessAsSystemBySession(lpExePath, lpParam, NULL, dwTokenSessionId, hExplorerToken); // 改动如下所示

// 改动
// 判断当前特权token是否已提升,如果已经提升,则直接通过当前的token启动,如果未提升,则通过提升后的token启动
+ HANDLE hNewToken = NULL;
+ if (GetElevatedToken(hExplorerToken, &hNewToken) && hNewToken)
+ {
+ res = CreateProcessByToken(hNewToken, hExplorerToken, lpExePath, lpParam, NULL, FALSE);
+ CloseHandle(hNewToken);
+ }
+ else
+ {
+ res = CreateProcessAsSystemBySession(lpExePath, lpParam, NULL, dwTokenSessionId, hExplorerToken);
+ }
+ CloseHandle(hExplorerToken);

} while (false);


return res;
}

判断token是否被提升:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
BOOL IsElevatedToken(HANDLE hToken, PBOOL pbElevated)
{
DWORD dwReturnBytes = 0;
DWORD dwElevateionType = 0;
BOOL bElevated = FALSE;

if (hToken && pbElevated)
{
if (GetTokenInformation(hToken, TokenElevationType, &dwElevateionType, sizeof(dwElevateionType), &dwReturnBytes))
{
if (dwElevateionType == TokenElevationTypeFull)
bElevated = TRUE;
else if (dwElevateionType == TokenElevationTypeDefault)
{
TOKEN_ELEVATION te;
ZeroMemory(&te, sizeof(te));

if (GetTokenInformation(hToken, TokenElevation, &te, sizeof(te), &dwReturnBytes))
{
if (te.TokenIsElevated)
bElevated = TRUE;
}
}
}

if (pbElevated)
*pbElevated = bElevated;

return TRUE;
}

return FALSE;
}

BOOL GetElevatedToken(HANDLE hToken, PHANDLE phNewToken)
{
BOOL bElevated = FALSE;

IsElevatedToken(hToken, &bElevated);

if (bElevated)
{
return DuplicateTokenEx(hToken, TOKEN_ALL_ACCESS, NULL, SecurityImpersonation, TokenPrimary, phNewToken);
}
else
{
DWORD dwReturnBytes = 0;
return GetTokenInformation(hToken, TokenLinkedToken, phNewToken, sizeof(HANDLE), &dwReturnBytes);
}

return FALSE;
}

获取资源管理器的 token

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

HANDLE GetExplorerToken()
{
PromotePrivilege();

HANDLE hSnapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot == INVALID_HANDLE_VALUE)
{
return NULL;
}

HANDLE hExplorerToken = NULL;
PROCESSENTRY32 pe = { 0 };
pe.dwSize = sizeof(pe);

BOOL bMore = ::Process32First(hSnapshot, &pe);
while (bMore)
{
if (StrCmpI(L"explorer.exe", pe.szExeFile) == 0)
{
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pe.th32ProcessID);
if (hProcess == NULL)
{
continue;
}
if (OpenProcessToken(hProcess, TOKEN_QUERY, &hExplorerToken))
{
CloseHandle(hProcess);
break;
}
}
bMore = ::Process32Next(hSnapshot, &pe);
}
CloseHandle(hSnapshot);

return hExplorerToken;
}

给本进程特权,以便访问系统进程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/*
* @fn PromotePrivilege
* @brief 调整进程权限
*
* @detail 将进程权限提升成具有调试权限的进程,这个权限应该是进程所能具有的最大权限
* 前提启动这个进程的账户必须是一个管理员,否则没法提升
*/
BOOL PromotePrivilege()
{
// 附给本进程特权,以便访问系统进程
HANDLE hToken;
// 打开一个进程的访问令牌
if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken))
{
// 取得特权名称为"SetDebugPrivilege"的LUID
LUID uID;
if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &uID))
{
CloseHandle(hToken);
return FALSE;
}
// 调整特权级别
TOKEN_PRIVILEGES tp;
tp.PrivilegeCount = 1; // 只启动调试权限,所以权限的个数是一个
tp.Privileges[0].Luid = uID;

//当Attributes = SE_PRIVILEGE_ENABLE时,激活权限
//当Attributes = 0时,关闭权限
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;

// AdjustTokenPrivileges函数激活或者关闭tp中给定的权限
if (!AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL))
{
CloseHandle(hToken);
return FALSE;
}
// 关闭访问令牌句柄
CloseHandle(hToken);
return TRUE;
}
return FALSE;
}

设置当前进程的会话信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
bool CreateProcessAsSystemBySession(LPCTSTR pszAppName, LPCTSTR pszCmd, LPCTSTR pszCwd, DWORD dwSession, HANDLE hEnvToken)
{
if (pszCmd == NULL)
return false;

bool bRet = false;

HANDLE hTokenThis = NULL;
bRet = OpenProcessToken(GetCurrentProcess(), TOKEN_DUPLICATE | TOKEN_QUERY, &hTokenThis);
if (!bRet || hTokenThis == NULL)
return false;

HANDLE hTokenDup = NULL;
bRet = DuplicateTokenEx(hTokenThis, MAXIMUM_ALLOWED, NULL, SecurityIdentification, TokenPrimary, &hTokenDup);
CloseHandle(hTokenThis);
if (!bRet || hTokenDup == NULL)
return false;

if (!SetTokenInformation(hTokenDup, TokenSessionId, &dwSession, sizeof(DWORD)))
{
CloseHandle(hTokenDup);
return false;
}

bRet = CreateProcessByToken(hTokenDup, hEnvToken, pszAppName, pszCmd, pszCwd, TRUE);
CloseHandle(hTokenDup);

return bRet;
}

调用接口创建进程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
typedef BOOL(STDMETHODCALLTYPE FAR* LPFNCREATEENVIRONMENTBLOCK) (LPVOID* lpEnvironment, HANDLE  hToken, BOOL    bInherit);
typedef BOOL(STDMETHODCALLTYPE FAR* LPFNDESTROYENVIRONMENTBLOCK) (LPVOID lpEnvironment);

bool CreateProcessByToken(HANDLE hToken, HANDLE hEnvToken, LPCTSTR pszAppName, LPCTSTR pszCmd, LPCTSTR pszCwd, BOOL bWndHide/* = FALSE*/)
{
STARTUPINFO si = { sizeof(si) };
PROCESS_INFORMATION pi = { 0 };

si.lpDesktop = (LPWSTR)L"Winsta0\\Default";

if (bWndHide)
{
si.dwFlags = STARTF_USESHOWWINDOW;
si.wShowWindow = SW_HIDE;
}
DWORD dwCreationFlag = NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE;
LPVOID pEnvironment = NULL;
LPFNCREATEENVIRONMENTBLOCK lpfnCreateEnvironmentBlock = NULL;
LPFNDESTROYENVIRONMENTBLOCK lpfnDestroyEnvironmentBlock = NULL;
HMODULE hUserEnvLib = NULL;
hUserEnvLib = LoadLibrary(L"userenv.dll");
if (NULL != hUserEnvLib)
{
lpfnCreateEnvironmentBlock = (LPFNCREATEENVIRONMENTBLOCK)GetProcAddress(hUserEnvLib, "CreateEnvironmentBlock");
lpfnDestroyEnvironmentBlock = (LPFNDESTROYENVIRONMENTBLOCK)GetProcAddress(hUserEnvLib, "DestroyEnvironmentBlock");
}

if (NULL != lpfnCreateEnvironmentBlock)
{
if (lpfnCreateEnvironmentBlock(&pEnvironment, hEnvToken, FALSE))
{
dwCreationFlag |= CREATE_UNICODE_ENVIRONMENT; // must specify
}
else
pEnvironment = NULL;
}


bool bRet = false;
BOOL bDisableRedirect = FALSE;

if (CreateProcessAsUser(hToken, pszAppName, (LPTSTR)pszCmd, NULL, NULL, FALSE, dwCreationFlag, pEnvironment, pszCwd, &si, &pi))
{
CloseHandle(pi.hThread);

DWORD dwRet = WaitForSingleObject(pi.hProcess, 2 * 60 * 60 * 1000);
if (WAIT_TIMEOUT == dwRet)
{


}
else if (WAIT_OBJECT_0 == dwRet)
{

bRet = true;
}
CloseHandle(pi.hProcess);
}

if (NULL != lpfnDestroyEnvironmentBlock)
lpfnDestroyEnvironmentBlock(pEnvironment);
if (NULL != hUserEnvLib)
FreeLibrary(hUserEnvLib);

return bRet;
}