Reputation: 923
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:
auto hMod = LoadLibrary("console_app.exe")
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.
I've investigated how my particular third-party exe gets cmd arguments and found that:
GetCommandLine
at all and do not call itcall _initterm
call argc
and argv
arguments are available through cs:argc
and cs:argv
(see pictures below)
Can you explain, please, what _initterm
actually do and where CMD arguments are actually stored?
Upvotes: 12
Views: 2332
Reputation: 180245
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.
Upvotes: 13
Reputation: 33794
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
Upvotes: 2