Jump to content
Tuts 4 You
Sign in to follow this  
LCF-AT

How to read from CLI till its finished

Recommended Posts

LCF-AT

Hi guys,

I have again a small question about piping a CLI output to my buffer.So I just wanna start a CLI app with paramters and reading the output (content I also see in CMD window) so long till its finished.So the only problem I have so far is how to wait till its finished.Lets say the CLI app needs a while (a minute etc).My question is what function/s I need to use for that?I see I cant use WaitForSingleObject with INFINITE paramter so this dosent work because the CLI app dosent exit itself so long I didnt had reading the bytes.If I just read the bytes using PeekNamedPipe / ReadFile function then I dont get the whole content = it quits to early and the CLI app is still working.

My goal is it to read the bytes on fly and checking whether the pipe / process is still working / activ or not.

Below some example code:

_CreatePipe PROC

Local  RetVal:DWORD

        invoke RtlZeroMemory,addr SecuAttr,sizeof SecuAttr
        invoke RtlZeroMemory,addr StartInfo,sizeof StartInfo
        invoke RtlZeroMemory,addr PI,sizeof PI
        mov RetVal,0
        mov SecuAttr.nLength,sizeof SECURITY_ATTRIBUTES
        mov SecuAttr.bInheritHandle,TRUE
        invoke CreatePipe,addr hRead,addr hWrite,addr SecuAttr,NULL
        .if eax == FALSE
            invoke MessageBox,0,chr$("CreatePipe failed!"),chr$("Problem!"),MB_ICONWARNING
            mov eax,FALSE
            ret
        .endif
	    invoke SetHandleInformation, hRead, HANDLE_FLAG_INHERIT, 0
        .if eax == FALSE
            invoke CloseHandle,hRead
            invoke CloseHandle,hWrite
            invoke MessageBox,0,chr$("SetHandleInformation failed!"),chr$("Problem!"),MB_ICONWARNING
            mov eax,FALSE
            ret
        .endif	
        mov RetVal,eax
        mov StartInfo.cb,sizeof StartInfo
        invoke GetStartupInfo,addr StartInfo
        xor eax,eax
        mov StartInfo.lpReserved,eax     
        mov eax,hWrite
        mov StartInfo.hStdOutput,eax
        mov StartInfo.hStdError,eax
	    mov StartInfo.dwFlags, STARTF_USESTDHANDLES
        mov StartInfo.wShowWindow,SW_SHOW
        mov eax,RetVal
        ret
_CreatePipe endp
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
		.elseif ax == IDB_TEST

		    invoke VirtualAlloc,0,50000h,MEM_COMMIT,PAGE_EXECUTE_READWRITE
		    mov esi, eax
		    push esi
		    invoke _CreatePipe
		    .if eax != FALSE
		        lea edi, chr$("cmd.exe /c C:\tool.exe -t -r")
		        invoke CreateProcess, NULL, edi, NULL,NULL,TRUE,CREATE_NO_WINDOW,NULL,NULL, addr StartInfo, addr PI 
		        .if eax != FALSE

		            @@:
		            invoke Sleep,300
		            invoke PeekNamedPipe,hRead,offset PIPEBUFFER,sizeof PIPEBUFFER,offset PIPEREADBYTES,addr AVAL,NULL
		            .if eax != FALSE
		                .if AVAL != 0h
		                    invoke ReadFile,hRead,esi,PIPEREADBYTES,addr BYTESREAD,0
		                    .if eax != FALSE
		                        add esi, BYTESREAD
		                        jmp @B
		                    .else
		                        invoke MessageBox,0,chr$("ReadFile failed!"),chr$("Problem!"),MB_ICONWARNING
		                        jmp @F
		                    .endif
		                .else
		                    pop esi
		                    push esi
		                    invoke MessageBox,0,esi,chr$("Info!"),MB_ICONINFORMATION
		                .endif
		            .else
		                invoke MessageBox,0,chr$("PeekNamedPipe failed!"),chr$("Problem!"),MB_ICONWARNING
		            .endif

		                @@:
		                invoke CloseHandle, [PI.hThread]
		                invoke CloseHandle, [PI.hProcess]
		                invoke CloseHandle, hRead	
		                invoke CloseHandle, hWrite	            
		        .else
		        invoke CloseHandle, hRead
		        invoke CloseHandle, hWrite
		        .endif
		    .endif
		    pop esi
		    invoke VirtualFree,esi,0,MEM_RELEASE

