Link to home
Start Free TrialLog in
Avatar of itsmeandnobodyelse
itsmeandnobodyelseFlag for Germany

asked on

Redirect stdin to pipe

I got a VB script template to invoke a runas command as a privileged user. The script uses Sendkeys to pass the plain admin password to the runas command like that:

    objShell.AppActivate(RunAsCommand)
    objShell.Sendkeys AdminPassword

I was told to encode my VB scripts based on that template by Microsoft Script Encoder. It turns a .vbs file to a .vbe file that can be started using cscript or wsript. The use of this tool is to be able to prevent people from looking at, or modifying, VB scripts.

However, it took me one minute to find tools in Internet that were able to decode these vbe scripts. So, i am looking for an alternative.

My first approach is to write a VC program that does the same as the VB script. I learned about redirecting standard io handles and pipes and finally got two programs. The 'startxp' prog invokes 'testchild' and tries to send a password to the input stream of testchild by using a pipe. All worked perfectly - no errors - but testchild  doesn't get any input from the pipe and i was still able to enter from keyboard.

Any idea what i made wrong?

---------------- startxp.cpp -----------------------
#pragma warning ( disable : 4786 )

#include <windows.h>
#include <iostream>
#include <fstream>
#include <string>  

#include <conio.h>

using namespace std;

#define MAX_LEN_CMD                     512
#define MAX_LEN_PW                      32
                                       
#define DEF_PROC_ATTR_SEC_DESC_NULL     NULL
#define DEF_THRD_ATTR_SEC_DESC_NULL     NULL
#define DEF_ENV_BLOCK_NULL              NULL
#define DEF_CUR_DIR_NULL                NULL
#define INHERIT_HANDLES_FALSE           FALSE
#define INHERIT_HANDLES_TRUE            TRUE

static char         szCmdLine[MAX_LEN_CMD] = { '\0' };

 
int main(int nArgs, char* szArgs[])
{
    if (nArgs < 2)
    {
        cout << endl << "syntax: " << szArgs[0] << " <prog_to_exec> <argument1> ..." << endl;
        return 1;
    }

    string strCmdLine;

    for (int n = 1; n < nArgs; n++)
    {
        strCmdLine += szArgs[n];
        strCmdLine += ' ';
    }

    char szPassword[MAX_LEN_PW];
    cout << "Please Enter Password ==>";
    int           j = 0;
    unsigned char c;

    while (j < MAX_LEN_PW-1 && (c = (unsigned char)getch()) != VK_RETURN )
    {
         szPassword[j++] = c;
         cout << '*';
    }
    szPassword[j] = '\0';

    strCmdLine = /*RUN_AS_KW_TEST_BSHTOP + */ strCmdLine;
    memcpy(szCmdLine, strCmdLine.c_str(), strCmdLine.size());
    szCmdLine[strCmdLine.size()] = '\0';  
   
    SECURITY_ATTRIBUTES sa;
    sa.nLength              = sizeof(sa);
    sa.lpSecurityDescriptor = NULL;
    sa.bInheritHandle       = TRUE;

    HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
    HANDLE hStdErr = GetStdHandle(STD_ERROR_HANDLE);

    HANDLE hReadPipe;
    HANDLE hWritePipe;

    if (!CreatePipe(&hReadPipe, &hWritePipe, &sa, 0))
    {
        cout << "CreatePipe failed " << GetLastError() << endl;
        return 4;
    }

    STARTUPINFO         si;
    memset(&si, 0, sizeof(STARTUPINFO));

    si.cb           = sizeof(STARTUPINFO);
    si.dwFlags      = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
    si.wShowWindow  = SW_SHOWMINIMIZED;
    si.hStdInput    = hReadPipe;
    si.hStdOutput   = hStdOut;
    si.hStdError    = hStdErr;

    PROCESS_INFORMATION pi;
    memset(&pi, 0, sizeof(PROCESS_INFORMATION));

    if (!CreateProcess(NULL,
                       szCmdLine,
                       DEF_PROC_ATTR_SEC_DESC_NULL,
                       DEF_THRD_ATTR_SEC_DESC_NULL,
                       INHERIT_HANDLES_TRUE,
                       CREATE_NEW_CONSOLE,
                       DEF_ENV_BLOCK_NULL,
                       DEF_CUR_DIR_NULL,
                       &si,
                       &pi)
       )
    {
        return GetLastError();
    }

    Sleep(500);
    strPwd += "\r\n";
    DWORD  lenWritten;
    if (!WriteFile(hWritePipe, strPwd.c_str(), strPwd.length(), &lenWritten, NULL))
    {
        cout << "WritePipe failed " << GetLastError() << endl;
        CloseHandle(hWritePipe);
        return 5;
    }
    CloseHandle(hWritePipe);

    // Wait until child process exits.
    WaitForSingleObject( pi.hProcess, INFINITE );

    CloseHandle(hReadPipe);
    CloseHandle(hWritePipe);

    // Close process and thread handles.
    CloseHandle( pi.hProcess );
    CloseHandle( pi.hThread );

    return 0;
}
-------------------- end startxp.cpp ---------------------------------------

