How to detect when subprocess asks for input in Windows
if we not want let to child process process user input, but simply kill it in this case, solution can be next:
- start child process with redirected stdin to pipe.
- pipe server end we create in asynchronous mode and main set pipebuffer to 0 size
- before start child - write 1 byte to this pipe.
- because pipe buffer is 0 size - operation not complete, until anotherside not read this byte
- after we write this 1 byte and operation in progress (pending) -start child process.
- finally begin wait what complete first: write operation or child process ?
- if write complete first - this mean, that child process begin readfrom stdin - so kill it at this point
one possible implementation on c++:
struct ReadWriteContext : public OVERLAPPED{ enum OpType : char { e_write, e_read } _op; BOOLEAN _bCompleted; ReadWriteContext(OpType op) : _op(op), _bCompleted(false) { }};VOID WINAPI OnReadWrite(DWORD dwErrorCode, DWORD dwNumberOfBytesTransfered, OVERLAPPED* lpOverlapped){ static_cast<ReadWriteContext*>(lpOverlapped)->_bCompleted = TRUE; DbgPrint("%u:%x %p\n", static_cast<ReadWriteContext*>(lpOverlapped)->_op, dwErrorCode, dwNumberOfBytesTransfered);}void nul(PCWSTR lpApplicationName){ ReadWriteContext wc(ReadWriteContext::e_write), rc(ReadWriteContext::e_read); static const WCHAR pipename[] = L"\\\\?\\pipe\\{221B9EC9-85E6-4b64-9B70-249026EFAEAF}"; if (HANDLE hPipe = CreateNamedPipeW(pipename, PIPE_ACCESS_DUPLEX|FILE_FLAG_OVERLAPPED, PIPE_TYPE_BYTE|PIPE_READMODE_BYTE|PIPE_WAIT, 1, 0, 0, 0, 0)) { static SECURITY_ATTRIBUTES sa = { sizeof(sa), 0, TRUE }; PROCESS_INFORMATION pi; STARTUPINFOW si = { sizeof(si)}; si.dwFlags = STARTF_USESTDHANDLES; si.hStdInput = CreateFileW(pipename, FILE_GENERIC_READ|FILE_GENERIC_WRITE, 0, &sa, OPEN_EXISTING, 0, 0); if (INVALID_HANDLE_VALUE != si.hStdInput) { char buf[256]; if (WriteFileEx(hPipe, "\n", 1, &wc, OnReadWrite)) { si.hStdError = si.hStdOutput = si.hStdInput; if (CreateProcessW(lpApplicationName, 0, 0, 0, TRUE, CREATE_NO_WINDOW, 0, 0, &si, &pi)) { CloseHandle(pi.hThread); BOOLEAN bQuit = true; goto __read; do { bQuit = true; switch (WaitForSingleObjectEx(pi.hProcess, INFINITE, TRUE)) { case WAIT_OBJECT_0: DbgPrint("child terminated\n"); break; case WAIT_IO_COMPLETION: if (wc._bCompleted) { DbgPrint("child read from hStdInput!\n"); TerminateProcess(pi.hProcess, 0); } else if (rc._bCompleted) {__read: rc._bCompleted = false; if (ReadFileEx(hPipe, buf, sizeof(buf), &rc, OnReadWrite)) { bQuit = false; } } break; default: __debugbreak(); } } while (!bQuit); CloseHandle(pi.hProcess); } } CloseHandle(si.hStdInput); // let execute pending apc SleepEx(0, TRUE); } CloseHandle(hPipe); }}
another variant of code - use event completion, instead apc. however this not affect final result. this variant of code give absolute the same result as first:
void nul(PCWSTR lpApplicationName){ OVERLAPPED ovw = {}, ovr = {}; if (ovr.hEvent = CreateEvent(0, 0, 0, 0)) { if (ovw.hEvent = CreateEvent(0, 0, 0, 0)) { static const WCHAR pipename[] = L"\\\\?\\pipe\\{221B9EC9-85E6-4b64-9B70-249026EFAEAF}"; if (HANDLE hPipe = CreateNamedPipeW(pipename, PIPE_ACCESS_DUPLEX|FILE_FLAG_OVERLAPPED, PIPE_TYPE_BYTE|PIPE_READMODE_BYTE|PIPE_WAIT, 1, 0, 0, 0, 0)) { static SECURITY_ATTRIBUTES sa = { sizeof(sa), 0, TRUE }; PROCESS_INFORMATION pi; STARTUPINFOW si = { sizeof(si)}; si.dwFlags = STARTF_USESTDHANDLES; si.hStdInput = CreateFileW(pipename, FILE_GENERIC_READ|FILE_GENERIC_WRITE, 0, &sa, OPEN_EXISTING, 0, 0); if (INVALID_HANDLE_VALUE != si.hStdInput) { char buf[256]; if (!WriteFile(hPipe, "\n", 1, 0, &ovw) && GetLastError() == ERROR_IO_PENDING) { si.hStdError = si.hStdOutput = si.hStdInput; if (CreateProcessW(lpApplicationName, 0, 0, 0, TRUE, CREATE_NO_WINDOW, 0, 0, &si, &pi)) { CloseHandle(pi.hThread); BOOLEAN bQuit = true; HANDLE h[] = { ovr.hEvent, ovw.hEvent, pi.hProcess }; goto __read; do { bQuit = true; switch (WaitForMultipleObjects(3, h, false, INFINITE)) { case WAIT_OBJECT_0 + 0://read completed__read: if (ReadFile(hPipe, buf, sizeof(buf), 0, &ovr) || GetLastError() == ERROR_IO_PENDING) { bQuit = false; } break; case WAIT_OBJECT_0 + 1://write completed DbgPrint("child read from hStdInput!\n"); TerminateProcess(pi.hProcess, 0); break; case WAIT_OBJECT_0 + 2://process terminated DbgPrint("child terminated\n"); break; default: __debugbreak(); } } while (!bQuit); CloseHandle(pi.hProcess); } } CloseHandle(si.hStdInput); } CloseHandle(hPipe); // all pending operation completed here. } CloseHandle(ovw.hEvent); } CloseHandle(ovr.hEvent); }}
My idea to find out if the subprocess reads user input is to (ab)use the fact that file objects are stateful: if the process reads data from its stdin, we should be able to detect a change in the stdin's state.
The procedure is as follows:
- Create a temporary file that'll be used as the subprocess's stdin
- Write some data to the file
- Start the process
- Wait a little while for the process to read the data (or not), then use the
tell()
method to find out if anything has been read from the file
This is the code:
import osimport timeimport tempfileimport subprocess# create a file that we can use as the stdin for the subprocesswith tempfile.TemporaryFile() as proc_stdin: # write some data to the file for the subprocess to read proc_stdin.write(b'whatever\r\n') proc_stdin.seek(0) # start the thing cmd = ["python","ask.py"] proc = subprocess.Popen(cmd, stdin=proc_stdin, stdout=subprocess.PIPE) # wait for it to start up and do its thing time.sleep(1) # now check if the subprocess read any data from the file if proc_stdin.tell() == 0: print("it didn't take input") else: print("it took input")
Ideally the temporary file could be replaced by some kind of pipe or something that doesn't write any data to disk, but unfortunately I couldn't find a way to make it work without a real on-disk file.