How to handle the wait operation correctly and to read all bytes / content so long the CLI app is activ / working?Maybe anyone could help a little to fix that problem.

Thank you

Share this post


Link to post
Share on other sites
evlncrn8

https://docs.microsoft.com/en-us/windows/desktop/api/processthreadsapi/nf-processthreadsapi-getexitcodeprocess

that at least lets you check 100% if the process has exited, the rest is just polling, to check if there's input and so on

also, 

        mov StartInfo.hStdOutput,eax
        mov StartInfo.hStdError,eax

was that intentional ?

theres stdout, stdin, and stderr.. also you didnt set hread from what i saw but i didnt go through the code in detail

  • Like 2

Share this post


Link to post
Share on other sites
atom0s

When you use CreateProcess the PROCESS_INFORMATION class gives back the main process handle and the main thread handle. You can use those to pull the exit codes of either.

 - GetExitCodeProcess and GetExitCodeThread

Keep in mind if the application is multi-threaded, it is not guaranteed that the main thread handle will remain alive for the entire duration of the app.

  • Like 1

Share this post


Link to post
Share on other sites
LCF-AT

Hi guys,

thanks for your answers.Ok I am just checking whether the process is still active or not using GetExitCodeProcess function.Ok,so this seems to work  so far and I can read while pipe content.Now I found another small problem.So if I start the CLI app and it does run for a longer while then I wanna also stop the process similar like using CRTL+C key press in CMD window.First I just tried to use TermniateProcess function with the hprocess ID of CLI app but this dosent work and CLI app keeps working in background / taskmanager and I need to wait till its finished by itself.Now I checked again the internet and found something about using GenerateConsoleCtrlEvent function to send CTRL_C_EVENT.I also found some example code like this...

https://codetitans.pl/blog/post/sending-ctrl-c-signal-to-another-application-on-windows

[DllImport("kernel32.dll", SetLastError = true)]
static extern bool AttachConsole(uint dwProcessId);

