Rambler's Top100
"Knowledge itself is power"
F.Bacon
Поиск | Карта сайта | Помощь | О проекте | ТТХ  
 Подземелье Магов
  
 

Фильтр по датам

 
 К н и г и
 
Книжная полка
 
 
Библиотека
 
  
  
 


Поиск
 
Поиск по КС
Поиск в статьях
Яndex© + Google©
Поиск книг

 
  
Тематический каталог
Все манускрипты

 
  
Карта VCL
ОШИБКИ
Сообщения системы

 
Форумы
 
Круглый стол
Новые вопросы

 
  
Базарная площадь
Городская площадь

 
   
С Л С

 
Летопись
 
Королевские Хроники
Рыцарский Зал
Глас народа!

 
  
ТТХ
Конкурсы
Королевская клюква

 
Разделы
 
Hello, World!
Лицей

Квинтана

 
  
Сокровищница
Подземелье Магов
Подводные камни
Свитки

 
  
Школа ОБЕРОНА

 
  
Арсенальная башня
Фолианты
Полигон

 
  
Книга Песка
Дальние земли

 
  
АРХИВЫ

 
 

Сейчас на сайте присутствуют:
 
  
 
Во Флориде и в Королевстве сейчас  23:45[Войти] | [Зарегистрироваться]

Защита объектов в NT

Юрий Спектор
дата публикации 07-08-2007 02:59

Защита объектов в NT
  1. Пользователи и группы
  2. Маркер доступа
  3. Защита объектов, дескрипторы безопасности
  4. Списки контроля доступа (ACL). DACL
  5. Работа с ACE
  6. Системный список управления доступом (SACL). Аудит
  7. Изменение дескриптора безопасности существующего объекта. Абсолютные и отностительные дескрипторы безопасности
  8. Заключение

Системы линейки 9x не являются многопользовательскими в том понимании, что не позволяют разграничить доступ к ресурсам, а лишь позволяют выбрать профиль - способ отображения данных в соответствии с настройками того или иного пользователя. В системах линейки NT доступ к объектам управляется операционной системой. Защищаемыми объектами могут быть файлы, устройства, почтовые ящики, каналы, задания, процессы, потоки, объекты синхронизации, порты завершения ввода-вывода, разделы общей памяти, сетевые ресурсы, разделы реестра и др. Механизмы, о которых пойдет речь далее, применимы только к системам линейки NT.

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

Пользователи и группы

Как уже было сказано, Windows NT является многопользовательской системой и позволяет управлять доступом к своим объектам между несколькими потребителями. Для каждого зарегистрированного пользователя система создает учетную запись - запись, содержащую сведения о данном пользователе. Учетные записи всех пользователей хранятся в некой системной базе данных. Она представляет собой таблицу, схематически показанную на рисунке 1.


Рисунок 1. База данных учетных записей

Для каждой учетной записи система хранит имена, пароли и уникальные идентификаторы - SID (Security Identifier). Последний используется системой в дальнейшем везде, где нужно однозначно сослаться на ту или иную учетную запись. Структуру SID рассматривать не будем, можете ознакомиться самостоятельно, для нас пока важна лишь возможность с его помощью идентифицировать учетную запись.

База данных учетных записей содержит сведения не только о пользователях, но и группах пользователей (например, "Администраторы"), которые также имеют SID. Группы позволяют нескольким пользователям задать общие права доступа. Управление учетными записями с помощью групп позволяет упростить работу администратора по контролю доступа пользователей к ресурсам.

Ниже приведен фрагмент кода, позволяющий получить строковое представление SID пользователя. На входе функции нужно задать имя компьютера (пустая строка - локальный компьютер) и пользователя. Описания API приводиться не будут, вы сами сможете найти их в MSDN.

function ConvertSidToStringSid(Sid: PSID; var StringSid: PChar): BOOL;
  stdcall; external advapi32 name 'ConvertSidToStringSidA';

function GetUserSIDStr(SystemName, AccountName: String): String;
var
  PSID, PRef: Pointer;
  SIDSize, RefSize, peUse: Cardinal;
  sSID: PChar;
