团队协作的句子:如何在Vista中不弹出UAC以本地系统账户启动程序

来源:百度文库 编辑:中财网 时间:2024/05/10 18:06:20

 本文讲解如何以本地系统账户从会话0至会话1启动一个程序。

 

 

         简介

         Vista中,你可能也遇到过这样的问题,当想要以管理员账户启动一个exe执行某些特定任务时,往往UAC权限提升对话框就出来了。为解决这个问题,可把一个服务以本地系统账户权限放入到当前用户会话中,来启动所需的程序,这就不需要我们响应UAC对话框了,并以尽可能最高的权限来运行程序。在某些情况中,这是必不可少的,因为某些程序需要在无用户干预的情况下,以管理员权限相互控制与通讯,并在操作系统启动时,在当前用户会话中执行一些管理性的任务。

 

 

         背景

         有一个普通用户模式的程序,其通过自定义消息与某个服务进行通讯,当接收到自定义消息时,服务将会以系统账户启动所请求的程序。这里使用了winlogon的令牌来获取本地系统账户,因为winlogon.exe正运行在本地系统账户下。(不像XP,在Vista中,服务运行在会话0中,而第一个登录在会话1中,以此类推)

 

 

         使用的代码

         先来看一下CustomMessageSender.cpp这个文件,这是一个用户模式程序,其负责与服务通讯,且没有任务特权。

 

 

#define SERVICE_NAME _T("CustomSvc")

//为服务自定义消息,以便在会话1中启动相关进程

#define SERVICE_CONTROL_CUSTOM_MESSAGE 0x0085

 

int _tmain(int argc, _TCHAR* argv[])

{

    SC_HANDLE hMyService,hSCM;

    BOOL bSuccess;

    SERVICE_STATUS status;

    hSCM = OpenSCManager(0,0,SC_MANAGER_CONNECT);

    if(!hSCM)

    {

        printf("Open SCM failed with error %u",GetLastError());

    }

    hMyService = OpenService(hSCM,SERVICE_NAME,SERVICE_USER_DEFINED_CONTROL);

    if(!hMyService)

    {

        printf("Open SCM failed with error %u",GetLastError());

    }

    bSuccess = ControlService(hMyService,SERVICE_CONTROL_CUSTOM_MESSAGE,&status);

    if(!bSuccess)

    {

        printf("Control Service failed with error %u",GetLastError());

    }

    CloseServiceHandle(hMyService);

    CloseServiceHandle(hSCM);

    return 0;

}

 

 

         代码非常简单,使用了SERVICE_USER_DEFINED_CONTROLSC_MANAGER_CONNECT访问权限,因为任何用户模式的程序都能连接到我们的服务,并向它发送自定义消息,在这并不需要管理员权限。这个程序向服务发送SERVICE_CONTROL_CUSTOM_MESSAGE,服务收到消息并启动相应的程序。以下是一段简单的服务代码:

 

 

//为服务自定义消息,以便在会话1中启动相关进程

#define SERVICE_CONTROL_CUSTOM_MESSAGE 0x0085

 

//以本地系统账户,在会话1下启动程序的方法

 

BOOL LaunchAppIntoDifferentSession();

.

省略...

 

void WINAPI ServiceCtrlHandler(DWORD Opcode)

{

  switch(Opcode)

  {

//////////////////////////////////////////////////////////////////////////

//从用户程序接受到一个自定义消息

 

    case SERVICE_CONTROL_CUSTOM_MESSAGE:

        LaunchAppIntoDifferentSession();

break;

 

 

         在这段代码中,声明了自定义消息与方法原型,先来说一下这个LaunchAppIntoDifferentSession方法。为达到以本地系统账户启动一个进程的目的,需要以下步骤:

 

 

1、   用WTSGetActiveConsoleSessionId,得到目前活动的控制台SessionID

2、   因为需要以系统账户启动程序,所以要使用从Winlogon得到的令牌(因为Winlogon运行在系统账户下)。取得Winlogon进程ID并复制令牌。

3、   在CreateProcessAsUser中,用复制的Winlogon令牌以会话1启动相应程序。

 

 

BOOL LaunchAppIntoDifferentSession()

{

   PROCESS_INFORMATION pi;

   STARTUPINFO si;

   BOOL bResult = FALSE;

   DWORD dwSessionId,winlogonPid;

   HANDLE hUserToken,hUserTokenDup,hPToken,hProcess;

   DWORD dwCreationFlags;

 

   dwSessionId = WTSGetActiveConsoleSessionId();

 

//////////////////////////////////////////

   //查找winlogon进程

////////////////////////////////////////

 

   PROCESSENTRY32 procEntry;

 

    HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);

    if (hSnap == INVALID_HANDLE_VALUE)

    {

        return 1 ;

    }

 

    procEntry.dwSize = sizeof(PROCESSENTRY32);

 

    if (!Process32First(hSnap, &procEntry))

    {

        return 1 ;

    }

 

    do

    {

        if (_stricmp(procEntry.szExeFile, "winlogon.exe") == 0)

        {

        //找到winlogon进程

        //确定它运行在控制台会话中

            DWORD winlogonSessId = 0;

            if (ProcessIdToSessionId(procEntry.th32ProcessID, &winlogonSessId)

                    && winlogonSessId == dwSessionId)

            {

                winlogonPid = procEntry.th32ProcessID;

                break;

            }

        }

 

    } while (Process32Next(hSnap, &procEntry));

 

////////////////////////////////////////////////////////////////////////


WTSQueryUserToken(dwSessionId,&hUserToken);

   dwCreationFlags = NORMAL_PRIORITY_CLASS|CREATE_NEW_CONSOLE;

   ZeroMemory(&si, sizeof(STARTUPINFO));

   si.cb= sizeof(STARTUPINFO);

   si.lpDesktop = "winsta0\\default";

   ZeroMemory(&pi, sizeof(pi));

   TOKEN_PRIVILEGES tp;

   LUID luid;

   hProcess = OpenProcess(MAXIMUM_ALLOWED,FALSE,winlogonPid);

 

   if(!::OpenProcessToken(hProcess,TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY

                 |TOKEN_DUPLICATE|TOKEN_ASSIGN_PRIMARY|TOKEN_ADJUST_SESSIONID

                          |TOKEN_READ|TOKEN_WRITE,&hPToken))

   {

               int abcd = GetLastError();

               printf("Process token open Error: %u\n",GetLastError());

   }

 

   if (!LookupPrivilegeValue(NULL,SE_DEBUG_NAME,&luid))

   {

       printf("Lookup Privilege value Error: %u\n",GetLastError());

   }

   tp.PrivilegeCount =1;

   tp.Privileges[0].Luid =luid;

   tp.Privileges[0].Attributes =SE_PRIVILEGE_ENABLED;

 

   DuplicateTokenEx(hPToken,MAXIMUM_ALLOWED,NULL,

            SecurityIdentification,TokenPrimary,&hUserTokenDup);

   int dup = GetLastError();

 

   //调整令牌权限

   SetTokenInformation(hUserTokenDup,

        TokenSessionId,(void*)dwSessionId,sizeof(DWORD));

 

   if (!AdjustTokenPrivileges(hUserTokenDup,FALSE,&tp,sizeof(TOKEN_PRIVILEGES),

                        (PTOKEN_PRIVILEGES)NULL,NULL))

   {

       int abc =GetLastError();

       printf("Adjust Privilege value Error: %u\n",GetLastError());

   }

 

   if (GetLastError()== ERROR_NOT_ALL_ASSIGNED)

   {

     printf("Token does not have the provilege\n");

   }

 

   LPVOID pEnv =NULL;

 

   if(CreateEnvironmentBlock(&pEnv,hUserTokenDup,TRUE))

   {

       dwCreationFlags|=CREATE_UNICODE_ENVIRONMENT;

   }

   else

      pEnv=NULL;

 

//在用户登录的会话中启动进程

 

  bResult = CreateProcessAsUser(

      hUserTokenDup,                  //用户的访问令牌

      _T("C:\\SessionLauncher\\a.exe"),   //要执行的文件

      NULL,                          //命令行

      NULL,                          //进程指针SECURITY_ATTRIBUTES

      NULL,                          //线程指针SECURITY_ATTRIBUTES

      FALSE,                         //句柄不可继承

      dwCreationFlags,                //创建标志

      pEnv,                          //指向新环境块的指针

      NULL,                         //当前目录名

      &si,                           //指向STARTUPINFO结构的指针

      &pi                           //新进程的相关信息

   );

 

   int iResultOfCreateProcessAsUser = GetLastError();//此处结果应为0

 

//关闭所有句柄

 

  CloseHandle(hProcess);

  CloseHandle(hUserToken);

  CloseHandle(hUserTokenDup);

  CloseHandle(hPToken);

 

 return 0;

}

 

 

         这样一来,普通用户模式的程序就能向服务发送一自定义消息,以在本地系统账户下启动自身,而无须弹出UAC对话框。

 

         可能你要问了:怎样用GetUserName() API来返回当前已登录用户、怎样在系统账户下访问HKCU。事实上,当我们启动一个需要提升权限的进程时,不可能从用户角度绕过UAC对话框,因为微软就是这样设计的。尽管写一些内核模式代码可能会达到此目的,但最好还是模仿系统账户来访问HKCU。在此使用了Explorer进程,因为它运行在用户账户中。

         模仿用户令牌会导致当前工作者线程运行于用户上下文中。请注意,如果你使用CreateProcess(),它仍会在系统账户下生成进程,因为我们总的进程仍是运行在本地系统账户之下。

         以下便是大致的代码,这段代码需要写进由服务启动的程序中:

 

 

DWORD dwSessionId,dwExplorerLogonPid,dwSize,dwRegDataSize;

HANDLE hProcess,hPToken;

char szUserName[MAX_PATH];

char szRegData[MAX_PATH];

char szRegPath[500] = "Software\\Microsoft\\Windows\\CurrentVersion\\Run";

HKEY hKey; //用于注册表键值的句柄

long lRegResult; //注册表操作结果

 

//取当前活动的桌面会话id

dwSessionId = WTSGetActiveConsoleSessionId();

 

 

//////////////////////////////////////////

   //查找explorer进程

////////////////////////////////////////

 

   PROCESSENTRY32 procEntry;

 

    HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);

    if (hSnap == INVALID_HANDLE_VALUE)

    {

        return 1 ;

    }

 

    procEntry.dwSize = sizeof(PROCESSENTRY32);

 

    if (!Process32First(hSnap, &procEntry))

    {

        return 1 ;

    }

 

    do

    {

        if (_stricmp(procEntry.szExeFile, "explorer.exe") == 0)

        {

          DWORD dwExplorerSessId = 0;

          if (ProcessIdToSessionId(procEntry.th32ProcessID, &dwExplorerSessId)

                    && dwExplorerSessId == dwSessionId)

            {

                dwExplorerLogonPid = procEntry.th32ProcessID;

                break;

            }

        }

 

    } while (Process32Next(hSnap, &procEntry));

 

