информационная безопасность
без паники и всерьез
 подробно о проектеRambler's Top100
Сетевые кракеры и правда о деле ЛевинаГде водятся OGRы
BugTraq.Ru
Русский BugTraq
 Анализ криптографических сетевых... 
 Модель надежности двухузлового... 
 Специальные марковские модели надежности... 
 Бэкдор в xz/liblzma, предназначенный... 
 Три миллиона электронных замков... 
 Doom на газонокосилках 
главная обзор RSN блог библиотека закон бред форум dnet о проекте
bugtraq.ru / форум / programming
Имя Пароль
ФОРУМ
все доски
FAQ
IRC
новые сообщения
site updates
guestbook
beginners
sysadmin
programming
operating systems
theory
web building
software
hardware
networking
law
hacking
gadgets
job
dnet
humor
miscellaneous
scrap
регистрация





Легенда:
  новое сообщение
  закрытая нитка
  новое сообщение
  в закрытой нитке
  старое сообщение
  • Напоминаю, что масса вопросов по функционированию форума снимается после прочтения его описания.
  • Новичкам также крайне полезно ознакомиться с данным документом.
[Win32] Хых, руки у мадемуазель зачесались ;-) 14.05.04 19:47  Число просмотров: 4533
Автор: HandleX <Александр М.> Статус: The Elderman
Отредактировано 18.05.04 08:04  Количество правок: 5
<"чистая" ссылка>
> В продолжение темы:
> А чем отличаются сервисы, от обычнх приложений?
> Наскоко трудно переделать обычную прогу в сервис - наверно
> нужно поддерживать некий специфический интерфейс?
Итак, службы... Да, поддерживают они особый интерфейс для "общения" с Service Control Manager'ом.
SCM их и запускает, исходя из того, какие параметры имеются в записи реестра об этой службе. Между
прочим, SCM запускает также и драйверы, поэтому "захват власти" над SCM является лакомой добычей
хакеров и вирусов, поскольку именно оттуда они могут запустить драйвер ядра. А драйвер ядра может
поиметь абсолютно полный контроль над системой.
Но написание драйверов ядра задача не для слабонервных. И не всякий компилятор сможет это
сделать. К примеру, Delphi даже при создании простейшего *.exe, который запускается и тут же
выходит, запихивает в него некий run-time, который использует user-mode API, и запуск
такого "драйвера" вызовет ошибку. Вообще, я не слышал, чтобы драйверы компилировали на
чём-нибудь отличном от M$ VC.

Однако вернёмся к user-mode службам. Сперва службу нужно написать ;-) Там всё просто, в MSDN есть
примеры ;-) Поскольку службу запускает SCM, он некоторое время ждёт, когда служба вызовет
StartServiceCtrlDispatcher(), параметром этой функции является массив из записей вида
"Имя службы-Точка входа в MainServiceProc), из чего делаем вывод, что в одном исполняемом файле
службы может "содержаться" несколько служб. Интересно, что возврат из StartServiceCtrlDispatcher()
происходит только после остановки всех прописанных в этом вызове служб. Если
StartServiceCtrlDispatcher() возвращает false, значит что-то не так, и поможет GetLastError() ;-)

Итак, если всё в порядке, SCM запускает новый поток и в конце концов передаёт управление на
ServiceMain(). В этой функции программист должен первым делать зарегистрировать свою функцию
ServiceControlHandler(), в которую SCM будет "кидать" сообщения о том, что нужно делать службе -
запуститься, перейти в паузу, выйти из паузы, остановиться и проч. Необходимо сказать о
многопоточности. ServiceControlHandler() и ServiceMain() исполняются в отдельных потоках, это нужно
учитывать. Но это и кое-что облегчает... Часто программисты для перевода службы в "паузу" просто
исполняют SuspendThread() для потока ServiceMain(), а с помощью ResumeThread восстанавливают
работу службы. Сервис может "отчитываться" SCM о своём состоянии, докладывать ему об ошибках
во время запуска, "говорить" SCM о том, какие "команды" он может в данный момент воспринимать.
В частности, службу можно запрограммировать так, что остановить её после запуска, или загнать в
паузу, будет невозможно, эдакий "неубиваемый" сервис... Единственное, его можно грохнуть через
TerminateProcess(), но это отдельная история...

