Почему функция createprocess не запускает lnk файлы


Содержание

Функция CreateProcess не запускает приложение полностью

У меня есть два приложения. Я просто позвоню им A и B. У меня есть доступ к коду приложения A. Я хочу запустить приложение B и открыть файл в этом приложении из A. Я использую метод CreateProcess для его получения. Но приложение B не открывается полностью. Я не уверен, существуют ли какие-либо общие DLL или другие зависимости между этими двумя приложениями.

Код, который я использую для открытия аппликатора B формы A, следующий:

Файл exe начинает загружаться, но он застревает во время инициализации. Но приложение B не заканчивается. Это происходит в некотором противоречивом состоянии. Эта проблема возникает только для приложения А, установленного у него установщика. Это не происходит, когда я запускаю выпуск или отладочную сборку. Для выпуска и сборки отладки мы используем VC10 . Но для сборки установщика мы используем VC12 . Я не уверен в оптимизации компилятора, которые существуют для сборки установщика.

Функция CreateProcess возвращает успех.

Состояние резьбы приложения B из проводника процесса:

Восстановление ассоциации файлов lnk и изменение ассоциаций файлов в Windows

Все программы в момент установки их в Windows прописывают в реестр свои ассоциации к файлам, с которыми они работают. Иногда при неграмотных действиях пользователя некоторые ассоциации могут изменится. Если добавить ассоциацию к ярлыкам, имеющим расширение .lnk, то программы с рабочего стола перестанут открываться. Разберемся почему так получается и как устранить.

Когда пользователь пытается открыть файл, который не имеет ассоциации, Windows предложит указать программу, с помощью которой можно это сделать. Но пользователь уверен, что ему по почте прислали именно документ в формате Word и пытается открыть файл программой Word.

В результате все файлы имеющие такое же расширение ассоциируются с этой программой.

Если в Windows появилась ассоциация ярлыков, то все ярлыки станут неработоспособными и программы с рабочего стола открываться не будут.

Восстановить ассоциацию файлов lnk можно через реестр путем удаления соответствующей записи.

Открываем окно «Выполнить» сочетанием клавиш «Win+R» и вводим команду «regedit»

В реестре переходим по ветке:
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\

И удаляем раздел .lnk

После этого достаточно выйти из учетной записи пользователя и зайти обратно. Windows добавит в реестр правильный раздел .lnk и ярлыки будут нормально функционировать.

Изменить ассоциацию файла, неправильно сопоставленного какой-либо программе, можно без вмешательства в реестр.

Изменение ассоциации файла программе настраивается через элемент Панели управления «Программы по-умолчанию».

Для этого откроем панель управления «Пуск / Панель управления» и выберем элемент «Программы по умолчанию»

Выбираем пункт «Сопоставление типов файлов или протоколов конкретным программам»

Выбираем расширение файла, для которого необходимо изменить ассоциацию, и нажимаем кнопку «Изменить программу»

Указываем программу, при помощи которой необходимо открывать данный файл.

Почему функция createprocess не запускает * lnk файлы?

Функция CreateProcess создает новый процесс и его первичный (главный) поток. Новый процесс запускает заданный исполняемый файл в контексте безопасности вызывающего процесса.

Если вызывающий процесс представляет другого пользователя, новый процесс использует маркер доступа для вызывающего процесса, а не маркер заимствования прав. Чтобы запустить новый процесс в контексте системы безопасности пользователя, обозначенного маркером заимствования прав, используйте функцию CreateProcessAsUser или CreateProcessWithLogonW .

[in] Указатель на строку, которая определяет модуль исполняемого кода, с символом нуля в конце. Заданный модуль может быть базирующейся на Windows прикладной программой. Это может быть какой-то другой тип модуля (например, MS-DOS или OS/2 ), если соответствующая подсистема доступна на локальном компьютере.

Строка может указывать полный путь и имя файла исполняемого модуля или она может определять часть имени. В случае если это часть имени, функция использует текущий диск и текущий каталог, чтобы завершить определение. Функция не будет использовать найденный путь. Если имя файла не содержит расширения, то подразумевается расширение .exe. Поэтому, если расширение имени файла — .com, в этот параметр должно включатся расширение .com.

Параметр lpApplicationName может быть значением ПУСТО (NULL). В этом случае, имя модуля должно быть в строке lpCommandLine как первое незаполненное пространство, разграниченное маркером.

Если Вы используете длинное имя файла, которое содержит пробел, применяйте строки в кавычках, чтобы обозначить, где имя файла заканчивается, и начинаются параметры; иначе, имя файла становится неоднозначным. Например, рассмотрим строку » c:\program files\sub dir\program name «. Эта строка может интерпретироваться несколькими способами. Попытки системы интерпретировать ее, возможны в нижеследующем порядке :

c:\program.exe files\sub dir\program name
c:\program files\sub.exe dir\program name
c:\program files\sub dir\program.exe name
c:\program files\sub dir\program name.exe

Windows NT/2000/XP: Если выполняемый модуль — 16-разрядное приложение, параметр lpApplicationName должен быть значением ПУСТО (NULL), а строка, указанная в параметре lpCommandLine должна задать выполняемый модуль.

[in, out] Указатель на строку с символом нуля в конце, определяющую командную строку для выполнения.

Windows NT /2000/XP: версия Уникода этой функции, CreateProcessW , завершится ошибкой, если этот параметр является строкой типа const .

Параметр lpCommandLine может быть значением ПУСТО (NULL). В этом случае, функция использует строку, указанную параметром lpApplicationName как командную строку.

Если и lpApplicationName и lpCommandLine не пустые (non-NULL), *lpApplicationName задает модуль выполнения, а * lpCommandLine определяет командную строку. Новый процесс может использовать функцию GetCommandLine , чтобы извлечь взятую в целом командную строку. Консольные процессы, C процессы периода выполнения могут использовать параметры argc и argv .

Консольные процессы, написанные на языке C, могут использовать параметры argc и argv , чтобы подробно анализировать командную строку. Поскольку argv [0] — имя модуля, C — программисты обычно повторяют имя модуля как первый маркер в командной строке.

Если lpApplicationName имеет значение ПУСТО (NULL), первое незаполненное пространство, ограниченное маркером командной строки, определяет имя модуля. Если Вы используете длинное имя файла, которое содержит пробел, используйте строки в кавычках, чтобы обозначить, где заканчивается имя файла, и начинаются параметры (см. объяснение параметра lpApplicationName ). Если имя файла не содержит расширения, предполагается расширение .exe. Поэтому, если расширение имени файла — .com, этот параметр должен включить в себя расширение .com. Если имя файла заканчивается точкой ( . ) без расширения, или имя файла содержит путь, расширение .exe не присоединяется. Если имя файла не содержит путь к каталогу, система ищет исполняемый файл в нижеследующей последовательности:

  1. Каталог, из которого загружена прикладная программа.
  2. Текущий каталог родительского процесса.
  3. Windows 95/98/Me: системный каталог Windows. Используйте функцию GetSystemDirectory , чтобы получить путь к этому каталогу.

Windows NT /2000/XP: 32-разрядный системный каталог Windows. Используйте функцию GetSystemDirectory, чтобы получить путь к этому каталогу. Имя (название) этого каталога — System32.

  • Windows NT/2000/XP: 16-разрядный системный каталог Windows. Нет функции, которая получает путь к этому каталогу, но он находится. Имя этого каталога — SYSTEM.
  • Каталог Windows. Используйте функцию GetWindowsDirectory , чтобы получить путь к этому каталогу.
  • Каталоги, которые внесены в список в PATH переменной окружения.
  • Система добавляет нулевой символ к командной строке, чтобы отделить имя файла от параметров. Он делит исходную строку на две строки для внутренней обработки.

    [in] Указатель на структуру SECURITY_ATTRIBUTES , которая обуславливает, может ли возвращенный дескриптор быть унаследован дочерними процессами. Если lpProcessAttributes имеет значение ПУСТО (NULL), дескриптор не может быть унаследован.

    Windows NT /2000/XP: член lpSecurityDescriptor структуры определяющей дескриптор безопасности для нового процесса. Если lpProcessAttributes имеет значение ПУСТО (NULL), или lpSecurityDescriptor имеет значение ПУСТО (NULL), процесс получает заданный по умолчанию дескриптор безопасности. Списки контроля доступа ( ACL ) в заданном по умолчанию дескрипторе безопасности для процесса происходят от первичного маркера или маркера заимствования прав создателя.

    [in] Указатель на структуру SECURITY_ATTRIBUTES , которая обуславливает, может ли возвращенный дескриптор быть унаследован дочерними процессами. Если lpThreadAttributes имеет значение ПУСТО (NULL), дескриптор не может быть унаследован.

    Windows NT /2000/XP: член lpSecurityDescriptor структуры определяющей дескриптор безопасности для главного потока. Если lpThreadAttributes имеет значение ПУСТО (NULL), или lpSecurityDescriptor имеет значение ПУСТО (NULL), поток получает заданный по умолчанию дескриптор безопасности. Списки контроля доступа ( ACL ) в заданном по умолчанию дескрипторе безопасности для потока происходят от первичного маркера или маркера заимствования прав создателя.

    [in] Если этот параметр — ИСТИНА (TRUE), каждый наследуемый дескриптор в вызывающем процессе наследуется новым процессом. Если этот параметр — ЛОЖЬ (FALSE), дескрипторы не наследуются.

    Обратите внимание! на то, что унаследованные дескрипторы имеют то же самое значение и права доступа, что и первоначальные дескрипторы.

    [in] Флажки, которые управляют классом приоритета и созданием процесса. За перечнем значений обратитесь к статье Флажки создания процесса .

    Этот параметр также управляет и классом приоритета нового процесса, который используется в определении приоритетов диспетчеризации потоков процесса. За перечнем значений обратитесь к статье о функции GetPriorityClass . Если ни один из флажков класса приоритета не установлен, значения по умолчанию класса приоритета NORMAL_PRIORITY_CLASS , если класс приоритета процесса созданного процесса не является IDLE_PRIORITY_CLASS или BELOW_NORMAL_PRIORITY_CLASS . В данном случае дочерние процессы получают заданный по умолчанию класс приоритета вызывающего процесса.

    [in] Указатель на блок конфигурации нового процесса. Если этот параметр имеет значение ПУСТО (NULL), новый процесс использует конфигурацию вызывающего процесса.

    Блок конфигурации состоит из блока строк с символом нуля в конце, который завершается также нулем. Каждая строка представляется в форме:

    Поскольку знак «=» используется как разделитель, он не должен быть использован в имени переменной окружения.

    Блок конфигурации может содержать или символы Unicode или ANSI . Если блок конфигурации, указанный параметром lpEnvironment , содержит символы Unicode , убедитесь, что в параметре dwCreationFlags установлен флажок CREATE_UNICODE_ENVIRONMENT . Если блок содержит символы ANSI , этот флажок будет сброшен.

    Обратите внимание! на то, что блок конфигурации в ANSI заканчивается двумя нулевыми байтами: один для последней строки, еще один, чтобы завершить блок. Блок конфигурации Уникода заканчивается четырьмя нулевыми байтами: два — для последней строки, еще два, чтобы завершить блок.

    [in] Указатель на строку с символом нуля в конце, определяющую текущий диск и каталог для дочернего процесса. Строка должна быть полным путем, который включает в себя букву (имя) диска. Если этот параметр является значением ПУСТО (NULL), новый процесс создается с тем же самым текущим диском и каталогом, что и вызывающий процесс. (Этот параметр дается, прежде всего, для оболочек, которым нужно запустить прикладную программу и установить ее исходный диск и рабочий каталог).

    [in] Указатель на структуру STARTUPINFO , которая устанавливает оконный режим терминала, рабочий стол, стандартные дескрипторы и внешний вид главного окна для нового процесса.


    [out] Указатель на структуру PROCESS_INFORMATION , которая принимает идентифицирующую информацию о новом процессе.

    Дескрипторы в структуре PROCESS_INFORMATION , когда они больше не нужны, должны быть закрыты функцией CloseHandle .

    Возвращаемые значения

    Если функция завершается успешно, величина возвращаемого значения — не ноль.

    Если функция завершается с ошибкой, величина возвращаемого значения — ноль. Чтобы получить дополнительные данные об ошибках, вызовите GetLastError .

    Замечания

    Процессу присваивается идентификатор. Идентификатор является правильным до тех пор, пока процесс не завершит работу. Он может быть использован, чтобы идентифицировать процесс, или открыть определяемый в функции OpenProcess дескриптор процесса. Начальный поток в процессе также получает свой идентификатор. Идентификатор правильный до тех пор, пока поток закончит свою работу и может быть использован, чтобы уникально идентифицировать поток в пределах системы. Эти идентификаторы возвращаются в структуре PROCESS_INFORMATION .

    Вызывающий поток может использовать функцию WaitForInputIdle , чтобы ждать до тех пор, пока новый процесс не завершит свою инициализацию и станет ждать от пользователя ввода данных без задержки ввода. Этот прием может быть полезным для синхронизации родительского и дочернего процессов, поскольку функция CreateProcess возвращает значение не ожидая, когда новый процесс закончит свою инициализацию. Например, создающий процесс должен использовать функцию WaitForInputIdle перед попыткой найти окно, связанное с новым процессом.

    Предпочтительным способом выключить процесс является использование функции ExitProcess , потому что эта функция уведомляет все динамически подключаемые библиотеки ( DLL ), связанные с процессом о приближающемся завершении работы. Другой способ завершения процесса не уведомляет связанные DLL .

    Обратите внимание! на то, что когда поток вызывает ExitProcess , другие потоки процесса, завершают работу без возможности выполнить какой-либо дополнительный код (включая код завершения потока связанных DLL ). За дополнительной информацией обратитесь к статье Завершение работы процесса.

    Если приложение предусматривает блок конфигурации, информация текущего каталога системных дисков автоматически не передается новому процессу. Например, имеется переменная окружения, с названием =C:, значение которой — текущий каталог на диске C. Приложение должно позаботиться, чтобы передать новому процессу информацию о текущем каталоге. Поступая таким образом, приложение должно явно создать эти строки переменной окружения, отсортировывать их в алфавитном порядке (потому что система использует отсортированные переменные) и поместить их в блок конфигурации. Как правило, они пойдут в начале блока конфигурации благодаря порядку установленному сортировкой блока конфигурации.

    Один из способов получить переменную текущего каталога для диска X состоит в том, чтобы вызвать GetFullPathName (» X: «. ). Это освобождает прикладную программу от необходимости сканировать блок конфигурации. Если полный возвращенный путь является X:\ , нет нужды передавать это значение как данные конфигурации, так как корневой директорией является заданный по умолчанию текущий каталог для диска X нового процесса .

    Обратите внимание! на то, что имя исполняемой программы в командной строке, которое операционная система предоставляет процессу, необязательно идентично тому, которое вызывающий процесс передает функции CreateProcess в командной строке. Операционная система может предварительно обуславливать полностью уточненный путь к имени исполняемой программы, которое дано без полного, имеющего необходимые данные пути.

    Windows NT/2000/XP: Когда процесс создается с установленным флажком CREATE_NEW_PROCESS_GROUP , неявный вызов функции SetConsoleCtrlHandler (NULL, TRUE) делается от имени нового процесса; это означает то, что новый процесс имеет заблокированным сочетание клавиш CTRL+C . Это позволяет оболочкам обрабатывать CTRL+C непосредственно и выборочно передавать этот сигнал подпроцессам. Сочетание клавиш CTRL+BREAK не блокируется и может быть использовано для прерывания процесса / группы процессов.

    Windows 95/98/Me: Функция CreateProcessW поддерживается подпрограммой Microsoft Layer for Unicode. Чтобы использовать ее, Вы должны добавить некоторые файлы к вашему приложению, как изложено в требованиях этой подпрограммы для систем Windows 95/98/Me.

    Замечания по безопасности

    Первый параметр, lpApplicationName , может иметь значение ПУСТО (NULL), в этом случае имя исполняемой программы должно быть первое незаполненное пространство разграничивающее строку в параметре lpCommandLine . Если имя пути или исполняемой программы имеют пробел, имеется риск того, что может быть запущена другая исполняемая программа из-за способа, которым функция подробно анализирует пробелы. Нижеследующий пример демонстрирует эту опасность, потому что функция вместо «MyApp.exe» будет пытаться запустить «Program.exe», если таковая существует .

    Если неграмотный пользователь создаст в системе прикладную программу, называемую «Program.exe», любая программа, которая неправильно вызывает функцию CreateProcess , используя каталог Program Files, будет запускать это приложение вместо заданной программы .

    Чтобы избежать этой проблемы, не передавайте значение ПУСТО (NULL) для параметра lpApplicationName . Если Вы передаете это значение ПУСТО (NULL) для lpApplicationName , используйте кавычки вокруг пути к исполняемой программе в параметре lpCommandLine , как показано в примере ниже.

    Функция CreateProcess

    Основной функцией для создания процесса является CreateProcess. Данная функция создает новый процесс с единственным первичным потоком. Десять параметров функции позволяют указать основные параметры создаваемого процесса.

    При вызове CreateProcess, система создает объект ядра «процесс» (структуру данных, через которую операционная система управляет процессом). Затем система создает для нового процесса виртуальное адресное пространство и загружает в него код и данные исполняемого файла и необходимых библиотек (DLL). Далее система формирует объект ядра «поток» для первичного потока (primary) нового процесса. Первичный поток отвечает за инициализацию процесса, обработку системных сигналов и событий. Этот поток “живет” до того момента, когда управление не будет возвращено операционной системе для завершения процесса. Первичный поток начинает с исполнения стартового кода программы (для C/C++ это функции WinMain, wWinMain, main или wmain).

    Рассмотрим параметры и возвращаемое значение функции CreateProcess.

    Параметры lpApplicationName и lpCommandLine используются вместе для указания исполняемой программы и аргументов командной строки. Если файл с указанным именем не найден, Windows приступает к поиску заданного файла и делает это в следующем порядке:

    1. Каталог, содержащий ЕХЕ-файл вызывающего процесса.

    2. Текущий каталог вызывающего процесса.

    3. Системный каталог Windows.

    4. Основной каталог Windows.

    5. Каталоги, перечисленные в переменной окружения PATH.

    lpProcessAttributes и lpThreadAttributes указатели на структуры атрибутов защиты процесса и потока. Значениям NULL соответствует использование атрибутов защиты, заданных по умолчанию.

    Флаг bInheritHandles показывает, наследует ли новый процесс открытый дескрипторы (файлов, файлов отображенных на память и т.д.) из вызывающего (родительского) процесса производящего запуск. Унаследованные дескрипторы имеют те же значения и права доступа, что и оригиналы.

    Параметр dwCreationFlags задает параметры создания процесса и его приоритет. Некоторые возможные значения данного флага перечислены ниже.

    CREATE_NEW_CONSOLE Новый процесс получает новую консоль вместо того, чтобы унаследовать родительскую.
    CREATE_NEW_PROCESS_GROUP Создаваемый процесс — корневой процесс новой группы.
    CREATE_SUSPENDED Первичная нить процесса создается в спящем (suspended) состоянии и не выполняется до вызова функции ResumeThread
    REALTIME_PRIORITY_CLASS Процесс получает наивысший приоритет – Real-time (реального времени). Потоки в этом процессе обязаны немедленно реагировать на события, обеспечивая выполнение критических по времени задач. Такие потоки вытесняют даже компоненты операционной системы.
    HIGH_PRIORITY_CLASS Потоки в процессе с приоритетом High (высокий) должны немедленно реагировать на события, обеспечивая выполнение критических по времени задач. Этот класс присвоен, например системной утилите Task Manager.
    ABOVE_NORMAL_PRIORITY_CLASS Класс приоритета Above Normal (выше среднего), промежуточный между Normal и High. Данный класс впервые введен в Windows 2000.
    NORMAL_PRIORITY_CLASS Потоки в процессе c приоритетом Normal (нормальный) не предъявляют особых требований к выделению им процессорного времени.
    BELOW_NORMAL_PRIORITY_CLASS Процесс получит класс приоритета Below Normal (ниже среднего), промежуточный между Normal и High. Данный класс впервые введен в Windows 2000.
    IDLE_PRIORITY_CLASS Потоки в процессе с приоритетом Idle выполняются, когда система не заняты другой работой. Этот класс приоритета обычно используется для утилит, работающих в фоновом режиме, экранных заставок и приложений, собирающих статистическую информацию.

    lpEnvironment казывает на блок переменных окружения нового процесса. Если передаваемое значение будет NULL, то будет использован блок родительского процесса. Блок среды это список строк с нулевым окончанием типа «имя=значение». Использование переменных окружения наиболее простой способ передачи информации в порождаемый процесс.

    lpCurrentDirectory – указатель но строку, содержащую путь к текущему каталогу нового процесса. Если значение не задано, то в качестве текущего каталога будет использоваться каталог родительского (порождающего) процесса.

    lpStartupInfo указатель на структуру STARTUPINFO, которая описывает внешний вид основного окна создаваемого процесса и содержит дескрипторы основных устройств нового процесса. Большинство приложений порождает процессы с атрибутами по умолчанию, в этом случае должны быть инициализировать все элементы структуры STARTUPINFO нулевыми значениями.

    lpProcessInformation – указатель на структуру PROCESS_INFORMATION, в которую Windows будут помещены, возвращаемые функцией значения дескрипторов и уникальные идентификаторы созданного процесса и его главного потока.

    Как видно из описания функции CreateProcess, возвращаемое ей значение не является дескриптором созданного объекта типа HANDLE, как это происходит в большинстве функций. Если системе удастся создать новый процесс и его первичный поток, тогда CreateProcess вернет ненулевое значение, иначе будет возвращено нулевое значение.

    Все ярлыки открываются одной программой. Или как вернуть ассоциацию ярлыков с программами

    Всем привет! В этом посте поговорим об одной очень жизненной ситуации, с которой на рабочем компьютере столкнулась сестра автора блога. Суть ее в том, что все ярлыки открываются одной программой.

    То есть смотрите, например, значок интернет-браузера Opera стал открываться через Word. К тому же в результате этого сбоя, перестал работать установленный на ПК сервер рабочей программы. А это уже, согласитесь, конкретный косяк.

    Содержание статьи:

    Так вот, друзья, давайте разбираться, почему так все-таки произошло и что нужно сделать, чтобы исправить подобную ситуацию. А все дело в том, что неосознанными действиями пользователя, были заданы неверные ассоциации ярлыков с родными программами.

    Как это могло получиться? Да очень просто. При открытии ярлыка была использована команда «Открыть с помощью», в результате чего всем файлам с расширением «.lnk» была присвоена неправильная программа для открытия.

    Но большого страха в этом нет, все можно вернуть на свои места без большой крови. Для этого автором блога в Сети был найден простой и легкий способ того, как можно вернуть ассоциации ярлыков с родными программами:

    Сразу скажем о том, что все манипуляции будем делать на примере Windows 10, хотя на других версиях данной ОС все будет аналогично. Итак, первым делом открываем с правами администратора редактор реестра.

    Для этого жмем по кнопке «Пуск» и вводим команду «regedit»:

    После его открытия помним, что здесь работать нужно очень осторожно, не делая никаких ошибочных манипуляций. На этом шаге нам нужно проследовать по пути:

    Найти там подраздел «UserChoice» и обязательно удалить его. Делается это нажатием по нему правой кнопки мыши:

    Вот, в принципе, и всего делов-то. После перезагрузки компьютера все ассоциации ярлыков вернутся к родным программам. Как видите, ничего сложного в этом нет. Кстати, сестра автора блога сумела исправить данную проблему даже по телефону.

    Но если кто-то из вас совершенно не дружит с редактором реестра либо боится там что-нибудь не то удалить, то есть еще один вариант. Он заключается в запуске на компьютере маленькой программки под названием Unassociate File Types.

    Стоит сказать, что она даже не требует установки. Поэтому берем ее по ссылке с официального сайта, затем распаковываем и запускаем. Но на этом шаге автора ждал облом, потому как на Windows 10 данная утилита не работает:

    Но если у кого-то установлена Vista или Windows 7, то бояться не стоит, программа запустится без проблем. Затем в графе «File types» находим нужное нам расширение «.lnk» и жмем кнопку «Remove file association»:

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

    Если у вас остались вопросы, спешите задать их в комментариях. Будем вместо решать ваши проблемы и неувязки. А в завершение можно посмотреть очередное жизненное видео.

    Блог GunSmoker-а

    . when altering one’s mind becomes as easy as programming a computer, what does it mean to be human.

    14 июля 2009 г.

    Этот проблемный CreateProcess.

    Вроде бы эта функция не слишком сложна для понимания, но слишком уж часто её используют неверно или не понимают.

    По крайней мере, регулярно появляются вопросы типа этого или этого.

    lpApplicationName [in, optional]
    The name of the module to be executed. This module can be a Windows-based application. It can be some other type of module (for example, MS-DOS or OS/2) if the appropriate subsystem is available on the local computer.

    The string can specify the full path and file name of the module to execute or it can specify a partial name. If it is a partial name, the function uses the current drive and current directory to complete the specification. The function does not use the search path. This parameter must include the file name extension; no default extension is assumed.


    The lpApplicationName parameter can be NULL, and the module name must be the first white space–delimited token in the lpCommandLine string. If you are using a long file name that contains a space, use quoted strings to indicate where the file name ends and the arguments begin; otherwise, the file name is ambiguous.

    lpCommandLine [in, out, optional]
    The command line to be executed. The maximum length of this string is 1024 characters. If lpApplicationName is NULL, the module name portion of lpCommandLine is limited to MAX_PATH characters.

    The function can modify the contents of this string. Therefore, this parameter cannot be a pointer to read-only memory (such as a const variable or a literal string). If this parameter is a constant string, the function may cause an access violation.

    The lpCommandLine parameter can be NULL, and the function uses the string pointed to by lpApplicationName as the command line.

    If both lpApplicationName and lpCommandLine are non-NULL, *lpApplicationName specifies the module to execute, and *lpCommandLine specifies the command line. The new process can use GetCommandLine to retrieve the entire command line. Console processes written in C can use the argc and argv arguments to parse the command line. Because argv[0] is the module name, C programmers typically repeat the module name as the first token in the command line.

    If lpApplicationName is NULL, the first white space–delimited token of the command line specifies the module name. If you are using a long file name that contains a space, use quoted strings to indicate where the file name ends and the arguments begin (see the explanation for the lpApplicationName parameter). If the file name does not contain an extension, .exe is appended. Therefore, if the file name extension is .com, this parameter must include the .com extension. If the file name ends in a period with no extension, or if the file name contains a path, .exe is not appended.

    (выделение моё, описание приведено не полностью)

    Не понимаете английского? Как вы вообще тогда можете быть программистом? Не будьте беспомощны! Вы можете воспользоваться любым авто-переводчиком:

    .
    lpApplicationName [In, необязательный]
    Имя модуля для запуска. Этот модуль может быть Windows-приложения. Она может быть несколько иной вид модуля (например, MS-DOS или OS / 2), если соответствующие подсистемы имеется на локальном компьютере.

    Строка может указать полный путь и имя файла модуля для выполнения или он может уточнить частичное название. Если это часть имени, то функция использует текущий диск и текущий каталог, в полной спецификации. Эта функция не используется путь поиска. Этот параметр должен содержать расширение имени файла; нет умолчанию предполагается продление.

    Параметр lpApplicationName может быть NULL, тогда имя модуля имя должно быть первым токеном, отделённым пробелом, в lpCommandLine. Если вы используете длинные названия файла, который содержит в пространстве, использование цитирует строки для указания, где имя файла заканчивается, и начинаются аргументы, в противном случае имя файла является двусмысленным.

    lpCommandLine [In, Out, необязательный]
    Командная строка для выполнения. Максимальная длина этой строки 1024 символов. Если lpApplicationName является NULL, модуль имя часть lpCommandLine ограничена MAX_PATH символов.

    Эта функция может модифицировать содержимое этой строки. Таким образом, этот параметр не может быть указателем для чтения памяти (например, Const переменной или буквальном строка). Если этот параметр является постоянной строки, функция может вызвать нарушение прав доступа.

    В lpCommandLine параметр может быть NULL, и функция использует строку отметили в lpApplicationName как из командной строки.

    Если оба lpApplicationName и lpCommandLine являются не-NULL, lpApplicationName определяет модуль для выполнения, а lpCommandLine указывает командную строку. Новый процесс может использовать GetCommandLine получить всю командную строку. Консоль процессов написаны на C можно использовать argc и argv для разбора аргументов командной строки. Поскольку argv [0] является именем модуля, C программистов, как правило, повторяют имя модуля как первый знак в командной строке.

    Если lpApplicationName равно NULL, первый токен до пробела в командной строке должен быть именем модуля для запуска. Если вы используете длинные названия файла, который содержит в пространстве, использование цитирует строки для указания, где имя файла заканчивается, и начинаются аргументы (см. пояснения к lpApplicationName параметров). Если имя файла не содержит расширения,. EXE прилагается. Поэтому, если в имени файла расширение. COM, этот параметр должен содержать. Ком продления. Если имя файла заканчивается в срок, не расширение, или если имя файла содержит путь. EXE не добавляется.
    .

    Это не так сложно сделать и так же не сложно понять.

    Тем не менее, постоянно встречаются ошибки:

    • Забываем про кавычки или ставим лишние. Кавычки нужны в командной строке (второй параметр CreateProcess ) и не нужны в имени модуля (первый параметр).
    • Забываем про имя модуля в командной строке, если явно указали запускаемый модуль (не забываем: командная строка передаётся процессу «как есть»).
    • Перемешиваем вообще всё, что возможно перемешать, пихая параметры командной строки в имя модуля.

    Если указывается первый параметр, то приложение должно быть указано дважды: первый раз в первом параметре, второй раз — в командной строке (второй параметр).

    Логика такая: командная строчка (второй параметр) передаётся программе «как есть». Программа может парсить командную строчку, как ей будет угодно. Соответственно, если в командной строке вы не укажете саму программу, а только её параметры, то сама программа в них запутается: она примет первый параметр за своё имя (нет, ParamStr(0) в Delphi вызывает GetModuleFileName , но другие могут), а второй параметр (если он есть) — за первый (тут ошибётся и Delphi программа тоже).

    Имя же программы для запуска загрузчиком ОС берётся из первого параметра, если он задан. Или пытается извлечься из второго, если нет.

    Вот это «пытается извлечься» и есть причина, почему всегда рекомендуется указывать первый параметр: Так ошибок не будет никогда (обратите внимание на расстановку кавычек и пробелов). А если вы его не укажете — у вашей программы могут быть серьёзные проблемы с безопасностью. Особенно, если вы не используете кавычки.

    Связано это с правилами поиска/автодополнения файлов и библиотек для запуска. Не буду тут особенно мыслью растекаться — и сами можете почитать у Рихтера или в MSDN.

    Да, кстати, получаете EAccessViolation («Access violation at address. «) при вызове CreateProcess на Delphi 2009? Внимательнее читайте описание функции:

    lpCommandLine [in, out, optional]
    .
    The Unicode version of this function, CreateProcessW, can modify the contents of this string. Therefore, this parameter cannot be a pointer to read-only memory (such as a const variable or a literal string). If this parameter is a constant string, the function may cause an access violation.

    Функция CreateProcess

    Процесс создается при вызове Вашим приложением функции CreateProcess

    Когда поток в приложении вызывает CreateProcess, система создает объект ядра «процесс» с начальным значением счстчика числа его пользователей, равным 1. Этот объект — не сам процесс, а компактная структура данных, через которую операци онная система управляет процессом. (Объект ядра «процесс» следует рассматривать как структуру данных со статистической информацией о процессе.) Затем система создает для нового процесса виртуальное адресное пространство и загружает в него код и данные как для исполняемого файла, тaк и для любых DLL (если таковые требу ются).

    Далее система формирует объект ядра «поток» (со счетчиком, равным 1) для пер вичного потоки нового процесса. Как и в первом случае, объект ядра «поток» — это компактная структура данных, через которую система управляет потоком. Первичный поток начинает с исполнения стартового кода из библиотеки С/С++, который в ко нечном счете вызывает функцию WinMain, wWinMain, main или wmain в Вашей про грамме. Если системе удастся создать новый процесс и его первичный поток, Create Process вернет TRUE

    CreateProcess возвращает TRUE до окончательной инициализации процесса. Это означает, что на данном этапе загрузчик операционной системы еще нс искал все необходимые DLL. Если он не сможет найти хотя бы одну из DLL или корректно провести инициализацию, процесс завершится. Но, поскольку Create Process уже вернула TRUE, родительский процесс ничего не узнает об этих про блемах.

    На этом мы закончим общее описание и перейдем к подробному рассмотрению параметров функции CreateProcess

    Параметры pszApplicationName и pszCommandLine

    Эти параметры определяют имя исполняемого файла, которым будет пользоваться новый процесс, и командную строку, передаваемую этому процессу. Начнем cpszCom mandLine.

    Обратите внимание на тип параметра pszCommandLine: PTSTR. Он означает, что CreateProcess ожидает передачи адреса строки, которая не является констан той Дело в том, что CreateProcess в процессе своего выполнения модифици рует переданную командную строку, но перед возвратом управления восста навливает ее.

    Это очень важно, если командная строка содержится в той части образа Вашего файла, которая предназначена только для чтения, возникнет ошибка доступа. Например, следующий код приведет к такой ошибке, потому что Visual С++ 6.0 поместит строку «NOTEPAD» в память только для чтения:

    STARTUPINFO si = < sizeof(si) >; PROCESS_INFORMATION pi;

    CreateProcess(NULL, TEXT(«NOTEPAD»), NULL, NULL, FALSE, 0, NULL. NULL, &si, &pi);

    Когда CreateProcess попытается модифицировать строку, произойдет ошиб ка доступа. (В прежних версиях Visual C++ эта строка была бы размещена в памяти для чтения и записи, и вызовы CreateProcess не приводили бы к ошиб кам доступа.)

    Лучший способ решения этой проблемы — перед вызовом CreateProcess ко пировать константную строку во временный буфер:

    STARTUPINFO si = < sizeof(si) >; PROCESS_INFORMATION pi;

    TCHAR szComrnandLine[] = TEXT(«NOTEPAD»);

    CreateProcess(NULL, szCommandLine, NULL, NULL, FALSE, 0, NULI, NULL, &si, &pi);

    Возможно, Вас заинтересуют ключи /Gf и /GF компилятора Visual C++, ко торые исключают дублирование строк и запрещают их размещение в области только для чтения. (Также обратите внимание на ключ /ZI, который позволяет задействовать отладочную функцию Edit & Continue, поддерживаемую Visual Studio, и подразумевает активизацию ключа /GF.) В общем, лучшее, что може те сделать Вы, — использовать ключ /GF или создать временный буфер. А еще лучше, если Microsoft исправит функцию CreateProcess, чтобы та не морочила нам голову. Надеюсь, в следующей версии Windows так и будет.

    Кстати, при вызове ANSI-версии CreateProcess в Windows 2000 таких про блем нет, поскольку в этой версии функции командная строка копируется во временный буфер (см. главу 2)

    Параметр pszCommandLme позволяет указать полную командную строку, исполь зуемую функцией CreateProcess при создании нового процесса. Разбирая эту строку, функция полагает, что первый компонент в ней представляет собой имя исполняе мого файла, который Вы хотите запустить. Если в имени этого файла не указано рас ширение, она считает его EXE. Далее функция приступает к поиску заданного файла и делает это в следующем порядке:

    1. Каталог, содержащий ЕХЕ-файл вызывающего процесса.

    2. Текущий каталог вызывающего процесса.

    3. Системный каталог Windows.

    4. Основной каталог Windows.

    5. Каталоги, перечисленные в переменной окружения PATH.

    Конечно, если в имени файла указан полный путь доступа, система сразу обраща ется туда и не просматривает эти каталоги. Найдя нужный исполняемый файл, она создает новый процесс и проецирует код и данные исполняемого файла на адресное пространство этого процесса Затем обращается к процедурам стартового кода из библиотеки С/С++. Тот в свою очередь, как уже говорилось, анализирует командную строку процесса и передает (w)WinMain адрес первого (за именем исполняемого фай ла) аргумента как pszCmdLine.

    Все, о чем я сказал, произойдет, только если параметр pszApplicationName равен NULL (что и бывает в 99% случаев). Вместо NULL можио передать адрес строки с име нем исполняемого файла, который надо запустить. Однако тогда придется указать не только его имя, но и расширение, поскольку в этом случае имя не дополняется рас ширением EXE автоматически. CreateProcess предполагает, что файл находится в те кущем каталоге (если полный путь не задан). Если в текущем каталоге файла нет, функция не станет искать его в других каталогах, и на этом все закончится

    Но даже при указанном в pszApplicationName имени файла CreateProcess все равно передает новому процессу содержимое параметра pszCommandLine как командную строку. Допустим, Вы вызвали CreateProcess так:

    // размещаем строку пути в области памяти для чтения и записи

    TCHAR szPath[] = TEXT(«WORDPAD README.TXT»);

    // порождаем новый процесс

    CreateProcess(TEXT(«C:\\WINNr\\SYSrEM32\\NOTEPAD EXE»), szPath );

    Система запускает Notepad, а в его командной строке мы видим «WORDPAD README.TXT». Странно, да? Но так уж она работает, эта функция CreateProcess. Упо мянутая возможность, которую обеспечивает пареметр pszApplicationName, на самом деле введена в CreateProcess для поддержки подсистемы POSIX в Windows 2000.

    Параметры psaProcess, psaThread и blnheritHandles

    Чтобы создать новый процесс, система должна сначала создать объекты ядра «про цесс» и «поток» (для первичного потока процесса). Поскольку это объекты ядра, ро дительский процесс получает возможность связать с ними атрибуты защиты. Пара метры psaProcess и psaThread позволяют определить нужные атрибуты защиты для объектов «процесс» и «поток» соответственно. В эти параметры можно занести NULL, и система закрепит за данными объектами дескрипторы защиты по умолчанию. В качестве альтернативы можно объявить и инициализировать две структуры SECU RITY_ATTRIBlITES; тем самым Вы создадите и присвоите объектам «процесс» и «по ток» свои атрибуты защиты.

    Структуры SECURITY_ATTRIBUTES для параметров psaProcess wpsaTbread исполь зуются и для того, чтобы какой-либо из этих двух объектов получил статус наследуе мого любым дочерним процессом. (О теории, на которой построено наследование описателей объектов ядра, я рассказывал в главе 3.)

    Короткая программа на рис. 4-2 демонстрирует, как наследуются описатели объек тов ядра. Будем считать, что процесс А порождает процесс В и заносит в параметр psaProcess адрес структуры SECURITY_ATTRIBUTES, в которой элемент blnheritHandle установлен как TRUE. Одновременно параметр psaThread указывает на другую струк туру SECURITY_ATTRIBUTES, в которой значение элемента bInheritHandle — FALSE.


    Создавая процесс В, система формирует объекты ядра «процесс» и «поток», а за тем — в структуре, на которую указывает параметрppiProcInfo (о нем поговорим поз же), — возвращает их описатели процессу А, и с этого момента тот может манипули ровать только что созданными объектами «процесс» и «поток».

    Теперь предположим, что процесс А собирается вторично вызвать функцию Create Process, чтобы породить процесс С. Сначала ему нужно определить, стоит ли предос тавлять процессу С доступ к своим объектам ядра. Для этого используется параметр blnberitHandles, Если он приравнен TRUE, система передаст процессу С все наследуе мые описатели В этом случае наследуется и описатель объекта ядра «процесс» про цесса В. А вот описатель объекта «первичный поток» процесса В не наследуется ни при каком значении bInberitHandles. Кроме того, если процесс А вызывает Create Process, передавая через параметр blnberitHandles значение FALSE, процесс С не насле дует никаких описателей, используемых в данный момент процессом А.

    Параметр fdwCreate

    Параметр fdwCreate определяет флаги, влияющие на то, как именно создается новый процесс Флаги комбинируются булевым оператором OR.

    Флаг DEBUG_PROCESS даст возможность родительскому процессу проводить отладку дочернего, а также всех процессов, которые последним могут быть порождены Если этот флаг установлен, система уведомляет родительский про цесс (он теперь получает статус отладчика) о возникновении определенных событий в любом из дочерних процессов (а они получают статус отлаживае мых).

    Флаг DEBUG_ONLY_THIS_PROCESS аналогичен флагу DEBUG_PROCESS с тем исключением, что заставляет систему уведомлять родительский процесс о воз никновении специфических событий только в одном дочернем процессе — его прямом потомке. Тогда, если дочерний процесс создаст ряд дополнительных, отладчик уже нс уведомляется о событиях, «происходящих» в них.

    Флаг CREATE_SUSPENDED позволяет создать процесс и в то же время приоста новить его первичный поток Это позволяет родительскому процессу модифи цировать содержимое памяти в адресном пространстве дочернего, изменять приоритет его первичного потока или включать этот процесс в задание (job) до того, как он получит шанс на выполнение. Внеся нужные изменения в до черний процесс, родительский разрешает выполнение его кода вызовом фун кции

    ResumeThread (см. главу 7).

    Флаг DFTACHED_PROCESS блокирует доступ процессу, инициированному кон сольной программой, к сопданному родительским процессом консольному окну и сообщает системе, что вывод следует перенаправить в новое окно CUI процесс, создаваемый другим CUI-процессом, по умолчанию использует кон сольное окно родительского процесса (Вы, очевидно, заметили, что при за пуске компилятора С из командного процессора новое консольное окно не создается, весь его вывод «подписывается» в нижнюю часть существующего консольного окна ) Таким образом, этот флаг заставляет новый процесс пере направлять свой вывод в новое консольное окно

    Флаг CREATE_NEW_CONSOLE приводит к созданию нового консольного окна для нового процесса. Имейте в виду, что одновременная установка флагов

    CREATE_NEW_CONSOLE и DETACHED_PROCESS недопустима.

    Флаг CREATE_NO_WINDOW не дает создавать никаких консольных окон для данного приложения и тем самым позволяет исполнять его без пользователь ского интерфейса.

    Флаг CREATE_NEW_PROCESS_GROUP служит для модификации списка процес сов, уведомляемых о нажатии клавиш Ctrl+C и Ctrl+Break Если в системе од новременно исполняется несколько CUI-процессов, то при нажагии одной из упомянутых комбинаций клавиш система уведомляет об этом только процес сы, включенные в группу. Указав этот флаг при создании нового СUI-процес ca, Вы создаете и новую группу

    Флаг CREATE_DEFAULT_ERROR_MODE сообщает системе, чтo новый процесс не должен наследовать режимы обработки ошибок, установленные в родитель ском (см. раздел, где я рассказывал о функции SetErrorMode ) .

    Флаг CREATE_SEPARATE_WOW VDM полезен только при запуске 16-разрядно го Wmdows-приложения в Windows 2000. Если он установлен, система созда ет отдельную виртуальную DOS-машину (Virtual DOS-machine, VDM) и запус кает 16-разрядное Windows-приложение именно в ней (По умолчанию все 16 разрядные Windows-приложения выполняются в одной, общей VDM.) Выпол нение приложения в отдельной VDM дает большое преимущество, «рухнув», приложение уничтожит лишь эту VDM, а программы, выполняемые в других VDM, продолжат нормальную работу. Кроме того, 16-разрядные Windows-при ложения, выполняемые в раздельных VDM, имеют и раздельные очереди вво да. Эго значит, что, если одно приложение вдруг «зависнет», приложения в других VDM продолжат прием ввода. Единственный недостаток работы с нес колькими VDM в том, что каждая из них требуеч значительных объемов физи ческой памяти. Windows 98 выполняет все 16-разрядные Windows-приложения только в одной VDM, и изменить тут ничего нельзя.

    Флаг CREATE_SHARED_WOW_VDM полезен только при запуске 16-разрядного Windows-приложения в Windows 2000. По умолчанию все 16-разрядные Windowsприложения выполняются в одной VDM, ссли только не указан флаг

    CREATE_SEPARATEWOW_VDM. Однако стандартное пoвeдeниeWindows 2000

    можно изменить, присвоив значение «yes» параметру DefaultSeparateVDM в paздeлe

    HKEY_LOCAL_MACHINE\System\CurгentControlSet\Contгol\WOW.(Пoc ле модификации этого параметра систему надо перезагрузить.) Установив зна чение

    «yes», но указав флаг CREATE_SHARED_WOW_VDM, Вы вновь заставите

    Windows 2000 выполнять все 16-разрядные Windows-приложения в одной VDM. Флаг CREATE_UNICODE_ENVIRONMENT сообщает системе, что блок перемен ных окружения дочернего процесса должен содержать Unicode-символы. По умолчанию блок формируется на основе ANSI-символов

    Флаг CREATE_FORCEDOS заставляет систему выполнять программу MS-DOS, встроенную в 16-разрядное приложение OS/2

    Флаг CREATE_BREAKAWAY_FROM_JOB позволяет процессу, включенному в за дание, создать новый процесс, отделенный от этого задания (см. главу 5).

    Параметр fdwCreate разрешает задать и класс приоритета процесса Однако это необязательно и даже, как правило, нс рекомендуется, система присваивает новому процессу класс приоритета по умолчанию. Возможные классы приоритета перечис лены в следующей таблице.

    Перехватываем запуск любого приложения в Windows и пытаемся ничего не сломать

    Если вы много занимаетесь отладкой приложений под Windows — вы, возможно, слышали о таком замечательном механизме, как Image File Execution Options (IFEO). Одна из предоставляемых им возможностей позволяет отлаживать приложение в условиях, более приближенных к боевым. Записав в нужное место в реестре специальный ключик, мы можем вместо программы автоматически запускать её отладчик, позволяя ему делать свои отладочные дела. Однако кто сказал, что этот механизм (фактически — перехвата запуска чего угодно) можно использовать только в подобных целях? Эта статья вовсе не об использовании вещей по назначению.

    Под катом рассказ о том, как я попытался выжать побольше из этого механизма в своих добрых целях, и какие подводные камни мне встретились на пути. А у меня тут хорошие, отборные камни.

    Вообще говоря, идея эта не нова. Я знаю как минимум три программы, которые используют этот механизм для не-отладочных целей: широко известные Process Explorer и Process Hacker — для замены стандартного диспетчера задач; и AkelPad — для замены блокнота. Но я решил пойти немного дальше.

    Итак, что мы имеем: мы можем запустить свою программу вместо любой другой наперёд заданной, если заранее пропишем себя в реестре. Причём не особо важно кто, как, и с какими правами будет её запускать.

    Не уверен, насколько полезным оказался бы этот механизм, если бы не было возможность его обойти: отладчику-то нужно запустить отлаживаемый процесс. Так что в Image File Execution Options есть одно исключение. Начнём с того, что в пользовательском режиме есть всего два метода запуска процессов: CreateProcess и ShellExecuteEx. Да и то, второй, в конце концов, тоже вызывает CreateProcess, но об этом позже. Так вот, исключение же это заключается в том, что запуски процессов с флагом DEBUG_PROCESS не перехватывается. Это и решает проблему с отладчиками, но, в моём случае, это также означает, что на IFEO нельзя полагаться полностью. И тем не менее, я не знаю ни одной программы (кроме отладчиков, разумеется), которые пользовались бы этим флагом. Нельзя просто поставить этот флаг и наслаждаться: понадобится дополнительный код, чтобы всё заработало.

    Регистрация файла в IFEO

    Если вы проследите с помощью Process Monitor’а за тем, что происходит при вызове функции CreateProcess — вы, вероятно, найдёте много чего интересного. Помимо коррекции ошибок в имени файла, о которой мы поговорим чуть позже, вы обнаружите обращение в реестр за настройками IFEO. Как это выглядит:

    Если этот ключ будет найден, то также будет проверено наличие определённых полей, в том числе строкового поля Debugger. Оно-то нам и нужно. В нём хранится путь к отладчику, или, в нашем случае, к программе, запускаемой вместо данной.

    Стоит заметить, что здесь есть свои ограничения:

    • Проверяется исключительно имя исполняемого файла. Это значит — никаких масок. Нельзя просто взять, и перехватить вызов *.exe.
    • Действие для всех программ с одинаковым именем исполняемого файла будет одинаковым. До тех пор, пока мы не воспользуемся ключом UseFilter. Тогда, да, можно назначать конкретный отладчик для конкретного исполняемого файла, основываясь на его полном пути. Опять же — без масок.

    Для использования фильтрации в ветке IFEO\YourExecutable.exe надо создать DWORD поле UseFilter c ненулевым значением. В этом случае, при попытке создания процесса, он обойдёт все под-ключи в данной ветке, и в каждом из них будет сверять значение строкового поля FilterFullPath с полным путём к исполняемому файлу. При совпадении будет запущен найденный в поле Debugger исполняемый файл. Если совпадений не будет найдено — будет запущен Debugger по умолчанию (то есть тот, что использовался бы без всяких фильтров).

    Замечание: любой под-ключ без поля FilterFullPath трактуется как совпадение, и все настройки берутся с него и имеют приоритет над теми, что были бы без фильтра.

    Назначение стандартных действий

    Изначальный смысл этой затеи для меня был в том, что, узнав про IFEO, я решил написать программу, которая предлагала бы пользователю набор типичных действий (реализованных в виде маленьких утилит), назначаемых для других программ. Это было ещё до того, как я решил написать статью, осознав, сколько тут занятных вещей можно рассказать. А потому, используем эти утилиты в качестве наглядного и практического примера для повествования. Итак, на что хватило моего воображения:

    • Ask.exe — оповещает пользователя о попытке запуска и спрашивает разрешения;
    • Deny.exe — отказывает в запуске. При этом может как оповещать пользователя, так и делать это молча. Очень удобно, если надо запретить винде запускать какую-нибудь телеметрию
    • Elevate.exe — всегда запрашивает UAC для повышения прав до Администратора;
    • Drop.exe — понижает привилегии процесса. Это просто венец творения. По смыслу аналогичен утилитам DropMyRights и PsExec с флагом -ℓ. Но в сочетании с IFEO — гораздо эффективнее;
    • PowerRequest.exe — не даёт компьютеру заснуть / погасить экран до тех пор, пока программа не завершится.

    Для регистрации этих действий было написано две версии представленной на скриншоте программы: GUI и консольная. Здесь и рассказывать не о чем. Просто читаем/пишем в реестр.

    Как можно догадаться из заголовка, самое интересное — это как подменить чужую программу во время исполнения, и при этом ничего не сломать. Но сперва несколько очевидных, хотя и необходимых вещей. Если уж и запускать перехваченный процесс, то надо как можно лучше воспроизвести те же условия, при которых запустили нас самих. А это значит:

    • Точно такая-же структура STARTUPINFO, установка флага bInheritHandles, и такая же рабочая директория;
    • Ожидание завершения запускаемого процесса для получения его кода возврата и передачи по цепочке;
    • И… ещё один трюк, о котором я расскажу чуть позже.

    Хорошо. А как мы узнаем, что запускать-то?

    Оценим возможности

    Пусть в IFEO в качестве отладчика для программы A.exe прописано следующее:

    Если кто-то попытается создать процесс «C:\Folder\A.exe» с параметром -a то будет создан процесс B.exe cо следующей командной строкой:

    Поскольку мы сами пишем код для B.exe — мы без проблем разберём командную строку на составляющие части и вполне можем запустить A.exe с теми же параметрами, с каким его хотел запустить этот безымянный кто-то. Здесь-то у нас и появляется полноценная свобода. Хотим — запускаем с другими привилегиями, захотим — сможем и передаваемые параметры поменять.

    Любопытно во всём этом вот что: как, по-вашему, реагирует на столь вольное обращение контроль учётных записей? Правильный ответ: а никак. Если в проводнике выбрать «Запуск от имени Администратора» на файле A.exe, то в сообщении UAC будет показываться информация именно о файле A.exe, и именно его цифровая подпись будет определять цвет окна. А то, что вместо него будет запущен B.exe — это дело десятое. Нет, здесь нет особых проблем с безопасностью: запись в IFEO сама требует административных привилегий. Для нас это значит нечто иное: записав наши утилиты в IFEO мы вообще не будем смущать пользователя неверными сообщениями контроля учётных записей. Ведь с его точки зрения — так оно и должно выглядеть.

    Как обычно запускают процессы

    С появлением контроля учётных записей в Windows Vista запуск процессов стал более сложным. Дело в том, что теперь для запуска некоторых программ вызов CreateProcess может оказаться неудачным по причине недостатка прав. В этом случае GetLastError возвращает ERROR_ELEVATION_REQUIRED . На такой случай в Windows встроено даже специальное исправление проблем с совместимостью, хотя я так и не заметил, чтобы оно хоть что-то исправляло. Современные же программы в ответ на эту ошибку должны вызывать ShellExecuteEx с действием «runas» для запроса повышения привилегий. Это значит, что типичный код создания процесса теперь выглядит так:

    Поскольку наши утилиты должны работать всегда, они не будут требовать повышенных привилегий для запуска, а значит тот процесс, который попытается запустить A.exe (и вместо него запустит наш B.exe) никогда не получит ERROR_ELEVATION_REQUIRED. Вроде бы: и ладно, ничего страшного, мы и сами cможем запросить повышение прав для него, если потребуется. А теперь представим себе, что так и произошло. Вот запустил кто-то A.exe, вместо него запустились мы, а поскольку A.exe требователен к привилегиям — мы не можем запустить его CreateProcess’ом, а потому должны использовать ShellExecuteEx. Уже догадались? ShellExecuteEx же всегда перехватывается IFEO, там нет флага DEBUG_PROCESS, который мог бы нас спасти. В результате мы снова запустим самого себя. Правда, на этот раз у нас уже хватит привилегий использовать CreateProcess для запуска A.exe. И всё это без каких-либо видимых следов со стороны UAC! Можно мозг сломать, не правда ли? Я и сам не до конца привык к концепции «почти всеобъемлющего перехвата своих же действий».

    По этой причине в утилите Elevate.exe нельзя обойтись одним лишь ShellExecuteEx’ом — мы просто не сможем обойти IFEO. В случае же Ask.exe это добавляет ещё одну проблему. Вот спросили мы пользователя, подтвердил он запуск. А затем ShellExecuteEx’ом в сочетании с IFEO мы запустили снова себя. Что, опять спрашивать будем? Пришлось добавить подавление вторичного вопроса. А ведь это невозможно сделать простым дописыванием специального параметра: куда бы мы его не дописали, мы же всё равно собираемся запускать A.exe, так что он будет неотличим от его собственных параметров. Это весьма неплохое упражнение для ума — попытаться заранее предугадать все эти сложности.

    Отличие CreateProcess от ShellExecuteEx

    Вы читали документацию на CreateProcess? Там есть один момент, который многие упускают, потенциально создавая некоторую уязвимость в безопасности своих приложений. Это же относится и к устаревшей функции WinExec, являющейся обёрткой над CreateProcess’ом. Два первых параметра функции определяют, что и с какими аргументами будет запускаться. Это lpApplicationName и lpCommandLine. Вот перевод куска текста из MSDN:

    Параметр lpApplicationName может иметь значение NULL. Тогда имя исполняемого файла должно быть первой отделенной пробелом подстрокой в lpCommandLine. Если вы используете длинные имена файлов, которые могут содержать пробелы, воспользуйтесь кавычками для указания, где заканчивается имя файла и начинаются аргументы. В противном случае имя файла является двусмысленным.

    Почему люди продолжают забывать ставить кавычки? Так ведь и так работает — в CreateProcess встроен механизм коррекции ошибок. Установим lpApplicationName в NULL и попытаемся запустить программу передав в lpCommandLine имя её исполняемого файла: C:\Program Files\Sub Dir\Program Name. Что будет делать CreateProcess? Искать в строке то, что можно запустить, пока не найдёт, да ещё и подставляя расширение файла:

    C:\Program Files\Sub Dir\Program Name
    C:\Program.exe Files\Sub Dir\Program Name
    C:\Program Files\Sub Dir\Program Name
    C:\Program Files\Sub.exe Dir\Program Name
    C:\Program Files\Sub Dir\Program Name
    C:\Program Files\Sub Dir\Program.exe Name
    C:\Program Files\Sub Dir\Program Name

    Если хотите поэкспериментировать — создайте файл C:\Program.exe у себя на компьютере и посмотрите, попадётся ли какая из программ на это. У себя я поймал на этом Process Hacker (fix уже есть в ночной сборке), Punto Switcher (надо бы мне им тоже написать), и один из плагинов к Far Manager. Кстати, проводник Windows тоже знает об этой проблеме:


    Возвращаясь к вопросу: а что это значит для нас? Из IFEO мы получаем именно lpCommandLine. Да, мы можем просто передать его в CreateProcess — если там и была такая проблема — она останется, здесь мы бессильны. Но также нам может понадобиться передавать его в ShellExecuteEx, а там, упс, такой коррекции ошибок нету. Там отдельно lpFile и отдельно lpParameters. Придётся самостоятельно разбирать строку по пробелам и искать имя первого существующего исполняемого файла также, как это делает CreateProcess. Супер.

    А сейчас мы немного отвлечёмся и поговорим о других любопытных вещах. Я собираюсь рассказать о том, как работает одна из утилит.

    Понижая привилегии

    Принцип «Запускать любую программу с минимальными необходимыми ей правами» здесь, вероятно, знаком всем. Но вот что делать, если автор понравившейся вам программы об этом принципе не слышал — вопрос уже более сложный. Иногда в таких ситуациях спасают механизмы устранения проблем совместимости:

    Это заметно снижает число случаев, когда CreateProcess не сможет запустить программу и вернёт ERROR_ELEVATION_REQUIRED. Но это работает не всегда. Например, над этим методом имеют приоритет *.sdb патчи .

    Ещё есть специальные утилиты, которые умеют запускать процессы с Elevated-токеном (т.е. как-бы от имени администратора), но при этом вырезают все соответствующие привилегии . Это DropMyRights, написанная Майклом Ховардом из Microsoft, и PsExec из широко известного комплекта Microsoft Sysinternals, запускаемая с ключом -ℓ. Как ни странно, но эти утилиты добиваются своего разными путями.

    Мне больше по душе оказался метод, используемый в DropMyRights. Да и исходники у него открытые. Используется там Windows Safer API, который позволяет буквально в несколько строк кода вычислять токен с урезанными привилегиями, который можно сразу использовать в CreateProcessAsUser. Эх, хотел бы я, чтобы все писатели инсталляторов знали, как это просто, и не запускали программы с максимальными правами по окончании установки…

    А теперь объединим оба упомянутых подхода, совместив их с IFEO. В результате получаем автоматическое понижение прав и минимум запросов к контролю учётных записей. Не знаю как вам, но мне это очень нравиться. А поскольку повышение прав путём вызова ShellExecuteEx’а перехватывается всегда, то, насколько я понимаю, у программы, находящейся под действием нашей утилиты, нет шансов самостоятельно выбраться до тех пор, пока пользователь не подтвердит UAC для другого исполняемого файла, находящегося в кооперации с ограничиваемым. Но для действительно серьёзных случаев — пользуйтесь песочницами вроде Sandboxie. Или виртуальными машинами, если на то пошло.

    Обход IFEO

    Возвратимся к основной теме. Я вот всё говорю, мол, запустим процесс с флагом DEBUG_PROCESS и будет нам счастье, на свои грабли сами не попадёмся. Но для того, чтобы всё заработало, необходимо сделать ещё кое-что. С этим флагом мы запустим процесс под отладкой, а значит, нам будут приходить отладочные события. Если их не обработать — процесс так и останется висеть неподвижно. Для их обработки понадобятся всего две функции: WaitForDebugEvent и ContinueDebugEvent.

    Однако не все программы нормально относятся к отладчикам, верно? Не то, чтобы я специально делал исключение ради вирусов, но отсоединение отладчика действительно будет лучшей идеей. Мы ведь здесь не ради написания отладчика собрались. А вот тут, неожиданно, наступает сложность: это действие, вероятно, относится к недокументированным возможностям, ибо на MSDN я его не нашёл. А потому будем пользоваться документацией Process Hacker’a. Итак, нам потребуется функция NtRemoveProcessDebug из ntdll.dll. Ей же нужен DebugObjectHandle, который можно получить с помощью NtQueryInformationProcess, запросив у неё ProcessDebugObjectHandle = 30 в качестве ProcessInformationClass. Вот и всё. Хотя… Лучше не делайте так с чужими процессами: их отладчик может в этот момент ждать отладочное событие, а для них самих может быть включён kill-on-close.

    UPD.
    Я был неправ, и потому пошёл более сложным путём. Документированная функция для этого есть, это DebugActiveProcessStop. Вместе с ней рекомендуется использовать DebugSetProcessKillOnExit.

    Стоит заметить, что здесь имеется особенность, связанная с разрядностью операционной системы: 32-битный процесс не может запускать 64-битный процесс с флагом DEBUG_PROCESS. Зато 64-битный может запускать кого угодно.

    Ах да, вы ещё помните? Выше я обещал рассказать про ещё один трюк, позволяющий добиться чуть лучшей совместимости при последующем запуске перехватываемых процессов.

    Магия контроля учётных записей

    Я уже говорил, что контроль учётных записей вообще никак не реагирует на происходящее. И если вы ещё тогда задались вопросом «А почему?», то вам точно сюда.

    Как работает UAC и каким образом позволяет повышать привилегии? Хорошо ответ на этот вопрос дан в англоязычной статье Vista UAC: The Definitive Guide. Если коротко: ShellExecuteEx, пробиваясь через дебри вызовов COM, обращается к сервису AppInfo. Тот создаёт процесс consent.exe, который и выводит то окно с предложением подтвердить запуск, и, при необходимости, ввести пароль. Само создание процесса, естественно, происходит уже после всего этого. И используется там самый обычный CreateProcessAsUser. Именно на этом этапе срабатывает IFEO, и, перехватывая создание процесса, запускает нас. Именно поэтому контроль учётных записей ничего об этом не знает.

    Самые догадливые уже должны были задаться вопросом: если процесс создаётся кем-то другим, то как получается, что его родительским процессом всё равно оказывается инициатор запроса?

    • Дескриптор процесса, которого мы делаем новым родителем, должен иметь права PROCESS_CREATE_PROCESS и жить вплоть до вызова DeleteProcThreadAttributeList;
    • Следует установить значение поля cb вложенного STARTUPINFO в SizeOf(STARTUPINFOEX).

    Часто ли вы при анализе, например, подозрительной активности на компьютере исходите из того, кто какой процесс запустил? О, теперь вы точно не станете так делать.

    Тут есть некоторые любопытные особенности. Мне вспоминается шутливая фраза из одного мультика, звучит она примерно так: «да что там, даже при моём рождении не присутствовал ни один из моих родителей…». Как ни странно, но здесь возможно нечто подобное. Если между вызовом OpenProcess’a и самим созданием процесса назначаемый родитель будет завершён — не беда. Просто дочерний процесс будет «как бы» создан родителем после своего завершения. Почему бы и нет.

    Всё бы хорошо, но этот трюк не сработает с ShellExecuteEx’ом — там всё просто: кто вызвал, тот и родитель. Однако поскольку у нас включён IFEO — финальное слово всегда остаётся за CreateProcess’ом: без него мы не выберемся из перехвата. Так что, пройдясь по цепочке процессов можно найти того, кто всё это затеял изначально, и назначить его родителем. Возможно, результат теперь не так красиво выглядит в Process Explorer’е и Process Hacker’е, зато с точки зрения чужого процесса всё будет в порядке. Ну, за исключением, разве что, «внебрачных» детей, которые появились при запуске и ожидают кода возврата перехваченного процесса. Тут уж ничего не поделать.

    На картинке схема того, как я из Far.exe запускаю nsx.exe, на который назначено действие Ask.exe, и которому для запуска требуются повышенные привилегии.

    Если честно, я думал, что идея с подменой родительского процесса — просто ещё одна прикольная возможность, не больше. Однако она действительно исправила несколько проблем. Так, тот же Far Manager может создавать второй процесс для выполнения более привилегированных действий, а при наличии перехвата эта функциональность ломалась. Подмена же родителя вновь починила это.

    Чего делать не стоит

    Я довольно долго не решался проверить это у себя на компьютере, ибо разворачивать виртуальную машину было лень: что будет, если в качестве запускаемой при перехвате программы установить её же саму? К счастью, ничего страшного. Но дальше я решил проявить благоразумие и не стал у себя на компьютере проверять гипотезы вроде «ой, а если попробовать создать цикл ABA…». Также, думаю, не стоит пытался устанавливать перехват важных системных процессов (я встроил их список в утилиту регистрации с подтверждением действия). Хотя, мне и любопытно, что произойдёт. Ибо всё, что я запускал от имени SYSTEM перехватывалось. Так что я добавил подавление всех диалоговых окон в нулевой сессии, ну, чтобы не зависеть в подобных случаях от сервиса UI0Detect.

    И последнее: я не рекомендовал бы ставить действия на программы, создающие много однотипных процессов. Одно дело — пара дополнительных процессов, другое — целые полчища. Также не стоит трогать группы процессов, активно взаимодействующих друг с другом. И тех, что используют сложные дополнительные защитные механизмы. В общем, вы поняли: на chrome.exe лучше ничего не назначать. Если обнаружите проблемы совместимости: пишите мне или сразу в исходники на Гитхаб. В программу встроен список подобных проблем для предостережения пользователей.

    Нерешённые проблемы

    В Windows сосуществуют сразу несколько подсистем для исполняемых файлов. Три самых известных это Native (драйвера), Windows CUI (консольные приложения), и Windows GUI (графические приложения). Поскольку драйвера нас сейчас совершенно не интересуют — нам надо разобраться в различиях между вторым и третьим вариантом, и выбрать как компилировать наши утилиты. Вот здесь и начинаются проблемы — как мы хотим: красиво или удобно? Различия между этими подсистемами не столь велики, поскольку консольное приложение может создавать окна, а графическое — консоль. Да и то, GUI даже не всегда означает взаимодействие с пользователем: фоновые сервисы запускаются в svchost.exe, который работает в подсистеме Windows GUI.

    Для нас существенным отличием являются начальные условия при запуске. За исключением особых случаев, приложения из консольной подсистемы при старте уже имеют консоль, притом видимую. Даже если первым делом при старте программы мы её спрячем, то она успеет появиться перед исчезновением. Хотим ли мы пугать неискушённого пользователя такими вещами? Наверное, нет. Я надеялся, что в исправлениях совместимости из Application Compatibility Toolkit найдётся что-то, что сможет исправить эту проблему, но, судя по всему, там нет функции «спрятать консоль по умолчанию». А потому, для себя я выбрал графическую подсистему. Всё бы хорошо, но у неё есть заметный минус. Если вы активно пользуетесь приложениями из консольной подсистемы, то сразу обратите внимание: все консольные программы, на которые назначено какое-либо стандартное действие, создают новую консоль, а не пользуются имеющейся. Это не критично, хотя и не всегда приятно. Если вам вдруг захочется сделать другой выбор в этом вопросе, а Delphi под рукой нет — просто пропатчите нужные файлы, отвечающие за стандартные действия: замените 02 на 03 по смещению 0x15C.

    Заключение

    Я надеюсь, что этот «экскурс в 1001 любопытный факт о работе Windows» оказался действительно любопытным и даже полезным. Я знаю, не все здесь жалуют Delphi, но уж с почти чистым WinApi, холиваров, надеюсь, не возникнет.

    Код получившихся программ и бинарники доступны на Гитхабе. Программа тестировалась на Windows 7 и выше, про Windows Vista — без понятия. Ниже — однозначно нет.

    P. S. Если у вас сложилось мнение, что в каком-то вопросе я основательно заблуждаюсь — не кидайтесь помидорами сразу, давайте сперва разрешим это недоразумение, и исправим неточности в статье. Спасибо.

    Почему функция createprocess не запускает * lnk файлы?

    На данный момент это основная функция запуска процесса, все остальные функции такие как WinExec и LoadModule оставлены для совместимости и используют CreateProcess.

    lpApplicationName. Указатель на строку которая заканчивается нулем и содержит имя выполняемого модуля. Этот параметр может быть NULL тогда имя модуля должно быть в lpCommandLine самым первым элементом. Если операционная система NT и модуль 16 разрядов этот параметр NULL обязательно. имя модуля может быть абсолютным или относительным. Если относительное то будет использована информация из lpCurrentDirectory или текущий каталог.

    lpCommandLine.Командная строка. Здесь передаются параметры. Она может быть NULL. Здесь можно указать и путь и имя модуля.

    lpProcessAttributes.Здесь определяются атрибуты защиты для нового приложения. Если указать NULL то система сделает это по умолчанию.

    lpThreadAttributes. Здесь определяются атрибуты защиты для первого потока созданного приложением. NULL опять приводит к установке по умолчанию.

    bInheritHandles. Флаг наследования от процесса производящего запуск. Здесь наследуются дескрипторы. Унаследованные дескрипторы имеют те же значения и права доступа, что и оригиналы.

    dwCreationFlags. Флаг способа создание процесса и его приоритет.

    lpEnvironment. Указывает на блок среды. Если NULL, то будет использован блок среды родительского процесса. Блок среды это список переменных имя=значение в виде строк с нулевым окончанием.

    lpCurrentDirectory.Указывает текущий диск и каталог. Если NULL то будет использован диск и каталог процесса родителя.

    lpStartupInfo Используется для настройки свойств процесса, например расположения окон и заголовок. Структура должна быть правильно инициализирована

    lpProcessInformation Структура PROCESS_INFORMATION с информацией о процессе. Будет заполнена Windows.

    В результате выполнение функций вернет FALSE или TRUE. В случае успеха TRUE. Пример использования.

    Как запустить в режиме отладки программы, которая вызывает связь с CreateProcess

    Я использую CreateProcess для вызова cl и link скомпилировать и скомпоновать другую программу C ++ ( TestProg.cxx ) в DLL. Я нашел правильную компиляции и сцепление варианты:

    Я призываю CreateProcess с:

    Запуск приложения из VS инструментов строки, он работает и длл создается.

    Но запустить его из отладчика VS я получаю следующее сообщение об ошибке LINK:

    или когда ссылка ЛИЭС удаляется из вариантов ссылок я получаю следующее сообщение об ошибке:

    Что я делаю не так?

    ИМО, кажется, что-то не хватает в настройках линии для запуска в режиме отладки, или путь поиска режима отладки не хватает некоторых каталогов. Я не знаю, как исправить любого из этих случаев. Я гугл это в последний день 1/2, но не нашел его. Использование Windows API является новым для меня.

    Вы должны указать расположение .lib файлов. В противном случае link не знает , где найти ваши .lib файлы. Вы можете сделать это либо в качестве аргументов командной строки , link ( /LIBPATH:dir ) или путем установки LIB переменной окружения.

    Где именно эти .lib файлы находятся в зависимости от установки. Откройте командную строку Visual Studio и введите set LIB выяснить , что является подходящим местом для установки.

    Сам я бы пытаюсь избежать необходимости использовать CreateProcess для автоматизации сборки , так как он имеет довольно болезненный интерфейс. Я смотрел бы на более высокий уровень языка сценариев. Я также предпочел бы использовать vcbuild вместо вызова cl и link вручную. Но , возможно , есть некоторые веские причины , почему вы должны это сделать из кода C ++ , что я не в курсе.

    Похоже, вы пропустили переменные окружения, которые вы получаете при запуске из командной строки VS. Частично это также библиотека поиска пути.

    См C:\Program Files\Microsoft Visual Studio XX\Common7\vsvars32.bat ( в зависимости от версии VS и установки пути, который указан в VS100COMNTOOLS (или VS90COMNTOOLS , или которые когда — либо) переменной окружения.

    Почему вы делаете это в любом случае? Вы можете использовать MSBuild для настройки проекта и построить его. Вы можете позвонить MSBuild из кода, в то время как проект уже настроен для Вас.

    Илон Маск рекомендует:  LongMonthNames - Переменная Delphi
    Понравилась статья? Поделиться с друзьями:
    Кодинг, CSS и SQL