////////////////////////////////////////////////////////////////////////

hProcess = OpenProcess(MAXIMUM_ALLOWED,FALSE,dwExplorerLogonPid);

 

   if(!::OpenProcessToken(hProcess,TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY

              |TOKEN_DUPLICATE|TOKEN_ASSIGN_PRIMARY|TOKEN_ADJUST_SESSIONID

              |TOKEN_READ|TOKEN_WRITE,&hPToken))

   {

               int abcd = GetLastError();

               printf("Process token open Error: %u\n",GetLastError());

   }

 

 

         我们需要模仿服务令牌,以便作为用户运行来访问注册表,这会导致我们的工作者线程运行在用户令牌上下文中。

 

 

//模仿explorer令牌,其运行于用户账户下

ImpersonateLoggedOnUser(hPToken);

 

int iImpersonateResult = GetLastError();

 

if(iImpersonateResult == ERROR_SUCCESS)

{

  GetUserName(szUserName,&dwSize);

 

 //因为线程作为用户运行,现在就可以访问HKCU

  dwRegDataSize = sizeof(szRegData);

  lRegResult = RegOpenKeyEx(HKEY_CURRENT_USER,

            szRegPath,0,KEY_QUERY_VALUE,&hKey);

  if (lRegResult == ERROR_SUCCESS)

     RegQueryValueEx(hKey,_T("SideBar"),NULL,NULL,

            (LPBYTE)&szRegData,&dwRegDataSize);

}

//一旦操作完成,回复到系统账户

RevertToSelf();