И вот, "скелет" службы создан, и руки чешутся нажать на кнопку "Run" компилятора... Однако спешка
нужна сами знаете где... Теперь службу нужно "прописать" в систему. Нужно или в самой программе
службы встроить реакцию на скажем, параметр командной строки типа -install либо написать
программу-установщик, которая зарегистрирует службу в системе. Делается это при помощи вызова
функций API OpenSCManager() и CreateService(), внимательное изучение параметров этих двух функций
поможет понять как управлять и инсталлировать службы, а также какие они бывают и когда SCM будет
их запускать. К примеру, можно указать SCM автоматический запуск службы во время старта OS, или
ручной, или вообще отключить службу...

Ну и наконец, после того, как служба инсталлирована в систему, можно попробовать её запустить...
Делается это встроенным в OS "запускателем" командной строки net start ИмяСлужбы или через
оснастку управления службами.
Отладка служб тоже занятие не для слабонервных ;-) Однако есть подсказки от M$ о том, как это делается.

> Кстати, наскоко я понимаю запускать приложения в качестве
> сервиса пожет токо супер пользователь?
При вызове функции OpenSCManager() одним из параметров является уровень доступа к службам...
По умолчанию обычный пользователь не может установить службу. А вот дальше доступ
(сможет ли пользователь запустить, остановить или даже удалить уже прописанную службу) задаётся
после создании службы при помощи функции SetServiceObjectSecurity(), иныим словами для каждой
службы можно сконфигурировать любой Список Контроля Доступа для любых пользователей и групп в системе.

Ну как, после всего вышесказанного не отпало желание написать службу? ;-)
Для интересующихся и начинающих изучать программирование приведу пример простейшей службы на
Delphi, которая может себя устанавливать и удалять, при запуске "пикает" в системный спикер, может
войти в паузу и быть остановлена.

Успехов.

program SimpleServ;
{$APPTYPE CONSOLE}
uses Windows, WinSVC; //Не используем SysUtils и прочий хлам - размер екзешника у нас будет всего ~25 килобайт

  Function IsWin2KorHigher: BOOL;
  Var aVer: OSVERSIONINFO;
  Begin
    ZeroMemory(@aVer, SizeOf(aVer));
    aVer.dwOSVersionInfoSize := SizeOf(aVer);
    GetVersionEx(aVer);
    Result := (aVer.dwPlatformId = VER_PLATFORM_WIN32_NT) And (aVer.dwMajorVersion >= 5);
  End;

Const
  ServicesCount = 1;
  ServiceName = 'SimpleServ';
  DisplayName = 'Simple Service';
  ServiceType = SERVICE_WIN32_OWN_PROCESS;
  ServiceDescription = 'SimpleServ from Handlex ;-))) This simple program will help you to understand WinNT Services. Mail to alex_wh@mail.ru.';

