Драйверы режима ядра: Часть 14: Базовая техника. Синхронизация: Использование объекта — Архив WASM.RU

Все статьи

Драйверы режима ядра: Часть 14: Базовая техника. Синхронизация: Использование объекта — Архив WASM.RU



Предположим, что у нас есть драйвер, собирающий какую-то статистическую информацию и программа управления, получающая и обрабатывающая эту информацию. Как организовать их взаимодействие? В самом простейшем случае надо создать в программе управления таймер и с некой периодичностью (скажем, раз в секунду) забирать у драйвера статистику через DeviceIoControl. За примерами далеко ходить не надо: RegMon, FileMon и т.д. Но что если события, которые отслеживает драйвер (например, создание процесса) происходят относительно редко? Большую часть времени мы будем гонять DeviceIoControl впустую. Увеличение интервала срабатывания таймера (скажем, до 10 секунд), приведет к раздражающим задержкам в работе программы управления. Очевидно, что в данном сценарии инициатором передачи очередной порции информации должен быть драйвер, т.к. о том, что отслеживаемое событие (события) произошло, узнает именно он. Значит, он должен каким-то образом извещать свою программу управления о том, что имеются свежие данные.

Есть два базовых и относительно хорошо документированных способа. Один заключается в асинхронном вызове DeviceIoControl. Пользовательский поток создает объект "событие" и соответствующим образом заполняет структуру OVERLAPPED, указатель на которую передается в последнем параметре. Описатель устройства должен быть открыт с флагом FILE_FLAG_OVERLAPPED. Получив такой запрос, драйвер откладывает его завершение до тех пор, пока не произойдет ожидаемое событие и возвращает STATUS_PENDING. На стороне режима пользователя вызов DeviceIoControl возвращает ошибку ERROR_IO_PENDING и поток должен ждать на объекте "событие". Когда происходит ожидаемое событие, драйвер завершает IRP и сигнализирует об этом пользовательскому потоку, освобождая объект "событие". При этом драйвер должен быть готов к тому, что ему придется обрабатывать несколько таких IRP. Т.е. он должен организовать очередь запросов ввода-вывода ожидающих завершения. Подробно разбирать этот способ мы не будем (см. DDK и MSDN). Второй способ несколько проще и заключается в том, что драйвер и его клиент режима пользователя совместно используют объект "событие". Клиент ждет на объекте и если оно переходит в сигнальное состояние (по указанию драйвера) синхронно вызывает DeviceIoControl, получая от драйвера необходимую информацию.

Итак, мы должны совместно использовать объект "событие". Тут есть несколько вариантов. Можно использовать именованный объект и обращаться к нему по имени. Вспомните, как мы использовали именованный объект "раздел" в Части 8 "Базовая техника: Работа с памятью. Совместно используемый раздел". Очевидный недостаток - этот объект будет виден всем. Поэтому лучше использовать безымянный объект. Обычной и официально документированной практикой здесь является создание объекта "событие" клиентом в режиме пользователя и передача его описателя драйверу через DeviceIoControl. Этим методом мы и воспользуемся. В этом примере мы будем отслеживать создание/удаление процессов.

Дополнительную информацию по теме можно почерпнуть из следующих источников:

  • MSDN Q228785 "OpenEvent Fails in a Non-Administrator Account"
  • MSDN Q176415 "Event.exe Shows How to Share and Signal an Event Object"
  • Пример в DDK \src\general\event\
  • Журнал "The NT Insider" за 2002 год, "Sharing is Caring - Sharing Events Between Kernel and User Mode"


14.1 Общие данные

Начнем с разбора содержимого файла common.inc.


 IOCTL_SET_NOTIFY        equ CTL_CODE(FILE_DEVICE_UNKNOWN, 800h, METHOD_BUFFERED, FILE_WRITE_ACCESS)
 IOCTL_REMOVE_NOTIFY     equ CTL_CODE(FILE_DEVICE_UNKNOWN, 801h, 0, 0)
 IOCTL_GET_PROCESS_DATA  equ CTL_CODE(FILE_DEVICE_UNKNOWN, 802h, METHOD_BUFFERED, FILE_READ_ACCESS)

 IMAGE_FILE_PATH_LEN equ 512

 PROCESS_DATA STRUCT
     bCreate             BOOL        ?
 
     dwProcessId         DWORD       ?
     szProcessPath       CHAR    IMAGE_FILE_PATH_LEN dup(?)

 PROCESS_DATA ENDS

Имеем три управляющих кода: IOCTL_SET_NOTIFY заставляет драйвер начать слежение за созданием/удалением процессов; IOCTL_REMOVE_NOTIFY, соответственно, делает обратное; IOCTL_GET_PROCESS_DATA возвращает в структуре PROCESS_DATA информацию о процессе. Эта информация состоит из идентификатора процесса, флага, определяющего, был ли процесс создан или удален, и полного пути к образу, создавшему процесс.



14.2 Исходный текст программы управления драйвером ProcessMon


 ;@echo off
 ;goto make

 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 ;
 ;  Программа управления драйвером ProcessMon
 ;
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

 .386
 .model flat, stdcall
 option casemap:none

 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 ;                               В К Л Ю Ч А Е М Ы Е    Ф А Й Л Ы
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

 include \masm32\include\windows.inc

 include \masm32\include\kernel32.inc
 include \masm32\include\user32.inc
 include \masm32\include\advapi32.inc
 include \masm32\include\comctl32.inc

 includelib \masm32\lib\kernel32.lib
 includelib \masm32\lib\user32.lib
 includelib \masm32\lib\advapi32.lib
 includelib \masm32\lib\comctl32.lib

 include \masm32\include\winioctl.inc

 include cocomac\ListView.mac
 include \masm32\Macros\Strings.mac

 include ..\common.inc

 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 ;                                      К О Н С Т А Н Т Ы                                           
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

 IDD_MAIN        equ 1000
 IDC_LISTVIEW    equ 1001
 IDI_ICON        equ 1002

 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 ;                     Н Е И Н И Ц И А Л И З И Р О В А Н Н Ы Е    Д А Н Н Ы Е                        
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

 .data?

 g_hInstance     HINSTANCE   ?
 g_hwndDlg       HWND        ?
 g_hwndListView  HWND        ?

 g_hSCManager    HANDLE      ?
 g_hService      HANDLE      ?
 g_hEvent        HANDLE      ?

 g_hDevice       HANDLE      ?

 g_fbExitNow     BOOL        ?

 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 ;                                           К О Д                                                   
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

 .code
 
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 ;                             MyUnhandledExceptionFilter                                            
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 
 MyUnhandledExceptionFilter proc lpExceptionInfo:PTR EXCEPTION_POINTERS
 
 local dwBytesReturned:DWORD
 local _ss:SERVICE_STATUS

     invoke DeviceIoControl, g_hDevice, IOCTL_REMOVE_NOTIFY, \
                     NULL, 0, NULL, 0, addr dwBytesReturned, NULL
 
     mov g_fbExitNow, TRUE
     invoke SetEvent, g_hEvent

     invoke Sleep, 100
 
     invoke CloseHandle, g_hEvent
     invoke CloseHandle, g_hDevice
 
     invoke ControlService, g_hService, SERVICE_CONTROL_STOP, addr _ss
     invoke DeleteService, g_hService
 
     invoke CloseServiceHandle, g_hService
     invoke CloseServiceHandle, g_hSCManager
 
     mov eax, EXCEPTION_EXECUTE_HANDLER
     ret
 
 MyUnhandledExceptionFilter endp
 
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 ;                                     ListViewInsertColumn                                          
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 
 ListViewInsertColumn proc
 
 local lvc:LV_COLUMN
 
     mov lvc.imask, LVCF_TEXT + LVCF_WIDTH 
     mov lvc.pszText, $CTA0("Process")
     mov lvc.lx, 354
     ListView_InsertColumn g_hwndListView, 0, addr lvc
 
     mov lvc.pszText, $CTA0("PID")
     or lvc.imask, LVCF_FMT
     mov lvc.fmt, LVCFMT_RIGHT
     mov lvc.lx, 40
     ListView_InsertColumn g_hwndListView, 1, addr lvc
 
     mov lvc.fmt, LVCFMT_LEFT
     mov lvc.lx, 80
     mov lvc.pszText, $CTA0("State")
     ListView_InsertColumn g_hwndListView, 2, addr lvc
 
     ret
 
 ListViewInsertColumn endp
 
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 ;                                        FillProcessInfo                                            
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 
 FillProcessInfo proc uses esi pProcessData:PTR PROCESS_DATA
 
 local lvi:LV_ITEM
 local buffer[1024]:CHAR
 
     mov esi, pProcessData
     assume esi:ptr PROCESS_DATA
 
     mov lvi.imask, LVIF_TEXT
 
     ListView_GetItemCount g_hwndListView
     mov lvi.iItem, eax
 
     invoke GetLongPathName, addr [esi].szProcessName, addr buffer, sizeof buffer
     .if ( eax == 0 ) || ( eax >= sizeof buffer )
         lea ecx, [esi].szProcessName
     .else
         lea ecx, buffer
     .endif

     and lvi.iSubItem, 0
     mov lvi.pszText, ecx
     ListView_InsertItem g_hwndListView, addr lvi
 
     inc lvi.iSubItem
     invoke wsprintf, addr buffer, $CTA0("%X"), [esi].dwProcessId
     lea eax, buffer
     mov lvi.pszText, eax
     ListView_SetItem g_hwndListView, addr lvi
 
     inc lvi.iSubItem
     .if [esi].bCreate
         mov lvi.pszText, $CTA0("Created")
     .else
         mov lvi.pszText, $CTA0("Destroyed")
     .endif
     ListView_SetItem g_hwndListView, addr lvi
 
     assume esi:nothing
 
     ret
 
 FillProcessInfo endp
 
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 ;                                     WaitForProcessData                                            
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 
 WaitForProcessData proc hEvent:HANDLE
 
 local ProcessData:PROCESS_DATA
 local dwBytesReturned:DWORD
 
     invoke GetCurrentThread
     invoke SetThreadPriority, eax, THREAD_PRIORITY_HIGHEST  
 
     .while TRUE
         invoke WaitForSingleObject, hEvent, INFINITE
         .if eax != WAIT_FAILED
 
             .break .if g_fbExitNow == TRUE
 
             invoke DeviceIoControl, g_hDevice, IOCTL_GET_PROCESS_DATA, NULL, 0, \
                         addr ProcessData, sizeof ProcessData, addr dwBytesReturned, NULL
 
             .if eax != 0
                 invoke FillProcessInfo, addr ProcessData
             .endif
 
         .else
             invoke MessageBox, g_hwndDlg, \
                 $CTA0("Wait for event failed. Thread now exits. Restart application."), \
                 NULL, MB_ICONERROR
             .break
         .endif
     .endw
 
     invoke ExitThread, 0
     ret                         ; Never executed.
 
 WaitForProcessData endp
     
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 ;                            Д И А Л О Г О В А Я    П Р О Ц Е Д У Р А                               
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 
 DlgProc proc hDlg:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
 
 local rect:RECT
 
     mov eax, uMsg
     .if eax == WM_INITDIALOG
 
         push hDlg
         pop g_hwndDlg
 
         invoke LoadIcon, g_hInstance, IDI_ICON
         invoke SendMessage, hDlg, WM_SETICON, ICON_BIG, eax
 
         invoke GetDlgItem, hDlg, IDC_LISTVIEW
         mov g_hwndListView, eax
         invoke SetFocus, g_hwndListView
 
         invoke GetClientRect, hDlg, addr rect
         invoke MoveWindow, g_hwndListView, rect.left, rect.top, rect.right, rect.bottom, FALSE
 
         ListView_SetExtendedListViewStyle g_hwndListView, LVS_EX_GRIDLINES + LVS_EX_FULLROWSELECT
 
         invoke ListViewInsertColumn
 
     .elseif eax == WM_SIZE
 
         mov eax, lParam
         mov ecx, eax
         and eax, 0FFFFh
         shr ecx, 16
         invoke MoveWindow, g_hwndListView, 0, 0, eax, ecx, TRUE
 
     .elseif eax == WM_COMMAND
 
         mov eax, wParam
         and eax, 0FFFFh
         .if eax == IDCANCEL
             invoke MessageBox, hDlg, $CTA0("Sure want to exit?"), \
                     $CTA0("Exit Confirmation"), MB_YESNO + MB_ICONQUESTION + MB_DEFBUTTON1
             .if eax == IDYES
                 invoke EndDialog, hDlg, 0
             .endif
         .endif
 
     .elseif uMsg == WM_GETMINMAXINFO
 
         mov ecx, lParam
         mov (MINMAXINFO PTR [ecx]).ptMinTrackSize.x, 380
         mov (MINMAXINFO PTR [ecx]).ptMinTrackSize.y, 150
 
     .else
 
         xor eax, eax
         ret
     
     .endif
 
     xor eax, eax
     inc eax
     ret
     
 DlgProc endp
 
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 ;                                         start                                                     
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 
 start proc
 
 local acModulePath[MAX_PATH]:CHAR
 local _ss:SERVICE_STATUS
 local dwBytesReturned:DWORD
 
     CTA  "This program was tested on Windows 2000+sp2/sp3/sp4,\n", szExecutionConfirmation
     CTA  "Windows XP no sp, Windows Server 2003 Std and\n"
     CTA  "seems to be workable. But it uses undocumented\n"
     CTA  "tricks in kernel mode and may crash your system\:\n"
     CTA  "\n"
     CTA0 "Are your shure you want to run it?\n"
 
     invoke MessageBox, NULL, addr szExecutionConfirmation, \
         $CTA0("Execution Confirmation"), MB_YESNO + MB_ICONQUESTION + MB_DEFBUTTON2
     .if eax == IDNO
         invoke ExitProcess, 0
     .endif
      
     invoke SetUnhandledExceptionFilter, MyUnhandledExceptionFilter
 
     invoke OpenSCManager, NULL, NULL, SC_MANAGER_ALL_ACCESS
     .if eax != NULL
         mov g_hSCManager, eax
 
         push eax
         invoke GetFullPathName, $CTA0("ProcessMon.sys"), sizeof acModulePath, addr acModulePath, esp
         pop eax
 
         invoke CreateService, g_hSCManager, $CTA0("ProcessMon"), \
             $CTA0("Process creation/destruction monitor"), \
             SERVICE_START + SERVICE_STOP + DELETE, SERVICE_KERNEL_DRIVER, SERVICE_DEMAND_START, \
             SERVICE_ERROR_IGNORE, addr acModulePath, NULL, NULL, NULL, NULL, NULL
 
         .if eax != NULL
             mov g_hService, eax
 
             invoke StartService, g_hService, 0, NULL
             .if eax != 0
 
                 invoke CreateFile, $CTA0("\\\\.\\ProcessMon"), \
                         GENERIC_READ + GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL
 
                 .if eax != INVALID_HANDLE_VALUE
                     mov g_hDevice, eax
 
                     invoke DeleteService, g_hService

                     invoke CreateEvent, NULL, FALSE, FALSE, NULL
                     mov g_hEvent, eax
 
                     and g_fbExitNow, FALSE

                     push eax
                     invoke CreateThread, NULL, 0, offset WaitForProcessData, g_hEvent, 0, esp
                     pop ecx
                     .if eax != NULL
                     
                         invoke CloseHandle, eax                             
 
                         ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 
                         invoke DeviceIoControl, g_hDevice, IOCTL_SET_NOTIFY, \
                                 addr g_hEvent, sizeof g_hEvent, NULL, 0, addr dwBytesReturned, NULL
 
                         .if eax != 0
 
                             invoke GetModuleHandle, NULL
                             mov g_hInstance, eax
                             invoke DialogBoxParam, g_hInstance, IDD_MAIN, NULL, addr DlgProc, 0
 
                             invoke DeviceIoControl, g_hDevice, IOCTL_REMOVE_NOTIFY, \
                                         NULL, 0, NULL, 0, addr dwBytesReturned, NULL
                         .else
                             invoke MessageBox, NULL, \
                                     $CTA0("Can't set notify."), NULL, MB_ICONSTOP
                         .endif
 
                         ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 
                         mov g_fbExitNow, TRUE
                         invoke SetEvent, g_hEvent
                     
                         invoke Sleep, 100
                     
                     .else
                         invoke MessageBox, NULL, $CTA0("Can't create thread."), NULL, MB_ICONSTOP
                     .endif
 
                     invoke CloseHandle, g_hEvent
                     invoke CloseHandle, g_hDevice
                 .else
                     invoke MessageBox, NULL, $CTA0("Can't open device."), NULL, MB_ICONSTOP
                 .endif
                 invoke ControlService, g_hService, SERVICE_CONTROL_STOP, addr _ss
             .else
                 invoke MessageBox, NULL, $CTA0("Can't start driver."), NULL, MB_ICONSTOP
             .endif
 
             invoke DeleteService, g_hService
             invoke CloseServiceHandle, g_hService
 
         .else
             invoke MessageBox, NULL, $CTA0("Can't register driver."), NULL, MB_ICONSTOP
         .endif
         invoke CloseServiceHandle, g_hSCManager
     .else
         invoke MessageBox, NULL, \
             $CTA0("Can't connect to SCM."), NULL, MB_ICONSTOP
     .endif
 
     invoke ExitProcess, 0
     invoke InitCommonControls
     ret
 
 start endp
 
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 ;                                                                                                   
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 
 end start
 
 :make
 
 set exe=ProcessMon
 
 :makerc
 if exist rsrc.obj goto final
     \masm32\bin\rc /v rsrc.rc
     \masm32\bin\cvtres /machine:ix86 rsrc.res
     if errorlevel 0 goto final
         echo.
         pause
         exit
 
 :final
 
 if exist rsrc.res del rsrc.res
 if exist ..\%exe%.exe del ..\%exe%.exe
 
 \masm32\bin\ml /nologo /c /coff %exe%.bat
 \masm32\bin\link /nologo /subsystem:windows %exe%.obj rsrc.obj
 
 del %exe%.obj
 move %exe%.exe ..
 if exist %exe%.exe del %exe%.exe
 
 echo.
 pause



14.3 Разбираем программу управления драйвером


     invoke SetUnhandledExceptionFilter, MyUnhandledExceptionFilter

Устанавливаем обработчик исключений, покрывающий все потоки процесса (подробнее см. Часть 9). При возникновении исключения в любом из потоков процесса наш обработчик попытается привести систему в состояние, предшествовавшее запуску программы.

В Части 9 "Базовая техника: Работа с памятью. Разделяемая память" в определении функции MyUnhandledExceptionFilter была допущена ошибка: функция не принимала параметров, хотя система передает обработчику один параметр - указатель на структуру EXCEPTION_POINTERS. В результате функция не возвращала стек в исходное состояние. Если бы действительно произошло исключение, то по выходе из обработчика из-за некорректного состояния стека произошло бы новое исключение, что повлекло бы за собой повторный его вызов и так до бесконечности. Так что, обратите внимание: функция MyUnhandledExceptionFilter принимает указатель на структуру EXCEPTION_POINTERS.


                     invoke CreateEvent, NULL, FALSE, FALSE, NULL
                     mov g_hEvent, eax

После регистрации и запуска драйвера создаем безымянный объект "событие" с автоматическим сбросом и в не сигнальном состоянии (nonsignaled). Именно этот объект и будет совместно использоваться.

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


                     and g_fbExitNow, FALSE

Явно сбрасываем глобальный флаг, который потребуется для того, чтобы сообщить рабочему потоку о прекращении работы.


                     push eax
                     invoke CreateThread, NULL, 0, offset WaitForProcessData, g_hEvent, 0, esp
                     pop ecx

Создаем рабочий поток. Этот поток будет ждать на описателе g_hEvent и по сигналу забирать у драйвера статистику.


                         invoke DeviceIoControl, g_hDevice, IOCTL_SET_NOTIFY, \
                                 addr g_hEvent, sizeof g_hEvent, NULL, 0, addr dwBytesReturned, NULL

Шлем драйверу управляющий код IOCTL_SET_NOTIFY, передавая ему описатель нашего объекта "событие". Драйвер должен начать слежение за созданием/удалением процессов. При создании или удалении какого-либо процесса, драйвер будет устанавливать объект "событие" в сигнальное состояние.


                         .if eax != 0

                             invoke GetModuleHandle, NULL
                             mov g_hInstance, eax
                             invoke DialogBoxParam, g_hInstance, IDD_MAIN, NULL, addr DlgProc, 0

Если слежение установлено, запускаем диалог. В самой диалоговой процедуре нет ничего интересного.


                             invoke DeviceIoControl, g_hDevice, IOCTL_REMOVE_NOTIFY, \
                                         NULL, 0, NULL, 0, addr dwBytesReturned, NULL

После закрытия диалога заставляем драйвер прекратить слежение.


                         mov g_fbExitNow, TRUE
                         invoke SetEvent, g_hEvent

Устанавливаем флаг g_fbExitNow и переводим объект "событие" в сигнальное состояние. Это пробудит рабочий поток, он увидит установленный флаг и прекратит работу.


                         invoke Sleep, 100

Немного подождем, пока рабочий поток завершается. Хотя он и имеет более высокий приоритет, небольшая задержка не повредит.



