Kernel Mode Drivers

Part 14: Basic technique. Synchronization:
Using an event object to communicate between a driver and a control program.



Suppose we have a driver that collects some kind of statistical information and a control program that receives and processes this information. How to organize their interaction? In the simplest case, you need to create a timer in the control program and, at a certain frequency (say, once a second), take statistics from the driver via DeviceIoControl. You don't have to look far for examples: RegMon, FileMon, etc. But what if the events monitored by the driver (for example, process creation) are relatively rare? Most of the time we will waste DeviceIoControl. Increasing the timer interval (say, up to 10 seconds) will result in annoying delays in the control program. Obviously, in this scenario, the driver must initiate the transmission of the next piece of information, since About, that the monitored event (events) has occurred, it is he who knows. This means that he must somehow inform his control program that there is fresh data.

There are two basic and relatively well-documented methods. One is to call DeviceIoControl asynchronously. The user thread creates an "event" object and fills the OVERLAPPED structure accordingly, the pointer to which is passed in the last parameter. The device descriptor must be opened with the FILE_FLAG_OVERLAPPED flag. Upon receiving such a request, the driver defers its completion until the expected event occurs and returns STATUS_PENDING. On the user mode side, the DeviceIoControl call returns ERROR_IO_PENDING and the thread must wait on the "event" object. When an expected event occurs, the driver completes the IRP and signals this to the user thread by releasing the event object. In this case, the driver must be ready to that it will have to handle several of these IRPs. Those. it must queue I / O requests pending completion. We will not analyze this method in detail (see DDK and MSDN). The second method is somewhat simpler in that the driver and its user-mode client share an event object. The client waits on the object and if it goes into the signal state (as instructed by the driver), it synchronously calls DeviceIoControl, receiving the necessary information from the driver.

So we have to share the event object. There are several options here. You can use a named object and refer to it by name. Recall how we used the named partition object in Part 8, Basic Technique: Working with Memory. Shared Partition. The obvious drawback is that this object will be visible to everyone. Therefore, it is better to use an unnamed object. It is common and officially documented practice here for the client to create an "event" object in user mode and pass its handle to the driver via DeviceIoControl. We will use this method. In this example, we will track the creation / removal of processes.

Additional information on the topic can be obtained from the following sources:



14.1 General

Let's start by parsing the contents of the common.inc file.

 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

We have three control codes: IOCTL_SET_NOTIFY makes the driver start tracking process creation / deletion; IOCTL_REMOVE_NOTIFY accordingly does the opposite; IOCTL_GET_PROCESS_DATA returns information about the process in the PROCESS_DATA structure. This information consists of the process ID, a flag indicating whether the process was created or deleted, and the full path to the image that created the process.



14.2 Source code of the ProcessMon driver control program

 ;@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 Disassembling the driver control program

     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

We create a worker thread. This thread will wait on the g_hEvent descriptor and, upon a signal, take statistics from the driver.

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

We send the IOCTL_SET_NOTIFY control code to the driver, passing it the handle of our "event" object. The driver should start tracking process creation / deletion. When creating or deleting a process, the driver will set the "event" object to the signal state.

                         .if eax != 0

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

If tracking is installed, launch the dialog. There is nothing interesting about the dialogue procedure itself.

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

After closing the dialog, we force the driver to stop tracking.

                         mov g_fbExitNow, TRUE
                         invoke SetEvent, g_hEvent

We set the flag g_fbExitNow and transfer the "event" object to the signal state. This will wake up the worker thread, see the flag set, and exit.

                         invoke Sleep, 100

Let's wait a bit while the worker thread finishes. Although it has a higher priority, a little delay doesn't hurt.



14.4 Workflow procedure

Let's now look at the workflow procedure.

     invoke GetCurrentThread
     invoke SetThreadPriority, eax, THREAD_PRIORITY_HIGHEST

We increase the priority of the thread, because if the driver has fresh data, I would like not to miss this moment. The fact is that the driver (to simplify the example) stores information about only one last created / deleted process. Given that this happens relatively rarely, we are unlikely to miss this point without even raising the priority. And yet.

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

In an endless loop, we are waiting for the "event" on the object.

             .break .if g_fbExitNow == TRUE

If it's time to leave, we interrupt the cycle.

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

We take the fresh data from the driver.

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

We display the information received from the driver.



14.5 FillProcessInfo Function

