Практика
Запуск “самого себя” с правами NT AUTHORITY

:: Меню ::
:: На главную ::
:: FAQ ::
:: Заметки ::
:: Практика ::
:: Win API ::
:: Проекты ::
:: Скачать ::
:: Секреты ::
:: Ссылки ::

:: Сервис ::
:: Написать ::

:: MVP ::

:: RSS ::

Яндекс.Метрика


Нашелся в сети один интересный модуль – RunAsSystem, предназначенный для запуска приложения с правами NT AUTHORITY. А что, какой пользователь не мечтает поднять свои права до уровня системных!? И как то между делом подумалось – запускать другие приложения с системными правами конечно хорошо, а как бы приложению самому запуститься от имени системы? Не писать же, в самом деле, загрузчик для своей программы, не серьезно это как то. Вот первое решение, которое пришло в голову (все изменения производятся в файле проекта):

program Test;

uses
  SysUtils,
  Forms,
  RunAsSystem,
  MainFrm in 'MainFrm.pas' {Form1};

{$R *.res}

var
  i: Integer;
  Run: Boolean;

begin
   if ParamCount > 0 then
      for i := 1 to ParamCount do
         if ParamStr( i ) = '/system' then
         begin
            Run := True;
            Break;
         end;

   if not Run then
      RunProcAsSystem( ParamStr( 0 ),
                       Copy( CmdLine, Pos( ' ', CmdLine )+1, Length( CmdLine ) ) + ' /system',
                       SystemIntegrityLevel )
   else
   begin
      Application.Initialize;
      Application.MainFormOnTaskbar := True;
      Application.CreateForm( TForm1, Form1 );
      Application.Run;
   end;
end.

Все, что делает приложение в момент запуска – создает свою копию с правами NT AUTHORITY. Параметр ‘/system’ (можно назвать его и по-другому) нужен для того, чтобы не попасть в бесконечный цикл.

Это решение, хоть и вполне работоспособное, имеет небольшой недостаток. Думаю, самые внимательные уже догадались о чём идет речь – если в момент запуска приложения в командной строке будет параметр '/system', приложение запустится с правами учетной записи, под которой работает пользователь, что совсем не логично (и для нас недопустимо).

Исправляется это достаточно просто – обнаружив в командной строке параметр ‘/system’ нужно проверить, из-под какой учетной записи нас запустили. Можно было бы просто проверить имя пользователя, запустившего приложение.

function GetUserName: string;
var
  Size: cardinal;
  pStr: PChar;
  Res: boolean;
begin
   pStr := nil;
   Size := MAX_COMPUTERNAME_LENGTH + 1;
   try
      pStr := StrAlloc( Size );
      Res := Windows.GetUserName( pStr, Size );
      if Res then
         Result := StrPas( pStr )
      else
         Result := 'Имя пользователя неизвестно';
   finally
      if pStr <> nil then StrDispose( pStr );
   end;
end;

Полученное имя можно было бы сравнить с именем ‘system’, но загвоздка здесь заключается в том, что функция GetUserName возвращает имя системного пользователя с учетом локализации операционной системы. То есть в системе с английской локализацией мы получим ‘system’, а с русской ‘система’. Этот метод не подходит из-за отсутствия в нем универсальности. Но что же тогда сравнивать? Идентификаторы безопасности (SID) учетных записей! Функция получения SID’а учетной записи выглядит следующим образом:

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;

После перебора параметров командной строки добавляем еще одну проверку:

if Run then
   if GetUserSIDStr( '', GetUserName ) <> GetUserSIDStr( '', 'system' ) then
      Run := not Run;

И все готово! Теперь программа всегда будет запускаться от имени системы! Но… только если учетная запись, из-под которой запускается приложение, принадлежит к группе администраторов. А что если это не так? Тогда программа не запустится вовсе, а такой поворот событий нас не устраивает. Нужна дополнительная проверка на наличие необходимых прав, и если их нет, то приложение должно запуститься в “штатном” режиме. Проверяем права пользователя:

function IsAdmin: Boolean;

  function IsWin9x: Boolean;
  asm
     mov  eax, fs:[030h]
     test eax, eax
     sets al
  end;

const
  SECURITY_NT_AUTHORITY: TSIDIdentifierAuthority = ( Value: ( 0, 0, 0, 0, 0, 5 ) );
  SECURITY_BUILTIN_DOMAIN_RID = $00000020;
  DOMAIN_ALIAS_RID_ADMINS     = $00000220;
var
  IsUserAnAdmin: function: BOOL; stdcall;
  hAccessToken: THandle;
  ptgGroups: PTokenGroups;
  dwInfoBufferSize: DWORD;
  psidAdministrators: PSID;
  i: Integer;
  bSuccess: BOOL;
  hMod: Thandle;
begin
   Result := True;
   if IsWin9x then
      Exit;
   Result := False;
   hAccessToken := 0;
   hMod := GetModuleHandle( 'shell32.dll' );
   if hMod = 0 then
      hMod := LoadLibrary( 'shell32.dll' );
   IsUserAnAdmin := GetProcAddress( hMod,'IsUserAnAdmin' );
   if not Assigned( IsUserAnAdmin ) then
   begin
      bSuccess := OpenThreadToken( GetCurrentThread, TOKEN_QUERY, True, hAccessToken );
      if not bSuccess then
         if GetLastError=ERROR_NO_TOKEN then
            bSuccess := OpenProcessToken( GetCurrentProcess, TOKEN_QUERY, hAccessToken );
      if bSuccess then
      begin
         GetMem( ptgGroups, 1024 );
         bSuccess := GetTokenInformation( hAccessToken, Windows.TokenGroups, ptgGroups,
                                          1024, dwInfoBufferSize );
         CloseHandle( hAccessToken );
         if bSuccess then
         begin
            AllocateAndInitializeSid( SECURITY_NT_AUTHORITY, 2, SECURITY_BUILTIN_DOMAIN_RID,
                                      DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, psidAdministrators );
            if ptgGroups.GroupCount > 0 then
               for i := 0 to ptgGroups.GroupCount-1 do
                  if EqualSid( psidAdministrators, ptgGroups.Groups[i].Sid ) then
                  begin
                     Result := True;
                     Break;
                  end;
            FreeSid( psidAdministrators );
         end;
         FreeMem( ptgGroups );
      end;
   end
   else
      Result := IsUserAnAdmin;
end;

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

{...}

procedure RunApplication;
begin
   Application.Initialize;
   Application.MainFormOnTaskbar := True;
   Application.CreateForm( TForm1, Form1 );
   Application.Run;
end;

var
  Param: string;

begin
   // Если нет прав администратора, запускаемся как обычно...
   if not IsAdmin then
      RunApplication
   else
   // ...иначе запускаемся от имени системы
   begin
      if ParamCount > 0 then
         for i := 1 to ParamCount do
         begin
            if Trim( ParamStr( i ) ) = '/system' then
            begin
               Run := True;
               Break;
            end;
         end;

      if Run then
         if GetUserSIDStr( '', GetUserName ) <> GetUserSIDStr( '', 'system' ) then
            Run := not Run;

      if not Run then
      begin
         Param := CmdLine;
         Delete( Param, 1, Length( ParamStr( 0 ) ) );
         RunProcAsSystem( ParamStr( 0 ),
                          Copy( Param, Pos( ' ', CmdLine )+1, Length( CmdLine ) ) + ' /system',
                          SystemIntegrityLevel );
      end
      else
         RunApplication;
   end;
end.

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

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

На этом все, успехов в программировании!

.: Пример к данной статье :.


При использовании материала - ссылка на сайт обязательна