[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
static extern bool FreeConsole();

[DllImport("kernel32.dll")]
static extern bool SetConsoleCtrlHandler(ConsoleCtrlDelegate handler, bool add);

// Delegate type to be used as the Handler Routine for SCCH
delegate Boolean ConsoleCtrlDelegate(CtrlTypes type);

// Enumerated type for the control messages sent to the handler routine
enum CtrlTypes : uint
{
    CTRL_C_EVENT = 0,
    CTRL_BREAK_EVENT,
    CTRL_CLOSE_EVENT,
    CTRL_LOGOFF_EVENT = 5,
    CTRL_SHUTDOWN_EVENT
}

[DllImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GenerateConsoleCtrlEvent(CtrlTypes dwCtrlEvent, uint dwProcessGroupId);

public static void StopProgram(uint pid)
{
    // It's impossible to be attached to 2 consoles at the same time,
    // so release the current one.
    FreeConsole();

    // This does not require the console window to be visible.
    if (AttachConsole(pid))
    {
        // Disable Ctrl-C handling for our program
        SetConsoleCtrlHandler(null, true);
        GenerateConsoleCtrlEvent(CtrlTypes.CTRL_C_EVENT, 0);

        // Must wait here. If we don't and re-enable Ctrl-C
        // handling below too fast, we might terminate ourselves.
        Thread.Sleep(2000);

        FreeConsole();

        // Re-enable Ctrl-C handling or any subsequently started
        // programs will inherit the disabled state.
        SetConsoleCtrlHandler(null, false);
    }
}

...I tried this like this for testing...

	                             invoke CreateProcess, NULL,addr commandline,NULL,NULL,TRUE,CREATE_NO_WINDOW,NULL,NULL,addr StartInfo,addr PI
	                             .if eax != FALSE
	                                 invoke Sleep,2000

	                                 invoke AttachConsole,PI.dwProcessId
	                                 .if eax != FALSE
	                                     invoke SetConsoleCtrlHandler,NULL,TRUE
	                                     .if eax != FALSE
	                                         invoke GenerateConsoleCtrlEvent,CTRL_C_EVENT,NULL
	                                         .if eax != FALSE
	                                             invoke Sleep,2000
	                                             invoke FreeConsole
	                                             invoke SetConsoleCtrlHandler,NULL,FALSE
	                                         .endif
	                                     .endif
	                                 .endif

...so it seems to work and the CLI app gets closed but I am not so sure whether this code example is good idea to use it on that way etc.In Olly it does crash after I did traced over this code after (not in outside of Olly).So is it needed to use FreeConsole & SetConsoleCtrlHandler at the end if the CLI is no more present?I dont think so or?I am using already cmd.exe /c paramter in CreateProcess function.

Other issue I see is that I dont use the flag CREATE_NEW_PROCESS_GROUP (in the link above they told to use it but then CTRL_C_EVENT isnt working).Maybe you have some another little clue how I should do it correctly to send CTRL_C_EVENT & wait without to get any trouble later and I also dont wanna exit the CLI process rough you know.

greetz

Share this post


Link to post
Share on other sites
LCF-AT

Hi again,

ok I used it so now with freeconsole & Setconsolehandler at the end.So if I dont do it then it dosent work anymore after first try.My thread code look so now...

local WASATTACHED:DWORD

    mov WASATTACHED, 0
    @@:
    .if STOP == 1  ; <--- stop CLI
        invoke GetExitCodeProcess,[PI.hProcess],addr ExitCode
        .if ExitCode == STILL_ACTIVE && WASATTACHED == NULL
            invoke AttachConsole,PI.dwProcessId
            mov WASATTACHED, 1
            invoke SetConsoleCtrlHandler,NULL,TRUE
            invoke GenerateConsoleCtrlEvent,CTRL_C_EVENT,NULL
        .endif       
    .endif
    invoke RtlZeroMemory,addr PIPEBUFFER,sizeof PIPEBUFFER
    invoke Sleep,30
    invoke PeekNamedPipe,hRead,offset PIPEBUFFER,sizeof PIPEBUFFER-10h,offset PIPEREADBYTES,addr AVAL,NULL
    .if eax != FALSE
        .if AVAL != 0h
            invoke RtlZeroMemory,addr PIPEBUFFER,sizeof PIPEBUFFER
            invoke ReadFile,hRead,addr PIPEBUFFER,PIPEREADBYTES,addr BYTESREAD,0
            .if eax != FALSE
                invoke SendMessage,RICHEDITHANDLE,EM_REPLACESEL,FALSE,addr PIPEBUFFER
                invoke RichSetPos, RICHEDITHANDLE, -1h
                jmp @B
            .else
                invoke MessageBox,0,chr$("ReadFile failed!"),chr$("Problem!"),MB_ICONWARNING
                jmp @F
            .endif
        .else
            invoke GetExitCodeProcess,[PI.hProcess],addr ExitCode
            .if ExitCode == STILL_ACTIVE
                jmp @B
            .endif
        .endif
    .else
        invoke MessageBox,0,chr$("PeekNamedPipe failed!"),chr$("Problem!"),MB_ICONWARNING
    .endif
    @@:
    invoke CloseHandle, hRead
    invoke CloseHandle, hWrite     
    invoke CloseHandle, [PI.hThread]
    invoke CloseHandle, [PI.hProcess] 
    .if STOP == 1 && WASATTACHED == 1
        invoke FreeConsole
        invoke SetConsoleCtrlHandler,NULL,FALSE
    .endif  
 
    mov STOP, 0
    xor eax,eax
	Ret

...seems to work fine so far. :)

Thanks again for the help.

greetz

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
Sign in to follow this  

×