How to detect when subprocess asks for input in Windows How to detect when subprocess asks for input in Windows windows windows

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:

  1. Create a temporary file that'll be used as the subprocess's stdin
  2. Write some data to the file
  3. Start the process
  4. 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.