Detecting Appliation Starting and Terminate

最近有朋友提到個需求,他的目的是想偵測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 Driver32-bit 應用程式互通訊息的情況。在本文最末我附上修改後可運行在Windows 7/8/8.1的版本,也處理了32-bit/64-bit Driver/App混用的情況。

 

ProcMon.c記得必須要用Visual Studio 2013搭配Windows 8.0或是8.1 DDK來編譯,下面是運用此DriverC#應用程式主要的程式碼,執行前記得要把ProcMon.c編譯出來的Kernel Driver放到執行檔所在的目錄下(ProcMon_x86.sysProcMon_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