【Windows】降权

296 阅读4分钟

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

背景说明

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

解决方案

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

实现逻辑

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

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

代码

接口

最终调用的接口为: CreateProcessAsUser

 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;
 }

实现

实现如下所示:

 /*
 * @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是否被提升:

 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

 ​
 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;
 }
 ​

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

 /*
 * @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;
 }

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

 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;
 }

调用接口创建进程:

 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;
 }