-------------------- begin testchild.cpp -----------------------------------
#pragma warning ( disable : 4786 )

#include <windows.h>
#include <iostream>
#include <fstream>
#include <string>  

#include <conio.h>

using namespace std;

#define MAX_LEN_PW                      32

void main()
{
    char szPassword[MAX_LEN_PW];
    cout << "Please Enter Password ==>";
    int           j = 0;
    unsigned char c;

    while (j < MAX_LEN_PW-1 && (c = (unsigned char)getch()) != VK_RETURN )
    {
        szPassword[j++] = c;
        cout << '*';
    }
    szPassword[j] = '\0';
    ofstream ofs("testchild.out");
    ofs << szPassword;
    ofs.close();
}
-------------------- end testchild.cpp -----------------------------------


Regards, Alex
 
ASKER CERTIFIED SOLUTION
Avatar of Axter
Axter
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of itsmeandnobodyelse

ASKER

@jkr

>> except strPwd being undeclared :o

i skipped some encrypting stuff and accidently removed that statement too:

   string strPwd = szPassword;

>> I guess you already know

Yes, i know it from one of your recent comments ;-)

>> I could not find any obvious problems

When i changed password input in testchild to

    string inp;
    getline(cin, inp);

i couldn't enter from the keyboard. However, testchild hangs til i closed the console window.

The Microsoft link and some other samples i saw are using DuplicateHandle for the pipe handles. There was a comment stating

>> // .... Otherwise, the child inherits the
>> // properties and, as a result, non-closeable handles to the pipes
>> // are created.

But i found samples that passed the handles as i did it above.

@axter

I made a download and the demo seems to work also when invoking a console window. But it takes me some time to unterstand (and modify) the code.

Thanks and Regards

Alex



Regards, Alex

     





>>The Microsoft link and some other samples i saw are using DuplicateHandle for the pipe handles

I even changed your code to do that, but...
One other thing:

>>  I got a VB script template to invoke a runas command as a privileged user

Why don't you use 'CreateProcessAsUser()'?
Avatar of grg99
grg99

How do you know your user isnt using one of the many debuggers or API tracers to snoop the password?

We are migrating from NT to XP Pro and users are not allowed to write to c:\programs or write to system part of registry. However, my installation batch requires these priviliges. The batch script (dos batch) gets distributed by mail but won't work on XP. The idea is now to use 'runas' command to gain the priviliges necessary.

Beside of /savecred i don't know any means to avoid a password input for that. And /savecred couldn't be used as it allows to invoke any program once given. Also, i couldn't walk to 100 locations entering the initial password.

So, i try to encrypt the password, hide it in my executable and invoke 'runas' from there, hoping that this is safer than using vbe scripts.

>> Why don't you use 'CreateProcessAsUser()'