It happens that the driver returns a short path. For example, "C: \ PROGRA ~ 1 \ WinZip \ WinZip32.EXE". I did not understand in detail the reasons for this phenomenon. Apparently, if, when a process is created, its parent launches it (more precisely, its executable image) along a "short" path, then this path gets into the corresponding FILE_OBJECT field. We will simply call the GetLongPathName function, which will do everything by itself. If a buffer of 1024 bytes in size suddenly turns out to be not enough, then we will simply show the path that the driver returned. For the sake of simplicity, I did not allocate additional memory and call GetLongPathName again.

 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 Driver Source Code

ProcessMon.bat module

 ;@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 Module

 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 ;                                      С Т Р У К Т У Р Ы                                            
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 
 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 Procedure DriverEntry

Among other things, in the DriverEntry procedure, we need to get the offset of the ImageFileName field of the EPROCESS structure.

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

ImageFileName stores the name of the image that created the process. As you can see, this field only allows you to store 16 characters. Longer names are truncated to 16 characters and, in this case, no terminating null is used. We will copy the contents of this field into our PROCESS_DATA structure, but only if we cannot get the full path to the image. The position of this field in the EPROCESS structure differs on different versions of the system (for w2k, wxp and w2k3 it is 01FCh, 0174h and 0154h, respectively), but there is a very simple and well-known way to find it. To do this, scan the EPROCESS structure of the System process and find the string "System" there - this will be the ImageFileName field. Of course, this trick will only work if this field is not intentionally changed by someone. Recently it has become fashionable to get into the core and do all sorts of nonsense there. Let's assume that our system is pristine. Otherwise, you will have to invent a more cunning way.

So, now we are in the DriverEntry procedure, i.e. in the context of the System.

             invoke GetImageFileNameOffset
             mov g_dwImageFileNameOffset, eax

We call the GetImageFileNameOffset procedure, which will return the offset of the ImageFileName field from the beginning of the EPROCESS structure, or zero if it cannot find it. The GetImageFileNameOffset procedure is simple and does the following.

     invoke IoGetCurrentProcess
     mov esi, eax

We get a pointer to the EPROCESS structures of the current process, i.e. process System.

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

We are looking for the string "system" in the first page from the beginning, and we use the _strnicmp function, which compares a strictly defined number of characters (6 in this case) and does not take into account their case.

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

If such a string is found, then this is the ImageFileName field - we return its offset or zero in case of failure.



14.8 Handling IOCTL_SET_NOTIFY

Having received the control code IOCTL_SET_NOTIFY, we need to set the process for creating / deleting processes.

             .if g_fbNotifyRoutineSet == FALSE

Just in case, let's check the g_fbNotifyRoutineSet flag - maybe the tracking is already set.

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

We extract the handle of the "event" object passed to us from the user mode.

In what follows, we will operate with a pointer to an object, not a descriptor, as it should be for drivers. Therefore, we must get this pointer and at the same time check whether the number passed to us is indeed a descriptor of the "event" object. Checking any data received from the user mode is a prerequisite for the stable operation of any driver. Both of these tasks, in this case, can be solved with one call to the ObReferenceObjectByHandle function. To do this, we need a pointer to an OBJECT_TYPE structure that describes an object type (in this case, it types the event objects). To understand what it is, let's make a small lyrical digression towards the world of Windows objects (for more details, see Sven Schreiber's book "Undocumented Windows 2000 Features").



14.9 Object "type"

So what is a "type" object? This object describes the common characteristics for all objects of some type. In total, there are 27 types of objects in Windows 2000 (there are already 29 in xp and w2k3). Having launched WinObjEx (see the section "TOOLS> Corner NT +" wasm.ru) and expanding the \ ObjectTypes directory, we will see the following picture:

Rice. 14-1. Type Objects in the Object Manager Namespace

These are the types of objects that exist in the system. By clicking on any of them, you can get some additional information. Each such object has a corresponding OBJECT_TYPE structure.

 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

When the system needs to create a new object, it refers to the structure corresponding to this type of object. The structure addresses of some "type" objects are exported. For example, for objects of type WindowStation, the address of the OBJECT_TYPE structure describing all objects of this type is stored in the exported kernel variable ExWindowStationObjectType, and for Event objects - ExEventObjectType.

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

By adding the above code to the driver, you can get the address of the OBJECT_TYPE structure for event objects. For me, this address turned out to be equal to 8188C200h. Knowing that on machines with a RAM capacity of 128 or more (for xp 256 or more, although this is not accurate data), a megabyte kernel is mapped into large (4MB) pages, and knowing that physical memory is mapped to virtual addresses 80000000h - 9FFFFFFFh by addresses 00000000h - 01FFFFFFFh we can use the services PhysMemBrowser (included in KmdKit). I use it quite often because sometimes it is much faster and more convenient than using a debugger.

