Creating a child process with redirected IO
In a recent experiment, I sought to interact with a hollowed process in a manner similar to a conventional console application. Although there is an example on MSDN about creating a child process with redirected I/O, it does not create the interactive session I so desired. In this snippet I will demonstrate how to create a child process with interactive I/O
To accomplish this we need to create two pipes which will act as stdout and stdin for the spawned process. It is necessary to mark ChildStdoutR
and ChildStdinW
as non-inheritable, as the child process does not need to read its output or write to its input
HANDLE ChildStdinR { NULL };
HANDLE ChildStdinW { NULL };
HANDLE ChildStdoutR{ NULL };
HANDLE ChildStdoutW{ NULL };
BOOL CreatePipes()
{
SECURITY_ATTRIBUTES PipeAttributes = { sizeof(SECURITY_ATTRIBUTES), NULL, TRUE };
if (!CreatePipe(&ChildStdoutR, &ChildStdoutW, &PipeAttributes, NULL))
{
return FALSE;
}
if (!SetHandleInformation(ChildStdoutR, HANDLE_FLAG_INHERIT, NULL))
{
return FALSE;
}
if (!CreatePipe(&ChildStdinR, &ChildStdinW, &PipeAttributes, NULL))
{
return FALSE;
}
if (!SetHandleInformation(ChildStdinW, HANDLE_FLAG_INHERIT, NULL))
{
return FALSE;
}
if (!ChildStdinR || !ChildStdinW || !ChildStdoutR || !ChildStdoutW)
{
return FALSE;
}
return TRUE;
}
Once the pipes have been successfully initialised, the next step is to create the desired process. In order for the process to utilise the newly created pipes, it is necessary to use the STARTF_USESTDHANDLES
flag and specify the standard input/output/error handle in the respective members of the STARTUPINFO
structure: hStdError
, hStdInput
and hStdInput
. Moreover, the handles for ChildStdoutW
and ChildStdinR
are closed, as there is no utility in writing to the output or reading the input of the created process
STARTUPINFOW StartupInfo{};
StartupInfo.cb = sizeof(STARTUPINFOW);
StartupInfo.hStdError = StartupInfo.hStdOutput = ChildStdoutW;
StartupInfo.hStdInput = ChildStdinR;
StartupInfo.wShowWindow = SW_HIDE;
StartupInfo.dwFlags |= (STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW);
if (!CreateProcessW(NULL, CommandLine, NULL, NULL, TRUE, CREATE_NEW_CONSOLE, NULL, NULL, &StartupInfo, *HollowedProcess))
{
return FALSE;
}
CloseHandle(ChildStdoutW);
CloseHandle(ChildStdinR);
return TRUE;
In order to read the child process's output, a function was designed to redirect ChildStdout
to ParentStdout
VOID ReadFromPipe()
{
HANDLE ParentStdout{ GetStdHandle(STD_OUTPUT_HANDLE) };
while (TRUE)
{
CHAR Buffer[BUFFER_SIZE];
DWORD BytesRead{};
if (!ReadFile(ChildStdoutR, Buffer, BUFFER_SIZE, &BytesRead, NULL))
{
break;
}
DWORD BytesWritten{};
if (!WriteFile(ParentStdout, Buffer, BytesRead, &BytesWritten, NULL))
{
break;
}
}
}
The redirection of input is achieved in a similar manner, utilising std::getline
to read from the console and WriteFile
to transmit the input to the ChildStdin
pipe
VOID WriteToPipe()
{
while (TRUE)
{
std::string Command;
std::getline(std::cin, Command);
Command += '\n';
DWORD BytesWritten{};
if (!WriteFile(ChildStdinW, Command.c_str(), Command.length(), &BytesWritten, NULL))
{
break;
}
}
}
In view of the necessity for seamless interaction, two threads are created to run the aforementioned functions
HANDLE ReadPipeThread { CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ReadFromPipe, NULL, 0, NULL) };
HANDLE WritePipeThread{ CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)WriteToPipe, NULL, 0, NULL) };
if (ReadPipeThread == NULL || WritePipeThread == NULL)
{
return EXIT_FAILURE;
}
WaitForSingleObject(ReadPipeThread, INFINITE);
The main thread will await the termination of the thread responsible for reading from the ChildStdout
pipe. At this point, it is possible to interact with the child process in the same way as with any console application