14.4 Процедура рабочего потока

Посмотрим теперь на процедуру рабочего потока.


     invoke GetCurrentThread
     invoke SetThreadPriority, eax, THREAD_PRIORITY_HIGHEST

Повышаем приоритет потока, т.к. если у драйвера появятся свежие данные, хотелось бы не упустить этот момент. Дело в том, что драйвер (для упрощения примера) хранит информацию только об одном последнем созданном/удаленном процессе. Учитывая то, что это происходит относительно редко, вряд ли мы пропустим этот момент, даже не повышая приоритет. И тем не менее.


     .while TRUE
         invoke WaitForSingleObject, hEvent, INFINITE
         .if eax != WAIT_FAILED

В бесконечном цикле ждем на объекте "событие".


             .break .if g_fbExitNow == TRUE

Если пора выходить, прерываем цикл.


             invoke DeviceIoControl, g_hDevice, IOCTL_GET_PROCESS_DATA, NULL, 0, \
                         addr ProcessData, sizeof ProcessData, addr dwBytesReturned, NULL

Забираем у драйвера свежие данные.


             .if eax != 0
                 invoke FillProcessInfo, addr ProcessData
             .endif

Выводим, полученную от драйвера информацию.



14.5 Функция FillProcessInfo

Случается, что драйвер возвращает "короткий" путь. Например, "C:\PROGRA~1\WinZip\WinZip32.EXE". Детально в причинах этого явления я не разбирался. Видимо, если при создании процесса его родитель запускает его (точнее его исполнимый образ) по "короткому" пути, то этот путь и попадает в соответствующее поле FILE_OBJECT. Мы просто вызовем функцию GetLongPathName, которая всё сделает сама. Если буфера размером 1024 байт, вдруг окажется недостаточно, то просто покажем тот путь, который вернул драйвер. Для простоты, я не стал выделять дополнительную память и вызывать GetLongPathName снова.


 local buffer[1024]:CHAR

 . . .
 
     invoke GetLongPathName, addr [esi].szProcessName, addr buffer, sizeof buffer
     .if ( eax == 0 ) || ( eax >= sizeof buffer )
         lea ecx, [esi].szProcessName
     .else
         lea ecx, buffer
     .endif

     and lvi.iSubItem, 0
     mov lvi.pszText, ecx
     ListView_InsertItem g_hwndListView, addr lvi



14.6 Исходный текст драйвера ProcessMon

Модуль ProcessMon.bat


 ;@echo off
 ;goto make
 
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 ;
 ;  ProcessMon - Пример того, как драйвер может сообщить
 ;                  режиму пользователя о наступлении интересующего его события и не только это.
 ;
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 
 .386
 .model flat, stdcall
 option casemap:none
 
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 ;                               В К Л Ю Ч А Е М Ы Е    Ф А Й Л Ы
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 
 include \masm32\include\w2k\ntstatus.inc
 include \masm32\include\w2k\ntddk.inc
 include \masm32\include\w2k\ntoskrnl.inc
 
 includelib \masm32\lib\w2k\ntoskrnl.lib
 
 include \masm32\Macros\Strings.mac

 include ..\common.inc
 include ProcPath.asm

 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 ;                             Н Е И З М Е Н Я Е М Ы Е    Д А Н Н Ы Е                                
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 
 .const
 
 CCOUNTED_UNICODE_STRING "\\Device\\ProcessMon", g_usDeviceName, 4
 CCOUNTED_UNICODE_STRING "\\DosDevices\\ProcessMon", g_usSymbolicLinkName, 4
 
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 ;                     Н Е И Н И Ц И А Л И З И Р О В А Н Н Ы Е    Д А Н Н Ы Е                        
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 
 .data?
 
 g_pkEventObject         PKEVENT         ?
 g_dwProcessNameOffset   DWORD           ?
 g_fbNotifyRoutineSet    BOOL            ?
 
 g_ProcessData           PROCESS_DATA    <>
 
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 ;                                           К О Д                                                   
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 
 .code
 
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 ;                                   DispatchCreateClose                                             
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 
 DispatchCreateClose proc pDeviceObject:PDEVICE_OBJECT, pIrp:PIRP
 
     mov ecx, pIrp
     mov (_IRP PTR [ecx]).IoStatus.Status, STATUS_SUCCESS
     and (_IRP PTR [ecx]).IoStatus.Information, 0
 
     fastcall IofCompleteRequest, ecx, IO_NO_INCREMENT
 
     mov eax, STATUS_SUCCESS
     ret
 
 DispatchCreateClose endp
 
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 ;                                     ProcessNotifyRoutine                                          
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 
 ProcessNotifyRoutine proc dwParentId:DWORD, dwProcessId:DWORD, bCreate:BOOL ; BOOLEAN
 
 local peProcess:PVOID               ; PEPROCESS
 local fbDereference:BOOL
 local us:UNICODE_STRING
 local as:ANSI_STRING
 
     push eax
     invoke PsLookupProcessByProcessId, dwProcessId, esp
     pop peProcess
     .if eax == STATUS_SUCCESS
         mov fbDereference, TRUE
     .else
         invoke IoGetCurrentProcess
         mov peProcess, eax
         and fbDereference, FALSE
     .endif
 
     mov eax, dwProcessId
     mov g_ProcessData.dwProcessId, eax
 
     mov eax, bCreate
     mov g_ProcessData.bCreate, eax
 
     invoke memset, addr g_ProcessData.szProcessName, 0, IMAGE_FILE_PATH_LEN
 
     invoke GetImageFilePath, peProcess, addr us
     .if eax == STATUS_SUCCESS
 
         lea eax, g_ProcessData.szProcessName
         mov as.Buffer,          eax
         mov as.MaximumLength,   IMAGE_FILE_PATH_LEN
         and as._Length,         0
 
         invoke RtlUnicodeStringToAnsiString, addr as, addr us, FALSE
 
         invoke ExFreePool, us.Buffer
     .else
 
         mov eax, g_dwImageFileNameOffset
         .if eax != 0
             add eax, peProcess
             invoke memcpy, addr g_ProcessData.szProcessName, eax, 16
         .endif
 
     .endif
 
     .if fbDereference
         fastcall ObfDereferenceObject, peProcess
     .endif
 
     invoke KeSetEvent, g_pkEventObject, 0, FALSE
 
     ret
 
 ProcessNotifyRoutine endp
 
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 ;                                     DispatchControl                                               
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 
 DispatchControl proc uses esi edi pDeviceObject:PDEVICE_OBJECT, pIrp:PIRP
 
 local liDelayTime:LARGE_INTEGER
 
     mov esi, pIrp
     assume esi:ptr _IRP
 
     mov [esi].IoStatus.Status, STATUS_UNSUCCESSFUL
     and [esi].IoStatus.Information, 0
 
     IoGetCurrentIrpStackLocation esi
     mov edi, eax
     assume edi:ptr IO_STACK_LOCATION
 
     .if [edi].Parameters.DeviceIoControl.IoControlCode == IOCTL_SET_NOTIFY
         .if [edi].Parameters.DeviceIoControl.InputBufferLength >= sizeof HANDLE
 
             .if g_fbNotifyRoutineSet == FALSE
 
                 mov edx, [esi].AssociatedIrp.SystemBuffer
                 mov edx, [edx]
 
                 mov ecx, ExEventObjectType
                 mov ecx, [ecx]
                 mov ecx, [ecx]
 
                 invoke ObReferenceObjectByHandle, edx, EVENT_MODIFY_STATE, ecx, \
                                         UserMode, addr g_pkEventObject, NULL
                 .if eax == STATUS_SUCCESS
 
                     invoke PsSetCreateProcessNotifyRoutine, ProcessNotifyRoutine, FALSE
                     mov [esi].IoStatus.Status, eax
 
                     .if eax == STATUS_SUCCESS
 
                         mov g_fbNotifyRoutineSet, TRUE
 
                         invoke DbgPrint, \
                                 $CTA0("ProcessMon: Notification was set\n")
     
                         mov eax, pDeviceObject
                         mov eax, (DEVICE_OBJECT PTR [eax]).DriverObject
                         and (DRIVER_OBJECT PTR [eax]).DriverUnload, NULL
 
                     .else
                         invoke DbgPrint, \
                         $CTA0("ProcessMon: Couldn't set notification\n")
                     .endif
 
                 .else
                     mov [esi].IoStatus.Status, eax
                     invoke DbgPrint, \
                     $CTA0("ProcessMon: Couldn't reference user event object. Status: %08X\n"), \
                     eax
                 .endif
             .endif
         .else
             mov [esi].IoStatus.Status, STATUS_BUFFER_TOO_SMALL
         .endif
 
     .elseif [edi].Parameters.DeviceIoControl.IoControlCode == IOCTL_REMOVE_NOTIFY
 
         .if g_fbNotifyRoutineSet == TRUE
 
             invoke PsSetCreateProcessNotifyRoutine, ProcessNotifyRoutine, TRUE
             mov [esi].IoStatus.Status, eax
 
             .if eax == STATUS_SUCCESS
 
                 and g_fbNotifyRoutineSet, FALSE
 
                 invoke DbgPrint, $CTA0("ProcessMon: Notification was removed\n")
 
                 or liDelayTime.HighPart, -1
                 mov liDelayTime.LowPart, -500000
     
                 invoke KeDelayExecutionThread, KernelMode, FALSE, addr liDelayTime
 
                 mov eax, pDeviceObject
                 mov eax, (DEVICE_OBJECT PTR [eax]).DriverObject
                 mov (DRIVER_OBJECT PTR [eax]).DriverUnload, offset DriverUnload
 
                 .if g_pkEventObject != NULL
                     invoke ObDereferenceObject, g_pkEventObject
                     and g_pkEventObject, NULL
                 .endif
             .else
                 invoke DbgPrint, \
                 $CTA0("ProcessMon: Couldn't remove notification\n")
             .endif
             
         .endif
 
     .elseif [edi].Parameters.DeviceIoControl.IoControlCode == IOCTL_GET_PROCESS_DATA
         .if [edi].Parameters.DeviceIoControl.OutputBufferLength >= sizeof PROCESS_DATA
 
             mov eax, [esi].AssociatedIrp.SystemBuffer
             invoke memcpy, eax, offset g_ProcessData, sizeof g_ProcessData
     
             mov [esi].IoStatus.Status, STATUS_SUCCESS
             mov [esi].IoStatus.Information, sizeof g_ProcessData
 
         .else
             mov [esi].IoStatus.Status, STATUS_BUFFER_TOO_SMALL
         .endif
 
     .else
         mov [esi].IoStatus.Status, STATUS_INVALID_DEVICE_REQUEST
     .endif
 
     push [esi].IoStatus.Status
     
     assume edi:nothing
     assume esi:nothing
 
     fastcall IofCompleteRequest, esi, IO_NO_INCREMENT
 
     pop eax
     ret
 
 DispatchControl endp
 
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 ;                                       DriverUnload                                                
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 
 DriverUnload proc pDriverObject:PDRIVER_OBJECT
 
     invoke IoDeleteSymbolicLink, addr g_usSymbolicLinkName
 
     mov eax, pDriverObject
     invoke IoDeleteDevice, (DRIVER_OBJECT PTR [eax]).DeviceObject
 
     ret
 
 DriverUnload endp
 
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 ;                              D I S C A R D A B L E   C O D E                                      
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 
 .code INIT
 
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 ;                                    GetImageFileNameOffset                                         
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 
 GetImageFileNameOffset proc uses esi ebx
 
     invoke IoGetCurrentProcess
     mov esi, eax
 
     xor ebx, ebx
     .while ebx < 1000h
         lea eax, [esi+ebx]
         invoke _strnicmp, eax, $CTA0("system"), 6
         .break .if eax == 0
         inc ebx
     .endw
 
     .if eax == 0
         mov eax, ebx
     .else
         xor eax, eax
     .endif
 
     ret
 
 GetImageFileNameOffset endp
 
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 ;                                       DriverEntry                                                 
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 
 DriverEntry proc pDriverObject:PDRIVER_OBJECT, pusRegistryPath:PUNICODE_STRING
 
 local status:NTSTATUS
 local pDeviceObject:PDEVICE_OBJECT
 
     mov status, STATUS_DEVICE_CONFIGURATION_ERROR
 
     invoke IoCreateDevice, pDriverObject, 0, addr g_usDeviceName, \
                 FILE_DEVICE_UNKNOWN, 0, TRUE, addr pDeviceObject
     .if eax == STATUS_SUCCESS
         invoke IoCreateSymbolicLink, addr g_usSymbolicLinkName, addr g_usDeviceName
         .if eax == STATUS_SUCCESS
             mov eax, pDriverObject
             assume eax:ptr DRIVER_OBJECT
             mov [eax].MajorFunction[IRP_MJ_CREATE*(sizeof PVOID)],          offset DispatchCreateClose
             mov [eax].MajorFunction[IRP_MJ_CLOSE*(sizeof PVOID)],           offset DispatchCreateClose
             mov [eax].MajorFunction[IRP_MJ_DEVICE_CONTROL*(sizeof PVOID)],  offset DispatchControl
             mov [eax].DriverUnload,                                         offset DriverUnload
             assume eax:nothing
 
             and g_fbNotifyRoutineSet, FALSE
             invoke memset, addr g_ProcessData, 0, sizeof g_ProcessData
         
             invoke GetImageFileNameOffset
             mov g_dwImageFileNameOffset, eax
 
             mov status, STATUS_SUCCESS
         .else
             invoke IoDeleteDevice, pDeviceObject
         .endif
     .endif
 
     mov eax, status
     ret
 
 DriverEntry endp
 
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 ;                                                                                                   
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 
 end DriverEntry
 
 :make
 
 set drv=ProcessMon
 
 \masm32\bin\ml /nologo /c /coff %drv%.bat
 \masm32\bin\link /nologo /driver /base:0x10000 /align:32 /out:%drv%.sys /subsystem:native /ignore:4078 %drv%.obj
 
 del %drv%.obj
 move %drv%.sys ..
 
 echo.
 pause