So, the physical address 0188C200h is mapped to the virtual address 8188C200h. We enter this value into PhysMemBrowser and get a dump of the OBJECT_TYPE structure for objects of the "event" type:

Rice. 14-2. Dump object "type"

At the address E1007C48h you can find the "Event" line, i.e. the name of the "type" object. Values ​​TotalNumberOfObjects (total number of "event" objects in the system), TotalNumberOfHandles (total number of open handles of "event" objects in the system), HighWaterNumberOfObjects (peak number of "event" objects that existed in the system), HighWaterNumberOfHandles (peak number of open handles of objects of type "event" that existed in the system) are equal (highlighted) 646h, 67Eh, 66Bh, 6A1h, respectively. If you click on the Event "type" object in the WinObjEx window, you can see the same values, only in decimal form.

Rice. 14-3. Properties of the "type" object

Keep in mind that the number of objects is constantly changing. I just did everything in the right order, so my values ​​are exactly the same.

Pool Type corresponds to PoolType, Paged Pool Usage corresponds to DefaultPagedPoolCharge, NonPaged Pool Usage corresponds to DefaultNonPagedPoolCharge, and Maintain Handle Database corresponds to MaintainHandleCount.

WinObjEx will also conveniently display the contents of the InvalidAttributes, GenericMapping, and ValidAccessMask fields. Compare with the dump and you will see that all the values ​​are the same.

Hopefully something cleared up with the type object. Let's go back to the driver code.



14.10 We continue to process 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

We make the driver unloadable. This is a very simple and effective way to prevent premature driver unloading. If our control program (by mistake) or someone else unloads the driver, then at the next process creation / deletion the procedure in the driver body will be called ... We cannot allow this.

So, everything went well and we are waiting for the moment when the number of processes in the system changes. Once this happens, our ProcessNotifyRoutine will be called.



14.11 Procedure 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

In the dwProcessId parameter, the system passes us the identifier of the process to be created / removed, and we need a pointer to the "process" object. First, we call PsLookupProcessByProcessId, and if it returns an error, IoGetCurrentProcess. The fact is that in Windows 2000 PsLookupProcessByProcessId does not work if it is called in the context of the process whose identifier was passed to it (perhaps this is so only at the stage of deleting the process - I did not check this). This will happen when the process is deleted. When created, we will be in the context of the parent process. That. In one way or another, we still get a pointer to the object of interest to us. Since PsLookupProcessByProcessId increments the object reference count, but IoGetCurrentProcess does not, use the fbDereference flag.

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

We write in the PROCESS_DATA structure the process identifier and a flag that determines whether a process is created or deleted.

     invoke memset, addr g_ProcessData.szProcessPath, 0, IMAGE_FILE_PATH_LEN

Preparing a place for the path to the image.

     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

A successful call to GetImageFilePath will return us.Buffer a unicode string with the full path to the image that created the process. Let's convert it to an ansi-string and write it to the address g_ProcessData.szProcessPath with one call of the RtlUnicodeStringToAnsiString function. Because GetImageFilePath itself allocates a buffer, then after a successful call, this buffer must be freed.

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

If we cannot get the full path, then we will be content with only the process name retrieved from the EPROCESS structure. I remind you that if the length of this name exceeds 16 characters (ansi), then it is truncated and, in this case, is not null terminated. Therefore, we copy exactly 16 bytes.

     .if fbDereference
         fastcall ObfDereferenceObject, peProcess
     .endif

If the fbDereference flag is set, decrement the object reference count.

     invoke KeSetEvent, g_pkEventObject, 0, FALSE

Now we can "honk" our control program. Placing an event object in a signaled state wakes up the custom worker thread. Upon awakening, it will send the IOCTL_GET_PROCESS_DATA control code to the driver and retrieve information about the process.



14.12 Processing IOCTL_REMOVE_NOTIFY

Everything is the same as what we did when processing IOCTL_SET_NOTIFY, but vice versa.

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

We no longer need system notifications about the creation / deletion of processes. As I said, DDK is deceiving us, and you can still remove the notification.

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

Just in case, we will wait a little, tk. it is theoretically possible that just at this moment the system creates or deletes some process and is just in our procedure ProcessNotifyRoutine. In fact, I am sure that such a situation is impossible, but in order to assert this it is necessary to spend some time on research, and as always it is not enough. Either way, a little delay won't hurt.

				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

The driver can now be safely unloaded and the event object reference counter returned to its previous state.

Thus, the event object is used by the driver to interact with the control program. Everything that we will discuss below is optional and has no direct relation to the topic of the article.



14.13 Finding the full path

It is good that the process name is present in the EPROCESS structure. It is not difficult to get it from there, but it would be nice to get the full path to the file image that created the process. This is much more difficult to do. In short, and I am not opening America here, then you need to go along the following route: SECTION.Segment -> SEGMENT.ControlArea -> CONTROL_AREA.FilePointer -> FILE_OBJECT.FileName -> UNICODE_STRING.Buffer. Some of the relationships between these structures are shown in Figure 14-4.

Rice. 14-4. Some relationships between the SECTION, SEGMENT, CONTROL_AREA, FILE_OBJECT, and SECTION_OBJECT_POINTERS structures

Some of these structures are documented, some are not. But they all exist unchanged (at least the fields we are interested in) in Windows 2000 / XP / 2003. And this is very good, because the code will be (almost) one.

First, we need a pointer to a SECTION structure that describes the section object. This section displays the executable file of the process. We will call this object the base section of the process. This is the very first object that is created when the process starts. In Windows 2000 (and NT4, probably, too) the handle for this object is placed in EPROCESS.SectionHandle and its value is always 4 (with one exception in SP4, which we will talk about later). We won't even look for him there. By the way, this fact is the basis for a well-known trick invented by Gary Nebbett, when an exe-module can delete itself from disk by calling several of the most common APIs in the required sequence, the key of which is CloseHandle (4). As you probably guess, in Windows XP this trick no longer works, because the descriptor number 4 is no longer there, which means that it is impossible to get to the base section of the process from user mode. Instead, the EPROCESS structure has a SectionObject field that stores a pointer to the base section of the process. In Windows Server 2003, this tradition continued, but the offset of the SectionObject field naturally changed. We will need to find him.