begin
  Result:='';
  SIDSize:=0;
  RefSize:=0;
  // Первый вызов функции позволяет получить необходимые размеры буферов
  // для SID и имени домена
  LookupAccountName(PChar(SystemName),PChar(AccountName),nil,SIDSize,nil,
    RefSize,peUse);
  GetMem(PSID,SIDSize);
  GetMem(PRef,RefSize);
  try
    // Получаем SID учетной записи
    if not LookupAccountName(PChar(SystemName),PChar(AccountName),PSID,
      SIDSize, PRef,RefSize,peUse) then RaiseLastOSError;
    // Конвертируем SID в строковое представление
    if ConvertSidToStringSid(PSID,sSID) then begin
      SetLength(Result,StrLen(sSID));
      StrCopy(PChar(Result),sSID);
      LocalFree(Cardinal(sSID));
    end;
  finally
    FreeMem(PRef);
    FreeMem(PSID);
  end;
end;

Кроме того, рекомендую самостоятельно ознакомиться с функциями GetUserName, InitializeSid, AllocateAndInitializeSid, GetSidLengthRequired, CopySid, EqualSid, FreeSid, GetLengthSid, IsValidSid, GetSidIdentifierAuthority, GetSidSubAuthority, GetSidSubAuthorityCount, LookupAccountSid, ConvertStringSidToSid, CreateWellKnownSid.

Маркер доступа

Теперь давайте рассмотрим, что происходит, когда пользователь входит в систему. После успешной проверки подлинности имени пользователя и пароля система создает так называемый маркер доступа (Access token). Маркер доступа представляет собой объект, который содержит кроме всего прочего SID пользователя, прошедшего аутентификацию, и SID групп, в которые этот пользователь входит.


Рисунок 2. Аутентификация.

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

Кроме SID пользователя и групп, маркер доступа содержит и другие элементы, (такие как список привилегий, например). В рамках данной статьи они рассматриваться не будут.

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

type
  PTokenUser = ^_TOKEN_USER;
  _TOKEN_USER = record
   User : TSidAndAttributes;
 end;

function SIDToStr(SID: PSID): String;
var
  sSID: PChar;
begin
  Result:='';
  if ConvertSidToStringSid(SID,sSID) then begin
    SetLength(Result,StrLen(sSID));
    StrCopy(PChar(Result),sSID);
    LocalFree(Cardinal(sSID));
  end;
end;

procedure GetSIDStrList(List: TStringList);
var
  hToken, Len: Cardinal;
  pTU: PTokenUser;
  pTG: PTokenGroups;
  i: Integer;
begin
  List.Clear;
  // Получаем маркер доступа процесса
  if not OpenProcessToken(GetCurrentProcess,TOKEN_QUERY,hToken) then
    RaiseLastOSError;
  try
    // Получаем указатель на SID пользователя и добавляем строковое
    // представление в List
    GetTokenInformation(hToken,TokenUser,nil,0,Len);
    GetMem(pTU,Len);
    try
      if not GetTokenInformation(hToken,TokenUser,pTU,Len,Len)
        then RaiseLastOSError;
      List.Add(SIDToStr(pTU^.User.Sid));
    finally
      FreeMem(pTU);
    end;
    // Получаем указатели на SID групп и добавляем строковые представления
    // в List
    GetTokenInformation(hToken,TokenGroups,nil,0,Len);
    GetMem(pTG,Len);
    try
      if not GetTokenInformation(hToken,TokenGroups,pTG,Len,Len) then
        RaiseLastOSError;
      for i:=0 to pTG^.GroupCount - 1 do begin
        List.Add(SIDToStr(pTG^.Groups[i].Sid));
      end;
    finally
      FreeMem(pTG);
    end;
  finally
    // Закрываем дескриптор маркера доступа
    CloseHandle(hToken);
  end;
end;

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

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

Защита объектов, дескрипторы безопасности

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

Для того чтобы ограничить доступ к объекту, его нужно снабдить дескриптором безопасности (Security descriptor). Это и есть тот охранник, стоящий на страже объекта и не пропускающий к нему никого чужого. Дескриптор безопасности представляют собой структуру следующего вида:

_SECURITY_DESCRIPTOR = record
  Revision: Byte;
  Sbz1: Byte;
  Control: SECURITY_DESCRIPTOR_CONTROL;
  Owner: PSID;
  Group: PSID;
  Sacl: PACL;
  Dacl: PACL;
end;

