12

I have a third-party console application. I need run it from my application but I cannot run it as a separate process (because I need to work with its dependencies: fill Import tables manually, setup hooks etc.). So probably I should call main function of this executable manually. Here is how I'm trying to do this:

  1. Load this EXE using auto hMod = LoadLibrary("console_app.exe")
  2. Fill Import table of this exe manually
  3. Get entry point of this EXE and call it

And I'm stuck with the last step.

Here is how I'm trying to call entry point:

void runMain(HINSTANCE hInst)
{
    typedef BOOL(WINAPI *PfnMain)(int, char*[]);

    auto imageNtHeaders = ImageNtHeader(hInst);
    auto pfnMain = (PfnMain)(DWORD_PTR)(imageNtHeaders->OptionalHeader.AddressOfEntryPoint + (DWORD_PTR)hInst);

    char* args[] = { R"(<console_app_path>)", R"(arg1)", R"(arg2)" };
    pfnMain(3, args);
}

It works. But it works as if there is no arguments.

Where am I wrong? How can I run an executable inside my process with arguments? Thanks.

UPDATE:

I've investigated how my particular third-party exe gets cmd arguments and found that:

  1. It doesn't import GetCommandLine at all and do not call it
  2. After call _initterm call argc and argv arguments are available through cs:argc and cs:argv (see pictures below) enter image description here enter image description here
  3. CMD arguments that I pass to my main console app are transferred to child EXE too.

Can you explain, please, what _initterm actually do and where CMD arguments are actually stored?

Community
  • 1
  • 1
Dmitry Katkevich
  • 883
  • 7
  • 26
  • The actual entry point may not be `main` itself, but a wrapper around `main`. In which case, the parameters are highly implementation dependent. – StoryTeller - Unslander Monica Jan 05 '17 at 12:27
  • @StoryTeller “Highly implementation defined” here should be “specified precisely in the PE header specification”. Because the Windows application loader needs to know that specification, of course. – Konrad Rudolph Jan 05 '17 at 12:29
  • @KonradRudolph - naturally. The OP however doesn't. – StoryTeller - Unslander Monica Jan 05 '17 at 12:30
  • 1) will worked only if your `EXE` have relocations. which not always true for `EXE` – RbMm Jan 05 '17 at 13:19
  • really best solution I think - exec your `EXE` in separate process, and inject own `DLL` to it at start point by `QueueUserAPC` which set the hooks – RbMm Jan 05 '17 at 13:21
  • @StoryTeller: The actual entry point **must** be a wrapper around main, because the PE entry point has the wrong signature and fails to retrieve the command line. – MSalters Jan 05 '17 at 15:29
  • 1
    What if the third-party program expects a different version of the CRT than the one in your host program? What if the third-party program doesn't have relocs or makes assumptions about where it's loaded? I think this approach will be painful if feasible. I think running it in a separate process will be safer and easier, even if it means having to inject code into that process to hook what you want. – Adrian McCarthy Jan 05 '17 at 19:12
  • your application can import `__getmainargs` or `__wgetmainargs` from `msvcrt.dll` - https://msdn.microsoft.com/en-us/library/ff770599.aspx also possible use `_acmdln`, or `_wcmdln` - https://msdn.microsoft.com/en-us/library/ff770586.aspx – RbMm Jan 06 '17 at 12:11
  • `_initterm` call global object constructors and global vars initialization – RbMm Jan 06 '17 at 12:42
  • How do you do #2? – Jason Apr 24 '17 at 23:08
  • @Jason, here is my another question where you can get code snippet http://stackoverflow.com/questions/40606514/fill-in-dll-import-table-manually-image-import-descriptors-name-field-stores-0 – Dmitry Katkevich Apr 26 '17 at 20:35

2 Answers2

12

You're calling the entry point of the application, not int main(int, char**). Now you may have read that the entry point of a C++ program is int main(int, char**) but that's just a C++ perspective.

The Win32 perspective is different; the entry point is a int (*)(void);. The Visual Studio linker looks for int mainCRTStartup(void); and uses that, unless you specify another entry point with /ENTRY. The default implementation of mainCRTStartup calls GetCommandLine() to fill in argv[] before calling main(argc,argv). There are also other things in mainCRTStartup which you might want to happen: run global ctors, initialize the CRT state, ...

Of course, that's assuming the other program was compiled with Visual C++, but whatever language it's been written in, it must be calling GetCommandLine.

Now, for your problem, here's an interesting observation: GetCommandLine() returns a writeable pointer. You can overwrite the existing command line. Of course, if you control the import tables, you decide what GetCommandLine means. (Remember, as usual there are A and W variants).

