• [C++] Redirect stdout of srcds.exe
    10 replies, posted
  • Greetings Facepunch I'm writing a cross-plattform server manager application which should start Source servers silently (no window) and capture their output. But the problem is that srcds.exe doesn't like it and refuses to cooperate. In detail: [code]//defined in process.h ::HANDLE hIn, hOut, hErr; ::PROCESS_INFORMATION pid; //------------- void Process::start(std::string exe) { std::wstring uexe; utf8::utf8to16(exe.begin(),exe.end(),std::back_inserter(uexe)); ::STARTUPINFO ProcSi; ::memset(&ProcSi, 0, sizeof(ProcSi)); ProcSi.cb = sizeof(ProcSi); ::SECURITY_ATTRIBUTES ProcSA; ProcSA.nLength = sizeof(SECURITY_ATTRIBUTES); ProcSA.lpSecurityDescriptor = 0; ProcSA.bInheritHandle = 1; ::HANDLE hInTmp[2], hOutTmp[2], hErrTmp[2]; //::CreatePipe(&hInTmp[1], &hInTmp[0], &ProcSA,0); ::CreatePipe(&hOutTmp[0],&hOutTmp[1],&ProcSA,0); ::CreatePipe(&hErrTmp[0],&hErrTmp[1],&ProcSA,0); //::DuplicateHandle(GetCurrentProcess(),hInTmp[0], GetCurrentProcess(),&hIn, 0,0,DUPLICATE_SAME_ACCESS); ::DuplicateHandle(GetCurrentProcess(),hOutTmp[0], GetCurrentProcess(),&hOut, 0,0,DUPLICATE_SAME_ACCESS); ::DuplicateHandle(GetCurrentProcess(),hErrTmp[0], GetCurrentProcess(),&hErr, 0,0,DUPLICATE_SAME_ACCESS); //::CloseHandle(hInTmp[0]); ::CloseHandle(hOutTmp[0]); ::CloseHandle(hErrTmp[0]); ProcSi.dwFlags = STARTF_USESHOWWINDOW | [B]STARTF_USESTDHANDLES[/B]; ProcSi.wShowWindow = SW_HIDE; //ProcSi.hStdInput = hInTmp[1]; ProcSi.hStdOutput = hOutTmp[1]; ProcSi.hStdError = hErrTmp[1]; BOOL e = ::CreateProcess( NULL, // No module name (use command line) const_cast<wchar_t*>(uexe.c_str()), // Command line NULL, // Process handle not inheritable NULL, // Thread handle not inheritable TRUE, // handle inheritance 0, // No creation flags NULL, // Use parent's environment block NULL, // Use parent's starting directory &ProcSi, // Pointer to STARTUPINFO structure &pid ); // Pointer to PROCESS_INFORMATION structure DWORD err = ::GetLastError(); //::CloseHandle(hInTmp[1]); ::CloseHandle(hOutTmp[1]); ::CloseHandle(hErrTmp[1]); if(!e && err) throw new Exception(__func__,err); } long Process::readStdout(char* data, ulong size) { DWORD read; BOOL e = ReadFile(hOut, data, (DWORD)size, &read, 0); DWORD err = ::GetLastError(); if(!e && err) throw new Exception(__func__,err); return (long) read; } [/code] CreateProcess (WinAPI) is configured to redirect stdin, -out and -err of the child process through a pipe to parent and then start srcds. Works well until srcds crashes with the error: [QUOTE]CTextConsoleWin32::GetLine: !GetNumberOfConsoleInputEvents[/QUOTE] [IMG_thumb]http://stuff.aix0.eu/trash/screens/2012050518323800.png[/IMG_thumb] I've searched for solutions and [URL="https://github.com/joyent/node/issues/2557"]one[/URL] is not to redirect stdin which I tried (as seen in code) but it still won't work. The only way without crash is to remove STARTF_USESTDHANDLES flag that also prevents from getting the std output. Hiding the srcds window appears also impossible with dwFlags and CreateProcess creation flag parameter. So my questions are: o Is there a workaround to get srcds.exe console output? o Is there also a way to hide it's window properly? o And how can I filter these junk bytes which I get by reading the stdout pipe? Thanks in advance.
  • Nope. Already tried with simple redirection to text files. Result: [QUOTE=stdout.txt] Using breakpad minidump system [stopped server here] Reference Count for Material __depthwrite00 (-1) != 0 Reference Count for Material __depthwrite01 (-1) != 0 Reference Count for Material __depthwrite10 (-1) != 0 Reference Count for Material __depthwrite11 (-1) != 0 Reference Count for Material __particlesdepthwrite (1) != 0 [/QUOTE] That's all I got. As I said, Source hates pipes so I need something other.
  • If srcds.exe is a win32 application it probably creating its own console window by using win32 api which is "AllocConsole" function. Then setting the std in out and err to different values which will work with new console. So creating the pipes when process is first started maybe taking the pointers which going to be replaced. There is lot of ways to accomplish this stuff. I am not investigated exe but, most hardcore way will be injecting dll which finds and patches AllocConsole or even printf function and pipe wherever you want (if they did this way). Let me investigate executables for accurate solution. [editline]6th May 2012[/editline] Okay first findings, srcds.exe is just a placeholder executable which loads the bin/dedicated.dll and informs the steam about start of application. dedicated.dll uses AllocConsole, FreeConsole stuff. When AllocConsole success it does some calls. So you may try DuplicateHandle after this point. Like using simple random untrustable delays of few second sleep or waiting for it properly. If you want to wait properly; Program stores its handles for console output and input after AllocConsole in virtual address of 100E4E40 and 100E4E2C in dedicated.dll. If you want to find this address you can create signatures for this function 10009F40. But it will be better to attach as debugger to your child process, and just search that signature and create a break point where it calls getstdhandle and use that handle for your dirty jobs. Another way is creating a small placeholder asm function or dll that you could inject to process and modify import table of dedicated.dll to route AllocConsole to your function. Which will call original AllocConsole and GetStdHandle and inform your application. There could be simple ways, but I like hackish ways :P For cross-platform , I don't think they used stuff like these in linux. So I assume regular piping should work. If you making this as open source or you willing send the code, I can implement these stuff in it.
  • Many thanks! I've made also some progress. srcds crash was resolved by letting real stdin to child process through. [QUOTE]- ProcSi.hStdInput = hInTmp[1]; + ProcSi.hStdInput = GetStdHandle(STD_INPUT_HANDLE);[/QUOTE] And "junk bytes" were removed by using utf16to8 conversion on the stdout pipe.
  • Reviving a retardedly old thread, because I got the same issue when playing with it. Here's the function that does this, in IDA: [code] .text:10005260 push ebp .text:10005261 mov ebp, esp .text:10005263 mov eax, 5020h .text:10005268 call __alloca_probe .text:1000526D push ebx .text:1000526E push esi .text:1000526F push edi .text:10005270 mov esi, ecx .text:10005272 lea eax, [ebp-8] .text:10005275 push eax .text:10005276 push dword ptr [esi+2020h] .text:1000527C call ds:GetNumberOfConsoleInputEvents .text:10005282 test eax, eax .text:10005284 jz loc_100053FD .text:1000528A lea ebx, [ebx+0] .text:10005290 .text:10005290 loc_10005290: ; CODE XREF: .text:100053F7j .text:10005290 cmp dword ptr [ebp-8], 0 .text:10005294 jbe loc_10005408 .text:1000529A lea eax, [ebp-4] .text:1000529D push eax .text:1000529E push 400h .text:100052A3 lea eax, [ebp-5020h] .text:100052A9 push eax .text:100052AA push dword ptr [esi+2020h] .text:100052B0 call ds:ReadConsoleInputA .text:100052B6 test eax, eax .text:100052B8 jz loc_10005413 .text:100052BE mov eax, [ebp-4] .text:100052C1 test eax, eax .text:100052C3 jz loc_10005408 .text:100052C9 xor ebx, ebx .text:100052CB test eax, eax .text:100052CD jle loc_100053E5 .text:100052D3 lea edi, [ebp-5016h] .text:100052D9 lea esp, [esp+0] [/code] Here is the same thing in the original code(sdk 2013) - /dedicated/console/TextConsoleWin32.cpp [code] char * CTextConsoleWin32::GetLine( void ) { while ( 1 ) { INPUT_RECORD recs[ 1024 ]; unsigned long numread; unsigned long numevents; if ( !GetNumberOfConsoleInputEvents( hinput, &numevents ) ) { Error("CTextConsoleWin32::GetLine: !GetNumberOfConsoleInputEvents"); return NULL; } if ( numevents <= 0 ) break; if ( !ReadConsoleInput( hinput, recs, ARRAYSIZE( recs ), &numread ) ) { Error("CTextConsoleWin32::GetLine: !ReadConsoleInput"); return NULL; } if ( numread == 0 ) return NULL; ... } [/code] For a crude fix, you want to nop this: [code] .text:1000527C call ds:GetNumberOfConsoleInputEvents .text:10005282 test eax, eax .text:10005284 jz loc_100053FD <=== this .text:1000528A lea ebx, [ebx+0] .text:10005290 .text:10005290 loc_10005290: ; CODE XREF: .text:100053F7j .text:10005290 cmp dword ptr [ebp-8], 0 .text:10005294 jbe loc_10005408 .text:1000529A lea eax, [ebp-4] .text:1000529D push eax .text:1000529E push 400h .text:100052A3 lea eax, [ebp-5020h] .text:100052A9 push eax .text:100052AA push dword ptr [esi+2020h] .text:100052B0 call ds:ReadConsoleInputA .text:100052B6 test eax, eax .text:100052B8 jz loc_10005413 <=== this .text:100052BE mov eax, [ebp-4] .text:100052C1 test eax, eax .text:100052C3 jz loc_10005408 <=== this .text:100052C9 xor ebx, ebx .text:100052CB test eax, eax .text:100052CD jle loc_100053E5 <=== this [/code] For a less crude fix, do it passively without redirecting stdio. Have fun. Note: this was taken a few versions of the library ago, the offsets are different. Shouldn't be too hard to find by the error message, it's the first and only xref.
  • [QUOTE]Reviving a retardedly old thread, because I got the same issue when playing with it.[/QUOTE] I'm pretty sure Source still has command line options for redirecting console to another app via passing handles to -HFILE, -HPARENT and -HCHILD parameters. Note that you should supply all 3 or it won't work. [IMG]http://i.imgur.com/nVOvahM.png[/IMG]