Last active
September 23, 2022 07:23
-
-
Save ondrasek/6b264ae0255addaf614e4e3700736186 to your computer and use it in GitHub Desktop.
spawn-interactive-process: How to launch a process from a Windows Service running in interactive user session(s)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// spawn-interactive-process.cpp : Defines the entry point for the console application. | |
// Do not forget to link against wtsapi32.lib | |
// How to test this: use psexec from SysInternals, such as ```psexec -s c:\windows\system32\cmd.exe``` | |
// to run CMD under NT AUTHORITY\SYSTEM account (you can confirm this by running whoami) and then | |
// run spawn-interactive-process (feel free to disable waiting for debugger). The result is a notepad process | |
// running under interactive user credentials and on the interactive desktop launched from a service running under | |
// system account. | |
#include <stdio.h> | |
#include <tchar.h> | |
#include <windows.h> | |
#include <WtsApi32.h> | |
void waitForDebugger() | |
{ | |
while (!IsDebuggerPresent()) | |
Sleep(1000); | |
DebugBreak(); | |
} | |
BOOL spawnProcess(HANDLE const hUserToken, PWSTR exeFileName) | |
{ | |
BOOL result = FALSE; | |
DWORD lastError = 0; | |
PROCESS_INFORMATION processInfo; | |
STARTUPINFO startupInfo; | |
HANDLE hToken; | |
ZeroMemory(&processInfo, sizeof(processInfo)); | |
ZeroMemory(&startupInfo, sizeof(startupInfo)); | |
startupInfo.cb = sizeof(startupInfo); | |
result = DuplicateTokenEx( | |
hUserToken, | |
MAXIMUM_ALLOWED, | |
NULL, | |
SecurityImpersonation, | |
TokenPrimary, | |
&hToken); | |
if (!result) | |
return result; | |
result = CreateProcessAsUser( | |
hToken, | |
(PWSTR) exeFileName, | |
NULL, | |
NULL, | |
NULL, | |
FALSE, | |
CREATE_BREAKAWAY_FROM_JOB | CREATE_NEW_PROCESS_GROUP, | |
NULL, | |
NULL, | |
&startupInfo, | |
&processInfo); | |
return result; | |
} | |
void dumpSession(WTS_SESSION_INFO_1* pSessionInfo) | |
{ | |
printf("SessionId: %d\n", pSessionInfo->SessionId); | |
printf("State: %d\n", pSessionInfo->State); | |
printf("Domain: %ls\n", pSessionInfo->pDomainName); | |
printf("Hostname: %ls\n", pSessionInfo->pHostName); | |
printf("FarmName: %ls\n", pSessionInfo->pFarmName); | |
printf("UserName: %ls\n", pSessionInfo->pUserName); | |
printf("\n"); | |
} | |
HANDLE getTokenFromSession(WTS_SESSION_INFO_1* pSessionInfo) | |
{ | |
HANDLE hUserToken = 0; | |
WTSQueryUserToken(pSessionInfo->SessionId, &hUserToken); | |
return hUserToken; | |
} | |
BOOL adjustPrivileges() | |
{ | |
LUID tcbPrivilege; | |
HANDLE hToken = GetCurrentProcessToken(); | |
BOOL result = FALSE; | |
TOKEN_PRIVILEGES tokenPrivileges; | |
DWORD dwSize; | |
result = LookupPrivilegeValue(NULL, SE_TCB_NAME, &tcbPrivilege); | |
if (!result) | |
goto error; | |
tokenPrivileges.PrivilegeCount = 1; | |
tokenPrivileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; | |
tokenPrivileges.Privileges[0].Luid = tcbPrivilege; | |
result = AdjustTokenPrivileges(hToken, FALSE, &tokenPrivileges, sizeof(tokenPrivileges), NULL, &dwSize); | |
goto final; | |
error: | |
return FALSE; | |
final: | |
return TRUE; | |
} | |
int main() | |
{ | |
BOOL retVal = FALSE; | |
WTS_SESSION_INFO_1 *pSessionInfo = NULL; | |
DWORD dwLevel = 1; | |
DWORD dwCount = 0; | |
HANDLE hWTSServer = WTS_CURRENT_SERVER_HANDLE; | |
waitForDebugger(); | |
retVal = adjustPrivileges(); | |
if (!retVal) | |
goto error; | |
retVal = WTSEnumerateSessionsEx( | |
hWTSServer, | |
&dwLevel, | |
0, | |
&pSessionInfo, | |
&dwCount); | |
if (retVal == FALSE) | |
goto error; | |
for (unsigned int i = 0; i < dwCount; i++) { | |
dumpSession(&pSessionInfo[i]); | |
if (pSessionInfo[i].pUserName != NULL) | |
{ | |
spawnProcess( | |
getTokenFromSession(&pSessionInfo[i]), | |
L"C:\\Windows\\Notepad.exe"); | |
} | |
} | |
goto final; | |
error: | |
printf("Error: %d.\n", GetLastError()); | |
goto final; | |
final: | |
if (pSessionInfo != NULL) { | |
WTSFreeMemoryEx(WTSTypeSessionInfoLevel1, (void*)pSessionInfo, dwCount); | |
pSessionInfo = NULL; | |
} | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thanks for it.
But I have managed to launch command interactively with a bit changes:
And also for passing arguments: