Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Рихтер Дж., Назар К. - Windows via C C++. Программирование на языке Visual C++ - 2009

.pdf
Скачиваний:
6265
Добавлен:
13.08.2013
Размер:
31.38 Mб
Скачать

132 Часть II. Приступаем к работе

Повышение привилегий процесса вручную

Прочитав подробное описание функции CreateProcess (см. выше), вы не могли не заметить отсутствия специальных флагов и параметров для повышения привилегий. Вместо них используется функция ShellExecuteEx:

BOOL ShellExecuteEx(LPSHELLEXECUTEINFO pExednfo);

typedef struct _SHELLEXECUTEINFO { DWORD cbSize;

ULONG fMask; HWND hwnd; PCTSTR lpVerb; PCTSTR lpFile;

PCTSTR lpParameters; PCTSTR lpDirectory; int nShow; HINSTANCE hInstApp; PVOID lpIDList; PCTSTR lpClass; HKEY hkeyClass; DWORD dwHotKey; union {

HANDLE hIcon; HANDLE hMonitor;

} DUMMYUNIONNAME;

HANDLE hProcess;

}SHELLEXECUTEINFO, *LPSHELLEXECUTEINFO;

Уструктуры SHELLEXECUTEINFO интересны лишь поля lpVerb, которое необходимо установить в «runas», и lpFile, куда необходимо занести путь к исполняемому файлу, который необходимо запустить с повышенными привилегиями (см. следующий пример кода).

// инициализируем структуру

SHELLEXECUTEINFO sei = { sizeof(SHELLEXECUTEINFO) };

//запрашиваем повышение привилегий sei.lpVerb = TEXT("runes");

//Создаем окно командной строки, в котором можно будет

//запускать и другие приложения с повышенными привилегиями. sei.lpFile | TEXTCcmd.exe");

//не забудьте этот параметр, иначе окно останется скрытым sei.nShow | SW_SH0WN0RMAL;

if (!ShellExecuteEx(&sei)) {

DWORD dwStatus I GetLastError();

Глава 4. Процессы.docx 133

if (dwStatus Щ ERROR_CANCELLED) {

// пользователь отказал в повышении привилегий } else

if (dwStatus == ERR0R_FILE_N0T_F0UND) {

//He найден файл, заданный lpFile,

//выводим сообщение об ошибке. }

}

}

Если пользователь отказал в повышении привилегий, ShellExecuteEx возвращает FALSE, такую ситуацию идентифицирует значение ERROR_CANCELLED в GetLastError.

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

кончится неудачей, а GetLastError вернет ERROR_ELEVATION_REQUIRED.

Подводя итог, можно сказать что «добропорядочные» приложения для Windows Vista должны «уметь» большую часть времени работать под учетной записью обычного пользователя, а на элементах интерфейса (кнопках, ссылках и т.п.), использование которых требует повышенных привилегий, должен отображаться соответствующий значок (см. программу-пример ниже). Поскольку административные задачи должны исполняться в отдельном процессе либо СОМ-сервером в другом процессе, следует сосредоточить все операции, требующие администраторских привилегий, во втором приложении и повышать его привилегии вызовом ShellExecuteEx с передачей «runas» в IpVerb и привилегированной операции — в параметре командной строки нового процесса (в поле структуры SHELLEXECUTEINFO).

Совет Отладка процессов, использующих повышение привилегий, может доставить немало неприятностей. Облегчить вам жизнь сможет «золотое правило»: запускайте Visual Studio с тем же уровнем привилегий, которой должен унаследовать отлаживаемый процесс.

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

Если отлаживаемый процесс требует администраторских привилегий (например, если это записано в манифесте), экземпляр Visual Studio для

134 Часть II. Приступаем к работе

его отладки следует запускать от имени администратора. Иначе вы получи-

те сообщение об ошибке «the requested operation requires elevation» (для за-

прошенной операции необходимо повышение привилегий), а отлаживаемый процесс вовсе не запустится.

О текущем контексте привилегий

Вернемся к примеру с Task Manager, в окне которого отображался значок с щитом либо флажок, в зависимости от текущего уровня привилегий. В этом примере есть еще два вопроса, заслуживающих отдельного рассмотрения. Первый вопрос - как определить, работает ли приложение под учетной записью с администраторскими привилегиями? Второй, более важный вопрос -как узнать, повышены ли привилегии приложения до уровня администраторских либо оно продолжает работать с отфильтрованным маркером защиты?

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

BOOL GetProcessElevation(TOKEN_ELEVATION_TYPE* pElevationType, BOOL* pIsAdmin) {

HANDLE hToken = NULL; DWORD dwSize;

// получаем текущий маркер защиты процесса

if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) return(FALSE);

BOOL bResult = FALSE;

// определяем тип повышения привилегий

if (GetTokenInformation(hToken, TokenElevationType, pElevationType, sizeof(TOKEN_ELEVATION_TYPE), &dwSize)) {

// Создаем SID, соответствующий группе Administrators group BYTE adminSID[SECURITY_MAX_SID_SIZE];

dwSize = sizeof(adminSID); CreateWellKnownSid(WinBuiltinAdministratorsSid, NULL, &adminSID,

&dwSize);

if (*pElevationType == TokenElevationTypeLimited) {

//Получаем описатель связанного маркера

//(если приложение работает под локальной учетной

//записью, маркер уже есть).

HANDLE hUnfilteredToken = NULL;

GetTokenlnformation(hToken, TokenLinkedToken, (VOID*)

&hUnfilteredToken, sizeof(HANDLE), AdwSize);

Глава 4. Процессы.docx 135

// проверяем SID администратора в исходном маркере

if (CheckTokenMembership(hUnfilteredToken, iadminSID, pIsAdmin)) { bResult = TRUE;

}

// не забывайте закрывать неотфильтрованный маркер!

CloseHandle(hUnfilteredToken); } else {

*pIsAdmin = IsUserAnAdmin(); bResult = TRUE;

}

// не забывайте закрывать описатель маркера процесса!

CloseHandle(hToken);

return(bResult);

}

Заметьте, что GetTokenInformation вызывается с передачей маркера, связанного с процессом, и параметра TokenElevationType, представляющего тип повышения привилегий. Этот параметр принимает значения перечислимого TOKEN_ELEVATION_TYPE (см. таблицу 4-9).

Табл. 4-9. Значения TOKEN_ELEVATION_TYPE

Значение

Описание

TokenElevationTypeDefault

Процесс работает с привилегиями пользователя по

 

умолчанию либо отключен UAC

TokenElevationTypeFull

Привилегии успешно повышены, процессу назначен

 

неотфильтрованный маркер защиты

TokenElevationTypeLimited

Процесс работает с ограниченными привилегиями,

 

соответствующими неотфильтрованному маркеру

 

защиты

Эти значения позволят выяснить, назначен ли процессу отфильтрованный маркер защиты или нет. Теперь можно определить, обладает ли текущий пользователь полномочиями администратора. Если текущий маркер защиты не отфильтрован, лучше всего вызвать для этого функцию IsUserAnAdmin. Если же используется отфильтрованный маркер, следует прежде получить исходный (неотфильтрованный) маркер, передав функции GetTokenInformation параметр TokenLinkedToken, а затем проверить наличие SID администратора в полученном маркере (с

помощью CreateWellKnownSid и CheckTokenMembership).

136 Часть II. Приступаем к работе

Так, приложение-пример Process Information (см. следующий раздел) использует показанную выше вспомогательную функцию в коде, обрабатывающем сообщение WM_INITDIALOG, чтобы вывести в заголовке окна сведения о повышении привилегий, а также показать либо убрать значок в виде щита.

Совет. Для того чтобы отобразить на кнопке или скрыть значок в виде щита, используется макрос Button_SetElevationRequiredState (определенный в CommCtrl.h). Напрямую получить этот значок можно, вызвав SHGetStocklconInfo с параметром SIID_SHIELD (и функция, и ее параметр объявлены в shellapi.h). Об остальных элементах интерфейса, поддерживающих вывод данного значка, см. в документации MSDN по ссылке http://msdn2.microsoft.com/en-us/library/aa905330.aspx.

Перечисление процессов, выполняемых в системе

Многие разработчики программного обеспечения пытаются создавать инструментальные средства или утилиты для Windows, требующие перечисления процессов, выполняемых в системе. Изначально в Windows API не было функций, которые позволяли бы это делать. Однако в Windows NT ведется постоянно обновляемая база данных Performance Data. В ней содержится чуть ли не тонна информации, доступной через функции реестра вроде RegQueryValueEx, для которой надо указать корневой раздел HKEY_PERFORMANCE_DATA. Мало кто из программистов знает об этой базе данных, и причины тому кроются, на мой взгляд, в следующем.

Для нее не предусмотрено никаких специфических функций; нужно использовать обычные функции реестра.

Ее нет в Windows 95 и Windows 98.

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

Чтобы упростить работу с этой базой данных, Майкрософт создала набор

функций под общим названием Performance Data Helper (содержащийся в PDH.dll). Если вас интересует более подробная информация о библиотеке PDH.dll, ищите раздел по функциям Performance Data Helper в документации Platform SDK.

Как я уже упоминал, в Windows 95 и Windows 98 такой базы данных нет. Вместо них предусмотрен набор функций, позволяющих перечислять процессы. Они включены в ToolHelp API. За информацией о них я вновь отсылаю вас к документации Platform SDK — ищите разделы по функциям Process32First и Process32Next.

Но самое смешное, что разработчики Windows NT, которым ToolHelpфункции явно не нравятся, не включили их в Windows NT. Для перечисления процессов они создали свой набор функций под общим названием Process Status (содержащийся в PSAPI.dll). Так что ищите в документации Platform SDK раздел по функции EnumProcesses.

Глава 4. Процессы.docx 137

Майкрософт, которая до сих пор, похоже, старалась усложнить жизнь разработчикам инструментальных средств и утилит, все же включила ToolHelpфункции в Windows 2000. Наконец-то и эти разработчики смогут унифицировать свой код хотя бы для Windows 95, Windows 98 и т. д. — до Windows Vista!

Программа-пример ProcessInfo

Эта программа, «04 ProcessInfo.exe», демонстрирует, как создать очень полезную утилиту на основе ToolHelp-функций. Файлы исходного кода и ресурсов программы находятся в каталоге 04-ProcessInfo внутри архива, доступного на сайте поддержки этой книги. После запуска ProcessInfo открывается окно, показанное на рис:4-4.

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

При просмотре списка процессов становится доступен элемент меню VMMap. (Он отключается, когда вы переключаетесь на просмотр информации о модулях.) Выбрав элемент меню VMMap, вы запускаете программу-пример VMMap (см. главу 14). Эта программа «проходит» по адресному пространству выбранного процесса.

В информацию о модулях входит список всех модулей (ЕХЕ- и DLLфайлов), спроецированных на адресное пространство текущего процесса.

138 Часть II. Приступаем к работе

Фиксированным модулем (fixed module) считается тот, который был неявно загружен при инициализации процесса. Для явно загруженных DLL показываются счетчики числа пользователей этих DLL. Во втором столбце выводится базовый адрес памяти, на который спроецирован модуль. Если модуль размещен не по заданному для него базовому адресу, в скобках появляется и этот адрес. В третьем столбце сообщается размер модуля в байтах, а в последнем - полное (вместе с путем) имя файла этого модуля. И, наконец, внизу показывается информация о потоках, выполняемых в данный момент в контексте текущего процесса. При этом отображается идентификатор потока (thread ID, TID) и его приоритет.

В дополнение к информации о процессах вы можете выбрать элемент меню Modules. Это заставит ProcessInfo перечислить все модули, загруженные в системе, и поместить их имена в верхний раскрывающийся список. Далее ProcessInfo выбирает первый модуль и выводит информацию о нем (рис. 4-5).

Рис. 4-5. ProcessInfo перечисляет все процессы, в адресные пространства которых загружен модуль Psapi.dll

В этом режиме утилита ProcessInfo позволяет легко определить, в каких процессах задействован данный модуль. Как видите, полное имя модуля появляется в верхней части текстового поля, а в разделе Process Information перечисляются все процессы, содержащие этот модуль. Там же показываются идентификаторы и имена процессов, в которые загружен модуль, и его базовые адреса в этих процессах.

Всю эту информацию утилита ProcessInfo получает в основном от различных ToolHelp-функций. Чтобы чуточку упростить работу с ToolHelp-функциями, я создал С++-класс CToolhelp (содержащийся в файле Toolhelp.h). Он инкапсулирует все, что связано с получением «моментального снимка» состояния системы, и немного облегчает вызов других ToolHelp-функций.

Особый интерес представляет функция GetModulePreferredBaseAddr в файле

Processlnfo.cpp:

Глава 4. Процессы.docx 139

PVOID GetModulePreferredBaseAddr(

DWORD dwProcessId,

PVOID pvModuleRemote);

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

Получить командную строку процесса невозможно. Как сказано в статье из

MSDN Magazine «Escape from DLL Hell with Custom Debugging and Instrumentation Tools and Utilities, Part 2», командную строку удаленного процесса можно извлечь из блока его переменных окружения (Process Environment Block, РЕВ). Однако здесь появились некоторые отличия от Windows XP, о которых необходимо рассказать отдельно. Во-первых, изменились команды WinDbg (его можно загрузить с http://www.microsoft.com/whdc/devtools/debugging/default.mspx) для получения сведения о структуре РЕВ. Вместо команды «strct», реализованной в расширении kdex2x86, используется «dt». Например, команда «dt nt! РЕВ» сгенерирует определение РЕВ следующего вида:

+0x000 InheritedAddressSpace

: UChar

+0x001

ReadImageFileExecOptions

: UChar

+0x002

BeingDebugged

: UChar

+0x003

BitField

: UChar

+0x003

ImageUsesLargePages

: Pos 0, 1 Bit

+0x003

IsProtectedProcess

: Pos 1, 1 Bit

+0x003

IsLegacyProcess

: Pos 2, 1 Bit

+0x003

IsImageDynamicallyRelocated

: Pos 3, 1 Bit

+0x003

SpareBits

: Pos 4, 4 Bits

+0x004

Mutant

: Ptr32 Void

+0x008

ImageBaseAddress

: Ptr32 Void

+0x00c Ldr

: Ptr32 _PEB_LDR_DATA

+0x010

ProcessParameters

: Ptr32 _RTL_USER_PROCESS_PARAMETERS

+0x014

SubSystemData

: Ptr32 Void

+0x018

ProcessHeap

: Ptr32 Void

 

 

Определение структуры RTL_USER_PROCESS_PARAMETERS можно полу-

чить, отдав WinDbg команду «dt nt!_RTL_USER_PROCESS_PARAMETERS»:

140 Часть II. Приступаем к работе

+0x000 NaximumLength

: Uint4B

+0x004

Length

: Uint4B

+0x008

Flags

: Uint4B

+0x00c DebugFlags

: Uint4B

+0x010

ConsoleHandle

: Ptr32 Void

+0x014

ConsoleFlags

: Uint4B

+0x018

StandardInput

: Ptr32 Void

+Ox01c StandardOutput

: Ptr32 Void

+0x020

StandardError

: Ptr32 Void

+0x024

CurrentDirectory

: _CURDIR

+0x030

DllPath

: _UNICODE_STRING

+0x038

ImagePathName

: _UNICODE_STRING

+0x040

CotnmandLine

: _UNICODE_STRING

+0x048

Environment

: Ptr32 Void

А так определяются внутренние структуры для доступа к командной строке:

typedef struct

{

DWORD Filler[4];

DWORD InfoBlockAddress; } __РЕВ;

typedef struct

{

DWORD Filler[17];

DWORD wszCmdLineAddress; } __INF0BL0CK;

Во-вторых, в Windows Vista системные DLL загружаются в адресное пространство процесса по случайным адресам (см. главу 14). Поэтому вместо жесткого программирования адреса РЕВ как 0x7ffdf000 (именно так делалось в Windows XP) следует вызвать функцию NtQueryInformationProcess с параметром ProcessBasicInformation. Помните, что в полученной таким образом информации встречаются незадокументированные элементы, которые могут изменяться от версии к версии Windows.

И последний важный момент. Можно заметить, что в программе Process Information список процессов не отражает дополнительную информацию, такую как сведения о загруженных DLL. Например, процесс audiodg.exe (Windows Audio Device Graph Isolation) является защищенным (protected process). B Windows Vista

появился новый тип процессов, обеспечивающих более полную изоляцию приложений, поддерживающих DRM. В этой связи не удивительно, что у удаленных процессов нет прав на доступ к виртуальной памяти защищенных процессов. Поскольку этот механизм необходим для получения списка загруженных DLL, ToolHelp API не удается получить

Глава 4. Процессы.docx 141

данную информацию. Подробнее о защищенных процессах см. по ссылке http://www.microsoft.com/whdc/system/vista/process_Vista.mspx.

Есть и другая причина, по которой Process Information не удается получить сведения о работающих процессах. Если запустить эту программу с обычным уровнем привилегий, ей может быть отказано в доступе для чтения (и уж точно будет отказано в доступе для записи) к процессам с повышенным уровнем привилегий. Это упрощенная картина, в действительности же все может оказаться сложнее. Так, Windows Vista поддерживает новый механизм защиты под названи-

ем Windows Integrity Mechanism (ранее известный как Mandatory Integrity Control).

В дополнение к традиционным дескрипторам защиты и спискам управления доступом теперь защищенным ресурсам назначаются т.н. уровни целостности (integrity levels). Для них в системных списках управления доступом (system access control list, SACL) предусмотрен новый тип записи в ACL (ACL entry, АСЕ) — mandatory label Система считает, что защищенным объектам с такой АСЕ неявно назначен средний (medium) уровень целостности. У каждого процесса также есть уровень доверия (level of trust), основанный на связанном маркере защиты (см. таблицу 4-10).

Табл. 4-10. Уровни доверия

Значение Описание

Low На этом уровне работает Internet Explorer в режиме высокой безопасности (Protected Mode) для предотвращения модификации загруженным кодом других приложений и среды Windows

Medium По умолчанию приложения запускаются на этом уровне и работают с отфильтрованными маркерами защиты

High На этом уровне работают приложения с повышенными привилегиями

System На этом уровне работают только процессы, запущенные под учетными записями Local System и Local Service

Когда программа пытается обратиться к объекту ядра, система сравнивает уровень доверия вызывающего процесса и уровень целостности, назначенный объекту ядра. Если последний выше первого, процессу будут запрещены операции модификации и удаления. Заметьте, что это сравнение выполняется до проверки ACL. Таким образом, даже при наличии у процесса прав на доступ к ресурсу, ему может быть отказано в доступе, если уровень доверия процесса ниже, чем уровень целостности ресурса. Это особенно важно в случае приложений, исполняющих код, включая сценарии, загруженные из Интернета. В частности, Internet Explorer 7 в Windows Vista использует этот механизм и работает на уровне Low, запрещая тем самым загруженному из веба коду модифицировать состояние других приложений, по умолчанию работающих на уровне Medium.

Соседние файлы в предмете Программирование на C++