Статья Службы Windows - перечисление и сбор информации

Как-то мне понадобилось собрать сведения о загруженных драйверах и службах, причём акцент делался на то, сколько их всего, какие активны, а какие нет, и т.д. Так на свет родилась утилита с которой хочу поделиться здесь. По сути организовать это дело просто, поэтому съехав с основной трассы я решил разобраться и с созданием кастомного окна Windows, прикрутив к нему элемент управления ListView в режиме таблицы, аля рабочая зона проводника "Total Commander". Статью можно считать продолжением недавнего поста про службы.

Под катом:

1. Функции Win32API для служб и драйверов​
2. Элемент управления "List-View" и строка статуса​
3. Заключение​



1. Функции Win32API для служб и драйверов

Для перечисления служб и драйверов в базе данных диспетчера управления службами SCM предусмотрена функция EnumServicesStatus() из библиотеки Advapi32.dll (расширенная, Advanced API). Она возвращает название и статус каждой службы в структуру ENUS_SERVICE_STATUS. Прототип функции выглядит так:

C-подобный:
BOOL EnumServicesStatusA(
  hSCManager            dd  0  ;// [in]  дескриптор от OpenSCManager()
  dwServiceType         dd  0  ;// [in]  тип запрашиваемой инфы = Драйвера или Службы
  dwServiceState        dd  0  ;// [in]  состояние служб: Все = 3
  lpServices            dd  0  ;// [out] линк на массив структур ENUM_SERVICE_STATUS
  cbBufSize             dd  0  ;// [in]  общий размер массива
  pcbBytesNeeded        dd  0  ;// [out] если буфер для массива мал, то размер хвоста
  lpServicesReturned    dd  0  ;// [out] число возвращённых структур
  lpResumeHandle        dd  0  ;// [in,out] линк на переменную со значением(0)

;//--------------------------
struct ENUM_SERVICE_STATUS
  lpServiceName       dd  0   ;// имя драйвера/службы,
  lpDisplayName       dd  0   ;// ..и её описание
  ServiceStatus       SERVICE_STATUS ;//----+
ends ;//                                    |
;//                                         |
struct SERVICE_STATUS  ;//<-----------------+
  dwServiceType              dd  0  ;// Тип = драйвер/служба
  dwCurrentState             dd  0  ;// Статус = запущена/остановлена
  dwControlsAccepted         dd  0
  dwWin32ExitCode            dd  0
  dwServiceSpecificExitCode  dd  0
  dwCheckPoint               dd  0
  dwWaitHint                 dd  0
ends

Обратите внимание на параметр(5) "cbBufSize" - функция ожидает в нём размера буфера для массива структур, чего мы не можем знать заранее. Поэтому функцию нужно вызывать дважды - при первом вызове она вернёт требуемый размер буфа по которому мы выделяем память через Heap/VirtualAlloc(), а при втором передаём уже этот размер обратно функции, и вызываем её второй раз. В на MSDN говорится, что макс.размер этого буфера не может превышать 260 КБ, хотя на практике требуется намного меньше, в зависимости от списка драйверов/служб на текущий момент. Вот пример:

C-подобный:
;//--- Пробуем получить доступ к диспетчеру SCM ----
          invoke  OpenSCManager,0,0,SC_MANAGER_ENUMERATE_SERVICE
          mov     [hScm],eax

;//--- Запрос статуса служб и выделяем буфер -------
;// выстрел(1) холостой, для вычисления размера приёмного буфера
;// выстрел(2) непосредственно читаем инфу в него

          invoke  EnumServicesStatus,[hScm],\
                                     [mode],\
                                     [state],0,0,\
                                     lpBytesNeeded,\
                                     lpServCount,\
                                     lpResumeHndl

          invoke  VirtualAlloc,0,[lpBytesNeeded], MEM_COMMIT, PAGE_READWRITE
          mov     [lpScBase],eax

          invoke  EnumServicesStatus,[hScm],\
                                     [mode],\
                                     [state],\
                                     eax,\
                                    [lpBytesNeeded],\
                                     lpBytesNeeded,\
                                     lpServCount,\
                                     lpResumeHndl

Так получим заполненный буфер в выделенной памяти, причём в переменной "lpServCount" будет указано общее кол-во структур ENUM_SERVICE_STATUS в массиве, и на каждом шаге цикла прибавляя размер одной структуры sizeof.ENUM_SERVICE_STATUS можно будет обойти их все. Только куда выводить нарытую информацию? Самый простой вариант - это конечно через форматированный printf() на консоль, но лучше конечно-же в форточку, вписав в неё таблицу по строкам и столбцам. Для этого предусмотрен элемент управления "ListView" - рассмотрим его подробней.


2. Окна и элемент управления "ListView"

ListView - это один из самых гибких и мощных компонентов для отображения коллекций данных в графических интерфейсах (WinForms, WPF, и в мобильных платформах). Предназначен для отображения списка элементов с различными уровнями детализации. Он может показывать данные в виде:

• Крупных значков - Large Icon, 32x32.​
• Мелких значков - Small Icon, 16x16.​
• Списка - Simple List, компактный режим без колонок, с иконками слева.​
• Таблицы - Details, самый популярный режим в виде строк и колонок, поддерживает сортировку.​

Всё, что мы видим в окне приложения принято называть окном. Например кнопка, поле ввода, надписи, чекбоксы и т.д. - всё это окна Window, которым мы можем посылать сообщения функцией SendMessage(), текст при помощи Get/SetWindowText(), или-же создавать динамически в процессе работы новые через CreateWindow(). Однако окном может называться и "элемент управления" в окне, что создаёт путаницу. С технической точки зрения окно(Window) и элемент управления(Control) ничем не отличается, но есть нюансы.

Окно - это базовый объект в Windows.
Примеры: Главное/Диалоговое/Всплывающее окно приложения.

• Занимает прямоугольную область экрана​
• Получает сообщения от системы и других окон​
• Имеет процедуру обработки сообщений​

Элемент управления - это окно, предназначенное для взаимодействия с пользователем:

• Обычно является дочерним (WS_CHILD)​
• Имеет зарегистрированный системой стандартный класс​
• Служит для ввода/вывода данных​
• Уведомляет родительское окно о событиях (через WM_COMMAND или WM_NOTIFY)​

Таким образом, каждый элемент управления - это окно, но не каждое окно - элемент управления. Это как "квадрат - это прямоугольник, но не каждый прямоугольник - квадрат". Все они живут в единой оконной подсистеме Windows, используют дескрипторы HWND, сообщения и процедуры окон, но выполняют разные роли в интерфейсе.

Элементы управления делятся на базовые, и расширенные классы - первые живут в User32.dll, а вторые в Comctl32.dll, что означает "Common Control". К базовым относятся:

• Button - кнопки, чекбоксы, боксы групп​
• Edit - многострочное поле для ввода текста​
• ListBox - простой список (выпадающий или обычный)​
• ComboBox - комбинированный список (поле ввода + выпадающий список)​
• Static - статический текст, рамки, иконки​
• ScrollBar - верт/горизонтальные полосы прокрутки​

Базовые элементы всегда доступны приложению, а вот расширенные нужно подключать уже явно вызовом InitCommonControls(). С полным их списком можно ознакомиться в инклуде fasm'a "comctl32.inc":

C-подобный:
;// Common control window classes

      HOTKEY_CLASS  equ  'msctls_hotkey32'
    PROGRESS_CLASS  equ  'msctls_progress32'
      STATUS_CLASS  equ  'msctls_statusbar32'
    TRACKBAR_CLASS  equ  'msctls_trackbar32'
      UPDOWN_CLASS  equ  'msctls_updown32'

     ANIMATE_CLASS  equ  'SysAnimate32'
DATETIMEPICK_CLASS  equ  'SysDateTimePick32'
      HEADER_CLASS  equ  'SysHeader32'
    LISTVIEW_CLASS  equ  'SysListView32'
    MONTHCAL_CLASS  equ  'SysMonthCal32'
  TABCONTROL_CLASS  equ  'SysTabControl32'
    TREEVIEW_CLASS  equ  'SysTreeView32'

       REBAR_CLASS  equ  'ReBarWindow32'    ;// (контейнер для панели инструментов)
     TOOLBAR_CLASS  equ  'ToolbarWindow32'  ;// (панель инструментов)
    TOOLTIPS_CLASS  equ  'tooltips_class32' ;// (всплывающие подсказки)

Обратите внимание, что классы элементов управления имеют разные префиксы, которые я разделил здесь на блоки - это не случайность, а отражение того, как они были включены в систему. Имена регистрируются в comctl32.dll и жёстко закодированы в ней. Главное различие кроется в истории создания и способе инициализации. Вот краткая сводка различий, причём для подключения последних требуется расширенная(Ex) инициализация InitCommonControlEx():

1. msctls - старая гвардия - некоторые пришли из библиотек мультимедиа, и сохранили исторический префикс.
2. Sys - новая волна - появились ещё в Win98 как часть comctl32.dll.
3. Без префикса - специалисты - появились позже, часто для специфических задач, нет единого стандарта именования

Начиная с версии v6.0 в либе comctl32.dll появились доп.украшательства типа: тени, прозрачность, округлые края, которые описываются в файлах xml. В каждой системной библиотеке должна присутствовать функция DllGetVersion(), чтобы разработчик мог узнать версию подключаемой либы. Однако в некоторых dll этой функции может и не быть, а потому вызывать её следует динамически через GetProcAddress(). Она возвращает паспорт либы в следующую структуру:

C-подобный:
struct DLLVERSIONINFO
  cbSize          dd  sizeof.DLLVERSIONINFO
  dwMajorVersion  dd  0
  dwMinorVersion  dd  0
  dwBuildNumber   dd  0
  dwPlatformID    dd  0
ends

Значит вставка элемента ListView осуществляется через 2 структуры - в первой описываются атрибуты столбцов (кол-во, текст в заголовке), а во-второй свойства строк. Вот прототипы этих структур:

C-подобный:
;// Описание строк
;//---------------------
struct LV_ITEM
  mask        dd  0    ;// флаг запроса элементов (см. LVIF_xx)
  iItem       dd  0    ;// индекс элемента начиная с нуля (первый столбец)
  iSubItem    dd  0    ;// индекс подэлемента, или нуль   (второй и сл.столбцы)
  state       dd  0    ;// состояние (см. LVIS_xx)
  stateMask   dd  0    ;// маска запросов состояния
  pszText     dd  0    ;// указатель на текст элемента
  cchTextMax  dd  0    ;// длина текста
  iImage      dd  0    ;// индекс значка
  lParam      dd  0    ;// специфичное для эл.значение
  iIndent     dd  0    ;// нуль, или значение отступа начиная с 1.
ends

;// Описание столбцов
;//---------------------
struct LV_COLUMN
  mask        dd  0
  fmt         dd  0
  cx          dd  0
  pszText     dd  0
  cchTextMax  dd  0
  iSubItem    dd  0
ends

После того-как структуры заполнены, вызывается SendMessage() с дескриптором элемента ListView, что приводит к отображению соответствующего поля в окне. Другими словами таблица строится по кирпичику при каждом вызове SendMessage(). Если атрибуты столбцов задаются отдельно, то строки обычно заполняются в цикле - в данном случае инфу будем брать из массива SCM, который получили на предыдущем шаге. Вот пример, где закомментированный стиль "EX_GRIDLINES" рисует линии по строкам и столбцам (сетка):

C-подобный:
;//--->>--->>---- Установить расширенный стиль списка "LIST_VIEW"
          invoke  SendMessage,[hLV],LVM_SETEXTENDEDLISTVIEWSTYLE,0,\
                                    LVS_EX_FULLROWSELECT + LVS_EX_HEADERDRAGDROP  ;// + LVS_EX_GRIDLINES

;//--->>--->>---- Вставить столбцы "LV_COLUMN" в список
          mov     [lvc.mask],LVCF_TEXT + LVCF_WIDTH

          mov     [lvc.cx],160            ;// имя сервиса
          mov     [lvc.pszText],colName0
          invoke  SendMessage,[hLV], LVM_INSERTCOLUMN, 0, lvc
          mov     [lvc.cx],145            ;// тип
          mov     [lvc.pszText],colName1
          invoke  SendMessage,[hLV], LVM_INSERTCOLUMN, 1, lvc
          mov     [lvc.cx],93             ;// статус
          mov     [lvc.pszText],colName2
          invoke  SendMessage,[hLV], LVM_INSERTCOLUMN, 2, lvc
          mov     [lvc.cx],410            ;// описание
          mov     [lvc.pszText],colName3
          invoke  SendMessage,[hLV], LVM_INSERTCOLUMN, 3, lvc
          mov     [lvc.cx],-2             ;// пустой столбец
          mov     [lvc.pszText],colName4
          invoke  SendMessage,[hLV], LVM_INSERTCOLUMN, 4, lvc

;//--->>--->>---- Меняем цвет фона и текста ListView
          invoke  SendMessage,[hLV], LVM_SETBKCOLOR,    0, 0x393431
          invoke  SendMessage,[hLV], LVM_SETTEXTCOLOR,  0, 0xeeeeee
          invoke  SendMessage,[hLV], LVM_SETTEXTBKCOLOR,0, 0x393431

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

C-подобный:
proc  ScmDataToListview hDlg
;//--->>--->>---- Заполнить строки списка "ListView" ------
;//------ Циклический вывод служб на консоль
          mov     [lvi.mask],LVIF_TEXT + LVIF_PARAM  ;// LVIF_PARAM для сортировки!
          mov     edi,[lpScBase]
          mov     ecx,[lpServCount]
          and     [count],0
@@:       push    ecx
          mov     eax,[edi+ENUM_SERVICE_STATUS.lpServiceName]
          mov     ebx,[count]
          mov     [lvi.iItem],ebx
          mov     [lvi.lParam],ebx
          mov     [lvi.pszText],eax    ;// имя сервиса
          mov     [lvi.iSubItem],0
          invoke  SendMessage,[hLV], LVM_INSERTITEM, 0, lvi

          and     [lvi.mask],not LVIF_PARAM

          mov     eax,[edi+ENUM_SERVICE_STATUS.dwServiceType]
          mov     esi,typeList

         .if eax=2             ;// вывод типа службы/драйвера по маске
             add esi,4
             .elseif  eax=10h
                      add esi,8
             .elseif  eax=20h
                      add esi,12
             .elseif  eax=110h
                      add esi,16
         .endif

          mov     esi,[esi]
          mov     [lvi.pszText],esi    ;// тип
          mov     [lvi.iSubItem],1
          invoke  SendMessage,[hLV], LVM_SETITEM, 0, lvi

          mov     esi,stateList
          mov     eax,[edi+ENUM_SERVICE_STATUS.dwCurrentState]
         .if eax=4
             inc [countRun]
         .endif
          dec     eax
          shl     eax,2
          add     esi,eax
          mov     esi,[esi]
          mov     [lvi.pszText],esi    ;// статус
          mov     [lvi.iSubItem],2
          invoke  SendMessage,[hLV], LVM_SETITEM, 0, lvi

          mov     eax,[edi+ENUM_SERVICE_STATUS.lpDisplayName]
          mov     [lvi.pszText],eax
          mov     [lvi.iSubItem],3
          invoke  SendMessage,[hLV], LVM_SETITEM, 0, lvi

          add     edi,sizeof.ENUM_SERVICE_STATUS
          inc     [count]
          pop     ecx
          dec     ecx
          jnz     @b

      ;// Строка 'Найдено всего:' в поле(0) StatusBar
          invoke  SendMessage,[hSBar],SB_SETTEXT,SBT_OWNERDRAW +0, 0
      ;// Строка 'Из них активных:' в поле(1) StatusBar
          invoke  SendMessage,[hSBar],SB_SETTEXT,SBT_OWNERDRAW +1, 0

          invoke  VirtualFree,[lpScBase]
          mov     [countRun],0
          ret
endp

Если хочется дополнительно посмотреть, как Win32-интерфейс собирается на практике и почему диалоговый подход иногда сильно упрощает работу с окнами, загляните в статью: Форк софта CPUZ - скан бортового железа.

3. Заключение

Полный исходный код + файл для тестов найдёте в скрепке, а результат работу утилиты выглядит так. Здесь видно, что в окне можно задать режим поиска, имеется сортировка данных по столбцам, а так-же вывод сведений о пользователе и процессоре, которые читаются из переменных окружения системы при помощи GetEnvironmentVariable(). Более того предусмотрена строка статуса в подвале окна, для вставки которой достаточно позвать на помощь CreateStatusWindow(), куда оседает инфа с счётчиком о найденных и активных из общего числа. Обратите внимание на версию либы comctl32.dll - если она старше/равно v6.0, значит графическая подсистема ОС поддерживает доп.плюшки для украшательств окна. Всем удачи, пока!

DrvList.webp

Ссылки по теме:
Описание свойств всех элементов управления
 

Вложения

Последнее редактирование модератором:
  • Нравится
Реакции: Luxkerr
Мы в соцсетях:

Взломай свой первый сервер и прокачай скилл — Начни игру на HackerLab