在 Windows 中以编程方式调用 "main" 函数

Call "main" function programmatically in Windows

我有一个第三方控制台应用程序。我需要 运行 它来自我的应用程序,但我不能 运行 它作为一个单独的进程(因为我需要使用它的依赖项:手动填充 Import tables,设置挂钩等)。所以我可能应该手动调用此 executable 的 main 函数。以下是我尝试执行此操作的方法:

  1. 使用 auto hMod = LoadLibrary("console_app.exe")
  2. 加载此 EXE
  3. 手动填写此 exe 的导入 table
  4. 获取此 EXE 的入口点并调用它

最后一步卡住了。

这是我尝试调用入口点的方式:

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);
}

有效。但它的工作方式就好像 没有 个参数。

我哪里错了?我如何 运行 executable inside 我的带参数的进程?谢谢。

更新:

我调查了我的特定第三方 exe 如何获取 cmd 参数并发现:

  1. 根本不导入 GetCommandLine 也不调用它
  2. call _initterm 调用 argcargv 后,参数可通过 cs:argccs:argv 获得(见下图)
  3. 我传递给我的主控制台应用程序的 CMD 参数被传输到 子 EXE 也是。

请您解释一下 _initterm 实际做什么以及 CMD 参数实际存储在哪里?

您正在调用应用程序的入口点,而不是 int main(int, char**)。现在您可能已经读到 C++ 程序的入口点是 int main(int, char**) 但这只是 C++ 的观点。

Win32视角不同;入口点是 int (*)(void);。 Visual Studio 链接器查找 int mainCRTStartup(void); 并使用它,除非您使用 /ENTRY 指定另一个入口点。 mainCRTStartup的默认实现在调用main(argc,argv)之前调用GetCommandLine()填充argv[]mainCRTStartup 中还有其他您可能希望发生的事情:运行 全局 ctors,初始化 CRT 状态,...

当然,这是假设另一个程序是用 Visual C++ 编译的,但无论它是用什么语言编写的,它都必须调用 GetCommandLine

现在,对于您的问题,这里有一个有趣的观察结果:GetCommandLine() returns 一个 可写 指针。您可以覆盖现有的命令行。当然,如果 控制导入表,你决定 GetCommandLine 意味着什么。 (请记住,像往常一样有 A 和 W 变体)。

一个警告:MSVCRT 未设计为初始化两次,无论是静态版本还是 DLL 版本。所以实际上你不能使用它,那会很疼。

[编辑] 您的更新显示对 _initterm 的调用。正如我已经暗示的那样,这是一个 MSVCRT 函数。具体来说,

/***
*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 *);

MSVCRT DLL 代表 EXE 调用 GetCommandLine()

可执行文件的入口点 (EP) 没有参数 - 所以你不能直接用参数调用它。

普通应用程序通过解析命令行获取参数。 [w]mainCRTStartup 执行此操作 - 如果您有链接到 c/c++ 运行time 的控制台应用程序 - 这是真实的 EP .

因此,如果您 Fill Import table of this exe manually - 为 GetCommandLineA and GetCommandLineW 函数设置异常 - 将其重定向到自我实现和 return 您的自定义命令行。

但如果应用程序使用 非静态 链接 CRT 它可以从 msvcrt.dll 导入 __getmainargs or __wgetmainargs or even _acmdln, or _wcmdln - 所以任务已经变得复杂。

并且您假设 relocs 在 EXE 中存在,您不处理 TLS 如果它存在,您不处理应用程序清单、可能的 dl 重定向等。

but I cannot run it as a separate process

这不是真的。您可以而且必须 运行 它作为单独的过程 - 这是最好的解决方案。

通过 CreateProcess 使用 CREATE_SUSPENDED 标志执行您的应用程序。在这里你可以自由轻松地设置你需要的任何命令行。您不需要手动且不完全正确加载 EXE 但系统会为您完成此任务。

创建进程后,您需要使用 QueueUserAPC (but not CreateRemoteThread !!) and finally call ResumeThread

将自身 DLL 注入其中

因此,您的 DLL 将在第一个 EXE 线程中加载并执行,就在应用程序 EP 之前 - 在这里您可以完成所有需要的任务