I don't know how to get the token representing the privileged user. It seems that the users would need more privileges as they have now and i have no influence on that.

Regards, Alex


>> How do you know your user isnt using one of the many debuggers or API tracers to snoop the password?

I don't know, but all i need is a solution that couldn't be cracked by a non-priviliged user. If an admin user cracks my algorithm he couldn't do more harm than he already was able to do with the rights he had. When using vbe scripts as i was told, even people that are only curious easily could crack a password i am responsible for.

But if you know any real safe way, let me know.

Regards, Alex
>>But if you know any real safe way, let me know.

Do you have access to modifying both programs?

If so, you should consider using MapView API functions instead.
A stoopid question - since the password needs to be typed anyway, the admin who knows it needs to be in front of the machine - so why not have him/her type that one into the 'runas' authentication dialog directly without an application that grabs the password and sends it to runas?
>>A stoopid question
FYI:
I think jkr is saying that he's asking a stoopid question. :-)

I first thought he was referring to the main question, or my followup question. :-O

>>I think jkr is saying that he's asking a stoopid question. :-)

Exactly - sorry if that lead to any misunderstandings *LOL* :o)
>> Do you have access to modifying both programs?

Both programs? Do you mean the start program and the installation program? Or startxp and testchild?

The non-privileged users should be able to invoke a batch file or script or executable sent by mail  that could write to their local disks and registry by needing priviliges that these users don't have theirselves.

>> MapView API

Isn't that Shared Memory API? What functions could help?

>> A stoopid question

No, it isn't stupid. I do not intend to enter the password but try to hide it in the executable in an encrypted way, e. g. having 50 string constants, where i get one random letter from any second constant, or by patching the executable with an hex editor, or by using encrypting/decrypting software.  The only ways to crack that is to 'know' the algorithm or to use a sniffer when the plain password get passed to runas command.

Regards, Alex

@Axter

The sendkey prog works by actively pressing key strokes using keybd_events. The child process gets started by 'pressing' the keys  VK_LWIN + r - you can see the 'Run dialog box' then - followed by the executable name, and so on.

It's hard to debug as the key strokes go to the Debugger windows !!

I'll have to test it with the 'runas' command on an XP machine tomorrow (i am developing on NT) but it doesn't look bad.

Regards, Alex
FYI:
I found a few minor bugs in the code for the SendKey.
The binary search algorithm has a bug that doesn't let it find some of the keys.
Also the function to make the application active uses a restore command, which I don't think is needed, and it was causing problems in a code I was testing.

And last, a command like the following would fail:
{DOWN 80}

I'll upload a corrected version to my web site, and post the link here.
Check out the updated code in the following link:
http://axter.com/code/SendKeys.cpp
http://axter.com/code/SendKeys.h

I commented out a section of code in CSendKeys::SendKeyDown() function.

It worked without the commented code for my requirements, but you might want to included it for your project.
I skipped all dialog parts and finally used that:

BOOL CSendKeysSampleApp::InitInstance()
{
    CSendKeys sk;
    sk.SendKeys(_T("{DELAY=50}@rcmd~{DELAY=10}j:\\spudev\\komp\\utl\\startxp\\debug\\testchild~{DELAY=100}password~exit~"));

    // Since the dialog has been closed, return FALSE so that we exit the
    //  application, rather than start the application's message pump.
    return FALSE;
}

It worked fine after i played with delays. However, if for any reason there was a problem to start the prog - testchild here in the sample - the password would be shown as plain text in the command window.

Any idea to prevent this?

I changed the text colours of the command window to the background colours. Voilá, no password if gray on gray. However, i would need a programmatical approach for that.

I am very appreciating your support.

Regards, Alex

I couldn't find a way - til now - to suppress output in the command window(s). There is a very strange behaviour on XP:

when calling a batch job like

   @echo off
   @runas /user:dddd\uuuuuuu "cmd /Q"