Var
  ServThrHndl: THandle = 0;
  StopEvent: THandle = 0;
  aServHndl: DWord = 0;
  aServStatus: SERVICE_STATUS;

  Function IntToStr(Value: Integer): String; //Included because we don't use SysUtils
  Var aSign: Bool;
  Begin
    Result := '';

    aSign := Value >= 0;
    If Not aSign Then Value := -Value;

    Repeat
      Result := Char(Value - (Value Div 10) * 10 + Byte('0')) + Result;
      Value := Value Div 10;
    Until Value = 0;

    If Not aSign Then Result := '-' + Result;
  End;

  // Helper function for windows error strings
  function SysErrorMessage(ErrorCode: Integer): string; //Included because we don't use SysUtils
  var
    Len: Integer;
    Buffer: array[0..255] of Char;
  begin
    Len := FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM or
      FORMAT_MESSAGE_ARGUMENT_ARRAY, nil, ErrorCode, 0, Buffer,
      SizeOf(Buffer), nil);
    while (Len > 0) and (Buffer[Len - 1] in [#0..#32]) do Dec(Len);
    SetString(Result, Buffer, Len);
    UniqueString(Result);
    ANSItoOEM(PChar(Result), PChar(Result));
    If Result <> '' Then
      Result := '(' + IntToStr(ErrorCode) + ') ' + Result;
  end;

  Procedure ShowInfo;
  Begin
    WriteLn;
    WriteLn('                    -=* SIMPLE TRAINING SERVICE BY HandleX *=-');
  End;

  Procedure ProcessStartupParams; //Реакция на install, uninstall

    Function SetServiceDescription(aSHndl: THandle; aDesc: String): Bool; //Устанавливает "описание" для службы, Win2k и выше
    Const SERVICE_CONFIG_DESCRIPTION: DWord = 1;
    Var
      DynChangeServiceConfig2: Function(
        hService: SC_HANDLE;                    // handle to service
        dwInfoLevel: DWORD;                     // information level
        lpInfo: Pointer): Bool; StdCall;        // new data
      aLibHndl: THandle;
      TempP: PChar;
    Begin
      aLibHndl := GetModuleHandle(advapi32);
      Result := aLibHndl <> 0; If Not Result Then Exit;
      DynChangeServiceConfig2 := GetProcAddress(aLibHndl, 'ChangeServiceConfig2A');
      Result := @DynChangeServiceConfig2 <> Nil; If Not Result Then Exit;
      TempP := PChar(aDesc); //ChangeServiceConfig2 хочет вместо строки указатель на указатель ;-)
      Result := DynChangeServiceConfig2(aSHndl, SERVICE_CONFIG_DESCRIPTION, @TempP);
    End;

  Type
    TToDo = (tdError, tdInstall, tdUninstall);
    TToDo_s = Set of TToDo;
  Const
    ParamStrings: Array[tdInstall..tdUninstall] of String = ('install', 'uninstall');

  Function MapParam(aParam: String): TToDo; //Узнаёт из параметра о вашем желании ;-)
  Var
    J: TToDo;
    TempStr: String;
  Begin
    Result := tdError;
    TempStr := aParam;
    If TempStr[1] In ['/', '-'] Then
      TempStr := Copy(TempStr, 2, Length(TempStr) - 1);
    UniqueString(TempStr);
      CharLower(PChar(TempStr));
    For J := Low(ParamStrings) to High(ParamStrings) Do
      If ParamStrings[J] = TempStr Then
      Begin
        Result := J;
        Exit;
      End;
  End;

  Var
    J: Integer;
    scHndl, sHndl: THandle;
    aStatus: TServiceStatus;
    toDo: TTodo_s;
  Begin
    toDo := [];
    For J := 1 to ParamCount Do
    Begin
      Include(ToDo, MapParam(ParamStr(J)));
      If tdError in toDo Then
      Begin
        ExitCode := ERROR_INVALID_PARAMETER;
        WriteLn('Unknown parameter - ' + ParamStr(J) + '. RTFM, please...');
        Exit;
      End;
    End;

    If [tdInstall, tdUninstall] <= toDo Then
    Begin
      ExitCode := ERROR_INVALID_PARAMETER;
      WriteLn('Error: you can not install and uninstall service simultaniosly. Check params.');
      Exit;
    End;

    If tdInstall in toDo Then //Устанавливаем сервис
    Begin
      Write('Connecting Service Control Manager...');
      scHndl := OpenSCManager(Nil, Nil, SC_MANAGER_CREATE_SERVICE);
      If scHndl = 0 Then
      Begin
        ExitCode := GetLastError;
        WriteLn('Failed!'); WriteLn('Error: ', SysErrorMessage(ExitCode));
        Exit;
      End;
      Try
        WriteLn('Ok');
        Write('Creating service database record...');
        sHndl := CreateService(
          SCHndl, ServiceName, DisplayName,
          SERVICE_QUERY_CONFIG Or SERVICE_CHANGE_CONFIG, ServiceType, SERVICE_DEMAND_START,
          SERVICE_ERROR_NORMAL, PChar(ParamStr(0)), Nil, Nil, Nil, Nil, Nil);

        If sHndl = 0 Then
        Begin
          ExitCode := GetLastError;
          WriteLn('Failed!'); WriteLn('Error: ', SysErrorMessage(ExitCode));
          Exit;
        End;
        Try
          WriteLn('Ok');
          If ServiceDescription <> '' Then
          Begin
            Write('Setting service description...');
            If Not SetServiceDescription(sHndl, ServiceDescription) Then
            Begin
              WriteLn('Failed!');
              WriteLn('Warning: ', SysErrorMessage(GetLastError));
              WriteLn('Warning: SetServiceDesc() failed, but service is installed!');
            End;
            WriteLn('Ok');
          End;
        Finally
          CloseServiceHandle(sHndl);
        End;
      Finally
        CloseServiceHandle(SCHndl);
      End;
      WriteLn('Service "', DisplayName, '" install success.');
    End;

    If tdUninstall in toDo Then //Удаляем сервис...
    Begin
      Write('Connecting Service Control Manager...');
      scHndl := OpenSCManager(Nil, Nil, GENERIC_EXECUTE);
      If scHndl = 0 Then
      Begin
        ExitCode := GetLastError;
        WriteLn('Failed!');
        WriteLn('Error: ', SysErrorMessage(ExitCode));
        Exit;
      End;
      Try
        WriteLn('Ok');

        Write('Opening and Quering Service...');
        sHndl := OpenService(SCHndl, ServiceName, STANDARD_RIGHTS_REQUIRED Or SERVICE_QUERY_STATUS Or SERVICE_STOP);
        If sHndl = 0 Then
        Begin
          ExitCode := GetLastError;
          WriteLn('Failed!'); WriteLn('Error: ', SysErrorMessage(ExitCode));
          Exit;
        End;
        Try
          If Not QueryServiceStatus(sHndl, aStatus) Then
          Begin
            ExitCode := GetLastError;
            WriteLn('Failed!'); WriteLn('Error: ', SysErrorMessage(ExitCode));
            Exit;
          End;
          WriteLn('Ok');

          If aStatus.dwCurrentState <> SERVICE_STOPPED Then
          Begin
            Write('Service is running, wait until stopped...');
            If Not ControlService(sHndl, SERVICE_CONTROL_STOP, aStatus) Then
            Begin
              ExitCode := GetLastError;
              WriteLn('Failed!'); WriteLn('Error: ', SysErrorMessage(ExitCode));
              Exit;
            End;
            While aStatus.dwCurrentState <> SERVICE_STOPPED Do
            Begin
              Sleep(250); Write('.');
              If Not QueryServiceStatus(sHndl, aStatus) Then
              Begin
                ExitCode := GetLastError;
                WriteLn('Failed!'); WriteLn('Error: ', SysErrorMessage(ExitCode));
                Exit;
              End;
            End;
            WriteLn('Stopped');
          End;

          Write('Deleting Service...');
          If Not DeleteService(sHndl) Then
          Begin
            ExitCode := GetLastError;
            WriteLn('Failed!'); WriteLn('Error: ', SysErrorMessage(ExitCode));
            Exit;
          End;
          WriteLn('Ok');
        Finally
          CloseServiceHandle(sHndl);
        End;
      Finally
        CloseServiceHandle(SCHndl);
      End;
      WriteLn('Service uninstall success.');
    End;

  End;

    Function SetState(aState: DWORD): DWORD;
    Begin
      aServStatus.dwCurrentState := aState;
      If aServHndl <> 0 Then
        SetServiceStatus(aServHndl, aServStatus);
      Result := aServStatus.dwCurrentState;
    End;

    Procedure ServiceHandler(fdwControl: DWORD); StdCall;
    Begin
      Case fdwControl Of
        SERVICE_CONTROL_STOP: Begin           //Requests the service to stop.
          SetState(SERVICE_STOP_PENDING);
          SetEvent(StopEvent);
          ResumeThread(ServThrHndl); //Если сервис был в паузе, то рабочий поток надо возобновить
        End;
        SERVICE_CONTROL_PAUSE: Begin          //Requests the service to pause.
          SetState(SERVICE_PAUSE_PENDING);
          SuspendThread(ServThrHndl); //Останавливаем рабочий поток сервиса
          SetState(SERVICE_PAUSED);
        End;
        SERVICE_CONTROL_CONTINUE: Begin       //Requests the paused service to resume.
          SetState(SERVICE_CONTINUE_PENDING);
          ResumeThread(ServThrHndl); //Восстанавливаем рабочий поток сервиса
          SetState(SERVICE_RUNNING);
        End;
        SERVICE_CONTROL_INTERROGATE: Begin    //Requests the service to update immediately its current status information to the service control manager.
          SetState(aServStatus.dwCurrentState); //Говорим SCM о том, в каком состоянии находится наша служба
        End;
        128..255: Begin                       //The service defines the action associated with the control code.
          SuspendThread(ServThrHndl);         //Протяжно пищим в спикер, потому что кто-то послал USER DEFINED CONTROL CODE ;-)
          Windows.Beep(1000, 500);
          ResumeThread(ServThrHndl);
        End;
      End;
    End;
  Procedure MainServiceProc(      // Каждая служба может иметь параметры своего запуска. У нас не используется ;-)
    dwArgc: DWORD;                // number of arguments
    lpszArgv: Pointer); StdCall   // array of arguments
  Begin
    aServHndl := RegisterServiceCtrlHandler(ServiceName, @ServiceHandler);
    If aServHndl = 0 Then
    Begin
      ExitCode := GetLastError;
      Exit; //Какая-то ошибка, срочно выходим, SCM будет ругаться, но сообщить мы ему ничего не можем...
    End;
    ZeroMemory(@aServStatus, SizeOf(aServStatus));
    aServStatus.dwServiceType := ServiceType;
    aServStatus.dwControlsAccepted := SERVICE_ACCEPT_STOP Or SERVICE_ACCEPT_PAUSE_CONTINUE;
    //aServStatus.dwWaitHint := 500; //Здесь может быть подсказка для небыстрых служб о том, как долго она реагирует на команды
    SetState(SERVICE_START_PENDING); //Извещаем SCM, что мы начали стартовать именно этот сервис...

    // Тут идёт рутина инициализации...

    If Not DuplicateHandle(GetCurrentProcess, GetCurrentThread, GetCurrentProcess, @ServThrHndl, 0, FALSE, DUPLICATE_SAME_ACCESS) Then
    Begin //Нам нужен реальный дескриптор потока службы, делаем его...
      aServStatus.dwWin32ExitCode := GetLastError;
      SetState(SERVICE_STOPPED);
      Exit;
    End;

    //Нам нужен unnamed event для реагирования на останов из ControlHandler...
    StopEvent := CreateEvent(Nil, True, False, Nil);
    If StopEvent = 0 Then //Какая-то ошибка - срочно останавливаемся и выходим...
    Begin
      aServStatus.dwWin32ExitCode := GetLastError;
      SetState(SERVICE_STOPPED);
      Exit;
    End;

    // Инит прошёл, пошла работа сервиса...

    SetState(SERVICE_RUNNING); //Момент истины - извещаем SCM что мы работаем!!!

    While WaitForSingleObject(StopEvent, 500) = WAIT_TIMEOUT Do
      Windows.Beep(10000, 10); //Крутим цикл, бипаем по таймауту, иначе выходим...

    // Выполняем остановку сервиса - вычищаемся и выходим...
    CloseHandle(ServThrHndl);
    ServThrHndl := 0;
    CloseHandle(StopEvent);
    StopEvent := 0;

    SetState(SERVICE_STOPPED); // Работа сервиса закончена...

  End;

Var
  ServTableEntryArray: Array[0..ServicesCount] Of TServiceTableEntryA;

begin
{$R *.res}

  //Старт программы...

  If ParamCount > 0 Then //От нас что-то хотят...
  Begin
    ShowInfo;
    ProcessStartupParams; //Выясняем что и выходим...
    Exit;
  End;

  //Готовимся к вызову StartServiceCtrlDispatcher
  ZeroMemory(@ServTableEntryArray, SizeOf(ServTableEntryArray));
  ServTableEntryArray[0].lpServiceName := ServiceName;
  ServTableEntryArray[0].lpServiceProc := @MainServiceProc;

  If Not StartServiceCtrlDispatcher(ServTableEntryArray[0]) Then
  Begin
    ExitCode := GetLastError;
    ShowInfo; //Какой-то косяк, поэтому выводим в консоль сообщение и выходим...
    WriteLn('Error: ', SysErrorMessage(ExitCode));
    WriteLn('This program is Windows NT Service, so it CAN NOT be run from command prompt.');
    WriteLn('You can install it with "/install" parameter.');
    Exit;
  End;

  // Все сервисы завершили свою работу, выходим...
end.

---
<programming> Поиск 






Rambler's Top100
Рейтинг@Mail.ru


  Copyright © 2001-2024 Dmitry Leonov   Page build time: 0 s   Design: Vadim Derkach