lupz
lupz

Reputation: 3638

CreateProcess and redirecting output

There are 2 apps.

AppCMD is a command line app and AppMAIN starts AppCMD with some command line args. Unfortunately AppMAIN does not seem to handle the output off AppCMD very well and something is going wrong.

I'd like to log the calls to AppCMD and its output to see what is going on.

In order to do so I want to replace AppCMD with another binary AppWRAP that forwards the calls to a renamed AppCMD and logs it's output. AppWRAP should act like a transparent Man-In-The-Middle.

For testing purposes I wrote a simple AppCMD that just outputs it's command line args:

#include <iostream>

using namespace std;

int main(int argc, char *argv[])
{
    cout << "#### Hello, I'm the test binary that wants to be wrapped." << endl;

    if (argc < 2) {
        cout << "#### There where no command line arguments." << endl;
    }
    else {
        cout << "#### These are my command line arguments:";
        for (int i = 1; i < argc; ++i) cout << " " << argv[i];
        cout << endl;
    }

    cout << "#### That's pretty much everything I do ... yet ;)" << endl;

    return 0;
}

I followed MSDN: Creating a Child Process with Redirected Input and Output to implement AppWrap but I got stuck since it does not return and I cant figure out why:

#include <iostream>
#include <sstream>
#include <Windows.h>


using namespace std;


const string TARGET_BINARY("TestBinary.exe");
const size_t BUFFSIZE = 4096;

HANDLE in_read        = 0;
HANDLE in_write       = 0;
HANDLE out_read       = 0;
HANDLE out_write      = 0;

int main(int argc, char *argv[])
{
    stringstream call;

    cout << "Hello, I'm BinTheMiddle." << endl;

//-------------------------- CREATE COMMAND LINE CALL --------------------------

    call << TARGET_BINARY;
    for (int i = 1; i < argc; ++i) {
        call << " " << argv[i];
    }

    cout << "Attempting to call '" << call.str() << "'" << endl;

//------------------------------ ARRANGE IO PIPES ------------------------------

    SECURITY_ATTRIBUTES security;
    security.nLength              = sizeof(SECURITY_ATTRIBUTES);
    security.bInheritHandle       = NULL;
    security.bInheritHandle       = TRUE;
    security.lpSecurityDescriptor = NULL;

    if (!CreatePipe(&out_read, &out_write, &security, 0)) {
        cout << "Error: StdoutRd CreatePipe" << endl;
        return -1;
    }
    if (!SetHandleInformation(out_read, HANDLE_FLAG_INHERIT, 0)) {
        cout << "Stdout SetHandleInformation" << endl;
        return -2;
    }
    if (!CreatePipe(&in_read, &in_write, &security, 0)) {
        cout << "Stdin CreatePipe" << endl;
        return -3;
    }
    if (!SetHandleInformation(in_write, HANDLE_FLAG_INHERIT, 0)) {
        cout << "Stdin SetHandleInformation" << endl;
        return -4;
    }
//------------------------------ START TARGET APP ------------------------------

    STARTUPINFO         start;
    PROCESS_INFORMATION proc;

    ZeroMemory(&start, sizeof(start));
    start.cb         = sizeof(start);
    start.hStdError  = out_write;
    start.hStdOutput = out_write;
    start.hStdInput  = in_read;
    start.dwFlags    |= STARTF_USESTDHANDLES;

    ZeroMemory(&proc, sizeof(proc));

    // Start the child process.
    if (!CreateProcess(NULL, (LPSTR) call.str().c_str(), NULL, NULL, TRUE,
                       0, NULL, NULL, &start, &proc))
    {
        cout << "CreateProcess failed (" << GetLastError() << ")" << endl;
        return -1;
    }

    // Wait until child process exits.
    WaitForSingleObject(proc.hProcess, INFINITE);
    // Close process and thread handles.
    CloseHandle(proc.hProcess);
    CloseHandle(proc.hThread);

//----------------------------------- OUTPUT -----------------------------------

    DWORD dwRead;
    CHAR  chBuf[127];

    while (ReadFile(out_read, chBuf, 127, &dwRead, NULL)) {
        cout << "Wrapped: " << chBuf << endl;
    }

    return 0;
}

It seems like it is waiting for ReadFile to return. Can anybody spot what I'm doing wrong?

I call the binary this way:

> shell_cmd_wrapper.exe param1 param2

This is the console output but the binary does not return.

Hello, I'm BinTheMiddle.
Attempting to call 'TestBinary.exe param1 param2'
Wrapped:#### Hello, I'm the test binary that wants to be wrapped.
#### These are my command line arguments: param1 param2
#### That'sD
Wrapped: pretty much everything I do ... yet ;)
s to be wrapped.
#### These are my command line arguments: param1 param2
#### That'sD

(Please ignore that I don't clear the buffer)

Upvotes: 2

Views: 2554

Answers (1)

Eryk Sun
Eryk Sun

Reputation: 34260

Close the out_write and in_read handles after calling CreateProcess. Otherwise ReadFile on out_read will block when the pipe is empty because there's still a potential writer even after the child has exited -- the out_write handle in the current process.

Also, as noted by Harry Johnston in a comment, waiting for the process to exit before reading from the pipe can potentially cause a deadlock. The child will block on WriteFile if the pipe fills up.

Upvotes: 4

Related Questions