Несмотря на то, что дескриптор безопасности - это структура, работать напрямую ее полями не следует. Нужно использовать специальные функции, которые будут рассмотрены чуть позже. А пока ознакомимся с полями этой структуры. Поле Revision - это номер версии, на данный момент может иметь только значение SECURITY_DESCRIPTOR_REVISION. Sbz1 служит для выравнивания и должно содержать нули. Control - набор флагов, описывающих свойства дескриптора безопасности. Owner - SID владельца объекта. Владелец объекта имеет право изменять настройки доступа к объекту, даже если в это явно запретить в DACL (см. далее). SID первичной группы (параметр Group) нужен исключительно для совместимости со стандартом POSIX. Поля Sacl и Dacl являются указателями на соответствующие списки контроля доступа, которые для нас (особенно DACL) представляют наибольшее значение. Чуть позже мы перейдем к более подробному их обсуждению, а пока рассмотрим некоторую ситуацию.

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

hEvent:=CreateEvent(nil,true,false,'Global\evMyServerObject');

Однако если мы попытаемся получить дескриптор этого объекта с помощью OpenEvent в приложении, выполняющемся под ограниченной учетной записью, мы получим отказ в доступе. Почему? Потому что мы не задали атрибуты безопасности для этого объекта (первый параметр) и не указали в них дескриптор безопасности. В результате, система создала объект и назначила ему дескриптор безопасности по умолчанию, который открывает полный доступ только создателю объекта и всем членам группы администраторов, а остальным - закрывает.

А как система узнала, что мы не имеем права доступа к этому объекту? Наверняка она проверила наш пропуск (маркер доступа) и на этом основании сделала вывод, что мы права доступа к объекту не имеем. А где написано, кто имеет доступ к объекту, а кто не имеет? Ответ - эта информация записана в дескрипторе защиты, а именно - в одном из его списках контроля доступа.

Списки контроля доступа (ACL). DACL

Как видно из структуры дескриптора безопасности, он содержит два поля, содержащих в своем названии 'ACL' (Access-Control List) - список контроля доступа. Таких списков два: Discretionary Access-Control List, (DACL) - список управления избирательным доступом и System Access-Control List (SACL) - системный список управления доступом. Их структура схожа, однако они выполняют совершенно разные функции. В данном разделе будет более подробно рассмотрен DACL, так как он имеет для нас наибольшее значение. Именно DACL формирует правила, кому разрешить доступ к объекту, а кому - запретить. Поэтому все, что будет сказано о списках контроля доступа и его элементах, в большей степени относится именно к DACL. SACL позволяет лишь управлять аудитом (об этом - ниже).

Каждый список контроля доступа (ACL) представляет собой набор элементов контроля доступа (Access Control Entries, или ACE). Чтобы не было путаницы в терминах, изобразим схематически (рисунок 3).


Рисунок 3. ACL и ACE
ACE бывает двух типов (разрешающий и запрещающий доступ) и обязательно содержит три поля:
  • SID пользователя или группы, к которому применяется данное правило
  • Вид доступа, на которое распространяется данное правило
  • Тип ACE - разрешающий или запрещающий.

Таким образом, ACL, изображенный на рисунке 3, (если это не просто ACL, а DACL) устанавливает следующие правила: пользователю SID1 разрешить доступ на чтение объекта, но запретить доступ на запись, а пользователю SID2 - разрешить полный доступ к объекту.

Кроме того, к дескриптору безопасности применимы следующие правила:

  • Если DACL отсутствует, то объект считается незащищенным, т.е. все имеют к нему неограниченный доступ.
  • Если DACL существует, но не содержит ни одного ACE, то доступ к объекту закрыт для всех.

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

var
  sa: TSecurityAttributes;
  sd: TSecurityDescriptor;
  hEvent: THandle;
begin
  // Создаем дескриптор безопасности
  InitializeSecurityDescriptor(@sd,SECURITY_DESCRIPTOR_REVISION);
  // DACL не установлен - объект незащищен
  SetSecurityDescriptorDacl(@sd,true,nil,false);
  // Настраиваем атрибуты безопасности, передавая туда указатель на
  // дескриптор безопасности sd и создаем объект-событие
  sa.nLength:=SizeOf(TSecurityAttributes);
  sa.lpSecurityDescriptor:=@sd;
  sa.bInheritHandle:=false;
  hEvent:=CreateEvent(@sa,true,false,'Global\evMyServerObject');

Система сопоставляет маркер доступа и дескриптор защиты не при каждом обращении к объекту, а только при получении его дескриптора (рисунок 4). Например, если имеется защищенный объект-мьютекс, система проверяет права только при вызове процессом функции OpenMutex. В этой же функции процесс сразу же указывает, какие виды доступа ему в дальнейшем потребуются. Если все указанные виды доступа разрешены - процесс получает дескриптор объекта и может его использовать сколь угодно долго в соответствии с запрошенными правами. Даже если DACL объекта изменится. После того как процесс получил дескриптор, система не сможет его забрать. Если же система принимает решение отказать в доступе, процесс просто не получит дескриптор объекта.


