最近有朋友提到個需求,他的目的是想偵測Windows系統中所有應用程式的啟動與結束,這通常用於防毒軟體及特定的領域,所以我做了一些研究及實驗。
Detecting Appliation Starting and Terminate
文/黃忠成
緣起
最近有朋友提到個需求,他的目的是想偵測Windows系統中所有應用程式的啟動與結束,這通常用於防毒軟體及特定的領域,所以我做了一些研究及實驗。
最簡單的方式
.NET Framework中提供了Process物件,透過其GetProcesses函式可以取得目前正在執行的應用程式列表,以此為基礎,我們可以先記錄一次列表,接著每秒以比對的方式來得知哪個應用程式被啟動或是關閉,這種手法並不算是正確的解法,因為只要應用程式的啟動跟結束發生在一秒內,這種手法就失效了,再者每秒去列舉所有執行中的應用程式必然會耗掉許多CPU資源,算是個可行但缺點大到不可接受的短線解法。
WH_SHELL Hook
透過WH_SHELL掛載System-Wide Hook是另一種解法,這解法的基礎是透過WH_SHELL得知Window的建立及摧毀,進而透過GetWindowThreadProcessId來取得Window對應的ProcessId,進而得知哪個應用程式被建立及摧毀,這手法的缺點有兩個,其必須要以C/C++撰寫,因為Managed Code(C#)是無法掛載System-Wide Hook的,另一個缺點是通常只能得知哪個Window被建立,進而取得哪個Process被啟動,但要透過Window摧毀的事件來得知Process被關閉就需要額外的手段了。
User mode下最佳解: WMI
		   WMI是在Managed Code環境下的最佳解,透過監控WMI事件可以幾近完美的解決這個需求,下列是WMI的實作Code。
		
		using System;
		using System.Collections.Generic;
		using System.IO;
		using System.Linq;
		using System.Runtime.InteropServices;
		using System.Text;
		using System.Threading.Tasks;
		
		namespace DemoApp
		{
		    class Program
		    {
		        static void Main(string[] args)
		        {
		            ProcessMonitor pm = new ProcessMonitor();
		            pm.EventArrived += pm_EventArrived;
		            pm.Activate(true);
		            Console.Read();
		            pm.Activate(false);
		            Console.Read();
		        }
		
		        static void pm_EventArrived(object sender, ProcessEventArgs e)
		        {
		            Console.WriteLine("id:" + e.ProcessID.ToString() + (e.Starting ? " created " : " termiated"));
		        }
		    }
		}
		
執行結果如下圖。
		 
看起來很OK不是嗎? 我說是”幾近”最佳解的原因是WMI的運作是基於一個Queue的概念,如果瞬間有太多Process同時啟動塞爆Queue,那麼就會有事件遺失的可能,事實上,就算沒有發生這種情況,在我的實務經驗中,WMI遺失事件的機率也頗高,原因不明,但通常只要重新啟動監測程式就會正常。
		 
		
終極招: Kernel Driver
那有沒有完美的解法? 就我目前所知,只有切到Kernel Mode才有可能,在Kernel Mode下有個PsSetCreateProcessNotifyRoutineEx API可以得知Process的啟動即結束,但這個API只有在Ring 0,也就是Kernel Driver的環境中才能呼叫,所知最早的資料是CodeProject上的一篇文章。
http://www.codeproject.com/Articles/2018/Detecting-Windows-NT-K-process-execution
這篇文章有點時間了,當時的code已經無法套用到現在的Windows 7/8/8.1 環境中,癥結點是在現在的Windows環境中,要求Kernel Driver必須要擁有簽章才能安裝,其次是這篇文章的背景時期64-bit並不算普及,所以裡面的code沒有處理到64-bit Driver與32-bit 應用程式互通訊息的情況。在本文最末我附上修改後可運行在Windows 7/8/8.1的版本,也處理了32-bit/64-bit Driver/App混用的情況。
ProcMon.c記得必須要用Visual Studio 2013搭配Windows 8.0或是8.1 DDK來編譯,下面是運用此Driver的C#應用程式主要的程式碼,執行前記得要把ProcMon.c編譯出來的Kernel Driver放到執行檔所在的目錄下(ProcMon_x86.sys、ProcMon_x64.sys)。
		 
		//---------------------------------------------------------------------------
		//
		// ProcObsrv.c
		//
		// SUBSYSTEM: 
		//                System monitor
		// MODULE:    
		//                Driver for monitoring NT process and DLLs mapping
		//              monitoring. 
		//
		// DESCRIPTION:
		//              This code is based on the James Finnegan sample 
		//              (MSJ January 1999).
		//
		// Ivo Ivanov, January 2002
		//                                                                         
		// code6421, 2015
		//---------------------------------------------------------------------------
		
		
		//---------------------------------------------------------------------------
		//
		// Includes
		//  
		//---------------------------------------------------------------------------
		#include <ntddk.h>
		//---------------------------------------------------------------------------
		//
		// Defines
		//  
		//---------------------------------------------------------------------------
		
		#define FILE_DEVICE_UNKNOWN             0x00000022
		#define IOCTL_UNKNOWN_BASE              FILE_DEVICE_UNKNOWN
		#define IOCTL_PROCOBSRV_ACTIVATE_MONITORING    \
		     CTL_CODE(IOCTL_UNKNOWN_BASE, 0x0800, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
		#define IOCTL_PROCOBSRV_GET_PROCINFO    \
		     CTL_CODE(IOCTL_UNKNOWN_BASE, 0x0801, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
		//---------------------------------------------------------------------------
		//
		// Forward declaration
		//  
		//---------------------------------------------------------------------------
		void UnloadDriver(
		     PDRIVER_OBJECT DriverObject
		     );
		NTSTATUS DispatchCreateClose(
		     IN PDEVICE_OBJECT DeviceObject,
		     IN PIRP Irp
		     );
		NTSTATUS DispatchIoctl(
		     IN PDEVICE_OBJECT DeviceObject,
		     IN PIRP Irp
		     );
		//
		// Process function callback
		//  
		VOID ProcessCallback(
		     IN HANDLE  hParentId,
		     IN HANDLE  hProcessId,
		     _Inout_opt_ PPS_CREATE_NOTIFY_INFO CreateInfo
		     );
		//
		// Structure for holding info about activating/deactivating the driver
		//
		typedef struct _ActivateInfo
		{
		     BOOLEAN  bActivated;
		     DWORD32 hEventHandle;
		} ACTIVATE_INFO, *PACTIVATE_INFO;
		
		#pragma pack(push, 8)
		//
		// Structure for process callback information
		//
		typedef struct _ProcessCallbackInfo
		{
		     DWORD32  hParentId;
		     DWORD32  hProcessId;
		     BOOLEAN bCreated;
		} PROCESS_CALLBACK_INFO, *PPROCESS_CALLBACK_INFO;
		#pragma pack(pop) 
		
		//
		// Private storage for process retreiving 
		//
		typedef struct _DEVICE_EXTENSION
		{
		     PDEVICE_OBJECT DeviceObject;
		     //
		     // Shared section
		     //
		     HANDLE  hProcessId;
		     //
		     // Process section data
		     //
		     PKEVENT ProcessEvent;
		     HANDLE  hParentId;
		     BOOLEAN bCreate;
		} DEVICE_EXTENSION, *PDEVICE_EXTENSION;
		
		//
		// Global variables
		//
		PDEVICE_OBJECT g_pDeviceObject;
		ACTIVATE_INFO  g_ActivateInfo;
		PKEVENT g_hEvent = NULL;
		OBJECT_HANDLE_INFORMATION  g_EventHandle = { 0 };
		
		
		#define DELAY_ONE_MICROSECOND   (-10)
		#define DELAY_ONE_MILLISECOND   (DELAY_ONE_MICROSECOND*1000)
		
		//
		// The main entry point of the driver module
		//
		NTSTATUS DriverEntry(
		     IN PDRIVER_OBJECT DriverObject,
		     IN PUNICODE_STRING RegistryPath
		     )
		{
		     NTSTATUS                  ntStatus;
		     UNICODE_STRING            uszDriverString;
		     UNICODE_STRING            uszDeviceString;
		     UNICODE_STRING            uszProcessEventString;
		     PDEVICE_OBJECT            pDeviceObject;
		     PDEVICE_EXTENSION         extension;
		     HANDLE                    hProcessHandle;
		
		     DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "Driver starting");
		     //    
		     // Point uszDriverString at the driver name
		     //
		     RtlInitUnicodeString(&uszDriverString, L"\\Device\\ProcMon");
		     //
		     // Create and initialize device object
		     //
		     ntStatus = IoCreateDevice(
		         DriverObject,
		         sizeof(DEVICE_EXTENSION),
		         &uszDriverString,
		         FILE_DEVICE_UNKNOWN,
		         0,
		         FALSE,
		         &pDeviceObject
		         );
		     if (ntStatus != STATUS_SUCCESS)
		     {
		         DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "Faild to IoCreateDevice .status : 0x%X \n", ntStatus);
		         return ntStatus;
		     }
		     //
		     // Assign extension variable
		     //
		     extension = pDeviceObject->DeviceExtension;
		     //
		     // Point uszDeviceString at the device name
		     //
		     RtlInitUnicodeString(&uszDeviceString, L"\\DosDevices\\ProcMon");
		     //
		     // Create symbolic link to the user-visible name
		     //
		     ntStatus = IoCreateSymbolicLink(&uszDeviceString, &uszDriverString);
		
		     if (ntStatus != STATUS_SUCCESS)
		     {
		         DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "Faild to IoCreateSymbolicLink .status : 0x%X \n", ntStatus);
		         //
		         // Delete device object if not successful
		         //
		         IoDeleteDevice(pDeviceObject);
		         return ntStatus;
		     }
		     DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "complete IoSymbolLink");
		     //
		     // Assign global pointer to the device object for use by the callback functions
		     //
		     g_pDeviceObject = pDeviceObject;
		     //
		     // Setup initial state
		     //
		     g_ActivateInfo.bActivated = FALSE;
		     //
		     // Load structure to point to IRP handlers
		     //
		     DriverObject->DriverUnload = UnloadDriver;
		     DriverObject->MajorFunction[IRP_MJ_CREATE] = DispatchCreateClose;
		     DriverObject->MajorFunction[IRP_MJ_CLOSE] = DispatchCreateClose;
		     DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DispatchIoctl;
		     DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "complete DriverObject Setting");
		
		     //not working in 8.1/xp/vista and some version
		     //
		     // Create event for user-mode processes to monitor
		     //
		     /*RtlInitUnicodeString(
		         &uszProcessEventString,
		         L"\\BaseNamedObjects\\ProcMonProcessEvent"
		         );
		         extension->ProcessEvent = IoCreateNotificationEvent(
		         &uszProcessEventString,
		         &hProcessHandle
		         );
		         g_EventHandle = hProcessHandle;*/
		     DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "complete ProessEvent %s", extension->ProcessEvent);
		     //
		     // Clear it out
		     //
		     //KeClearEvent(extension->ProcessEvent);
		     //
		     // Return success
		     //
		     return ntStatus;
		}
		
		//
		// Create and close routine
		//
		NTSTATUS DispatchCreateClose(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
		{
		     Irp->IoStatus.Status = STATUS_SUCCESS;
		     Irp->IoStatus.Information = 0;
		     IoCompleteRequest(Irp, IO_NO_INCREMENT);
		     return STATUS_SUCCESS;
		}
		
		//
		// Process function callback
		//
		VOID ProcessCallback(
		     IN HANDLE  hParentId,
		     IN HANDLE  hProcessId,
		     _Inout_opt_ PPS_CREATE_NOTIFY_INFO CreateInfo
		     )
		{
		     DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "enter process callback");
		     __try
		     {
		         PDEVICE_EXTENSION extension;
		         //
		         // Assign extension variable
		         //
		         extension = g_pDeviceObject->DeviceExtension;
		         //
		         // Assign current values into device extension.  
		         // User-mode apps will pick it up using DeviceIoControl calls.
		         //
		         extension->hParentId = hParentId;
		         extension->hProcessId = hProcessId;
		         extension->bCreate = CreateInfo != NULL;
		         //
		         // Signal the event thus the user-mode apps listening will be aware
		         // that something interesting has happened.  
		         //
		         if (g_hEvent != NULL)
		         {
		              KeSetEvent(g_hEvent, 0, FALSE);
		              KeClearEvent(g_hEvent);
		         }
		     }
		     __except (1)
		     {
		         DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "callback process fail.");
		     }
		     DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "exit process callback");
		}
		
		//
		// IOCTL handler for setting the callback
		//
		NTSTATUS ActivateMonitoringHanlder(
		     IN PIRP           Irp
		     )
		{
		     NTSTATUS               ntStatus = STATUS_UNSUCCESSFUL;
		     PIO_STACK_LOCATION     irpStack = IoGetCurrentIrpStackLocation(Irp);
		     PACTIVATE_INFO         pActivateInfo;
		
		     DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "activate monitor");
		     if (irpStack->Parameters.DeviceIoControl.InputBufferLength >=
		         sizeof(ACTIVATE_INFO))
		     {
		         pActivateInfo = Irp->AssociatedIrp.SystemBuffer;
		         if (g_hEvent == NULL)
		         {
		              NTSTATUS status2 = ObReferenceObjectByHandle(
		                  (HANDLE)pActivateInfo->hEventHandle,
		                  GENERIC_ALL,
		                  NULL,
		                  KernelMode,
		                  &g_hEvent,
		                  &g_EventHandle);
		         }
		
		         if (g_ActivateInfo.bActivated != pActivateInfo->bActivated)
		         {
		              if (pActivateInfo->bActivated)
		              {
		                  //
		                  // Set up callback routines
		                  //
		                  ntStatus = PsSetCreateProcessNotifyRoutineEx(ProcessCallback, FALSE);
		                  if (ntStatus != STATUS_SUCCESS)
		                  {
		                       DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "Faild to PsSetCreateProcessNotifyRoutineEx .status : 0x%X \n", ntStatus);
		                       return ntStatus;
		                  }
		                  // Setup the global data structure
		                  //
		                  g_ActivateInfo.bActivated = pActivateInfo->bActivated;
		              } // if
		              else
		              {
		                  //
		                  // restore the call back routine, thus givinig chance to the 
		                  // user mode application to unload dynamically the driver
		                  //
		                  ntStatus = PsSetCreateProcessNotifyRoutineEx(ProcessCallback, TRUE);
		                  ObDereferenceObject(g_hEvent);
		                  g_hEvent = NULL; //remove event
		                  if (ntStatus != STATUS_SUCCESS)
		                       return ntStatus;
		                  else
		                  {
		                       g_ActivateInfo.bActivated = FALSE;
		                       DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "Faild to PsSetCreateProcessNotifyRoutineEx .status : 0x%X \n", ntStatus);
		                  }
		              }
		              DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Load\n");
		
		              ntStatus = STATUS_SUCCESS;
		         } // if
		     } // if
		
		     return ntStatus;
		}
		
		//
		// The dispatch routine
		//
		NTSTATUS DispatchIoctl(
		     IN PDEVICE_OBJECT DeviceObject,
		     IN PIRP           Irp
		     )
		{
		     NTSTATUS               ntStatus = STATUS_UNSUCCESSFUL;
		     PIO_STACK_LOCATION     irpStack = IoGetCurrentIrpStackLocation(Irp);
		     PDEVICE_EXTENSION      extension = DeviceObject->DeviceExtension;
		     PPROCESS_CALLBACK_INFO pProcCallbackInfo;
		     UNICODE_STRING            uszProcessEventString;
		     DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "enter dispatchIoCtl");
		     DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "control code = %d\n", irpStack->Parameters.DeviceIoControl.IoControlCode);
		     //
		     // These IOCTL handlers are the set and get interfaces between
		     // the driver and the user mode app
		     //
		     switch (irpStack->Parameters.DeviceIoControl.IoControlCode)
		     {
		     case IOCTL_PROCOBSRV_ACTIVATE_MONITORING:
		     {
		         ntStatus = ActivateMonitoringHanlder(Irp);
		         DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "Activate Monitoring receive");
		         ntStatus = STATUS_SUCCESS;
		         break;
		     }
		     case IOCTL_PROCOBSRV_GET_PROCINFO:
		     {
		         DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "IOCTL_PROCOBSRV_GET_PROCINFO. OutputBufferLength = %d\n", irpStack->Parameters.DeviceIoControl.OutputBufferLength);
		         DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "sizeof(PROCESS_CALLBACK_INFO) = %d\n", sizeof(PROCESS_CALLBACK_INFO));
		         if (irpStack->Parameters.DeviceIoControl.OutputBufferLength >=
		              sizeof(PROCESS_CALLBACK_INFO))
		         {
		              pProcCallbackInfo = Irp->AssociatedIrp.SystemBuffer;
		              pProcCallbackInfo->hParentId = (DWORD32)extension->hParentId;
		              pProcCallbackInfo->hProcessId = (DWORD32)extension->hProcessId;
		              pProcCallbackInfo->bCreated = extension->bCreate;
		
		              ntStatus = STATUS_SUCCESS;
		         }
		         else
		              DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "buffer to small");
		
		         break;
		     }
		
		     default:
		         break;
		     }
		
		     Irp->IoStatus.Status = ntStatus;
		     //
		     // Set number of bytes to copy back to user-mode
		     //
		     if (ntStatus == STATUS_SUCCESS)
		         Irp->IoStatus.Information =
		         irpStack->Parameters.DeviceIoControl.OutputBufferLength;
		     else
		         Irp->IoStatus.Information = 0;
		
		     IoCompleteRequest(Irp, IO_NO_INCREMENT);
		     return ntStatus;
		}
		
		//
		// Driver unload routine
		//
		void UnloadDriver(
		     IN PDRIVER_OBJECT DriverObject
		     )
		{
		     UNICODE_STRING  uszDeviceString;
		     //
		     //  By default the I/O device is configured incorrectly or the 
		     // configuration parameters to the driver are incorrect.
		     //
		     NTSTATUS        ntStatus = STATUS_DEVICE_CONFIGURATION_ERROR;
		
		     if (g_ActivateInfo.bActivated)
		         //
		         // restore the call back routine, thus givinig chance to the 
		         // user mode application to unload dynamically the driver
		         //
		         ntStatus = PsSetCreateProcessNotifyRoutineEx(ProcessCallback, TRUE);
		
		     IoDeleteDevice(DriverObject->DeviceObject);
		
		     RtlInitUnicodeString(&uszDeviceString, L"\\DosDevices\\ProcMon");
		     IoDeleteSymbolicLink(&uszDeviceString);
		}
		
		//----------------------------End of the file -------------------------------
		 
		DemoApp/Program.cs
		using System;
		using System.Collections.Generic;
		using System.IO;
		using System.Linq;
		using System.Runtime.InteropServices;
		using System.Text;
		using System.Threading.Tasks;
		
		namespace DemoApp
		{
		    class Program
		    {
		        static void Main(string[] args)
		        {
		            ProcessMonitor pm = new ProcessMonitor();
		            pm.EventArrived += pm_EventArrived;
		            pm.Activate(true);
		            Console.Read();
		            pm.Activate(false);
		            Console.Read();
		        }
		
		        static void pm_EventArrived(object sender, ProcessEventArgs e)
		        {
		            Console.WriteLine("id:" + e.ProcessID.ToString() + (e.Starting ? " created " : " termiated"));
		        }
		    }
		}
		
		
整個Kernel Driver及應用程式的Source code可以在本文最末的壓縮檔找到,下面是執行的結果。
		 
最後提醒讀者,你必須擁有簽章來簽署Driver才能運行於Production環境中,在沒有簽章的情況下,可以透過關閉Windows的簽章檢驗機制來測試。
https://msdn.microsoft.com/en-us/library/windows/hardware/ff547565(v=vs.85).aspx
喔,最後的最後再提醒一件事,Kernel Driver不比一般應用程式,寫錯了可是會讓你看藍藍的畫面看不完哦。
完整Source code下載
http://www.code6421.com/BlogPics/apdet/Samples.zip