Подтвердить что ты не робот

Программный вызов функции "main" в Windows

У меня есть стороннее консольное приложение. Мне нужно запустить его из моего приложения, но я не могу запустить его как отдельный процесс (потому что мне нужно работать со своими зависимостями: заполнить таблицы импорта вручную, настроить крючки и т.д.). Поэтому, вероятно, я должен вызвать функцию main этого исполняемого файла вручную. Вот как я пытаюсь это сделать:

  • Загрузите этот EXE с помощью auto hMod = LoadLibrary("console_app.exe")
  • Заполнить таблицу импорта этого exe вручную
  • Получить точку входа этого 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);
}

Это работает. Но он работает так, как будто нет аргументов.

Где я ошибаюсь? Как запустить исполняемый файл внутри моего процесса с помощью аргументов? Спасибо.

UPDATE:

Я исследовал, как мой сторонний exe получает аргументы cmd и обнаружил, что:

  • Он не импортирует GetCommandLine вообще и не называет его
  • После call _initterm вызов argc и argv аргументы доступны через cs:argc и cs:argv (см. рисунки ниже) введите описание изображения здесь введите описание изображения здесь
  • Аргументы CMD, которые я передаю в основное приложение консоли, переносятся на дочерний EXE тоже.

Можете ли вы объяснить, пожалуйста, что _initterm действительно и где аргументы CMD на самом деле хранятся?

4b9b3361

Ответ 1

Вы вызываете точку входа приложения, а не int main(int, char**). Теперь вы, возможно, читали, что точка входа в программу на С++ int main(int, char**), но это просто перспектива С++.

Учетная точка Win32 отличается; точкой входа является int (*)(void);. Компонент Visual Studio ищет int mainCRTStartup(void); и использует это, если вы не укажете другую точку входа с /ENTRY. Реализация по умолчанию mainCRTStartup вызывает GetCommandLine() для заполнения argv[] перед вызовом main(argc,argv). В mainCRTStartup есть и другие вещи, которые могут возникнуть: запустить глобальные ctors, инициализировать состояние CRT,...

Конечно, предполагая, что другая программа была скомпилирована с Visual С++, но на любом языке, на котором она была написана, она должна вызывать GetCommandLine.

Теперь, для вашей проблемы, здесь интересное наблюдение: GetCommandLine() возвращает указатель writeable. Вы можете перезаписать существующую командную строку. Конечно, если вы управляете таблицами импорта, вы решаете, что означает 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 *);

DLL MSVCRT вызывает GetCommandLine() от имени EXE.

Ответ 2

точка входа исполняемого файла (EP) не имеет аргументов - поэтому вы и не можете направить вызов с помощью аргументов.

обычное приложение получило аргументы путем разбора командной строки. [w]mainCRTStartup сделайте это - если у вас есть консольное приложение, связанное с временем выполнения c/С++ - это реально EP.

так что если вы Fill Import table of this exe manually - установите исключение для GetCommandLineA и GetCommandLineW - перенаправить его на самостоятельную реализацию и вернуть пользовательскую командную строку.

но если приложение использует не статический связанный CRT, он может импортировать __getmainargs или __wgetmainargs или даже _acmdln или _wcmdln от msvcrt.dll - поэтому уже задача становится сложной.

и вы предполагаете, что релокс завершает работу в EXE, вы не обрабатываете TLS, если он существует, вы не обрабатываете манифест приложения, возможные перенаправления dl и т.д.

но я не могу запустить его как отдельный процесс

это неверно. вы можете и должны запускать его как отдельный процесс - это лучшее решение.

выполните ваше приложение CreateProcess с флагом CREATE_SUSPENDED. здесь вы можете легко установить любую CommandLine, которая вам нужна. вам не нужно вручную и не полностью корректировать нагрузку EXE, но система выполняет эту задачу для вас.

После создания процесса вам нужно ввести self DLL в него, используя QueueUserAPC (но не CreateRemoteThread!!) и, наконец, позвоните ResumeThread

в результате ваш DLL будет загружен и выполнен в первом потоке EXE непосредственно перед приложением EP - и здесь вы можете выполнить все необходимые задачи