Модуль ProcPath.asm


 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 ;                                      С Т Р У К Т У Р Ы                                            
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 
 OBJECT_HEADER STRUCT                        ; sizeof = 018h
     PointerCount            SDWORD      ?   ; 0000h
     union
         HandleCount         SDWORD      ?   ; 0004h
         SEntry              PVOID       ?   ; 0004h PTR SINGLE_LIST_ENTRY
     ends
     _Type                   PVOID       ?   ; 0008h PTR OBJECT_TYPE
     NameInfoOffset          BYTE        ?   ; 000Ch
     HandleInfoOffset        BYTE        ?   ; 000Dh
     QuotaInfoOffset         BYTE        ?   ; 000Eh
     Flags                   BYTE        ?   ; 000Fh
     union
         ObjectCreateInfo    PVOID       ?   ; 0010h PTR OBJECT_CREATE_INFORMATION
         QuotaBlockCharged   PVOID       ?   ; 0010h
     ends
     SecurityDescriptor      PVOID       ?   ; 0014h
 ;   Body                    QUAD        <>  ; 0018h
 OBJECT_HEADER ENDS
 
 _SEGMENT STRUCT                             ; sizeof = 40h
     ControlArea             PVOID       ?   ; 000 PTR CONTROL_AREA
     SegmentBaseAddress      PVOID       ?   ; 004
     TotalNumberOfPtes       DWORD       ?   ; 008
     NonExtendedPtes         DWORD       ?   ; 00C
     SizeOfSegment           QWORD       ?   ; 010 ULONG64
     ImageCommitment         DWORD       ?   ; 018
     ImageInformation        PVOID       ?   ; 01C PTR SECTION_IMAGE_INFORMATION
     SystemImageBase         PVOID       ?   ; 020
     NumberOfCommittedPages  DWORD       ?   ; 024
     SegmentPteTemplate      DWORD       ?   ; 028 MMPTE
     BasedAddress            PVOID       ?   ; 02C
     ExtendInfo              PVOID       ?   ; 030 PTR MMEXTEND_INFO
     PrototypePte            PVOID       ?   ; 034 PTR MMPTE
     ThePtes                 DWORD 1 dup(?)  ; 038 array of MMPTE
 _SEGMENT ENDS
 
 CONTROL_AREA STRUCT                             ; sizeof = 38h
     _Segment                    PVOID       ?   ; 000 PTR _SEGMENT
     DereferenceList             LIST_ENTRY  <>  ; 004
     NumberOfSectionReferences   DWORD       ?   ; 00C
     NumberOfPfnReferences       DWORD       ?   ; 010
     NumberOfMappedViews         DWORD       ?   ; 014
     NumberOfSubsections         WORD        ?   ; 018
     FlushInProgressCount        WORD        ?   ; 01A
     NumberOfUserReferences      DWORD       ?   ; 01C
     union u
         LongFlags               DWORD       ?   ; 020
         Flags                   DWORD       ?   ; 020 MMSECTION_FLAGS
     ends
     FilePointer                 PVOID       ?   ; 024 PTR FILE_OBJECT
     WaitingForDeletion          PVOID       ?   ; 028 PTR EVENT_COUNTER
     ModifiedWriteCount          WORD        ?   ; 02C
     NumberOfSystemCacheViews    WORD        ?   ; 02E
     PagedPoolUsage              DWORD       ?   ; 030
     NonPagedPoolUsage           DWORD       ?   ; 034
 CONTROL_AREA ENDS
 
 MMADDRESS_NODE STRUCT                           ; sizeof = 14h
     StartingVpn                 DWORD       ?   ; 00 ULONG_PTR
     EndingVpn                   DWORD       ?   ; 04 ULONG_PTR
     Parent                      PVOID       ?   ; 08 PTR MMADDRESS_NODE
     LeftChild                   PVOID       ?   ; 0C PTR MMADDRESS_NODE
     RightChild                  PVOID       ?   ; 10 PTR MMADDRESS_NODE
 MMADDRESS_NODE ENDS
 PMMADDRESS_NODE typedef ptr MMADDRESS_NODE
 
 SECTION STRUCT                                      ; sizeof = 28h
     Address                     MMADDRESS_NODE  <>  ; 00
     _Segment                    PVOID           ?   ; 14 PTR _SEGMENT
     SizeOfSection               LARGE_INTEGER   <>  ; 18
     union u
         LongFlags               DWORD           ?   ; 20
         Flags                   DWORD           ?   ; 20 MMSECTION_FLAGS
     ends
     InitialPageProtection       DWORD           ?   ; 24
 SECTION ENDS
 PSECTION typedef ptr SECTION
 
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 ;                                      К О Н С Т А Н Т Ы                                            
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 
 WINVER_UNINITIALIZED    equ -1
 WINVER_2K               equ 0
 WINVER_XP_OR_HIGHER     equ 1
 
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 ;                 И Н И Ц И А Л И З И Р О В А Н Н Ы Е    Д А Н Н Н Ы Е                              
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 
 .data
 
 g_dwWinVer  DWORD   WINVER_UNINITIALIZED
 
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 ;                                           К О Д                                                   
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 
 .code
 
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 ;                                  IsAddressInPoolRanges                                            
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 
 IsAddressInPoolRanges proc uses ebx pAddress:PVOID
 
 local fOk:BOOL
 
     and fOk, FALSE
 
     mov eax, MmSystemRangeStart
     mov eax, [eax]
     mov eax, [eax]
 
     .if eax == 80000000h
              
         mov ebx, pAddress
 
         xor ecx, ecx
         xor edx, edx
 
         .if ( ebx > 80000000h ) && ( ebx < 0A0000000h )
             inc ecx
         .endif
 
         .if ( ebx > 0E1000000h ) && ( ebx < 0FFBE0000h )
             inc edx
         .endif
 
         or ecx, edx
         .if !ZERO?
             mov fOk, TRUE   ; OK
         .endif
 
     .endif
 
     mov eax, fOk
     ret
 
 IsAddressInPoolRanges endp
 
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 ;                                     IsLikeObjectPointer                                           
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 
 IsLikeObjectPointer proc uses esi pObject:PVOID
 
 local fOk:BOOL
 
     and fOk, FALSE
 
     mov esi, pObject
 
     invoke IsAddressInPoolRanges, esi
     .if eax == TRUE
 
         mov eax, esi
         and eax, (8 - 1)
         .if eax == 0
                 
             invoke MmIsAddressValid, esi
             .if al
 
                 mov eax, esi
                 and eax, (PAGE_SIZE-1)
                 .if eax < sizeof OBJECT_HEADER
 
                     sub esi, sizeof OBJECT_HEADER
     
                     invoke MmIsAddressValid, esi
                     .if al
 
                         mov fOk, TRUE
 
                     .endif
 
                 .else
                 
                     mov fOk, TRUE
 
                 .endif
             .endif
         .endif
     .endif
 
     mov eax, fOk
     ret
 
 IsLikeObjectPointer endp
 
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 ;                                     GetImageFilePath                                              
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 
 GetImageFilePath proc uses ebx esi edi peProcess:PVOID, pusImageFilePath:PUNICODE_STRING
 
 local status:NTSTATUS
 local pSection:PVOID            ; PTR SECTION
 local usDosName:UNICODE_STRING
 
     mov status, STATUS_UNSUCCESSFUL
 
     PROCESS_QUERY_INFORMATION equ 400h  ; winnt.inc
 
     mov ecx, PsProcessType
     mov ecx, [ecx]
     mov ecx, [ecx]
 
     invoke ObReferenceObjectByPointer, peProcess, PROCESS_QUERY_INFORMATION, ecx, UserMode
     .if eax == STATUS_SUCCESS
 
         .if g_dwWinVer == WINVER_UNINITIALIZED
 
             invoke IoIsWdmVersionAvailable, 1, 20h
             .if al
                 mov g_dwWinVer, WINVER_XP_OR_HIGHER
             .else
                 mov g_dwWinVer, WINVER_2K
             .endif
 
         .endif
 
         .if g_dwWinVer == WINVER_XP_OR_HIGHER
 
             mov esi, peProcess
             mov ebx, 80h            ; Start at offset 80h
             .while ebx < 204h
 
                 ; Filter unreasonable candidates
 
                 mov edi, [esi][ebx]
                 invoke IsLikeObjectPointer, edi
                 .if eax == TRUE
 
                     mov eax, edi
                     sub eax, sizeof OBJECT_HEADER
 
                     .if ([OBJECT_HEADER PTR [eax]].PointerCount <= 4)
                     .if ([OBJECT_HEADER PTR [eax]].HandleCount <= 1)
 
                         mov ecx, MmSectionObjectType
                         mov ecx, [ecx]
                         mov ecx, [ecx]
 
                         invoke ObReferenceObjectByPointer, edi, SECTION_QUERY, ecx, UserMode
                         .if eax == STATUS_SUCCESS
 
                             mov status, eax
                             mov pSection, edi
 
                              .break
                         .endif
                     .endif
                     .endif
                 .endif
 
                 add ebx, 4

             .endw
 
         .else
 
             xor ebx, ebx
             mov edi, 4
             .while ebx < 3
 
                 invoke IoGetCurrentProcess
                 .if eax == peProcess
                     
                     mov ecx, MmSectionObjectType
                     mov ecx, [ecx]
                     mov ecx, [ecx]  ; PTR OBJECT_TYPE
                 
                     invoke ObReferenceObjectByHandle, edi, SECTION_QUERY, ecx, KernelMode, addr pSection, NULL
                     mov status, eax
 
                 .else
 
                     invoke KeAttachProcess, peProcess
 
                     mov ecx, MmSectionObjectType
                     mov ecx, [ecx]
                     mov ecx, [ecx]
 
                     invoke ObReferenceObjectByHandle, edi, SECTION_QUERY, ecx, KernelMode, addr pSection, NULL
                     mov status, eax
 
                     invoke KeDetachProcess
 
                 .endif
 
                 .break .if status == STATUS_SUCCESS
                     
                 .if ebx == 0
                     
                     mov edi, 03F8h  ; Try 03F8h handle.
                         
                 .elseif ebx == 1
                     
                     mov eax, peProcess
                     add eax, 01ACh
                     mov eax, [eax]
                     mov edi, eax
 
                     and eax, (4 - 1)
                     .break .if ( eax != 0 ) || ( edi >= 800h )
                         
                 .endif
                 
                 inc ebx
             .endw
 
         .endif
 
         .if status == STATUS_SUCCESS
 
             mov status, STATUS_UNSUCCESSFUL
 
             mov ebx, pSection
             mov ebx, (SECTION PTR [ebx])._Segment
 
             invoke IsAddressInPoolRanges, ebx
             push eax
             invoke MmIsAddressValid, ebx
             pop ecx
             .if al && ( ecx == TRUE )
 
                 mov esi, ebx
 
                 mov ebx, (_SEGMENT PTR [ebx]).ControlArea
 
                 invoke IsAddressInPoolRanges, ebx
                 push eax
                 invoke MmIsAddressValid, ebx
                 pop ecx
                 .if al && ( ecx == TRUE ) && ([CONTROL_AREA PTR [ebx]]._Segment == esi )
 
                     mov ebx, (CONTROL_AREA PTR [ebx]).FilePointer
 
                     invoke IsLikeObjectPointer, ebx
                     .if eax == TRUE
 
                         mov ecx, IoFileObjectType
                         mov ecx, [ecx]
                         mov ecx, [ecx]          ; PTR OBJECT_TYPE
 
                         invoke ObReferenceObjectByPointer, ebx, FILE_READ_ATTRIBUTES, ecx, UserMode
                         .if eax == STATUS_SUCCESS
 
                             invoke ExAllocatePool, PagedPool, (IMAGE_FILE_PATH_LEN+1) * sizeof WCHAR
                             .if eax != NULL
 
                                 mov edi, pusImageFilePath
                                 assume edi:ptr UNICODE_STRING
 
                                 mov [edi].Buffer, eax
 
                                 invoke memset, eax, 0, (IMAGE_FILE_PATH_LEN+1) * sizeof WCHAR
 
                                 mov [edi].MaximumLength,    IMAGE_FILE_PATH_LEN * sizeof WCHAR
                                 and [edi]._Length,          0

                                 invoke RtlVolumeDeviceToDosName, \
                                         (FILE_OBJECT PTR [ebx]).DeviceObject, addr usDosName
                                 .if eax == STATUS_SUCCESS
  
                                     invoke RtlCopyUnicodeString, edi, addr usDosName
                                     invoke ExFreePool, usDosName.Buffer

                                 .endif
 
                                 invoke RtlAppendUnicodeStringToString, edi, \
                                                 addr (FILE_OBJECT PTR [ebx]).FileName
 
                                 assume edi:nothing
 
                                 mov status, STATUS_SUCCESS
 
                             .endif
 
                             invoke ObDereferenceObject, ebx
                         .endif
                     .endif
                 .endif
             .endif
 
             invoke ObDereferenceObject, pSection
         .endif
 
         invoke ObDereferenceObject, peProcess
     .endif
 
     mov eax, status
     ret
 
 GetImageFilePath endp