Having a pointer to the basic section of the process, and going through the connections shown in Fig. 14-4, we can get to the full path to the module that created the process. To begin with, I did it by using the NTObjects utility (http://www.smidgeonsoft.com/) Fig. 14-5.

Rice. 14-5. Dumps of SECTION, SEGMENT, CONTROL_AREA, FILE_OBJECT and SECTION_OBJECT_POINTERS structures

NTObjects allows you to view in a relatively convenient form all objects whose descriptors fall into the process descriptor table. Therefore, you will not be able to use its services to view the basic section of the process in Windows XP and higher, and you will have to use some other tool, for example, SoftICE.



14.14 Procedure GetImageFilePath

All the code responsible for obtaining the full path has been placed in a separate ProcPath.asm file. I do not insist that the proposed method is the only possible one. In this particular case, one could be limited to two hard-wired offsets (for XP and 2003). But you may have come across some pretty good tools that work in one version of the system and do not work in another. This is the result of a vicious practice of using fixed offsets, function addresses, etc. If there is the slightest opportunity to dynamically find any undocumented structure field or non-exportable function, why not try it.

     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

Let's check if the address passed to us in the peProcess variable is a pointer to the "process" object. Because we are going to "dig" into the very depths of the system without its permission, we will have to move with the utmost caution.

The actual behavior of the ObReferenceObjectByPointer function is very different from what can be found in the DDK.

First, the DDK states that the second parameter must contain the requested access rights to the object. This is not true. ObReferenceObjectByPointer ignores this parameter altogether and you can pass anything in it. But we will still pass the required access mask, since the behavior of the ObReferenceObjectByPointer function may change in the future.

Second, the DDK states that the third parameter can only be a pointer to two "type" objects, corresponding to the "file" and "event" objects. These pointers can be retrieved from the exported variables IoFileObjectType and ExEventObjectType, respectively. This is also not the case, and you can pass a pointer to any "type" object. And this is just great, tk. will allow us to check for lousy random pointers. Of the 27 object types in Windows 2000 and 29 in Windows XP / 2003, about 15 pointers to "type" objects are exported. Among the exported ones are all we need, namely: IoDeviceObjectType, PsProcessType, MmSectionObjectType, IoFileObjectType. And that's great too.

Thirdly, something completely indistinct was said about the last parameter. To me, personally, his presence is generally incomprehensible. Usually, when accessing objects, it makes sense to specify the access mode when switching from user mode to kernel mode. Those. when converting a handle to a pointer. For example, in the ObReferenceObjectByHandle function, the AccessMode parameter is quite logical. But if we already have a pointer, then why specify the access rights? And it is still impossible to use the pointer in user mode (In any case, by documented means. By the way, using the Physical Memory Browser, we accessed the object directly from the user mode). If you are not lazy and take a look at the disassembled listing of the ObReferenceObjectByPointer function, then make sure of its very strange behavior, if the parameter AccessMode = KernelMode, namely ... If a pointer to any piece of valid memory is passed in the Object parameter, then the function, without checking anything, will consider that this is a valid pointer to the object's body. Subtracts the size of the OBJECT_HEADER structure from this pointer, i.e. will get a pointer to the object's title and increment the PointerCount field by one. Those. the function considers that if AccessMode = KernelMode, then the already existing pointer to the object was obtained somewhere earlier and is valid. That. if AccessMode = KernelMode, under no circumstances can a random pointer be passed. But if AccessMode = UserMode, the function will still check if the object type matches the declared one. This circumstance will allow us to check random pointers. In any case, all of the above, to put it mildly, is slightly different from what is written in the DDK.

With a good knowledge of the structures and principles of organizing objects, you can easily write your own validation function. For now, let's use the ObReferenceObjectByPointer services.


         .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

We look at which version of the system we are in. To avoid doing this every time, we use the g_dwWinVer variable.

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

If we are in XP and higher, we will look for the SectionObject field in the EPROCESS structure within 80h - 200h (I hope this range will be enough for all updates).

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

The IsLikeObjectPointer procedure removes "garbage". If it returns TRUE, then it is likely that the edi register contains a pointer to an object.

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

If we are at the stage of creating a process, then the reference count of the base section will be 3, and the descriptor counter is 1. If the process is deleted, then 2 and 0, respectively. These checks will further limit the number of possible candidates for the section object. I threw a one over the counter - just in case. As I already said, with a good level of knowledge, you can write a procedure that almost unambiguously identifies an object, but in order not to complicate the already complex code, we go easy ways. In order to finally make sure that the edi register contains a pointer to the "section" object, we use ObReferenceObjectByPointer.

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

Here we are already sure that we have found what we were looking for.

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

             .endw

If the SectionObject field is still not found, continue searching. Pointers in structures are always double-word aligned. Therefore, we move with DWORDs.

         .else

If we are in Windows 2000, then everything is not as simple as we would like. More precisely, the difficulties begin with service pack # 4. Before this ill-fated SP4, everything is simple. It is enough to "set" the ObReferenceObjectByHandle function to the number 4 and get a pointer to the base section of the process. But on SP4, if the process is started from the command line or bat-file, the descriptor of the base section of the process has the value 03E8h. Moreover, it is also fixed. At least on my machine and on another 5 machines of our esteemed colleagues who kindly agreed to test this example ( nerst , Noble Ghost , hGoblin , mokc0der , Vladimir ), the meaning was exactly that. Where does this strange meaning come from?

An object descriptor is an index into the process descriptor table. The descriptor table is implemented according to a three-level scheme, similar to the implementation of the address translation mechanism in x86 systems. The least significant 24 bits of the descriptor are interpreted as three 8-bit indices for each level. The first two levels consist of arrays of 256 elements, which contain pointers to the next level array. The lowest-level array is the secondary descriptor table and contains the actual descriptor table elements, each of which is eight bytes in size (a pointer to the object header and flags). The very last entry (256th) of each empty secondary descriptor table is initialized to -1. Since the pointer is four bytes in size, the descriptors are divisible by four. A descriptor with a value of 0 (the 1st element in the first secondary descriptor table) is not used. That. the very first object in the process will receive descriptor 4 (2nd element in the first secondary descriptor table), the second - 8 (3rd element in the first secondary descriptor table), etc. up to 3F8h (255th element in the first secondary descriptor table). Upon reaching the 3FCh descriptor (the 256th and last element in the first secondary descriptor table), the object manager will see -1, select the second secondary descriptor table if necessary, and fill the 256th element with the first (replacing -1). Subsequent descriptors (400h - 800h) will go into the second table of secondary descriptors, etc. the second is 8 (3rd element in the first secondary descriptor table), and so on. up to 3F8h (255th element in the first secondary descriptor table). Upon reaching the 3FCh descriptor (the 256th and last element in the first secondary descriptor table), the object manager will see -1, select the second secondary descriptor table if necessary, and fill the 256th element with the first (replacing -1). Subsequent descriptors (400h - 800h) will go into the second table of secondary descriptors, etc. the second is 8 (3rd element in the first secondary descriptor table), and so on. up to 3F8h (255th element in the first secondary descriptor table). Upon reaching the 3FCh descriptor (the 256th and last element in the first secondary descriptor table), the object manager will see -1, select the second secondary descriptor table if necessary, and fill the 256th element with the first (replacing -1). Subsequent descriptors (400h - 800h) will go into the second table of secondary descriptors, etc.

But on W2K + SP4 systems, when starting the process from the command line, the first table of secondary descriptors is filled from top to bottom: 3F8h - 4. Then the object manager has to fill in the last element in the first table of secondary descriptors 3FCh. And then everything continues as usual: 400h - and further in ascending order. For the sake of interest, I looked at how it happens on Windows XP: 7FCh - 4, 804h - and further in ascending order. I don't know where the 800h descriptor got to.

I understand perfectly well that if you are not familiar with the organization of the descriptor table, then you hardly understood anything from this explanation. Read the section "Object Descriptors and Process-Owned Descriptor Table" in the book "Inside Microsoft Windows 2000", pick up SoftICE and use these structures, and a lot will become clear. The ObjectTable field offset is 0128h, 00C4h, and 00C4h for Windows 2000, XP, and Server 2003, respectively. The Table field stores a pointer to a three-level 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

The answer to the question of why the object manager behaves exactly as described above is left to you.

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

We make three attempts to get a pointer to the base section of the process. First, let's try a descriptor with a value of 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

If we are not in the context of the process being created / removed, switch to it by calling KeAttachProcess.

                 .break .if status == STATUS_SUCCESS

If the attempt is successful, exit the loop.

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

If not, try the same with 03F8h.

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

Last try. There is nothing left but to take the descriptor value directly from the EPROCESS.SectionHandle field.

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

Just in case, let's check the descriptor for multiples of four and beyond reasonable limits.

                 .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 we got a pointer to the basic section of the process, then according to the diagram in Fig. 14-4 trying to get a pointer to the corresponding file object. Everything should be clear here and without explanation. About the IsAddressInPoolRanges function a bit later.

                         .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

The DeviceObject field of the FILE_OBJECT structure stores a pointer to the device object to which the file belongs. The device name can be retrieved from the device object. But then we get the path to the file relative to the device object. For example, "\ Device \ HarddiskVolume1 \ WINNT \ system32 \ notepad.exe". Use the RtlVolumeDeviceToDosName function to convert it to the more familiar "C: \ WINNT \ system32 \ notepad.exe". The DDK says that starting from XP we have to use IoVolumeDeviceToDosName. This is not necessary, since for backward compatibility, both functions have the same entry point.

                                 .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

Composing the full path to the image. DeviceToDosName itself allocates a buffer for the string, therefore, do not forget to free it.

Now let's take a look at the leftovers. "Let us analyze" - it is strongly said. I have been writing this article for quite some time, and the desire to finish it is growing in me as soon as possible with each new line :) I hope that you are able to understand what the remaining functions that have not been parsed are doing. I will just say a few words about each. There are also a lot of comments in the source code.



14.15 Procedure IsAddressInPoolRanges

The IsAddressInPoolRanges procedure is simple and checks to see if the address passed to it is within the boundaries of the system pools. The start and end addresses of the pools are well known. Details can be found in the book "The Internal Structure of Microsoft Windows 2000". I will only add that objects that are not synchronization objects (which the section is) are placed in the swapped pool. The IsAddressInPoolRanges procedure checks both paged and non-paged pools. IsAddressInPoolRanges assumes that it is running on a system with 2GB of system address space. For systems with PAE (Physical Address Extension) support and systems running with the / 3GB switch in boot.ini, it will have to be slightly modified.



14.16 Procedure IsLikeObjectPointer

This procedure makes an informed conclusion about whether the address passed to it can be a pointer to an object. Note: "may be" but not "is". It still needs to be checked by calling the ObReferenceObjectByPointer function. An address can be a pointer to an object if ...

Rice. 14-6. The result of the ProcessMon program. Installing service packs turns out to be quite process-greedy.

Because In the example, undocumented tricks are used, then, firstly, I do not guarantee its stable operation, and secondly, I am interested to know about any manifestations of instability. Therefore, please report any bugs found.

The source code of the driver in the archive .