Логотип StingRay

Поделиться
FacebookFacebookRSSTwitterYouTubeВ контактеОдноклассники
FacebookFacebookRSSTwitterYouTubeВ контактеОдноклассники
Силуэт человека

Служба журнализации входа в Windows

Вашему вниманию предлагается небольшая статья, посвящённая написанию службы (service) журнализации входа/выхода пользователя в операционной системе Windows линейки NT (Windows NT/2000/XP/2003 и т. п. – именно в этих операционных системах присутствует понятие службы). Служба – это, согласно официальному определению Windows 2000, программа или процесс, выполняющий конкретную системную функцию по поддержке других программ, особенно на низком (близком к аппаратному) уровне. Служба отличается от обычного приложения тем, что может запускаться до входа интерактивного пользователя в систему. Таким образом, она фактически никак не связана с наличием интерактивного пользователя в системе, и поэтому задача определения этого факта для самой службы является нетривиальной.


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

Итак, в Delphi всё начинается с создания нового проекта (с помощью пункта меню File > New… или кнопки New на панели инструментов), а именно приложения служб (Service Application) из предлагаемых элементов хранилища объектов (Object Repository). В результате мы имеем новый проект, сразу включающий в себя одну службу (ибо приложение служб, не реализующее хотя бы одну службу, не имеет смысла).

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

Проблема 1. Как определить момент входа/выхода пользователя? Насколько мне известно, операционные системы Windows не посылают на этот счёт никаких специальных сообщений, не вызывают никаких специальных обработчиков событий и не выставляют никаких флагов. Поэтому придётся обходиться «подручными средствами». Как показала практика, для определения наличия в системе интерактивного пользователя достаточно обнаружить хотя бы один пользовательский процесс, например, Explorer.exe. Но в связи с этим возникает ещё ряд смежных проблем.

Проблема 1.1. Как обнаружить в системе тот или иной процесс? В разных версиях операционных систем Windows это можно сделать по-разному, однако нас интересует максимально универсальный способ. Им, как оказалось, является использование WinAPI-функций CreateToolHelp32Snapshot, Process32First и Process32Next – снятие общесистемного списка процессов и последовательная итерация по его элементам до момента обнаружения процесса с соответствующим именем исполнимого файла.

function TLogService.IsExplorerRunning: Boolean;
  var
    ProcessEntry: TProcessEntry32;
    ProcessExists: Boolean;
    Snapshot: Integer;
  begin
    Result := False;
    ProcessEntry.DWSize := SizeOf (TProcessEntry32);
    try
      Snapshot := CreateToolHelp32Snapshot (Th32CS_SnapProcess, 0);
      ProcessExists := Process32First (Snapshot, ProcessEntry);
      while ProcessExists do
        begin
          if ANSIUpperCase (ExtractFileName (ProcessEntry.SzExeFile)) = 'EXPLORER.EXE' then
              Result := True;
              Break;
            end;
          ProcessExists := Process32Next (Snapshot, ProcessEntry);
        end;
    except
      Result := False;
    end;
  end;

Проблема 1.2. В какие моменты пытаться обнаружить тот или иной процесс? Когда в системе нужно что-либо отслеживать во времени, то это обычно наиболее эффективно делается на основе событий (уведомлений) и их обработчиков, однако при отсутствии такой возможности (как в нашем случае) приходится делать это просто с определённой периодичностью. Так я пришёл к выводу о необходимости использовать таймер (компонент TTimer в Delphi или обычный WinAPI-таймер, привязанный к некоторому виртуальному окну). Эмпирически определённая дискретность срабатывания – 1 секунда (этого достаточно, чтобы оперативно определить наличие пользователя в системе и не перегружать последнюю).

Определение того, вошёл ли пользователь в систему или вышел из неё осуществляется путём сопоставления факта наличия пользовательского процесса Explorer.exe и предыдущего состояния пользователя.

type TUserState = (usIndefinite, usLoggedOff, usLoggedOn);

var PrevUserState: TUserState;

function TLogService.GetUserState: TUserState;
  var ExplorerRunning: Boolean;
  begin
    Result := usIndefinite;
    ExplorerRunning := IsExplorerRunning;
    if ExplorerRunning and (PrevUserSTate = usLoggedOff) then
      Result := usLoggedOn
    else if (not ExplorerRunning) and (PrevUserSTate = usLoggedOn) then
      Result := usLoggedOff;
  end;

Проблема 2. Как определить имя текущего интерактивного пользователя? Это, пожалуй, самая большая проблема, к тому же на момент написания данной статьи ещё не разрешённая… Как было отмечено выше, служба обычно дистанцируется от понятия интерактивного пользователя, и поэтому если определить имя текущего компьютера определить не проблема (с использованием WinAPI-функции GetComputerName), то определить имя пользователя не так просто… Очень часто службы работают под специальной системной учётной записью LocalSystem (в нашем случае – тоже, см. список зарегистрированных Win32-служб выше), поэтому WinAPI-функция GetUserName неизменно возвращает значение SYSTEM, вне зависимости от имени текущего интерактивного пользователя и даже при его отсутствии. Попытки определить имя текущего пользователя через ветку системного реестра HKEY_CURRENT_USER или переменную окружения %USERNAME% также ничего не дают, так как и то, и другое относится к контексту самой службы.