14.7 Процедура DriverEntry

Кроме всего прочего в процедуре DriverEntry нам надо получить смещение поля ImageFileName структуры EPROCESS.


 EPROCESS STRUCT
 . . .
     ImageFileName BYTE 16 dup(?)
 . . .
 EPROCESS ENDS

ImageFileName хранит имя образа, создавшего процесс. Как видите, это поле позволяет хранить всего 16 символов. Более длинные имена усекаются до 16 символов и, в этом случае, завершающий ноль не используется. Содержимое этого поля мы будем копировать в нашу структуру PROCESS_DATA, но только в том случае, если не удастся получить полный путь к образу. Положение этого поля в структуре EPROCESS отличается на разных версиях системы (для w2k, wxp и w2k3 оно равно 01FCh, 0174h и 0154h, соответственно), но есть очень простой и хорошо известный способ его найти. Для этого надо просканировать структуру EPROCESS процесса System и найти там строку "System" - это и будет поле ImageFileName. Разумеется, этот трюк будет работать только в том случае, если это поле преднамеренно кем-то не изменено. В последнее время стало модно залезать в ядро и вытворять там всякие глупости. Будем считать, что наша система девственно чиста. В противном случае придется изобретать способ похитрее.

Итак, сейчас мы в процедуре DriverEntry, т.е. в контексте процесса System.


             invoke GetImageFileNameOffset
             mov g_dwImageFileNameOffset, eax

Вызываем процедуру GetImageFileNameOffset, которая вернет нам смещение поля ImageFileName от начала структуры EPROCESS или ноль, если не сможет его найти. Процедура GetImageFileNameOffset проста и делает следующее.


     invoke IoGetCurrentProcess
     mov esi, eax

Получаем указатель на структуры EPROCESS текущего процесса, т.е. процесса System.


     xor ebx, ebx
     .while ebx < 1000h
         lea eax, [esi+ebx]
         invoke _strnicmp, eax, $CTA0("system"), 6
         .break .if eax == 0
         inc ebx
     .endw

Ищем в первой странице от начала строку "system", причем используем функцию _strnicmp, которая сравнивает строго определенное количество символов (6 в данном случае) и не учитывает их регистр.


     .if eax == 0
         mov eax, ebx
     .else
         xor eax, eax
     .endif
 
     ret
 

Если такая строка найдена, то это поле ImageFileName - возвращаем его смещение или ноль в случае неудачи.



14.8 Обрабатываем IOCTL_SET_NOTIFY

Получив управляющий код IOCTL_SET_NOTIFY, мы должны установить слежение за созданием/удалением процессов.


             .if g_fbNotifyRoutineSet == FALSE

На всякий случай, проверим флаг g_fbNotifyRoutineSet - возможно слежение уже установлено.


                 mov edx, [esi].AssociatedIrp.SystemBuffer
                 mov edx, [edx]

Извлекаем, переданный нам из режима пользователя описатель объекта "событие".

В дальнейшем мы будем оперировать указателем на объект, а не описателем, как и полагается драйверам. Поэтому мы должны этот указатель получить и заодно проверить, действительно ли переданное нам число является описателем объекта "событие". Проверка любых данных полученных из режима пользователя - это обязательное условие для стабильной работы любого драйвера. Обе эти задачи, в данном случае, можно решить одним вызовом функции ObReferenceObjectByHandle. Для этого нам потребуется указатель на структуру OBJECT_TYPE, описывающую объект "тип" (object type) (в данном случае он типизирует объекты "событие"). Чтобы понять, что это такое сделаем небольшое лирическое отступление в сторону мира объектов Windows (более подробно см. книгу Свена Шрайбера "Недокументированные возможности Windows 2000").



14.9 Объект "тип"

Итак, что такое объект "тип"? Этот объект описывает общие характеристики для всех объектов какого-то типа. Всего в Windows 2000 существует 27 типов объектов (в xp и w2k3 их уже 29). Запустив WinObjEx (см. в разделе "ИНСТРУМЕНТЫ > Уголок NT+" wasm.ru) и раскрыв каталог \ObjectTypes мы увидим следующую картину:

Рис. 14-1. Объекты "тип" в пространстве имен диспетчера объектов

Это типы объектов, существующие в системе. Щелкнув на любом из них можно получить кое-какую дополнительную информацию. Каждому такому объекту соответствует структура OBJECT_TYPE.


 OBJECT_TYPE_INITIALIZER STRUCT                       ; sizeof = 04Ch
      _Length                     WORD        ?       ; 0000h
      UseDefaultObject            BYTE        ?       ; 0002h
      Reserved                    BYTE        ?       ; 0003h
      InvalidAttributes           DWORD       ?       ; 0004h
      GenericMapping              GENERIC_MAPPING <>  ; 0008h
      ValidAccessMask             DWORD       ?       ; 0018h
      SecurityRequired            BYTE        ?       ; 001Ch
      MaintainHandleCount         BYTE        ?       ; 001Dh
      MaintainTypeList            BYTE        ?       ; 001Eh
                                  db 1 dup(?)         ; padding
      PoolType                    SDWORD      ?       ; 0020h
      DefaultPagedPoolCharge      DWORD       ?       ; 0024h
      DefaultNonPagedPoolCharge   DWORD       ?       ; 0028h
      . . .
  OBJECT_TYPE_INITIALIZER ENDS

  OBJECT_TYPE STRUCT                                  ; sizeof = 0B0h
      Mutex                       ERESOURCE       <>  ; 0000h
      TypeList                    LIST_ENTRY      <>  ; 0038h
      _Name                       UNICODE_STRING  <>  ; 0040h
      DefaultObject               PVOID           ?   ; 0048h
      Index                       DWORD           ?   ; 004Ch
      TotalNumberOfObjects        DWORD           ?   ; 0050h
      TotalNumberOfHandles        DWORD           ?   ; 0054h
      HighWaterNumberOfObjects    DWORD           ?   ; 0058h
      HighWaterNumberOfHandles    DWORD           ?   ; 005Ch
      TypeInfo                    OBJECT_TYPE_INITIALIZER <>  ; 0060h
      Key                         DWORD           ?   ; 00ACh
  OBJECT_TYPE ENDS

Когда системе требуется создать новый объект, она обращается к структуре, соответствующей этому типу объектов. Адреса структур некоторых объектов "тип" экспортируются. Например, для объектов типа WindowStation адрес структуры OBJECT_TYPE, описывающей все объекты этого типа, хранится в экспортируемой переменной ядра ExWindowStationObjectType, а для объектов Event - ExEventObjectType.


                 pushad
                 mov ecx, ExEventObjectType
                 mov ecx, [ecx]
                 mov ecx, [ecx]
                 invoke DbgPrint, $CTA0("\nExEventObjectType: %08X\n"), ecx
                 popad

Добавив в драйвер вышеприведенный код, можно получить адрес структуры OBJECT_TYPE для объектов "событие". Для меня этот адрес оказался равным 8188C200h. Зная, что на машинах с объемом оперативной памяти 128 и более (для xp 256 и более, хотя это не точные данные) мегабайт ядро отображается в большие (по 4Мб) страницы, и зная, что на виртуальные адреса 80000000h - 9FFFFFFFh отображается физическая память по адресам 00000000h - 01FFFFFFFh мы можем воспользоваться услугами PhysMemBrowser (входит в состав KmdKit). Я довольно часто им пользуюсь, т.к. иногда это значительно быстрее и удобнее чем использовать отладчик.

Итак, на виртуальный адрес 8188C200h отображен физический адрес 0188C200h. Вводим это значение в PhysMemBrowser и получаем дамп структуры OBJECT_TYPE для объектов типа "событие":

Рис. 14-2. Дамп объекта "тип"

По адресу E1007C48h можно обнаружить строку "Event", т.е. имя объекта "тип". Значения TotalNumberOfObjects (общее кол-во объектов типа "событие" в системе), TotalNumberOfHandles (общее кол-во открытых описателей объектов типа "событие" в системе), HighWaterNumberOfObjects (пиковое кол-во объектов типа "событие", существовавших в системе), HighWaterNumberOfHandles (пиковое кол-во открытых описателей объектов типа "событие" существовавших в системе) равны (выделены) 646h, 67Eh, 66Bh, 6A1h соответственно. Если щелкнуть на объекте "тип" Event в окне WinObjEx, то можно увидеть эти же самые значения, только в десятичной форме.

Рис. 14-3. Свойства объекта "тип"

Имейте в виду то, что количество объектов постоянно меняется. Я просто сделал всё в нужном порядке, поэтому у меня значения точно совпадают.

Pool Type соответствует полю PoolType, Paged Pool Usage - полю DefaultPagedPoolCharge, NonPaged Pool Usage - полю DefaultNonPagedPoolCharge, а Maintain Handle Database соответствует полю MaintainHandleCount.

WinObjEx также покажет в удобной форме содержимое полей InvalidAttributes, GenericMapping и ValidAccessMask. Сравните с дампом и увидите, что все значения совпадают.

Надеюсь, что с объектом "тип" кое-что прояснилось. Вернемся к коду драйвера.



14.10 Продолжаем обрабатывать IOCTL_SET_NOTIFY


                 mov ecx, ExEventObjectType
                 mov ecx, [ecx]
                 mov ecx, [ecx]

                 invoke ObReferenceObjectByHandle, edx, EVENT_MODIFY_STATE, ecx, \
                                         UserMode, addr g_pkEventObject, NULL

Вызываем ObReferenceObjectByHandle. Первый параметр - описатель объекта "событие", который мы получили от программы управления. Второй параметр - требуемый тип доступа. Третий - указатель на структуру OBJECT_TYPE для объектов типа "событие". Используя именно этот указатель, система будет проверять, а действительно ли описатель соответствует объекту "событие" и если да, то вернет нам в переменной g_pkEventObject ссылку на этот объект. Четвертый параметр определяет, в какой таблице описателей следует искать объект. Если это KernelMode, то будет просматриваться таблица описателей ядра (подробнее см. часть 11), а это совсем не то, что нам нужно. Если это UserMode, то система будет исследовать таблицу описателей текущего процесса, что нам и нужно. Надеюсь, вы помните, что при обработке IRP_MJ_DEVICE_CONTROL мы как одноуровневый драйвер находимся в контексте вызывающего процесса. Последний параметр функции ObReferenceObjectByHandle - это указатель на структуру OBJECT_HANDLE_INFORMATION. В DDK написано, что нам следует всегда выставлять его в NULL. Ну, вы знаете, как относиться к подобным категоричным заявлениям :) В данном случае, нам не требуется эта информация.


                 .if eax == STATUS_SUCCESS

Если в таблице описателей текущего процесса есть такой описатель, и он соответствует объекту "событие" и запрашиваемый тип доступа может быть предоставлен, мы получим в переменной g_pkEventObject необходимый нам указатель на объект.


                     invoke PsSetCreateProcessNotifyRoutine, ProcessNotifyRoutine, FALSE
                     mov [esi].IoStatus.Status, eax

Вызов функции PsSetCreateProcessNotifyRoutine заставляет систему поместить/удалить адрес нашей процедуры ProcessNotifyRoutine в/из список процедур, которые она (система) будет вызывать при создании или удалении процесса. К сожалению, этот список ограничивается восьмью членами. Если второй параметр FALSE, процедура добавляется в список, если TRUE - удаляется из списка. В DDK написано, что если драйвер успешно зарегистрировал свою процедуру, он должен оставаться в памяти до выключения системы. Для аналогичных функций PsSetCreateThreadNotifyRoutine и PsSetLoadImageNotifyRoutine это действительно так (у них вообще нет параметра Remove), но для PsSetCreateProcessNotifyRoutine нет. После снятия зарегистрированной процедуры, драйвер может быть выгружен.


                     .if eax == STATUS_SUCCESS
 
                         mov g_fbNotifyRoutineSet, TRUE

Если мы удачно зарегистрировались, выставляем глобальный флаг.


                         mov eax, pDeviceObject
                         mov eax, (DEVICE_OBJECT PTR [eax]).DriverObject
                         and (DRIVER_OBJECT PTR [eax]).DriverUnload, NULL

Делаем драйвер невыгружаемым. Это очень простой и эффективный способ предотвратить преждевременную выгрузку драйвера. Если наша программа управления (по ошибке) или кто-то другой выгрузит драйвер, то при следующем создании/удалении процесса будет вызвана процедура, находившаяся в теле драйвера… Мы не можем этого допустить.

Итак, всё прошло успешно и мы ждем момента, когда количество процессов в системе изменится. Как только это произойдет, будет вызвана наша ProcessNotifyRoutine.



14.11 Процедура ProcessNotifyRoutine


     push eax
     invoke PsLookupProcessByProcessId, dwProcessId, esp
     pop peProcess
     .if eax == STATUS_SUCCESS
         mov fbDereference, TRUE
     .else
         invoke IoGetCurrentProcess
         mov peProcess, eax
         and fbDereference, FALSE
     .endif

В параметре dwProcessId система передает нам идентификатор создаваемого/удаляемого процесса, а нам нужен указатель на объект "процесс". Для начала вызываем PsLookupProcessByProcessId, и если она вернет ошибку - IoGetCurrentProcess. Дело в том, что в Windows 2000 PsLookupProcessByProcessId не работает, если вызвана в контексте процесса, идентификатор которого ей передан (возможно, это так только на этапе удаления процесса - я этого не проверял). Это будет происходить при удалении процесса. При создании мы окажемся в контексте процесса-родителя. Т.о. тем или иным способом мы всё же получаем указатель на интересующий нас объект. Поскольку PsLookupProcessByProcessId увеличивает счетчик ссылок на объект, а IoGetCurrentProcess нет, используем флаг fbDereference.


     mov eax, dwProcessId
     mov g_ProcessData.dwProcessId, eax
 
     mov eax, bCreate
     mov g_ProcessData.bCreate, eax

Записываем в структуру PROCESS_DATA идентификатор процесса и флаг, определяющий, создается процесс или удаляется.


     invoke memset, addr g_ProcessData.szProcessPath, 0, IMAGE_FILE_PATH_LEN

Готовим место для пути к образу.


     invoke GetImageFilePath, peProcess, addr us
     .if eax == STATUS_SUCCESS
 
         lea eax, g_ProcessData.szProcessPath
         mov as.Buffer,          eax
         mov as.MaximumLength,   IMAGE_FILE_PATH_LEN
         and as._Length,         0
 
         invoke RtlUnicodeStringToAnsiString, addr as, addr us, FALSE
 
         invoke ExFreePool, us.Buffer

Удачный вызов GetImageFilePath вернет нам в us.Buffer unicode-строку с полным путем к образу создавшему процесс. Преобразуем её в ansi-строку и запишем по адресу g_ProcessData.szProcessPath одним вызовом функции RtlUnicodeStringToAnsiString. Т.к. GetImageFilePath сама выделяет буфер, то после удачного её вызова этот буфер необходимо освободить.


     .else
 
         mov eax, g_dwImageFileNameOffset
         .if eax != 0
             add eax, peProcess
             invoke memcpy, addr g_ProcessData.szProcessPath, eax, 16
         .endif
 
     .endif

Если нам не удается получить полный путь, то будем довольствоваться только именем процесса, извлекаемым из структуры EPROCESS. Напоминаю о том, что если длина этого имени превышает 16 символов (ansi), то оно усекается и, в этом случае, не завершается нулем. Поэтому копируем ровно 16 байт.


     .if fbDereference
         fastcall ObfDereferenceObject, peProcess
     .endif

Если флаг fbDereference установлен, уменьшаем счетчик ссылок на объект.


     invoke KeSetEvent, g_pkEventObject, 0, FALSE

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



14.12 Обрабатываем IOCTL_REMOVE_NOTIFY

Тут всё то же самое, что мы делали при обработке IOCTL_SET_NOTIFY, но наоборот.


			invoke PsSetCreateProcessNotifyRoutine, ProcessNotifyRoutine, TRUE
			mov [esi].IoStatus.Status, eax

Уведомления системы о создании/удалении процессов нам больше не нужны. Как я уже сказал, DDK нас обманывает, и снять уведомление всё же можно.


				or liDelayTime.HighPart, -1
				mov liDelayTime.LowPart, -1000000
	
				invoke KeDelayExecutionThread, KernelMode, FALSE, addr liDelayTime

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


				mov eax, pDeviceObject
				mov eax, (DEVICE_OBJECT PTR [eax]).DriverObject
				mov (DRIVER_OBJECT PTR [eax]).DriverUnload, offset DriverUnload

				.if g_pkEventObject != NULL
					invoke ObDereferenceObject, g_pkEventObject
					and g_pkEventObject, NULL
				.endif

Теперь драйвер можно безопасно выгрузить, а счетчик ссылок на объект "событие" вернуть в прежнее состояние.

Таким образом, объект "событие" используется драйвером для взаимодействия с программой управления. Всё, что мы обсудим ниже, носит факультативный характер и прямого отношения к теме статьи не имеет.



14.13 Ищем полный путь

Хорошо, что в структуре EPROCESS присутствует имя процесса. Достать его оттуда не представляет большого труда, но было бы неплохо получить полный путь к образу файла, создавшему процесс. Сделать это гораздо сложнее. Если коротко, и я не открываю здесь Америку, то необходимо пройти по следующему маршруту: SECTION.Segment -> SEGMENT.ControlArea -> CONTROL_AREA.FilePointer -> FILE_OBJECT.FileName -> UNICODE_STRING.Buffer. Некоторые взаимосвязи этих структур показаны на рисунке 14-4.

Рис. 14-4. Некоторые взаимосвязи структур SECTION, SEGMENT, CONTROL_AREA,FILE_OBJECT и SECTION_OBJECT_POINTERS

Некоторые из этих структур документированы, некоторые нет. Но все они существуют без изменений (по крайней мере, интересующие нас поля) в Windows 2000/XP/2003. И это очень хорошо, т.к. код будет единым (почти).

Для начала нам нужен указатель на структуру SECTION, описывающую объект "секция". На эту секцию отображается исполняемый файл процесса. Будем называть этот объект базовой секцией процесса. Это самый первый объект, создаваемый при запуске процесса. В Windows 2000 (и NT4, наверное, тоже) описатель этого объекта помещается в EPROCESS.SectionHandle и его значение всегда равно 4 (с одним исключением в SP4, о чем мы поговорим позже). Мы даже не будем его там искать. На этом факте, кстати, основан хорошо известный трюк, придуманный Гари Неббеттом, когда exe-модуль может сам себя удалить с диска, вызвав в нужной последовательность несколько самых обычных API, ключевой из которых является CloseHandle(4). Как вы, наверное, догадываетесь, в Windows XP этот фокус уже не проходит, ибо описателя за номером 4 теперь нет, а значит добраться до базовой секции процесса из режима пользователя невозможно. Взамен в структуре EPROCESS появилось поле SectionObject хранящее указатель на базовую секцию процесса. В Windows Server 2003 эта традиция продолжена, но смещение поля SectionObject естественно изменилось. Нам нужно будет его найти.

Имея указатель на базовую секцию процесса, и пройдя по связям изображенным на рис. 14-4, мы можем добраться до полного пути к модулю, создавшему процесс. Для начала я проделал это путем с помощью утилиты NTObjects ( http://www.smidgeonsoft.com/ ) рис. 14-5.

Рис. 14-5. Дампы структур SECTION, SEGMENT, CONTROL_AREA,FILE_OBJECT и SECTION_OBJECT_POINTERS

NTObjects позволяет в относительно удобной форме просмотреть все объекты, описатели которых попадают в таблицу описателей процесса. Поэтому воспользоваться её услугами для просмотра базовой секции процесса в Windows XP и выше не удастся и придется использовать какой-нибудь другой инструмент, например, SoftICE.



14.14 Процедура GetImageFilePath

Весь код, отвечающий за получение полного пути, помещен в отдельный файл ProcPath.asm. Я не настаиваю на том, что предлагаемый способ единственно возможный. В данном конкретном случае, можно было бы ограничится двумя жестко зашитыми смещениями (для XP и 2003). Но возможно, вам приходилось встречать неплохие инструменты, работающие в одной версии системы и не работающие в другой. Это результат порочной практики использования фиксированных смещений, адресов функций и т.п. Если есть малейшая возможность динамически найти какое-либо недокументированное поле структуры или не экспортируемую функцию, почему бы ни попробовать это сделать.


     PROCESS_QUERY_INFORMATION equ 400h

     mov ecx, PsProcessType
     mov ecx, [ecx]
     mov ecx, [ecx]

     invoke ObReferenceObjectByPointer, peProcess, PROCESS_QUERY_INFORMATION, ecx, UserMode
     .if eax == STATUS_SUCCESS

Проверим, является ли адрес, переданный нам в переменной peProcess, указателем на объект "процесс". Т.к. мы собираемся "копаться" в самых недрах системы без её разрешения, придется двигаться с максимальной осторожностью.

Реальное поведение функции ObReferenceObjectByPointer весьма значительно отличается от того, что можно найти в DDK.

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

Во-вторых, DDK заявляет, что третий параметр может быть указателем только на два объекта "тип", соответствующие объектам "файл" и "событие". Эти указатели можно извлечь из экспортируемых переменных IoFileObjectType и ExEventObjectType, соответственно. Это тоже не так и передавать можно указатель на любой объект "тип". И это просто замечательно, т.к. позволит нам проверять случайные указатели "на вшивость". Из 27-ми типов объектов в Windows 2000 и 29-ти в Windows XP/2003 экспортируется около 15 указателей на объекты "тип". Среди экспортируемых имеются все нужные нам, а именно: IoDeviceObjectType, PsProcessType, MmSectionObjectType, IoFileObjectType. И это тоже здорово.

В-третьих, что-то совершенно невнятное сказано про последний параметр. Мне, лично, его присутствие вообще непонятно. Обычно при обращении к объектам режим доступа имеет смысл указывать при переходе от режима пользователя к режиму ядра. Т.е. при преобразовании описателя в указатель. Например, в функции ObReferenceObjectByHandle параметр AccessMode вполне логичен. Но если у нас уже есть указатель, то зачем указывать права доступа? А воспользоваться указателем в режиме пользователя всё равно невозможно (Во всяком случае, документированными средствами. Кстати, используя Physical Memory Browser мы обратились к объекту прямо из режима пользователя). Если вы не поленитесь и загляните в дизассемблировнный листинг функции ObReferenceObjectByPointer, то убедитесь в весьма странном её поведении, в случае если параметр AccessMode = KernelMode, а именно… Если в параметре Object передать указатель на любой кусок валидной памяти, то функция, ничего не проверяя, посчитает, что это валидный указатель на тело объекта. Вычтет из этого указателя размер структуры OBJECT_HEADER, т.е. получит указатель на заголовок объекта и увеличит на единицу поле PointerCount. Т.е. функция считает, что если AccessMode = KernelMode, то уже имеющийся у нас указатель на объект был получен где-то ранее и валиден. Т.о. если AccessMode = KernelMode, передавать случайный указатель ни в коем случае нельзя. А вот если AccessMode = UserMode, функция таки проверит, соответствует ли тип объекта заявленному. Это обстоятельство и позволит нам проверять случайные указатели. В любом случае всё вышесказанное, мягко говоря, немного отличается от того, что написано в DDK.

При хорошем знании структур и принципов организации объектов можно без особого труда написать свою функцию проверки. А пока воспользуемся услугами ObReferenceObjectByPointer.


         .if g_dwWinVer == WINVER_UNINITIALIZED
         
             invoke IoIsWdmVersionAvailable, 1, 20h
             .if al
                 mov g_dwWinVer, WINVER_XP_OR_HIGHER
             .else
                 mov g_dwWinVer, WINVER_2K
             .endif
 
         .endif

Смотрим, в какой версии системы мы находимся. Чтобы не делать это каждый раз, используем переменную g_dwWinVer.


         .if g_dwWinVer == WINVER_XP_OR_HIGHER
  
             mov esi, peProcess
             mov ebx, 80h
             .while ebx < 204h

Если мы оказались в XP и выше, будем искать поле SectionObject в структуре EPROCESS в пределах 80h - 200h (надеюсь, что этого диапазона будет достаточно для всех обновлений).


                 mov edi, [esi][ebx]
                 invoke IsLikeObjectPointer, edi
                 .if eax == TRUE

Процедура IsLikeObjectPointer отсеивает "мусор". Если она вернула TRUE, то вполне вероятно, что в регистре edi содержится указатель на объект.


                     mov eax, edi
                     sub eax, sizeof OBJECT_HEADER
 
                     .if ([OBJECT_HEADER PTR [eax]].PointerCount <= 4)
                     .if ([OBJECT_HEADER PTR [eax]].HandleCount <= 1)

Если мы на стадии создания процесса, то счетчик ссылок базовой секции будет равен 3, а счетчик описателей 1. Если процесс удаляется, то 2 и 0, соответственно. Эти проверки позволят ещё сильнее ограничить число возможных кандидатов на объект "секция". Я накинул единичку к счетчику - на всякий случай. Как я уже говорил, при хорошем уровне знаний можно написать процедуру, практически однозначно идентифицирующую объект, но дабы не усложнять и без того непростой код мы идем легкими путями. Для того чтобы окончательно убедиться в том, что регистр edi содержит указатель на объект "секция", используем ObReferenceObjectByPointer.


                         mov ecx, MmSectionObjectType
                         mov ecx, [ecx]
                         mov ecx, [ecx]
 
                         invoke ObReferenceObjectByPointer, edi, SECTION_QUERY, ecx, UserMode
                         .if eax == STATUS_SUCCESS

Здесь мы уже уверены, что нашли то, что искали.


                             mov status, eax
                             mov pSection, edi
 
                             .break
                         .endif
                     .endif
                     .endif
                 .endif
 
                 add ebx, 4

             .endw

Если поле SectionObject всё ещё не найдено, продолжаем поиск. Указатели в структурах всегда выровнены по двойному слову. Поэтому двигаемся DWORD'ами.


         .else

Если мы в Windows 2000, то тут тоже не всё так просто, как хотелось бы. Точнее, сложности начинаются с сервис пака №4. До этого злосчастного SP4 всё просто. Достаточно "натравить" функцию ObReferenceObjectByHandle на цифру 4 и получить указатель на базовую секцию процесса. Но на SP4, в случае если процесс запускается из командной строки или bat-файлом, описатель базовой секции процесса, имеет значение 03E8h. Причем оно также фиксировано. По крайней мере, на моей машине и на ещё 5 машинах наших уважаемых коллег, любезно согласившихся протестировать этот пример (nerst, Noble Ghost, hGoblin, mokc0der, Vladimir), значение было именно таким. Откуда берется такое странное значение?

Описатель объекта представляет собой индекс в таблице описателей процесса. Таблица описателей реализована по трехуровневой схеме аналогично реализации механизма трансляции адресов в x86 системах. Младшие 24 бита описателя интерпретируются как три 8-битных индекса для каждого уровня. Первые два уровня состоят из массивов по 256 элементов, которые содержат указатели на массив следующего уровня. Массив самого нижнего уровня - это таблица вторичных описателей и содержит собственно элементы таблицы описателей, каждый из которых имеет размер в восемь байт (указатель на заголовок объекта и флаги). Самый последний элемент (256-ой) каждой незаполненной таблицы вторичных описателей инициализируется значением -1. Поскольку указатель имеет размер в четыре байта, то и описатели кратны четырем. Описатель со значением 0 (1-ый элемент в первой таблице вторичных описателей) не используется. Т.о. самый первый объект в процессе получит описатель 4 (2-ой элемент в первой таблице вторичных описателей), второй - 8 (3-ий элемент в первой таблице вторичных описателей) и т.д. до 3F8h (255-ый элемент в первой таблице вторичных описателей). Дойдя до описателя 3FCh (256-ой и последний элемент в первой таблице вторичных описателей) диспетчер объектов увидит -1, выделит при необходимости вторую таблицу вторичных описателей и заполнит 256-ой элемент первой (заместив -1). Последующие описатели (400h - 800h) будут попадать во вторую таблицу вторичных описателей и т.д.

Но на системах W2K+SP4 при запуске процесса из командной строки первая таблица вторичных описателей заполняется сверху вниз: 3F8h - 4. Затем диспетчеру объектов приходится таки заполнить последний элемент в первой таблице вторичных описателей 3FCh. И дальше всё продолжается как обычно: 400h - и дальше по возрастающей. Интереса ради, я посмотрел как это происходит на Windows XP: 7FCh - 4, 804h - и дальше по возрастающей. Куда подевался описатель 800h я не знаю.

Я прекрасно понимаю, что если вы не знакомы с организацией таблицы описателей, то вряд ли что-то поняли из этого объяснения. Читайте раздел "Описатели объектов и таблица описателей, принадлежащая процессу" в книге "Внутреннее устройство Microsoft Windows 2000", берите в руки SoftICE используйте эти структуры и многое прояснится. Смещение поля ObjectTable равно 0128h, 00C4h и 00C4h для Windows 2000, XP и Server 2003 соответственно. Поле Table хранит указатель на трехуровневую таблицу.


 EPROCESS STRUCT
 . . .
     ObjectTable    PVOID  ?   ; PTR HANDLE_TABLE
 . . .
 EPROCESS ENDS

 HANDLE_TABLE STRUCT
     Flags         DWORD   ?
     HandleCount   DWORD   ?
     Table         PVOID   ?   ; PTR PTR PTR HANDLE_TABLE_ENTRY
 . . .
 HANDLE_TABLE ENDS

Ответ на вопрос, почему диспетчер объектов поступает именно так как описано выше, я оставляю за вами.


             xor ebx, ebx
             mov edi, 4
             .while ebx < 3

Делаем три попытки получить указатель на базовую секцию процесса. Сначала попробуем описатель со значением 4.


                 invoke IoGetCurrentProcess
                 .if eax == peProcess
                     
                     mov ecx, MmSectionObjectType
                     mov ecx, [ecx]
                     mov ecx, [ecx]
                 
                     invoke ObReferenceObjectByHandle, edi, SECTION_QUERY, ecx, KernelMode, addr pSection, NULL
                     mov status, eax
 
                 .else
 
                     invoke KeAttachProcess, peProcess
 
                     mov ecx, MmSectionObjectType
                     mov ecx, [ecx]
                     mov ecx, [ecx]
 
                     invoke ObReferenceObjectByHandle, edi, SECTION_QUERY, ecx, KernelMode, addr pSection, NULL
                     mov status, eax
 
                     invoke KeDetachProcess
 
                 .endif

Если мы не в контексте создаваемого/удаляемого процесса, переключаемся на него вызовом KeAttachProcess.


                 .break .if status == STATUS_SUCCESS

Если попытка удалась, выходим из цикла.


                 .if ebx == 0
                     
                     mov edi, 03F8h  ; Try 03F8h handle.

Если нет - попробуем то же самое со значением 03F8h.


                 .elseif ebx == 1
                     
                     mov eax, peProcess
                     add eax, 01ACh
                     mov eax, [eax]
                     mov edi, eax

Последняя попытка. Ничего другого не остается, как взять значение описателя прямо из поля EPROCESS.SectionHandle.


                     and eax, (4 - 1)
                     .break .if ( eax != 0 ) || ( edi >= 800h )

На всякий случай, проверим описатель на кратность четырем и на выход за разумные пределы.


                 .endif
                 
                 inc ebx
             .endw
 
         .endif
 
         .if status == STATUS_SUCCESS
 
             mov status, STATUS_UNSUCCESSFUL
 
             mov ebx, pSection
             mov ebx, (SECTION PTR [ebx])._Segment
 
             invoke IsAddressInPoolRanges, ebx
             push eax
             invoke MmIsAddressValid, ebx
             pop ecx
             .if al && ( ecx == TRUE )
 
                 mov esi, ebx
 
                 mov ebx, (_SEGMENT PTR [ebx]).ControlArea
 
                 invoke IsAddressInPoolRanges, ebx
                 push eax
                 invoke MmIsAddressValid, ebx
                 pop ecx
                 .if al && ( ecx == TRUE ) && ([CONTROL_AREA PTR [ebx]]._Segment == esi )
 
                     mov ebx, (CONTROL_AREA PTR [ebx]).FilePointer
 
                     invoke IsLikeObjectPointer, ebx
                     .if eax == TRUE
 
                         mov ecx, IoFileObjectType
                         mov ecx, [ecx]
                         mov ecx, [ecx]          ; PTR OBJECT_TYPE
 
                         invoke ObReferenceObjectByPointer, ebx, FILE_READ_ATTRIBUTES, ecx, UserMode

Если мы поимели указатель на базовую секцию процесса, то по схеме на рис. 14-4 пытаемся получить указатель на соответствующий файловый объект. Здесь должно быть всё понятно и без пояснений. О функции IsAddressInPoolRanges чуть позже.


                         .if eax == STATUS_SUCCESS
 
                             invoke ExAllocatePool, PagedPool, (IMAGE_FILE_PATH_LEN+1) * sizeof WCHAR
                             .if eax != NULL
 
                                 mov edi, pusImageFilePath
                                 assume edi:ptr UNICODE_STRING
 
                                 mov [edi].Buffer, eax
 
                                 invoke memset, eax, 0, (IMAGE_FILE_PATH_LEN+1) * sizeof WCHAR
 
                                 mov [edi].MaximumLength, IMAGE_FILE_PATH_LEN * sizeof WCHAR
                                 and [edi]._Length, 0

                                 invoke RtlVolumeDeviceToDosName, \
                                         (FILE_OBJECT PTR [ebx]).DeviceObject, addr usDosName

Поле DeviceObject структуры FILE_OBJECT хранит указатель на объект "устройство", которому принадлежит файл. Из объекта "устройство" можно извлечь его имя. Но тогда мы получим путь к файлу относительно объекта "устройство". Например, "\Device\HarddiskVolume1\WINNT\system32\notepad.exe". С помощью функции RtlVolumeDeviceToDosName преобразуем его в более привычный "C:\ WINNT\system32\notepad.exe". DDK говорит, что начиная с XP мы должны использовать IoVolumeDeviceToDosName. Это не обязательно, т.к. для обратной совместимости обе функции имеют одну и ту же точку входа.


                                 .if eax == STATUS_SUCCESS
  
                                     invoke RtlCopyUnicodeString, edi, addr usDosName
                                     invoke ExFreePool, usDosName.Buffer

                                 .endif
 
                                 invoke RtlAppendUnicodeStringToString, edi, \
                                                 addr (FILE_OBJECT PTR [ebx]).FileName
 
                                 assume edi:nothing
 
                                 mov status, STATUS_SUCCESS

Составляем полный путь к образу. DeviceToDosName сама выделяет буфер для строки, поэтому, не забываем его освободить.

Теперь разберем остатки. "Разберем" - сильно сказано. Я уже довольно давно пишу эту статью, и желание закончить её поскорей растет во мне с каждой новой строчкой :) Надеюсь, что вы в состоянии понять, что делают оставшиеся не разобранными функции. Я лишь скажу о каждой несколько слов. Также в исходном коде имеется достаточное количество комментариев.



14.15 Процедура IsAddressInPoolRanges

Процедура IsAddressInPoolRanges проста и проверяет, находится ли переданный ей адрес в границах системных пулов. Адреса начала и конца пулов хорошо известны. Подробности можно почитать в книге "Внутреннее устройство Microsoft Windows 2000". Добавлю только то, что объекты не являющиеся объектами синхронизации (каковым объект секция и является), помещаются в подкачиваемый пул. Процедура IsAddressInPoolRanges же проверяет как подкачиваемый, так и неподкачиваемый пулы. IsAddressInPoolRanges предполагает, что выполняется в системе с 2Гб системным адресным пространством. Для систем с поддержкой PAE (Physical Address Extension) и систем запущенных с ключом /3GB в boot.ini придется её несколько видоизменить.



14.16 Процедура IsLikeObjectPointer

Эта процедура делает обоснованное заключение о том, может ли переданный ей адрес являться указателем на объект. Обратите внимание: "может являться", но не "является". Это ещё надо проверить вызовом функции ObReferenceObjectByPointer. Адрес может являться указателем на объект, если…

  • находится в границах системных пулов;
  • кратен как минимум 8-ми байтам (подробнее см. комментарий в исходнике);
  • является действительным (память по этому адресу выделена);
  • <адрес - 18h>, т.е. заголовок предполагаемого объекта также находится в действительной памяти.

Рис. 14-6. Результат работы программы ProcessMon. Установка сервис паков, оказывается, довольно жадна до процессов.

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

Исходный код драйвера в архиве.

2002-2013 (c) wasm.ru