Рисунок 4. Запрос доступа к объекту.

Таким образом, продолжая аналогию, дескриптор безопасности - это охранник, маркер доступа - это пропуск, а DACL - это приказ охраннику, кому давать доступ, а кому - отказывать.

Ну и в заключение главы отметим, что ядро обращается к объектам не через дескрипторы, а через указатели. Права доступа в режиме ядра не проверяются, другими словами, система полностью сама себе доверяет и всегда имеет доступ к объекту.

Работа с ACE

Рассмотрим такую ситуацию. А что, если два ACE противоречат друг другу? Например, один ACE дает полный доступ членам определенной группы, а другой - запрещает доступ определенному пользователю из этой группы. Получит ли этот пользователь доступ к объекту? А это зависит от того, в каком порядке ACE расположены.

Когда процесс запрашивает определенный вид доступа к защищенному дескриптором безопасности объекту, система действует по следующему алгоритму:

  • Извлекает маркер доступа процесса, в котором содержится SID пользователя и групп, в которые он входит.
  • Просматриваются все ACE в DACL дескриптора безопасности от первого к последнему.
  • Определяющую роль играет первый встреченный элемент, дающий возможность пользователю воспользоваться запрошенной услугой или отказывающий в этом.
  • Если хотя бы один из видов запрошенного доступа не предоставлен (запрещен или достигнут конец DACL), система принимает решение отказать в доступе к объекту. Из этого можно сделать вывод, что запрещающие элементы не имеет смысла размещать внизу DACL, так как если перед ними нет соответствующих разрешающих, доступ все равно будет закрыт. Запрещающие элементы обычно размещают вверху списка. Особенно если нужно запретить доступ конкретному пользователю, который может его получить, воспользовавшись членством в группе.

Чтобы было понятнее, поясним на примере. Допустим, процесс, запущенный от имени пользователя User1, входящего в группу Group1, запрашивает доступ к объекту на чтение и запись. DACL дескриптора безопасности объекта имеет следующий вид:

Учетная запись Вид доступа Разрешить, запретить
User1 чтение разрешить
Group1 запись разрешить
User1 запись запретить

Доступ к объекту с таким DACL на чтение и запись будет разрешен, несмотря на последний элемент. Так как проход по DACL начинается сверху вниз, а первый элемент предоставит право на чтение, а второй - на запись. На этом система и остановится. А если последний элемент поместить наверх, то доступ будет запрещен.

А теперь поставим обратную задачу: в системе зарегистрировано 5 пользователей: User1..User5. User1 и User2 входят в группу Group1. User3, User4 и User5 входят в группу Group2. Необходимо составить такой DACL, который:

  • Дает полный доступ к объекту всем членам группы Group1.
  • Дает доступ только на чтение всем членам группы Group2.
  • Не дает никакого доступа пользователю User5.
DACL будет выглядеть, например, так:
Учетная запись Вид доступа Разрешить, запретить
User5 полный доступ запретить
Group2 чтение разрешить
Group1 полный доступ разрешить

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

// Эта функция будет работать только на системах, начиная с WinXP
function CreateWellKnownSid(WellKnownSidType: Cardinal; DomainSid, Sid: PSID;
  var cbSID: Cardinal): Bool; stdcall; external advapi32 name 'CreateWellKnownSid';

const
  WinWorldSid = 1;
  aclSize = 1024;
  ACL_REVISION = 2; // По неизвестным причинам, ее нет в Windows.pas (D7)

var
  pi: TProcessInformation;

procedure TForm1.Button1Click(Sender: TObject);
var
  si: TStartupInfo;
  sa: TSecurityAttributes;
  sd: TSecurityDescriptor;
  dacl: PACL;
  SID: PSID;
  SIDLength: Cardinal;
