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\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();
     
       //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\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;
     
    // 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啟動交互式進程。

關於參數中"@"winsta0\default"":這是一個硬編碼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 = @"winsta0\default"; // 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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM