Драйверы режима ядра: Часть 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.
Код (Text):
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
Код (Text):
;@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 Разбираем программу управления драйвером
Код (Text):
invoke SetUnhandledExceptionFilter, MyUnhandledExceptionFilterУстанавливаем обработчик исключений, покрывающий все потоки процесса (подробнее см. Часть 9). При возникновении исключения в любом из потоков процесса наш обработчик попытается привести систему в состояние, предшествовавшее запуску программы.
В Части 9 "Базовая техника: Работа с памятью. Разделяемая память" в определении функции MyUnhandledExceptionFilter была допущена ошибка: функция не принимала параметров, хотя система передает обработчику один параметр - указатель на структуру EXCEPTION_POINTERS. В результате функция не возвращала стек в исходное состояние. Если бы действительно произошло исключение, то по выходе из обработчика из-за некорректного состояния стека произошло бы новое исключение, что повлекло бы за собой повторный его вызов и так до бесконечности. Так что, обратите внимание: функция MyUnhandledExceptionFilter принимает указатель на структуру EXCEPTION_POINTERS.
Код (Text):
invoke CreateEvent, NULL, FALSE, FALSE, NULL mov g_hEvent, eaxПосле регистрации и запуска драйвера создаем безымянный объект "событие" с автоматическим сбросом и в не сигнальном состоянии (nonsignaled). Именно этот объект и будет совместно использоваться.
Говоря "в не сигнальном состоянии" я сознательно отступаю от традиции использовать только официально устоявшиеся термины. В умных книжках, в данном случае, был бы употреблен термин "в занятом состоянии". Этот термин используется для всех объектов синхронизации, но к одним он подходит, а к другим нет. Поэтому, чтобы избежать лишней путаницы будем говорить "в сигнальном состоянии" или "в не сигнальном состоянии".
Код (Text):
and g_fbExitNow, FALSEЯвно сбрасываем глобальный флаг, который потребуется для того, чтобы сообщить рабочему потоку о прекращении работы.
Код (Text):
push eax invoke CreateThread, NULL, 0, offset WaitForProcessData, g_hEvent, 0, esp pop ecxСоздаем рабочий поток. Этот поток будет ждать на описателе g_hEvent и по сигналу забирать у драйвера статистику.
Код (Text):
invoke DeviceIoControl, g_hDevice, IOCTL_SET_NOTIFY, \ addr g_hEvent, sizeof g_hEvent, NULL, 0, addr dwBytesReturned, NULLШлем драйверу управляющий код IOCTL_SET_NOTIFY, передавая ему описатель нашего объекта "событие". Драйвер должен начать слежение за созданием/удалением процессов. При создании или удалении какого-либо процесса, драйвер будет устанавливать объект "событие" в сигнальное состояние.
Код (Text):
.if eax != 0 invoke GetModuleHandle, NULL mov g_hInstance, eax invoke DialogBoxParam, g_hInstance, IDD_MAIN, NULL, addr DlgProc, 0Если слежение установлено, запускаем диалог. В самой диалоговой процедуре нет ничего интересного.
Код (Text):
invoke DeviceIoControl, g_hDevice, IOCTL_REMOVE_NOTIFY, \ NULL, 0, NULL, 0, addr dwBytesReturned, NULLПосле закрытия диалога заставляем драйвер прекратить слежение.
Код (Text):
mov g_fbExitNow, TRUE invoke SetEvent, g_hEventУстанавливаем флаг g_fbExitNow и переводим объект "событие" в сигнальное состояние. Это пробудит рабочий поток, он увидит установленный флаг и прекратит работу.
Код (Text):
invoke Sleep, 100Немного подождем, пока рабочий поток завершается. Хотя он и имеет более высокий приоритет, небольшая задержка не повредит.
14.4 Процедура рабочего потока
Посмотрим теперь на процедуру рабочего потока.
Код (Text):
invoke GetCurrentThread invoke SetThreadPriority, eax, THREAD_PRIORITY_HIGHESTПовышаем приоритет потока, т.к. если у драйвера появятся свежие данные, хотелось бы не упустить этот момент. Дело в том, что драйвер (для упрощения примера) хранит информацию только об одном последнем созданном/удаленном процессе. Учитывая то, что это происходит относительно редко, вряд ли мы пропустим этот момент, даже не повышая приоритет. И тем не менее.
Код (Text):
.while TRUE invoke WaitForSingleObject, hEvent, INFINITE .if eax != WAIT_FAILEDВ бесконечном цикле ждем на объекте "событие".
Код (Text):
.break .if g_fbExitNow == TRUEЕсли пора выходить, прерываем цикл.
Код (Text):
invoke DeviceIoControl, g_hDevice, IOCTL_GET_PROCESS_DATA, NULL, 0, \ addr ProcessData, sizeof ProcessData, addr dwBytesReturned, NULLЗабираем у драйвера свежие данные.
Код (Text):
.if eax != 0 invoke FillProcessInfo, addr ProcessData .endifВыводим, полученную от драйвера информацию.
14.5 Функция FillProcessInfo
Случается, что драйвер возвращает "короткий" путь. Например, "C:\PROGRA~1\WinZip\WinZip32.EXE". Детально в причинах этого явления я не разбирался. Видимо, если при создании процесса его родитель запускает его (точнее его исполнимый образ) по "короткому" пути, то этот путь и попадает в соответствующее поле FILE_OBJECT. Мы просто вызовем функцию GetLongPathName, которая всё сделает сама. Если буфера размером 1024 байт, вдруг окажется недостаточно, то просто покажем тот путь, который вернул драйвер. Для простоты, я не стал выделять дополнительную память и вызывать GetLongPathName снова.
Код (Text):
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
Код (Text):
;@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
Код (Text):
;::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ; С Т Р У К Т У Р Ы ;::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: 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.
Код (Text):
EPROCESS STRUCT . . . ImageFileName BYTE 16 dup(?) . . . EPROCESS ENDSImageFileName хранит имя образа, создавшего процесс. Как видите, это поле позволяет хранить всего 16 символов. Более длинные имена усекаются до 16 символов и, в этом случае, завершающий ноль не используется. Содержимое этого поля мы будем копировать в нашу структуру PROCESS_DATA, но только в том случае, если не удастся получить полный путь к образу. Положение этого поля в структуре EPROCESS отличается на разных версиях системы (для w2k, wxp и w2k3 оно равно 01FCh, 0174h и 0154h, соответственно), но есть очень простой и хорошо известный способ его найти. Для этого надо просканировать структуру EPROCESS процесса System и найти там строку "System" - это и будет поле ImageFileName. Разумеется, этот трюк будет работать только в том случае, если это поле преднамеренно кем-то не изменено. В последнее время стало модно залезать в ядро и вытворять там всякие глупости. Будем считать, что наша система девственно чиста. В противном случае придется изобретать способ похитрее.
Итак, сейчас мы в процедуре DriverEntry, т.е. в контексте процесса System.
Код (Text):
invoke GetImageFileNameOffset mov g_dwImageFileNameOffset, eaxВызываем процедуру GetImageFileNameOffset, которая вернет нам смещение поля ImageFileName от начала структуры EPROCESS или ноль, если не сможет его найти. Процедура GetImageFileNameOffset проста и делает следующее.
Код (Text):
invoke IoGetCurrentProcess mov esi, eaxПолучаем указатель на структуры EPROCESS текущего процесса, т.е. процесса System.
Код (Text):
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 в данном случае) и не учитывает их регистр.
Код (Text):
.if eax == 0 mov eax, ebx .else xor eax, eax .endif retЕсли такая строка найдена, то это поле ImageFileName - возвращаем его смещение или ноль в случае неудачи.
14.8 Обрабатываем IOCTL_SET_NOTIFY
Получив управляющий код IOCTL_SET_NOTIFY, мы должны установить слежение за созданием/удалением процессов.
Код (Text):
.if g_fbNotifyRoutineSet == FALSEНа всякий случай, проверим флаг g_fbNotifyRoutineSet - возможно слежение уже установлено.
Код (Text):
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.
Код (Text):
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.
Код (Text):
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
Код (Text):
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. Ну, вы знаете, как относиться к подобным категоричным заявлениям
В данном случае, нам не требуется эта информация.
Код (Text):
.if eax == STATUS_SUCCESSЕсли в таблице описателей текущего процесса есть такой описатель, и он соответствует объекту "событие" и запрашиваемый тип доступа может быть предоставлен, мы получим в переменной g_pkEventObject необходимый нам указатель на объект.
Код (Text):
invoke PsSetCreateProcessNotifyRoutine, ProcessNotifyRoutine, FALSE mov [esi].IoStatus.Status, eaxВызов функции PsSetCreateProcessNotifyRoutine заставляет систему поместить/удалить адрес нашей процедуры ProcessNotifyRoutine в/из список процедур, которые она (система) будет вызывать при создании или удалении процесса. К сожалению, этот список ограничивается восьмью членами. Если второй параметр FALSE, процедура добавляется в список, если TRUE - удаляется из списка. В DDK написано, что если драйвер успешно зарегистрировал свою процедуру, он должен оставаться в памяти до выключения системы. Для аналогичных функций PsSetCreateThreadNotifyRoutine и PsSetLoadImageNotifyRoutine это действительно так (у них вообще нет параметра Remove), но для PsSetCreateProcessNotifyRoutine нет. После снятия зарегистрированной процедуры, драйвер может быть выгружен.
Код (Text):
.if eax == STATUS_SUCCESS mov g_fbNotifyRoutineSet, TRUEЕсли мы удачно зарегистрировались, выставляем глобальный флаг.
Код (Text):
mov eax, pDeviceObject mov eax, (DEVICE_OBJECT PTR [eax]).DriverObject and (DRIVER_OBJECT PTR [eax]).DriverUnload, NULLДелаем драйвер невыгружаемым. Это очень простой и эффективный способ предотвратить преждевременную выгрузку драйвера. Если наша программа управления (по ошибке) или кто-то другой выгрузит драйвер, то при следующем создании/удалении процесса будет вызвана процедура, находившаяся в теле драйвера… Мы не можем этого допустить.
Итак, всё прошло успешно и мы ждем момента, когда количество процессов в системе изменится. Как только это произойдет, будет вызвана наша ProcessNotifyRoutine.
14.11 Процедура ProcessNotifyRoutine
Код (Text):
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.
Код (Text):
mov eax, dwProcessId mov g_ProcessData.dwProcessId, eax mov eax, bCreate mov g_ProcessData.bCreate, eaxЗаписываем в структуру PROCESS_DATA идентификатор процесса и флаг, определяющий, создается процесс или удаляется.
Код (Text):
invoke memset, addr g_ProcessData.szProcessPath, 0, IMAGE_FILE_PATH_LENГотовим место для пути к образу.
Код (Text):
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 сама выделяет буфер, то после удачного её вызова этот буфер необходимо освободить.
Код (Text):
.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 байт.
Код (Text):
.if fbDereference fastcall ObfDereferenceObject, peProcess .endifЕсли флаг fbDereference установлен, уменьшаем счетчик ссылок на объект.
Код (Text):
invoke KeSetEvent, g_pkEventObject, 0, FALSEТеперь можно "посигналить" нашей программе управления. Перевод объекта "событие" в сигнальное состояние пробуждает пользовательский рабочий поток. Пробудившись, он пошлет драйверу управляющий код IOCTL_GET_PROCESS_DATA и заберет информацию о процессе.
14.12 Обрабатываем IOCTL_REMOVE_NOTIFY
Тут всё то же самое, что мы делали при обработке IOCTL_SET_NOTIFY, но наоборот.
Код (Text):
invoke PsSetCreateProcessNotifyRoutine, ProcessNotifyRoutine, TRUE mov [esi].IoStatus.Status, eaxУведомления системы о создании/удалении процессов нам больше не нужны. Как я уже сказал, DDK нас обманывает, и снять уведомление всё же можно.
Код (Text):
or liDelayTime.HighPart, -1 mov liDelayTime.LowPart, -1000000 invoke KeDelayExecutionThread, KernelMode, FALSE, addr liDelayTimeНа всякий случай, чуть-чуть подождем, т.к. теоретически возможно, что как раз в этот момент система создает или удаляет какой-нибудь процесс и находится как раз в нашей процедуре ProcessNotifyRoutine. На самом деле я уверен, что такая ситуация невозможна, но для того чтобы это утверждать надо потратить некоторое время на исследования, а его как всегда не хватает. В любом случае, небольшая задержка не повредит.
Код (Text):
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). Но возможно, вам приходилось встречать неплохие инструменты, работающие в одной версии системы и не работающие в другой. Это результат порочной практики использования фиксированных смещений, адресов функций и т.п. Если есть малейшая возможность динамически найти какое-либо недокументированное поле структуры или не экспортируемую функцию, почему бы ни попробовать это сделать.
Код (Text):
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.
Код (Text):
.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.
Код (Text):
.if g_dwWinVer == WINVER_XP_OR_HIGHER mov esi, peProcess mov ebx, 80h .while ebx < 204hЕсли мы оказались в XP и выше, будем искать поле SectionObject в структуре EPROCESS в пределах 80h - 200h (надеюсь, что этого диапазона будет достаточно для всех обновлений).
Код (Text):
mov edi, [esi][ebx] invoke IsLikeObjectPointer, edi .if eax == TRUEПроцедура IsLikeObjectPointer отсеивает "мусор". Если она вернула TRUE, то вполне вероятно, что в регистре edi содержится указатель на объект.
Код (Text):
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.
Код (Text):
mov ecx, MmSectionObjectType mov ecx, [ecx] mov ecx, [ecx] invoke ObReferenceObjectByPointer, edi, SECTION_QUERY, ecx, UserMode .if eax == STATUS_SUCCESSЗдесь мы уже уверены, что нашли то, что искали.
Код (Text):
mov status, eax mov pSection, edi .break .endif .endif .endif .endif add ebx, 4 .endwЕсли поле SectionObject всё ещё не найдено, продолжаем поиск. Указатели в структурах всегда выровнены по двойному слову. Поэтому двигаемся DWORD'ами.
Код (Text):
.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 хранит указатель на трехуровневую таблицу.
Код (Text):
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Ответ на вопрос, почему диспетчер объектов поступает именно так как описано выше, я оставляю за вами.
Код (Text):
xor ebx, ebx mov edi, 4 .while ebx < 3Делаем три попытки получить указатель на базовую секцию процесса. Сначала попробуем описатель со значением 4.
Код (Text):
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.
Код (Text):
.break .if status == STATUS_SUCCESSЕсли попытка удалась, выходим из цикла.
Код (Text):
.if ebx == 0 mov edi, 03F8h ; Try 03F8h handle.Если нет - попробуем то же самое со значением 03F8h.
Код (Text):
.elseif ebx == 1 mov eax, peProcess add eax, 01ACh mov eax, [eax] mov edi, eaxПоследняя попытка. Ничего другого не остается, как взять значение описателя прямо из поля EPROCESS.SectionHandle.
Код (Text):
and eax, (4 - 1) .break .if ( eax != 0 ) || ( edi >= 800h )На всякий случай, проверим описатель на кратность четырем и на выход за разумные пределы.
Код (Text):
.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 чуть позже.
Код (Text):
.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. Это не обязательно, т.к. для обратной совместимости обе функции имеют одну и ту же точку входа.
Код (Text):
.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. Установка сервис паков, оказывается, довольно жадна до процессов.
Т.к. в примере используются недокументированные трюки, то, во-первых, я не гарантирую его стабильную работу, а во-вторых, мне интересно знать о любых проявлениях нестабильности. Поэтому, большая просьба сообщить о любых обнаруженных багах.
Исходный код драйвера в архиве.
© Four-F
Драйверы режима ядра: Часть 14: Базовая техника. Синхронизация: Использование объекта
Дата публикации 3 авг 2004