I'm trying to launch a child executable to run as the logged in user(written in golang) from a windows service running as SYSTEM. I am using the CreateProcessAsUser
method to start the process. I've observed that the child process actually starts, but i'm unable to read the stdout of it. I've followed the steps described in https://learn.microsoft.com/en-us/windows/win32/procthread/creating-a-child-process-with-redirected-input-and-output to redirect the stdin and stdout of the child.
I have also set the permissions on the pipe to the Everyone SID, but still getting the same issue.
The parent process's code is as follows (after further edits based on suggestions in the comments)
#include "test.h"
#include <windows.h>
#include <winbase.h>
#include <userenv.h>
#include <tchar.h>
#include <wtsapi32.h>
#include <strsafe.h>
#include <processthreadsapi.h>
#define BUFSIZE 4096
#define PROC_THREAD_ATTRIBUTE_HANDLE_LIST ( 2 | 0x00020000)
typedef struct _STARTUPINFOEXA {
STARTUPINFOA StartupInfo;
LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList;
} STARTUPINFOEXA, *LPSTARTUPINFOEXA;
void ReadAndHandleOutput(HANDLE hPipeRead, FILE* fp)
{
CHAR lpBuffer[BUFSIZE];
DWORD nBytesRead;
DWORD bytesAvailable;
DWORD bytesLeftInMsg;
do
{
if (!PeekNamedPipe(hPipeRead, lpBuffer, sizeof(lpBuffer), &nBytesRead, &bytesAvailable, &bytesLeftInMsg))
{
if (GetLastError() == ERROR_BROKEN_PIPE) {
fprintf(fp, "Broken Pipe \n");
fflush(fp);
break; // pipe done - normal exit path.
}
else {
fprintf(fp, "ReadFileError %d\n", GetLastError()); // Something bad happened.
fflush(fp);
}
}
} while(bytesLeftInMsg > 0);
fprintf(fp, "GOT OUTPUT, %s -- %d -- %d -- %d\n", lpBuffer, nBytesRead, bytesAvailable, bytesLeftInMsg);
fflush(fp);
}
bool LaunchProcess(char *process_path)
{
FILE *fp = fopen("C:\\test.log", "a");
DWORD SessionId = WTSGetActiveConsoleSessionId();
if (SessionId == -1) { // no-one logged in
fprintf(fp, "Session ID is -1\n");
fclose(fp);
return false;
}
HANDLE hToken;
BOOL ok = WTSQueryUserToken(SessionId, &hToken);
if (!ok) {
fprintf(fp, "Unable to get the token for session id %d\n", SessionId);
fclose(fp);
return false;
}
void *environment = NULL;
ok = CreateEnvironmentBlock(&environment, hToken, TRUE);
if (!ok)
{
fprintf(fp, "Unable to create environment with session ID %d\n", SessionId);
CloseHandle(hToken);
fclose(fp);
return false;
}
HANDLE hServerPipe = CreateNamedPipe("\\\\.\\Pipe\\test-pipe.em", PIPE_ACCESS_DUPLEX, PIPE_TYPE_BYTE|PIPE_READMODE_BYTE, 1, 0, 0, 0, 0);
HANDLE hClientPipe;
if (hServerPipe != INVALID_HANDLE_VALUE)
{
static SECURITY_ATTRIBUTES sa = { sizeof(sa), 0, TRUE };
hClientPipe = CreateFile("\\\\.\\Pipe\\test-pipe.em", FILE_GENERIC_READ|FILE_GENERIC_WRITE, 0, &sa, OPEN_EXISTING, 0, 0);
if (hClientPipe == INVALID_HANDLE_VALUE)
{
fprintf(fp, "Error creating client handle %d", GetLastError());
CloseHandle(hServerPipe);
return false;
}
} else {
fprintf(fp, "Error creating server handle %d", GetLastError());
return false;
}
STARTUPINFOEXA si = { { sizeof(si) } };
PROCESS_INFORMATION pi;
si.StartupInfo.dwFlags = STARTF_USESTDHANDLES;
si.StartupInfo.hStdInput = si.StartupInfo.hStdOutput = si.StartupInfo.hStdError = hClientPipe;
DWORD dwCreationFlags = CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT;
BOOL fInit = FALSE;
SIZE_T Size;
if (!InitializeProcThreadAttributeList(0, 1, 0, &Size) && GetLastError() == ERROR_INSUFFICIENT_BUFFER &&
InitializeProcThreadAttributeList(si.lpAttributeList = (LPPROC_THREAD_ATTRIBUTE_LIST)alloca(Size), 1, 0, &Size))
{
fInit = TRUE;
if (UpdateProcThreadAttribute(si.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST, &si.StartupInfo.hStdError, sizeof(HANDLE), 0, 0)) {
dwCreationFlags |= EXTENDED_STARTUPINFO_PRESENT;
}
}
fprintf(fp, "calling the process %s\n", process_path);
fflush(fp);
ok = CreateProcessAsUser(hToken, NULL, process_path, NULL, NULL, TRUE, dwCreationFlags, environment, NULL, &si.StartupInfo, &pi);
fprintf(fp, "Running process %s and %d with PID %d\n", process_path, ok, pi.dwProcessId);
fflush(fp);
if (!ok) {
wchar_t buf[BUFSIZE];
FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
buf, (sizeof(buf) / sizeof(wchar_t)), NULL);
fprintf(fp, "Failed to create processes as user - %d, %d, %S, %s\n", SessionId, GetLastError(), buf, process_path);
fclose(fp);
return false;
} else {
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
}
// Close pipe handles (do not continue to modify the parent).
// You need to make sure that no handles to the write end of the
// output pipe are maintained in this process or else the pipe will
// not close when the child process exits and the ReadFile will hang.
if (!CloseHandle(si.StartupInfo.hStdError)) {
fprintf(fp, "CloseHandle - outputwrite Error %d\n", GetLastError());
fclose(fp);
return false;
}
ReadAndHandleOutput(hServerPipe, fp);
CloseHandle(hServerPipe);
fclose(fp);
return true;
Cleanup:
fprintf(fp, "Something went wrong");
fclose(fp);
return false;
}
The child process's code is as follows
package main
import (
"fmt"
"os"
"time"
)
func main() {
os.Stdout.Write([]byte("Hello from the GO side..."))
f, err := os.OpenFile("C:\\log.text", os.O_APPEND|os.O_WRONLY, 0600)
if err != nil {
panic(err)
}
defer f.Close()
var output = fmt.Sprintf("I'm running as %d launched by %d", os.Getpid(), os.Getppid())
if _, err = f.WriteString(output); err != nil {
panic(err)
}
os.Stdout.Close()
}
I'm able to see the current PID and the parent's PID(the service) in the log.text file, However I keep getting a pipe broken error on the parent side.
Any pointers are appreciated