Наиболее перспективным направлением исследований, на мой взгляд, может стать получение имени текущего интерактивного пользователя через информацию о безопасности обнаруженного пользовательского процесса Explorer.exe, посредством последовательности использования WinAPI-функций OpenProcess, OpenProcessToken, GetTokenInformation и т. д. Есть какие-нибудь идеи? Давайте их обсудим!


P.S. За помощь в подготовке данного материала автор выражает свою благодарность своим друзьям-коллегам Артёму Петрову и Кузьме Каткову.

Альтернативная реализация

Как стало известно позднее, уже после завершения статьи на постскриптуме, существует альтернативный путь регистрации входа/выхода пользователя (об этом мне рассказал Андрей Смирнов). В операционных системах линейки Windows NT (2000, XP, 2003) используется раздел HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\WinLogon\Notify системного реестра, в котором можно создать подраздел с любым допустимым именем, например, MyLogger, а в нём – следующие параметры:

Имя Тип Значение
DLLName строковый Путь к динамической библиотеке (DLL), служащей обработчиком событий.
Logon строковый Название функции в этой библиотеке, которая вызывается при входе пользователя в систему.
Logoff строковый Название функции в библиотеке, которая вызывается при выходе пользователя из системы.
Impersonate двойное слово Использовать ли контекст вошедшего пользователя (Impersonate = 1) или обезличенный системный контекст (имя пользователя – SYSTEM, Impersonate = 0) при вызове фунций-обработчиков.
Asynchronous двойное слово Использовать ли асинхронный вызов функций-обработчиков (может быть особенно важно для работы со службой).

Реализация такой библиотеки-обработчика и её регистрация в указанном разделе системного реестра (гарантией последнего может выступать служба журнализации, о которой шла речь изначально) позволяет решить сразу все проблемы: чёткую регистрацию как входа, так и выхода пользователя, а также определение его имени (функция GetUserName в случае Impersonate = 1 возвращает имя текущего интерактивного пользователя). Пример реализации библиотеки на языке Object Pascal (в среде Delphi) вместе с подборкой тематических статей MSDN можно скачать в виде архива.zip.

В других операционных системах Windows (9x, ME) может существовать аналогичный (недокументированный) раздел в системном реестре – HKEY_LOCAL_MACHINE\System\CurrentControlSet\MPRServices – в нём тоже можно создать подраздел с любым именем и следующими параметрами:

Имя Тип Значение
DLLName строковый Путь к динамической библиотеке (DLL), служащей обработчиком события запуска системы.
EntryPoint строковый Название функции (точки входа) в этой библиотеке, которая вызывается при запуске системы.

Очевидно, что возможности этого способа регистрации входа пользователя в систему существенно ограничены. Во-первых, не факт, что функция DLLName.EntryPoint вызывается именно при входе интерактивного пользователя в систему. Во-вторых, неизвестно, в каком контексте вызывается функция, то есть насколько реально определить имя пользователя (всё та же проблема № 2). В-третьих, функция не вызывается и выходе пользователя из системы (вторая половина проблемы № 1).

23.12.2005 11:50:51 Evgen (IP) Цитата #1
Привет
Вопрос по теме Служба журнализации входа в Windows
Неподскажеш какой раздел в реестре использовать в winNT 4.0
23.12.2005 12:56:11 Станислав (IP) Цитата #2
К сожалению, я вряд ли смогу тебе чем-либо помочь хотя бы потому, что сам ранее «вживую» с Windows NT 4.0 практически не сталкивался, а сейчас так и вообще найти эту операционную систему не смогу… Но если тебе удастся с этим разобраться – буду рад дополнить упомянутую статью твоими сведениями.
21.09.2007 18:15:08 Дмитрий (IP) Цитата #3
Прочитать имя текущего пользователя можно из реестра, из ключа HKEY_CURRENT_USER\Software\Microsoft\Active Setup\Installed Components\{44BBA840-CC51-11CF-AAFA-00AA00B6015C}, параметр Username
Добавьте свой комментарий или войдите, чтобы подписаться/отписаться.
Имя: OpenId
Результат операции:
Предпросмотр Улыбка Подмигивание Дразнит Оскал Смех Огорчение Сильное огорчение Шок Сумасшествие Равнодушие Молчание Крутизна Злость Бешенство Смущение Сожаление Влюблённость Ангел Вопрос Восклицание Жирный Курсив Подчёркивание Зачёркивание Размер шрифта Гиперссылка Цитата
Загрузка…