interactively from a command window the '@' prefix works and there is no output. When calling same batch using SendKeys neither a '@' nor a '/Q' has any effect. For example, the runas command was shown very slowly - because of the Delay switch - and everybody could read the name of the admin user. When using shorter delays, the prog doesn't work and i can see my plain password somewhere in a command window.

I started to call my installation batch file directly by runas. That worked fine from the command line, however, when i made a desktop link, using same call, only one of three attempts work. When it fails, it shows the Run Dialog and some mixed letters of my password.

------------------------------------------------------------------------------------------------------
Today i again tried LogonUser and CreateProcessAsUser. I got error 1314 == ERROR_PRIVILEGE_NOT_HELD and finally found that the SE_TCB_NAME couldn't be adjusted. I coded thousands of lines calling or implementing LogonUser, GetProcessWindowStation, OpenWindowStation, SetProcessWindowStation, OpenDesktop, GetLogonSID, AddAceToWindowStation, AddAceToDesktop, ImpersonateLoggedOnUser, AdjustTokenPrivilige, and ... nothing. All these fu...nny functions are made for a priviliged server that wants to get a client's user environment, but not otherway round.

Regards, Alex


Is the program your calling small, and stand alone?

If so, would you be able to email it, or upload it to a link so that I can download it, and try it out with my sendkey app?

Things to think about:

When the target app is waiting for password does it have a unique title in the console window?

Can you hide the window first, and then perform SendKey command?

When your target application is running, does it run full screen, or in window mode?
If it runs in window mode, you can check the contents of the application from your program by peforming sendKey command to put the contents into the clipboard.
Is the program your calling small, and stand alone?

It's small and uses your sendkey.cpp and sendkey.h .

-------------------------------------begin---------------------------------
// runasp.cpp : Defines the entry point for the application.
//

#include <windows.h>
#include "sendkeys.h"

#include <string>
using namespace std;

int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow)
{
    string sendBuf;                      
    sendBuf   = "{DELAY=100}@rrunastest~{DELAY=200}password\r\n";

    CSendKeys sk;
    sk.SendKeys(sendBuf.c_str());

    return 0;
}
--------------------------------------end---------------------------------

The 'program' runastest is a simple batch:

-------------------------------------begin---------------------------------
@echo off
@runas /user:domain\username "\\serverxx\share$\ourproduct\xpserverstarttop.bat"
--------------------------------------end---------------------------------


And xpserverstarttop.bat is that:

-------------------------------------begin---------------------------------
@echo off
net use t: \\serverxx\share$
call t:\ourproduct\serverstarttop.bat
--------------------------------------end---------------------------------


Finally, serverstarttop.bat is a lengthy installation batch file using choice.exe to ask the user whether they want to install or overwrite their current version. That batch writes to registry (HKEY_LOCAL_MACHINE), creates a subdir of "C:\Program Files\OurProduct.Version, installs icons on the 'All Users' desktop, ...

----------------------------------------------------------
My youngest approach is to write the vbs script programmatically in VC++ - password including - convert it to vbe, and execute it. After that, i delete the vbe script. The advantage of vbe script before C++ SendKeys is that there is no need of using the 'Run Dialog' and it cannot be interrupted by user's keystrokes.

Regards, Alex





 



SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
>> Try removing "CREATE_NEW_CONSOLE"

I tried but console input of the testchild using getch() to get password still wasn't redirected to pipe. However as i changed input function of testchild to gets(szPassword) redirection works fine. I assume that getch() ignores standard input handle reading only key input.

When trying to invoke the 'runas' command i get an error message that either the user name or password is invalid. I increased the Sleep time between CreateProcess and WriteFile to 2000 msec but it doesn't help. Then, i passed any single character of the password by a separate call having a Sleep time of 300 msec between any call. Now, the error message was shown long before all characters have been written to the pipe. That means, that 'runas' does accept my input somehow - as it doesn't accept keyboard input instead - but was able to refuse them as 'not actually typed'.

Regards, Alex