Go Premium for a chance to win a PS4. Enter to Win

x
?
Solved

Redirect stdin to pipe

Posted on 2004-09-01
22
Medium Priority
?
1,992 Views
Last Modified: 2011-04-14
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
 
0
Comment
Question by:itsmeandnobodyelse
  • 9
  • 6
  • 5
  • +2
22 Comments
 
LVL 30

Accepted Solution

by:
Axter earned 1200 total points
ID: 11953861
Check out the following link:
http://codeproject.com/cpp/sendkeys_cpp_Article.asp 

Has code to simulate the VB SendKeys function.
0
 
LVL 86

Assisted Solution

by:jkr
jkr earned 600 total points
ID: 11954037
I guess you already know http://support.microsoft.com/default.aspx?scid=http://support.microsoft.com:80/support/kb/articles/Q190/3/51.ASP&NoWebContent=1 ("How To Spawn Console Processes with Redirected Standard Handles")

Apart from that, I could not find any obvious problems (err, except strPwd being undeclared :o) - I have similar code that works, but this is amazing...
0
 
LVL 39

Author Comment

by:itsmeandnobodyelse
ID: 11954319
@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

     





0
Concerto's Cloud Advisory Services

Want to avoid the missteps to gaining all the benefits of the cloud? Learn more about the different assessment options from our Cloud Advisory team.

 
LVL 86

Expert Comment

by:jkr
ID: 11954469
>>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...
0
 
LVL 86

Expert Comment

by:jkr
ID: 11954542
One other thing:

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

Why don't you use 'CreateProcessAsUser()'?
0
 
LVL 22

Expert Comment

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

0
 
LVL 39

Author Comment

by:itsmeandnobodyelse
ID: 11954837
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


0
 
LVL 39

Author Comment

by:itsmeandnobodyelse
ID: 11955159
>> 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
0
 
LVL 30

Expert Comment

by:Axter
ID: 11955255
>>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.
0
 
LVL 86

Expert Comment

by:jkr
ID: 11955266
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?
0
 
LVL 30

Expert Comment

by:Axter
ID: 11955304
>>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

0
 
LVL 86

Expert Comment

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

Exactly - sorry if that lead to any misunderstandings *LOL* :o)
0
 
LVL 39

Author Comment

by:itsmeandnobodyelse
ID: 11955654
>> 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

0
 
LVL 39

Author Comment

by:itsmeandnobodyelse
ID: 11955889
@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
0
 
LVL 30

Expert Comment

by:Axter
ID: 11956831
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.
0
 
LVL 30

Expert Comment

by:Axter
ID: 11957228
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.
0
 
LVL 39

Author Comment

by:itsmeandnobodyelse
ID: 11960950
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

0
 
LVL 39

Author Comment

by:itsmeandnobodyelse
ID: 11963368
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


0
 
LVL 30

Expert Comment

by:Axter
ID: 11963473
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.
0
 
LVL 39

Author Comment

by:itsmeandnobodyelse
ID: 11964150
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





 



0
 
LVL 4

Assisted Solution

by:anthony_w
anthony_w earned 200 total points
ID: 11970946
Try removing "CREATE_NEW_CONSOLE" from your original code. This flag forces the child to have a new console window, and therefore stdin and stdout are assigned to that window, rather than inherited.
0
 
LVL 39

Author Comment

by:itsmeandnobodyelse
ID: 11998262
>> 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




0

Featured Post

How to Use the Help Bell

Need to boost the visibility of your question for solutions? Use the Experts Exchange Help Bell to confirm priority levels and contact subject-matter experts for question attention.  Check out this how-to article for more information.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Often, when implementing a feature, you won't know how certain events should be handled at the point where they occur and you'd rather defer to the user of your function or class. For example, a XML parser will extract a tag from the source code, wh…
Container Orchestration platforms empower organizations to scale their apps at an exceptional rate. This is the reason numerous innovation-driven companies are moving apps to an appropriated datacenter wide platform that empowers them to scale at a …
The goal of the video will be to teach the user the difference and consequence of passing data by value vs passing data by reference in C++. An example of passing data by value as well as an example of passing data by reference will be be given. Bot…
The viewer will learn how to pass data into a function in C++. This is one step further in using functions. Instead of only printing text onto the console, the function will be able to perform calculations with argumentents given by the user.

916 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question