begin
  // Инициализируем дескриптор безопасности
  InitializeSecurityDescriptor(@sd,SECURITY_DESCRIPTOR_REVISION);
  GetMem(dacl,aclSize);
  try
    // Инициализируем DACL. Выделяем буфер заведомо большего размера
    // В принципе, можно точно рассчитать необходимый размер буфера.
    // Как это сделать написано в MSDN в описании функции InitializeAcl
    InitializeAcl(dacl^,aclSize,ACL_REVISION);
    SIDLength:=0;
    // Получаем SID группы, включающей всех пользователей
    CreateWellKnownSid(WinWorldSid,nil,nil,SIDLength);
    GetMem(SID,SIDLength);
    try
      CreateWellKnownSid(WinWorldSid,nil,SID,SIDLength);
      // Добавляем в DACL ACE, разрешающий любой вид доступа, кроме
      // PROCESS_TERMINATE всем пользователям. Запрещающий ACE добавляется
      // с помощью функции AddAccessDeniedAce
      AddAccessAllowedAce(dacl^,ACL_REVISION,PROCESS_ALL_ACCESS and
        not PROCESS_TERMINATE,SID);
      // Устанавливаем DACL в дескриптор безопасности
      SetSecurityDescriptorDacl(@sd,true,dacl,false);
      // Инициализируем атрибуты безопасности и создаем процесс
      sa.nLength:=SizeOf(TSecurityAttributes);
      sa.lpSecurityDescriptor:=@sd;
      sa.bInheritHandle:=false;
      FillChar(si,SizeOf(TStartupInfo),0);
      si.cb:=SizeOf(TStartupInfo);
      CreateProcess('C:\WINDOWS\system32\calc.exe',nil,@sa,nil,false,0,
        nil,nil,si,pi);
      CloseHandle(pi.hThread);
    finally
      FreeMem(SID);
    end;
  finally
    FreeMem(dacl);
  end;
end;

procedure TForm1.Button2Click(Sender: TObject);
var
  hProcess: Cardinal;
begin
  hProcess:=OpenProcess(PROCESS_TERMINATE,false,pi.dwProcessId);
  if hProcess = 0 then
    RaiseLastOSError
  else
    TerminateProcess(hProcess,0);
end;

В примере используется функция CreateWellKnownSid. С помощью нее мы получаем указатель на SID группы 'everyone' (SID этой группы всегда один и тот же - S-1-1-0), которая включает всех пользователей. Эта функция в данном случае мне показалась наиболее простой и удобной, однако на системах младше XP работать не будет. Вы можете использовать другой способ, например, с помощью AllocateAndInitializeSid.

Кроме функции для работы с ACL и ACE, использующихся в примере, рекомендую самостоятельно ознакомиться с AddAccessDeniedAce, AddAce, DeleteAce, GetAce, GetAclInformation, IsValidAcl, SetAclInformation, SetEntriesInAcl.

Системный список управления доступом (SACL). Аудит

Дескриптор безопасности содержит указатель на System Access-Control List (SACL) - список, похожий на DACL, но отвечающий не за разрешение или запрет на доступ, а за аудит (протоколирование в журнале безопасности) успешных и безуспешных попыток доступа к объекту. Благодаря системе аудита, администратор может узнать, кто, каким образом и когда пользовался (или пытался пользоваться, но получил отказ в доступе) интересующими его ресурсами. Для работы с SACL используются как общие для всех ACL функции, так и специфичные - AddAuditAccessAce, AddAuditDeniedAce, GetSecurityDescriptorSacl, SetSecurityDescriptorSacl.

Изменение дескриптора безопасности существующего объекта. Абсолютные и относительные дескрипторы безопасности

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

Для того чтобы изменить DACL объекта, процесс должен обладать правом WRITE_DAC, однако, во-первых, мы не запрещали его в DACL (PROCESS_ALL_ACCESS включает это право), а даже если бы запретили - оно все равно у нас было бы на правах владельца (поле Owner в дескрипторе безопасности), так как владелец всегда имеет право изменять DACL.

Для получения дескриптора безопасности процесса можно воспользоваться функцией GetKernelObjectSecurity. Но использование этой функции порождает некоторые проблемы. Дело в том, что при вызове функции InitializeSecurityDescriptor, получается так называемый абсолютный (absolute) дескриптор безопасности. Свое название такой тип получил из-за того, что указатели, содержащиеся в нем, представляют собой абсолютный адрес в памяти процесса и указывают на структуры памяти, не входящие в сам дескриптор. При связывании дескриптора с объектом, система объединяет все данные, относящиеся к дескриптору в одну компактную структуру. Такой тип дескриптора называется самоопределяющийся относительный (self-relative). Название данный тип получил из-за того, что указатели в нем теперь содержат не абсолютный адрес, а относительное смещение элемента в данной структуре. Такой тип дескриптора удобен, если его нужно сбросить в какое-либо хранилище, (например - в файл) или переслать по сети. Также файловая система NTFS хранит дескриптор безопасности для каждого файла или папки именно в таком формате. Проверить формат дескриптора можно с помощью GetSecurityDescriptorControl.

Когда мы вызываем GetKernelObjectSecurity (или другие подобные функции), система возвращает именно относительный дескриптор. Многие функции, такие как SetSecurityDescriptorDacl, требуют дескриптор безопасности именно в абсолютном формате. Имеются специальные функции, позволяющие преобразовывать дескриптор из одной формы в другую (MakeAbsoluteSD, MakeSelfRelativeSD), однако их использование делает код очень громоздким (функция MakeAbsoluteSD имеет 11 параметров!). Другая неприятность заключается в том, что для работы с разными типами объектов нужно использовать разные функции: для объектов ядра, таких как процесс, поток, мьютекс и др. - Get(Set)KernelObjectSecurity, для файлов - Get(Set)FileSecurity, для user-объектов - Get(Set)UserObjectSecurity, для ключей реестра - RegGet(Set)KeySecurity и др.

К счастью, у этих проблем есть одно простое и элегантное решение - функции GetSecurityInfo и SetSecurityInfo. Их можно использовать на системах, начиная от NT 4.0. Для того чтобы продемонстрировать преимущества этих функций, напишем с их помощью код, снимающий запрет на завершение процесса. В модуле Windows.pas этих функций нет (по крайней мере, на Delphi 7), можете импортировать их самостоятельно или воспользоваться заголовочными файлами JEDI API - http://jedi-apilib.sourceforge.net/.

Итак, бросаем на форму из примера с защищенным процессом еще одну кнопку и прописываем ей такой обработчик щелчка:

uses JwaAclApi, JwaAccCtrl; // JEDI

procedure TForm1.Button3Click(Sender: TObject);
var
  hProcess: Cardinal;
begin
  // Открываем процесс. Запрашиваем доступ на изменение DACL
  hProcess:=OpenProcess(WRITE_DAC,false,pi.dwProcessId);
  if hProcess <> 0 then begin
    // Устанавливаем указатель на DACL равным nil - объект незащищен
    SetSecurityInfo(hProcess,SE_KERNEL_OBJECT,DACL_SECURITY_INFORMATION,
      nil,nil,nil,nil);
    CloseHandle(hProcess);
  end
  else
    RaiseLastOSError;
end;

Вот и все. В функции SetSecurityInfo мы просто устанавливаем объекту nil в качестве DACL. Для сравнения, без использования этой функции нам нужно было бы:

  1. С помощью GetKernelObjectSecurity получить дескриптор безопасности (относительный). Причем функцию пришлось бы вызывать два раза - один для получения размера буфера, второй - для получения самого дескриптора.
  2. Вызвать функцию MakeAbsoluteSD для получения размеров буферов для DACL, SACL, SID и GROUP.
  3. Выделить память под эти элементы.
  4. Еще раз вызвать MakeAbsoluteSD для преобразования дескриптора в абсолютный формат.
  5. С помощью SetSecurityDescriptorDacl установить nil в качестве DACL.
  6. Вызовом SetKernelObjectSecurity установить объекту новый дескриптор безопасности.
  7. Освободить память, выделенную в п.1. и п.2.

Даже не буду пытаться привести код для сравнения, так как выигрыш при использовании SetSecurityInfo очевиден. Хотя, именно в данном случае можно было не получать установленный объекту дескриптор и преобразовывать его в абсолютный, а создать новый с нуля. Однако в большинстве случаев нужно сохранить многие параметры существующего дескриптора, которые заранее неизвестны.

Теперь запускаем пример на выполнение. Щелкаем сначала на первую кнопку - создаем процесс, затем на вторую - получаем отказ в доступе, на третью - снимаем с процесса защиту, и снова вторую - вуаля! Процесс завершился.

Хочу также обратить ваше внимание еще на две функции, подобные SetSecurityInfo и GetSecurityInfo - SetNamedSecurityInfo и GetNamedSecurityInfo. Их основное отличие от упомянутых ранее в том, что вместо дескриптора объекта нужно указать его имя.

Заключение

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

Благодарности: SLoW - за советы, конструктивную критику и просто поддержку при написании данной статьи.




Смотрите также материалы по темам:
[Безопасность системы]

 Обсуждение материала [ 08-11-2008 00:17 ] 7 сообщений
  
Время на сайте: GMT минус 5 часов

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

Web hosting for this web site provided by DotNetPark (ASP.NET, SharePoint, MS SQL hosting)  
Software for IIS, Hyper-V, MS SQL. Tools for Windows server administrators. Server migration utilities  

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

Яндекс цитирования