最近有朋友提到個需求,他的目的是想偵測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