有時在我們編寫的前臺程序需要開機運行,當有後臺程序時可以註冊服務方式進行啓動(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);
參考: