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

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

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

Глава 3. Объекты ядра.docx

37

va_end(argList);

}

/////////////////////////////////////////////////////////////////////

void Dlg_OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify)

{

switch (id) { case IDOK: case IDCANCEL:

//Пользователь щелкнул кнопку Exit

//либо нажал ESCAPE. EndDialog(hwnd, id); break;

}

}

/////////////////////////////////////////////////////////////////////

void CheckInstances()

{

// создаем дескриптор границы

g_hBoundary = CreateBoundaryOescriptor(g_szBoundary, 0);

//Создаем SID, соответствующий локальной группе Administrators BYTE localAdminSID[SECURITY_MAX_SID_SIZE];

PSID pLocalAdminSID = &localAdminSIO; DWORD cbSID = sizeof(localAdminSID); if (!CreateWellKnownSid(

WinBuiltinAdministratorsSid, NULL, pLocalAdminSID, &cbSID)) { AddText(TEXT("AddSIDToBoundaryDescriptor failed: Xu\r\n"),

GetLastErrorO);

return;

}

//Связываем полученный SID с дескриптором границы. В итоге

//только приложения, запущенные администратором, смогут

//получить доступ к объектам ядра из этого пространства имен, if (!AddSIDToBoundaryDescriptor(&g_hBoundary, pLocalAdminSID)) {

AddText(TEXT("AddSIDToBoundaryDescriptor failed: Xu\r\n"), GetLastError());

return;

}

//Создать пространство имен, доступное только локальному администратору

SECURITY_ATTRIВUTES sa;

38 Часть I Материалы для обязательного чтения

sa.nLength = sizeof(sa); sa.bInheritHandle = FALSE;

if (!ConvertStringSecurityDescriptorToSecurityDescriptor( TEXT("D:(A;;GA;;;BA)"),

SDDL_REVISION_1, &sa.lpSecurityDescriptor, NULL)) { AddText(TEXT(“Security Descriptor creation failed: %u\r\n”), GetLastError());

return;

}

g_hNamespace =

CreatePrivateNamespace(&sa, g_hBoundary, g_szNamespace);

//не забываем освободить память, занятую дескриптором

LocalFree(sa.lpSecurityDescriptor);

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

DWORD dwLastError = GetLastError(); if (g_hNamespace == NULL) {

//Если в доступе отказано, делать нечего: этот код работает

//только под учетной записью локального администратора,

if (dwLastError == ERROR_ACCESS_DENIED) {

AddText(TEXT("Access denied when creating the namespace.\r\n")); AddText(TEXT(" You must be running as Administrator.\r\n\r\n")); return;

} else {

if (dwLastError == ERROR_ALREADY_EXISTS) {

//Если другой экземпляр уже создал пространство имен,

//следует открыть его.

AddText(TEXT("CreatePrivateNamespace failed: %u\r\n"), dwLastError);

g_hNamespace = OpenPrivateNamespace(g_hBoundary, g_sz Namespace); if (g_hNamespace == NULL) {

AddText(TEXT(" and OpenPrivateNamespace failed: %u\r\n"), dwLastError);

return; } else {

g_bNamespaceOpened = TRUE;

AddText(TEXT(" but OpenPrivateNamespace succeeded\r\n\r\n"));

}

} else {

AddText(TEXT("Unexpected error occurred: %u\r\n\r\n"),dwLastError); return;

Глава 3. Объекты ядра.docx

39

}

}

}

//Попытаемся создать мьютекс

//в закрытом пространстве имен.

TCHAR szMutexName[64];

StringCchPrintf(szMutexName, _countof(szMutexName), TEXT("%s\\Xs"), g_szNamespace, TEXT("Singleton"));

g_hSingleton = CreateMutex(NULL, FALSE, szMutexName); if (GetLastError() == ERROR_ALREADY_EXISTS) {

//Уже есть экземпляр этого singleton-объекта

AddText(TEXT("Another instance of Singleton is running:\r\n")); AddText(TEXT("--> Impossible to access application features.\r\n"));

}else {

//singleton-еще не создан

AddText(TEXT("First instance of Singleton:\r\n")); AddText(TEXT("--> Access application features now.\r\n"));

}

}

/////////////////////////////////////////////////////////////////////

BOOL Dlg_OnInitDialog(HWND hwnd, HWN0 hwndFocus, LPARAM lParam) {

chSETDLGICONS(hwnd, IDI_SINGLETON);

//переменная для описателя главного диалога g_hDlg = hwnd;

//проверить, нет ли других экземпляров программы

CheckInstances();

return(TRUE);

}

/////////////////////////////////////////////////////////////////////

INT_PTR WINAPI Dlg_Proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {

switch (uMsg) {

chHANDLE_DLGMSG(hwnd, WM_COMMAND, Dlg_OnCommand); chHANDLE_DLGMSG(hwnd, WM_INITDIALOG, Dlg_OnInitDialog);

}

40 Часть I Материалы для обязательного чтения

return(FALSE);

}

/////////////////////////////////////////////////////////////////////

int APIENTRY _tWinMain(HINSTANCE hInstance,

HINSTANCE

hPrevInstance,

LPTSTR

lpCmdLine,

int

nCmdShow)

{

UNREFERENCED_PARAMETER(hPrevInstance); UNREFERENCED_PARAHETER(lpCmdLine);

// показываем главное окно

DialogBox(hInstance, MAKEINTRESOURCE(IDD_SIN6LETQN), NULL, DlgJ>roc);

// Не забываем очистить и освободить ресурсы ядра if (g_hSingleton != NULL) {

CloseHandle(g_hSingleton);

}

if (g_hNamespace ! = NULL) {

if (g_bNamespaceOpened) { // Пространство имен открыто ClosePrivateNamespace(g_hNamespace, 0);

} else { // Пространство имен создано

ClosePrivateNamespace(g_hNamespace, PRIVATE_NAMESPACE_FLAG_ DESTROY);

}

}

if (g_hBoundary != NULL) { DeleteBoundaryDescriptor(g_hBoundary);

}

return(0);

}

////////////////////////// End of File //////////////////////////////

Рассмотрим, как работает функция CkeckInstances. Во-первых, для создания дескриптора границы необходима строка, идентифицирующая закрытое пространство имен. Она передается как первый параметры следующей функции:

HANDLE CreateBoundaryDescrlptor( PCTSTR pszName,

0W0RD dwFlags);

Глава 3. Объекты ядра.docx

41

В текущих версиях Windows второй параметр не используется, поэтому в нем следует передавать 0. заметьте, что сигнатура функции говорит о том, что она возвращает описатель объекта ядра, однако это не так. На самом деле возвращается указатель на структуру пользовательского режима, содержащую определение границы. Поэтому значение, которое вернула эта функция, ни в коем случае не следует передавать функции CloseHandle, оно должно передаваться

DeleteBoundaryDescriptor.

Далее следует связать SID привилегированной пользовательской группы, от имени которой должно запускаться приложение, с дескриптором границы. Это делается так:

B00L AddSIDToBoundaryDescriptor(

HANDLE* phBoundaryDescriptor,

PSID pRequiredSid);

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

AllocateAndInitializeSid с параметрами SECURITY_BUILTTN_DOMAIN_RID и DOMAIN_ALIAS_RID_ADMINS (эти параметры описывают группу). Список стандартных групп определен в заголовочном файле WinNT.h.

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

HANDLE CreatePrivateNamespace(

PSECURITY_ATTRIBUTES psa,

PV0ID pvBoundaryDescriptor,

PCTSTR pszAliasPrefix);

Первый параметр SECUR_TY_ATTRIBUTES, указывает ли разрешить приложению, вызывающему OpenPrivateNamespace, доступ к пространству имен для создания или открытия в нем объектов. Параметры управления доступом здесь те же, что и для каталога файловой системы. Этот механизм представляет своего рода фильтр для доступа к пространству имен. Добавленный к дескриптору границы SID определяет, кому разрешено пересекать границу и работать с этим пространством имен. В нашем примере SECURITY_ATTRIBUTE создается вызовом функ-

ции ConvertStringSecurityDescriptorToSecurityDescriptor, принимающей в качестве первого параметра строку со сложным синтаксисом. Описание синтаксиса дескрипторов защиты доступно ПО ссылкам http://msdn2.microsoft.com/enus/library/aa374928.aspx и http://msdn2.microsoft.com/en-us/library/aa379602.aspx.

Тип pvBoundaryDescriptor — PVOID, несмотря на то, что CreateBoundaryDescriptor возвращает HANDLE, даже в Майкрософт его называют псевдоописателем. Строка с префиксом для имен объектов ядра, которые будут создаваться в вашем пространстве имен, передается в третьем параметре. При попытке создания уже существующего пространства имен CreatePrivateNamespace возвращает

NULL, а GetLastError — ERROR_ALREADY_EXISTS. В этом случае следует от-

крыть существующее пространство имен вызовом следующей

42 Часть I Материалы для обязательного чтения

функции:

HANDLE OpenPrivateNamespace(

PVOID pvBoundaryDescriptor,

PCTSTR pszAliasPrefix);

Заметьте, что значения HANDLE, возвращаемее функциями CreatePrivateNamespace и OpenPrivateNamespace, не являются описателями объектов ядра. Эти псевдоописатели закрывают вызовом ClosePrivateNamespace:

BOOLEAN ClosePrivateNamespace(

HANDLE hNamespace,

DWORD dwFlags);

Если после закрытия ваше пространство имен должно полностью исчезнуть, следует передать во втором параметре флаг

PRIVATE_NAMESPACE_FLAG_DESTROY и 0 в противном случае. Граница уничтожается после завершения процесса либо при вызове функции DeleteBoundaryDescriptor принимающей единственный параметр — псевдоописатель дескриптора границы. Не следует закрывать пространство имен, пока используются расположенные в нем объекты ядра. В противном случае можно будет создавать объекты ядра с теми же именами, если воссоздать это пространство имен с идентичными границами, то есть снова откроется уязвимость для DoS-атак.

Итак, подведем итоги. Закрытое пространство имен — это просто каталог, в котором вы создаете объекты ядра. Как и у любого каталога, у пространства имен есть связанный с ним дескриптор защиты, который создается при вызове CreatePrivateNamespace. Однако, в отличие от каталогов файловой системы, у пространства имен нет ни родительского каталога, ни имени, имя ему заменяет дескриптор границы. Именно поэтому объекты ядра, созданные с префиксом из закрытого пространства имен, отображаются утилитой Process Explorer с префиксом «…\» вместо «пространство_имен\». Префикс «…\» скрывает информацию, усиливая защиту приложения от потенциальных атак взломщиков. Имя, то есть псевдоним, данный вами закрытому пространству имен, видим только внутри процесса. Другие процессы (и тот же самый процесс) могут, открыв это пространство имен, назначить ему другой псевдоним.

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

Дублирование описателей объектов

Последний Механизм совместного использования объектов ядра несколькими процессами требует функции DuplicateHandle:

BOOL DuplicateHandle(

Глава 3. Объекты ядра.docx

43

HANDLE hSourceProcessHandle,

HANDLE hSourceHandle,

HANDLE hTargetProcessHandle,

PHANDLE phTargetHandle,

DWORD dwDesiredAccess,

BOOL bInheritHandle,

DWORD dwOptions);

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

Первый и третий параметры функции DuplicateHandle, hSourceProcessHandle

и hTargetProcessHandle, представляют собой описатели объектов ядра, специфичные для вызывающего процесса. Кроме того, эти параметры должны идентифицировать именно процессы — функция завершится с ошибкой, если вы передадите описатели на объекты ядра любого другого типа. Подробнее объекты ядра «процессы» мы обсудим в главе 4, а сейчас вам достаточно знать только одно: объект ядра «процесс» создается при каждой инициации в системе нового процесса.

Второй параметр, hSourceHandle, — описатель объекта ядра любого типа. Однако его значение специфично не для процесса, вызывающего DuplicateHandle, а для того, на который указывает описатель hSourceProcessHandle. Параметр phTargetHandle — это адрес переменной типа HANDLE, в которой возвращается индекс записи с копией описателя из процесса-источника. Значение возвращаемого описателя специфично для процесса, определяемого параметром hTargetProcessHandle.

Предпоследние два параметра DuplicateHandle позволяют задать маску доступа и флаг наследования, устанавливаемые для данного описателя в процессеприемнике. И, наконец, параметр dwOptions может быть 0 или любой комбинаци-

ей двух флагов: DUPLICATE_SAME__ACCESS и

DUPLICATE_CLOSE_SOURCE.

Первый флаг подсказывает DuplicateHandle: у описателя, получаемого про- цессом-приемником, должна быть та же маска доступа, что и у описателя в про- цессе-источнике. Этот флаг заставляет DuplicateHandle игнорировать параметр dwDesiredAccess.

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

Попробуем проиллюстрировать работу функции DuplicateHandle на примере. Здесь S — это процесс-источник, имеющий доступ к какому-то объекту ядра, T — это процесс-приемник, который получит доступ к тому же объекту ядра, а С — процесс-катализатор, вызывающий функцию DuplicateHandle. Очевидно, вы никогда не станете передавать в DuplicateHandle жестко зашитые значения, как это сделал я, просто демонстрируя работу функции. В ре-

44 Часть I Материалы для обязательного чтения

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

Таблица описателей в процессе С (см. таблицу 3-4) содержит два индекса — 1 и 2. Описатель с первым значением идентифицирует объект ядра «процесс S», описатель со вторым значением — объект ядра «процесс T».

Табл. 3-4. Таблица описателей в процессе С

Индекс

Указатель на блок па-

Маска доступа (DWORD с на-

Флаги (DWORD с набором

 

мяти объекта ядра

бором битовых флагов)

битовых флагов)

1

0xF0000000

0x????????

0x00000000

(объект ядра процесса S)

 

 

 

2

0xF0000010

0x????????

0x00000000

(объект ядра процесса T)

 

 

 

Таблица 3-5 иллюстрирует таблицу описателей в процессе S, содержащую единственную запись со значением описателя, равным 2. Этот описатель может идентифицировать объект ядра любого типа, а не только «процесс».

Табл. 3-5. Таблица описателей в процессе S

Индекс

Указатель на блок

Маска доступа (DWORD с

Флаги (DWORD с набором

 

памяти объекта ядра

набором битовых флагов)

битовых флагов)

1

0x00000000

(неприменим)

(неприменим)

 

0xF0000020

 

 

2

(объект ядра любого

0x????????

0x00000000

 

типа)

 

 

В таблице 3-6 показано, что именно содержит таблица описателей в процессе T перед вызовом процессом С функции DuplicateHandle. Как видите, в ней всего одна запись со значением описателя, равным 2, а запись с индексом 1 пока пуста.

Табл. 3-6. Таблица описателей в процессе T перед вызовом DuplicateHandle

Индекс

Указатель на блок

Маска доступа (DWORD с

Флаги (DWORD с набором

 

памяти объекта ядра

набором битовых флагов)

битовых флагов)

1

0x00000000

(неприменим)

(неприменим)

 

0xF0000030

 

 

2

(объект ядра любого

0x????????

0x00000000

 

типа)

 

 

Если процесс С теперь вызовет DuplicateHandle так:

DuplicateHandle(1, 2, 2, &h0bj, 0, TRUE, DUPLICATE_SAME__ACCESS);

то после вызова изменится только таблица описателей в процессе T (см. таблицу

3-7).

 

 

Глава 3. Объекты ядра.docx

45

Табл. 3-7. Таблица описателей в процессе T после вызова DuplicateHandle

 

Индекс

Указатель на блок памя-

Маска доступа (DWORD

Флаги (DWORD с набо-

 

ти объекта ядра

с набором битовых фла-

ром битовых флагов)

 

 

гов)

 

 

1

0xF0000020

0x????????

0x00000001

 

2

0xF0000030

0x????????

0x00000000

 

(объект ядра любого типа)

 

 

 

 

 

Вторая строка таблицы описателей в процессе S скопирована в первую строку таблицы описателей в процессе Т. Функция DuplicateHandle присвоила также переменной hObj процесса С значение 1 — индекс той строки таблицы в процессе T, в которую занесен новый описатель.

Поскольку функции DuplicateHandle передан флаг DUPLICATE_SAME_ACCESS, маска доступа для этого описателя в процессе T идентична маске доступа в процессе S. Кроме того, данный флаг заставляет DuplicateHandle проигнорировать параметр dwDesiredAccess. Заметьте также, что система установила битовый флаг наследования, так как в параметре bInheritHandle функции DuplicateHandle мы передали TRUE.

Как и механизм наследования, функция DuplicateHandle тоже обладает одной странностью: процесс-приемник никак не уведомляется о том, что он получил доступ к новому объекту ядра. Поэтому процесс С должен каким-то образом сообщить процессу T, что тот имеет теперь доступ к новому объекту; для этого нужно воспользоваться одной из форм межпроцессной связи и передать в процесс T значение описателя в переменной hObj. Ясное дело, в данном случае не годится ни командная строка, ни изменение переменных окружения процесса T, поскольку этот процесс уже выполняется. Здесь придется послать сообщение окну или задействовать какой-нибудь другой механизм межпроцессной связи.

Я рассказал вам о функции DuplicateHandle в самом общем виде. Надеюсь, вы увидели, насколько она гибка. Но эта функция редко используется в ситуациях, требующих участия трех разных процессов. Обычно ее вызывают применительно

кдвум процессам. Представьте, что один процесс имеет доступ к объекту, к которому хочет обратиться другой процесс, или что один процесс хочет предоставить другому доступ к «своему» объекту ядра. Например, если процесс S имеет доступ

кобъекту ядра и вам нужно, чтобы к этому объекту мог обращаться процесс T,

используйте DuplicateHandle так:

//весь приведенный ниже код исполняется процессом S

//создаем объект-мыотекс, доступный процессу S

HANDLE hObjInProcessS = CreateMutex(NULL, FALSE, NULL);

// открываем описатель объекта ядра "процесс T"

HANDLE hProcessT = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessIdT);

46 Часть I Материалы для обязательного чтения

HANDLE hObjInProcessT; // неинициализированный описатель, связанный

//с процессом T

//предоставляем процессу T доступ к объекту-мьютексу

DuplicateHandle(GetCurrentProcess(), hObjInProcessS, hProcessT, &hObjInProcessT, 0, FALSE, DUPLICATE_SAME_ACCESS);

//используем какую-нибудь форму межпроцессной связи, чтобы передать

//значение описателя из hObjProcessS в процесс T

//связь с процессом T больше не нужна

CloseHandle(hProcessT);

//если процессу S не нужен объект-мыотекс, он должен закрыть его

CloseHandle(hObjInProcessS);

Вызов GetCurrentProcess возвращает псевдоописатель, который всегда идентифицирует вызывающий процесс, в данном случае — процесс S. Как только функция DuplicateHandle возвращает управление, hObjProcessT становится описателем, связанным с процессом T и идентифицирующим тот же объект, что и описатель hObjProcessS (когда на него ссылается код процесса S). При этом процесс S ни в коем случае не должен исполнять следующий код:

//Процесс S никогда не должен пытаться исполнять код,

//закрывающий продублированный описатель.

CloseHandle(hObjInProcessT);

Если процесс S выполнит этот код, вызов может дать (а может и не дать) ошибку. Он будет успешен, если у процесса S случайно окажется описатель с тем же значением, что и в hObjProcessT. При этом неизвестно, какой объект будет закрыт процессом S, и что будет потом — остается только гадать.

Теперь о другом способе применения DuplicateHandh. Допустим, некий процесс имеет полный доступ (для чтения и записи) к объекту «проекция файла» и из этого процесса вызывается функция, которая должна обратиться к проекции файла и считать из нее какие-то данные. Так вот, если мы хотим повысить отказоустойчивость приложения, то могли бы с помощью DuplicateHandle создать новый описатель существующего объекта и разрешить доступ только для чтения. Потом мы передали бы этот описатель функции, и та уже не смогла бы случайно что-то записать в проекцию файла. Взгляните на код, который иллюстрирует этот пример:

int WINAPI _tWinMain(HINSTANCE hInstExe, HINSTANCE, LPTSTR szCndLine, int nCmdShow) {

// создаем объект "проекция файла";

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