One warning: the MSVCRT isn't designed to be initialized twice, neither the static version nor the DLL one. So practically speaking you can't use it, and that will hurt.

[edit] Your update shows a call to _initterm. That's a MSVCRT function, as I already hinted. Specifically,

/***
*crtexe.c - Initialization for console EXE using CRT DLL
*
*       Copyright (c) Microsoft Corporation. All rights reserved.
*
...
/*
 * routine in DLL to do initialization (in this case, C++ constructors)
 */
extern int __cdecl _initterm_e(_PIFV *, _PIFV *);
extern void __cdecl _initterm(_PVFV *, _PVFV *);

The MSVCRT DLL calls GetCommandLine() on behalf of the EXE.

MSalters
  • 173,980
  • 10
  • 155
  • 350
  • yes, we can overwrite the existing command line - but new command line must be not longer then existing – RbMm Jan 05 '17 at 12:42
  • "the MSVCRT isn't designed to be initialized twice, neither the static version nor the DLL one." - and so what ? DLL once loaded in process, and many other DLLs and exe can share use it. static CRT is linked to one DLL and also will be initialized only once - so this is about nothing – RbMm Jan 05 '17 at 18:13
  • @RbMm: The problem is that if the loader program _and_ the loaded program both contain their own copy of `mainCRTStartup`, then `mainCRTStartup` will be called twice. Both expect they're the first to initialize the CRT. A DLL doesn't contain `mainCRTStartup`; it's startup code must look for an initialized CRT first. – MSalters Jan 05 '17 at 18:33
  • you mistake. the different `mainCRTStartup` in different modules. with different global variables ! not called twice. but 2 different functions called – RbMm Jan 05 '17 at 19:34
  • @RbMm: There would be only one set of CRT variables, if both executables load the DLL CRT. But there's more than that, the CRT code also calls `ExitProcess` after `main` returns. Obviously, that will hurt because the loaded application will kill the whole process. – MSalters Jan 06 '17 at 12:44
  • "one set of CRT variables" in `msvcrt.dll` - and this set will be initialized only **once** when `msvcrt.dll` (or others CRT DLLs) will be loaded in process. and many other modules in process can share use it - here no conflict. about `ExitProcess` after `main` returns - this is true – RbMm Jan 06 '17 at 12:51
  • @RbMm: Go read the CRT sources; they're in `C:\Program Files (x86)\Microsoft Visual Studio XX.0\VC\crt\src`. Also, go read the rules on what you can do when a DLL is loaded into process, in particular wrt "Loader Lock". The `DllMain` in the CRT DLL can't do that, because it runs **before** the executable's entry point (!!) – MSalters Jan 06 '17 at 12:58
  • you totally mistake. you confuse CRT DLL initialization, which do when DLL loaded in process and initialization of EXE which used this CRT DLL – RbMm Jan 06 '17 at 13:02
  • in CRT DLLs - global vars **per process**. it initialized once at `DLL_PROCESS_ATACH` OP not reinitialize CRT `DLLs` - so here no any problem. from another side `[w]mainCRTStartup` - have globals vars **per module** - it not conflict in self intializations with others modules. and it used `CRT` which can be statically or dynamically(this is OP case) linked to it. again not cofuse `CRT` api and vars with litle PE(dll or exe stub) which used `CRT` – RbMm Jan 06 '17 at 13:09
2

the entrypoint of executable (EP) have no arguments - so you and can not direct call it with arguments.

usual application got arguments by parsing command line. [w]mainCRTStartup do this - if you have console application linked to c/c++ runtime - this is real EP .

so if you Fill Import table of this exe manually - set exception for GetCommandLineA and GetCommandLineW functions - redirect it to self implementation and return your custom command line.

but if app used not static linked CRT it can import __getmainargs or __wgetmainargs or even _acmdln, or _wcmdln from msvcrt.dll - so already task become complex.

and you assume that relocs exits in EXE, you not handle TLS if it exist, you not handle application manifest, possible dl redirections, etc.

but I cannot run it as a separate process

this is not true. you can and must run it as separate process - this is the best solution.

exec your app by CreateProcess with CREATE_SUSPENDED flag. here you free easy set any CommandLine which you need. you not need manually and not fully correct load EXE but system do this task for you.

after process is created you need inject self DLL to it by using QueueUserAPC (but not CreateRemoteThread !!) and finally call ResumeThread

as result your DLL will be loaded and executed in first EXE thread, just before application EP - and here you can do all needed tasks

RbMm
  • 31,280
  • 3
  • 35
  • 56