zoukankan      html  css  js  c++  java
  • Windows下后台服务程序启动前台可交互界面程序

    有时在我们编写的前台程序需要开机运行,当有后台程序时可以注册服务方式进行启动(system权限),前台程序的启动需要等待用户登录到桌面后运行(涉及界面交互等),前台程序的启动主要有几种方式:

    1. 写入启动项注册表进行自动启动,这时启动的程序是以当前用户权限运行,弊端是权限低且不安全,用户可人为或被其他安全软件禁用;

    2. 创建计划任务方式启动,计划任务属性中可设置以最高权限运行,相当于管理员权限运行前台程序,可设置任何时间段运行程序,弊端也是不安全,人为或其他安全软件可禁用计划任务;

    3. 通过后台服务拉前台程序,这种是最安全的方式(后台服务也可实时守护该程序运行),也是大多数安装软件所使用的,主要有两种方式启动:

    (1) 绕过UAC以最高权限启动

    自Vista操作系统之后,微软考虑到安全因素,在系统管理员账户和标准用户之间创出了UAC(用户账户控制)。当标准用户启动需管理员权限的操作时要弹框让用户确认,这样可防止恶意软件或间谍软件随意修改系统造成破坏。

    但对于必须要通过最高权限运行交互进程来说就造成问题,微软API接口提供CreateProcessAsUser函数用于在后台服务程序中启动前台进程,但启动时要请求UAC权限(由于后台服务是最高权限启动,其创建的子进程也继承最高权限),这时后台UAC窗口无法显示在前台界面上,造成程序永远等待无法启动。

    vista之后,微软会为每个登录用户分配一个会话,后台服务在系统启动时最先启动,分配的会话ID为0,其后每登录一个用户会话ID加1:

    问题来了,由于有会话隔离,我们无法在一个会话程序中直接启动另一会话的程序。但微软有一个特殊的进程,对于每个会话会有一个对应的进程,这个进程就是winlogin.exe:

    winlogin进程的作用

    Winlogon.exe进程是微软公司为其Windows操作系统定义的一个非常重要的系统核心进程,被称为“Windows登陆应用程序”,它会随着系统启动而启动并一直运行。通常情况下此进程应该是安全的,并且只占用很少的CPU及内存资源,如果资源占用过多则很有可能被病毒“劫持”。

    请不要尝试将本进程终止(也无法在任务管理器将其终止)或将此进程从系统中删除,这会导致你的系统无法正常运行。因为Winlogon.exe进程为系统提供了有以下4项重要的功能:

        在登录系统时加载的用户配置文件,以及执行注销用户与锁定计算机;
        负责处理Ctrl+Alt+Del快捷键(SAS)的功能;
        监控键盘和鼠标使用情况来决定什么时候启动屏幕保护程序;
        检验Windows操作系统激活密钥是否为合法许可;

    可以发现winlogin进程是后台服务进程,但所属登录用户会话,那是不是可以通过这个进程来达到我们绕过UAC的限制启动前台交互程序呢?没错!!!

    有了winlogin进程,我们可以在后台服务中先查询到winlogin进程信息,获取其访问令牌,最后通过CreateProcessAsUser将进程启动到活动登录用户当前活动会话。由于和前台界面所属同一会话,启动后的程序便可以进行交互。Exciting!!!

    好了,来看看代码吧:

    1. C++代码

        BOOL LaunchAppIntoDifferentSession()
        {
           PROCESS_INFORMATION pi;
           STARTUPINFO si;
           BOOL bResult = FALSE;
           DWORD dwSessionId,winlogonPid;
           HANDLE hUserToken,hUserTokenDup,hPToken,hProcess;
           DWORD dwCreationFlags;
         
        // Log the client on to the local computer.
         
           dwSessionId = WTSGetActiveConsoleSessionId();
         
        //////////////////////////////////////////
           // Find the winlogon process
        ////////////////////////////////////////
         
           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 (_wcsicmp(procEntry.szExeFile, L"winlogon.exe") == 0)
                {
                    // We found a winlogon process...
                // make sure it's running in the console session
                    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 = L"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 ",GetLastError());
           }
         
           if (!LookupPrivilegeValue(NULL,SE_DEBUG_NAME,&luid))
           {
               printf("Lookup Privilege value Error: %u ",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();
         
           //Adjust Token privilege
           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 ",GetLastError());
           }
         
           if (GetLastError()== ERROR_NOT_ALL_ASSIGNED)
           {
             printf("Token does not have the provilege ");
           }
         
           LPVOID pEnv =NULL;
         
           if(CreateEnvironmentBlock(&pEnv,hUserTokenDup,TRUE))
           {
               dwCreationFlags|=CREATE_UNICODE_ENVIRONMENT;
           }
           else
              pEnv=NULL;
         
        // Launch the process in the client's logon session.
         
          bResult = CreateProcessAsUser(
              hUserTokenDup,                     // client's access token
              _T("cmd.exe"),    // file to execute
              NULL,                 // 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
           );
        // End impersonation of client.
         
        //GetLastError Shud be 0
         
           int iResultOfCreateProcessAsUser = GetLastError();
         
        //Perform All the Close Handles tasks
         
          CloseHandle(hProcess);
          CloseHandle(hUserToken);
          CloseHandle(hUserTokenDup);
          CloseHandle(hPToken);
         
         return 0;
        }

    简单讲解一下:

    (1)通过WTSGetActiveConsoleSessionId获取当前活动会话;

    (2)通过CreateToolhelp32Snapshot获取当前所有活动进程,查找到当前活动会话的winlogin进程信息(pid);

    (3)通过WTSQueryUserToken,OpenProcessToken等复制winlogin进程的访问令牌信息;

    (4)通过LookupPrivilegeValue,AdjustTokenPrivileges等进行提权操作;

    (5)最后通过CreateProcessAsUser启动交互式进程。

    关于参数中"@"winsta0default"":这是一个硬编码string,微软任意选择向操作系统表明我们即将产生的进程CreateProcessAsUser应该具有对交互式windowstation和桌面的完全访问权限,这基本上意味着它允许在桌面上显示UI元素。

     

    2. C#代码

        public static bool StartProcessAndBypassUAC(String applicationName, out PROCESS_INFORMATION procInfo)
                {
                    uint winlogonPid = 0;
                    IntPtr hUserTokenDup = IntPtr.Zero, hPToken = IntPtr.Zero, hProcess = IntPtr.Zero;            
                    procInfo = new PROCESS_INFORMATION();
         
                    // obtain the currently active session id; every logged on user in the system has a unique session id
                    uint dwSessionId = WTSGetActiveConsoleSessionId();
                    // obtain the process id of the winlogon process that is running within the currently active session
                    Process[] processes = Process.GetProcessesByName("winlogon");
                    foreach (Process p in processes)
                    {
                        if ((uint)p.SessionId == dwSessionId)
                        {
                            winlogonPid = (uint)p.Id;
                        }
                    }
         
                    // obtain a handle to the winlogon process
                    hProcess = OpenProcess(MAXIMUM_ALLOWED, false, winlogonPid);
         
                    // obtain a handle to the access token of the winlogon process
                    if (!OpenProcessToken(hProcess, TOKEN_DUPLICATE, ref hPToken))
                    {
                        CloseHandle(hProcess);
                        return false;
                    }
         
                    // Security attibute structure used in DuplicateTokenEx and CreateProcessAsUser
                    // I would prefer to not have to use a security attribute variable and to just
                    // simply pass null and inherit (by default) the security attributes
                    // of the existing token. However, in C# structures are value types and therefore
                    // cannot be assigned the null value.
                    SECURITY_ATTRIBUTES sa = new SECURITY_ATTRIBUTES();
                    sa.Length = Marshal.SizeOf(sa);
         
                    // copy the access token of the winlogon process; the newly created token will be a primary token
                    if (!DuplicateTokenEx(hPToken, MAXIMUM_ALLOWED, ref sa, (int)SECURITY_IMPERSONATION_LEVEL.SecurityIdentification, (int)TOKEN_TYPE.TokenPrimary, ref hUserTokenDup))
                    {
                        CloseHandle(hProcess);
                        CloseHandle(hPToken);
                        return false;
                    }
         
                    // By default CreateProcessAsUser creates a process on a non-interactive window station, meaning
                    // the window station has a desktop that is invisible and the process is incapable of receiving
                    // user input. To remedy this we set the lpDesktop parameter to indicate we want to enable user
                    // interaction with the new process.
                    STARTUPINFO si = new STARTUPINFO();
                    si.cb = (int)Marshal.SizeOf(si);
                    si.lpDesktop = @"winsta0default"; // interactive window station parameter; basically this indicates that the process created can display a GUI on the desktop
         
                    // flags that specify the priority and creation method of the process
                    int dwCreationFlags = NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE;
         
                    // create a new process in the current user's logon session
                    bool result = CreateProcessAsUser(hUserTokenDup,        // client's access token
                                                    null,                   // file to execute
                                                    applicationName,        // command line
                                                    ref sa,                 // pointer to process SECURITY_ATTRIBUTES
                                                    ref sa,                 // pointer to thread SECURITY_ATTRIBUTES
                                                    false,                  // handles are not inheritable
                                                    dwCreationFlags,        // creation flags
                                                    IntPtr.Zero,            // pointer to new environment block
                                                    null,                   // name of current directory
                                                    ref si,                 // pointer to STARTUPINFO structure
                                                    out procInfo            // receives information about new process
                                                    );
         
                    // invalidate the handles
                    CloseHandle(hProcess);
                    CloseHandle(hPToken);
                    CloseHandle(hUserTokenDup);
         
                    return result; // return the result
                }

    参考:https://www.codeproject.com/Articles/35773/Subverting-Vista-UAC-in-Both-32-and-64-bit-Archite

               https://www.codeproject.com/Articles/18367/Launch-your-application-in-Vista-under-the-local-s

    (2)获取explorer进程的令牌信息保证前台进程以低权限方式运行

    用户登录后,explorer管理器会启动,且是基于当前用户权限的,所以获取explorer令牌的方式启动的前台进程也是基于当前用户权限,这种方式和直接手动运行前台程序效果一样,弊端就是当程序中含有高权限操作(如对系统目录/注册表等写操作)会出现权限不足而失败,所以当出现这类操作时最好使用上面的启动方式。

        BOOL CreateProcessByExplorer(LPCWSTR process, LPCWSTR cmd)
        {
            BOOL ret = FALSE;
            
            HANDLE hProcess = 0, hToken = 0, hDuplicatedToken = 0;
            LPVOID lpEnv = NULL;
            do
            {
                DWORD explorerPid = GetActiveProcessId(L"explorer.exe", TRUE); // 获取explorer进程号,自行实现
                if (explorerPid == -1)
                    break;
                hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, TRUE, explorerPid);
                if (INVALID_HANDLE_VALUE == hProcess)
                    break;
         
                if (!OpenProcessToken(hProcess, TOKEN_ALL_ACCESS, &hToken))
                    break;
         
                DuplicateTokenEx(hToken, MAXIMUM_ALLOWED, NULL, SecurityIdentification, TokenPrimary, &hDuplicatedToken);
                CreateEnvironmentBlock(&lpEnv, hDuplicatedToken, FALSE);
         
                wstring processCmd = L""";
                processCmd += process;
                if (NULL != cmd)
                    processCmd += wstring(L"" "") + cmd;
                processCmd += L""";
         
                STARTUPINFO si = {0};
                PROCESS_INFORMATION pi = {0};
                si.cb = sizeof(STARTUPINFO);
                si.lpDesktop = L"winsta0\default";
                si.dwFlags = STARTF_USESHOWWINDOW;
                si.wShowWindow = SW_HIDE;
                if (!CreateProcessAsUser(hToken, NULL, const_cast<LPWSTR>(processCmd.c_str()), 0, 0, FALSE, CREATE_UNICODE_ENVIRONMENT, lpEnv, 0, &si, &pi))
                    break;
                ret = TRUE;
            } while (0);
            if (INVALID_HANDLE_VALUE != hProcess)
                CloseHandle(hProcess);
            if (INVALID_HANDLE_VALUE != hToken)
                CloseHandle(hToken);
            if (INVALID_HANDLE_VALUE != hDuplicatedToken)
                CloseHandle(hDuplicatedToken);
            if (NULL != lpEnv)
                DestroyEnvironmentBlock(lpEnv);
            return ret;
        }

    调用方式:

    CreateProcessByExplorer(L"test.exe", NULL);

    参考:

    https://blog.csdn.net/huanglong8/article/details/53574120

  • 相关阅读:
    BZOJ1059|矩阵游戏
    Tyvj3632|超级英雄Hero
    BZOJ1192 鬼谷子的钱袋
    BZOJ1003 ZJOI2006物流运输trans 0'
    BZOJ1008|HNOI2008 越狱
    添加setuptools脚本
    更新linux下python版本
    函数式编程正确姿势
    python 多线程操作数据库
    python with语句
  • 原文地址:https://www.cnblogs.com/lidabo/p/13275165.html
Copyright © 2011-2022 走看看