64位內核開發第十二講,進程監視,ring3跟ring0事件同步.


一丶同步與互斥詳解,以及實現一個進程監視軟件.

1.用於線程同步的 KEVENT

事件很簡單分別分為 事件狀態. 以及事件類別.

事件狀態:
有信號 Signaled
無信號 Non-signaled
事件類別
自動恢復 Synchronization 自動設置
不自動恢復. Notification 手動設置

事件的創建函數

** IoCreateNotificationEvent() **

** KeClearEvent() ** 設置為無信號狀態

** KeResetEvent() ** 設置為無信號狀態並返回之前的狀態
** KeSetEvent()** 設置事件為有信號.

其實理解有信號跟無信號就可以了. 有信號了.我的等待函數才可以等待.
無信號就會阻塞在哪里.

事件的類別就是等待之后 根據你的設置.是自動設置為無信號. 還是不設置.
如果自動設置為無信號.那么下個線程來就會阻塞.直到我們設置為事件為有信號.才可以.

在內核中一般使用事件是使用匿名內核事件. 而不是使用IoCreateNotificationEvent

代碼如下:

KEVENT myKevent;
KeInitializeEvent(&myKevent, NotificationEvent,FALSE);

//設置事件為有信號

KeSetEvent(&myKevent,IO_NO_INCREMENT,FALSE);

//等待事件
KeWaitForSingleObject(&myEvent,Executive,KernerMode,False,NULL);

//因為設置的類別是手動設置類別.所以我們自己要把事件信號設置為無信號

//調用兩個都可以
KeResetEvent(&myEvent);
KeClearEvent(&myEvent);

2.進程監視 ring0 與 ring3同步使用Event

如果ring0跟ring3通訊.就要使用我們上面說的
ring0 -> ring3通訊的命名函數了.

IoCreateNotificationEvent
在使用Ring0 -> ring3通訊的時候.我們要了解下這個函數以及其它相關的知識

1.ring0 創建命名事件 - > ring3使用這個事件. 那么就需要建立一個名字了.名字如下;
** L"\\BaseNamedObjects\\你自定義的名字 **
2.再探 IoCreateDevice函數的作用.

IoCreateDevice 函數.眾所周知.是建立設備對象.
定義如下:

NTSTATUS IoCreateDevice(
  PDRIVER_OBJECT  DriverObject,
  ULONG           DeviceExtensionSize,   //設備擴展大小
  PUNICODE_STRING DeviceName,
  DEVICE_TYPE     DeviceType,
  ULONG           DeviceCharacteristics,
  BOOLEAN         Exclusive,
  PDEVICE_OBJECT  *DeviceObject
);

我們先前所說.創建設備對象的時候.第二個參數是設備擴展大小.
我們一直給0.但是現在因為 ring0 - ring3通信. 需要我們自定義數據結構.進行存儲ring0的數據. 那么可以使用這個設備擴展了.
如:
我們創建一個結構體. 將這個結構體的大小傳入到第二個參數中.
使用的時候在我們創建的設備對象中.有一個成員.是s DeviceExtension.這個就是我們設備擴展為我們申請的那塊內存.
我們可以轉化為我們自定義結構體大小.
代碼很簡單.如下:

typedef struct _MYDEVICE_EXTENSION
{
  //自定義數據
}MYDEVICE_EXTENSION,*PMYDEVICE_EXTENSION;

IoCreateDevice(DriverObject,sizeof(MYDEVICE_EXTENSION),....);
主要就是第二個參數.

使用:
PMYDEVICE_EXTENSION pMyDevice = (PMYDEVICE_EXTENSION)Device->DeviceExtension; 這個成員就指向我們擴展的內存.
強轉為我們的指針即可.

pMyDevice->xxx = xxx;
pMyDevice->xxx = xxx;

最后使用內核創建事件 進行創建即可. IoCreateNotificationEvent

ring3想使用ring0下定義的Event很簡單.
如下:

#define EVENT_NAME L"\\Global\\xxx"

HANDLE hEvent = OpenEventW(SYNCHRONIZE,FASE,EVENT_NAME);

while(WaitForSingleObject(hEvent,INFINITE))
{
  發送 DeviceIoControl讀取內核層的數據即可.(上面說的設備擴展數據)

}

ring3等待ring0的事件就很簡單了. 直接打開事件.等待即可.

3.進程監視代碼.

進程監視.首先會用到上面所說內容.然后分為下面幾個步驟

1.創建設備對象.設備對象中擴展屬性我們自定義結構體.傳入結構體大小即可.
2.創建全局設備對象變量指針.保存創建的設備對象

3.創建符號鏈接,ring3 跟 ring 0進行通訊

4.創建控制派遣函數.接受ring3下發的控制嗎.

5.使用IoCreateNotificationEvent創建事件對象.用於Ring3跟Ring0的事件同步.

6.注冊進程控制回調函數.當進程創建.或者銷毀會調用回調

7.回調函數,全局設備對象指針的子成員.指向我們自定義結構體.
轉換一下. 賦值參數.並且設置事件對象

8.ring3讀取數據的時候.控制函數將回調函數中賦值出來的數據拷貝給
ring3即可.

9.ring3進行打開事件.等待事件.發送DeviceIoControl控制嗎.讀取數據.顯示 數據.

代碼如下:

ring0:


#include <ntddk.h>
#include <ntstrsafe.h>





/*
符號連接名
設備對象名
事件等待名
*/


#define  IBINARY_LINKNAME L"\\DosDevices\\IBinarySymboliclnk"
#define  IBINARY_DEVICENAME  L"\\Device\\IBinaryProcLook"
#define  IBINARY_EVENTNAME       L"\\BaseNamedObjects\\ProcLook"

//定義 ring0 ring3控制碼
#define CTRLCODE_BASE 0x8000
#define MYCTRL_CODE(i) \
CTL_CODE(FILE_DEVICE_UNKNOWN,CTRLCODE_BASE +i,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define IOCTL_PROCESS_LOCK_READ MYCTRL_CODE(1)
UNICODE_STRING g_uSymbolicLinkName = { 0 };
//控制派遣函數.以及卸載函數.

void DriverUnLoad(PDRIVER_OBJECT pDriverObject);
NTSTATUS InitDeviceAnSymbolicLink(
	PDRIVER_OBJECT pDriverObj,
	UNICODE_STRING uDeviceName, 
	UNICODE_STRING uSymbolicLinkName,
	UNICODE_STRING uEventName);

NTSTATUS DisPatchComd(PDEVICE_OBJECT pDeviceObject, PIRP pIrp);
NTSTATUS DisPatchIoControl(PDEVICE_OBJECT pDeviceObject,PIRP pIrp);
VOID pMyCreateRoutine (IN HANDLE pParentId,HANDLE hProcessId,BOOLEAN bisCreate);

//自定義設備擴展.以及全局變量指針.進行保存的.
typedef struct _Device_Exten
{
	/*
	自定義數據.比如保存
	進程PID 父PID
	進程事件對象
	全局事件對象
	*/
	HANDLE  hProcess;         //進程句柄
	PKEVENT pkProcessEvent;   //全局事件對象,ring3使用
	HANDLE  hProcessId;       //進程的PID
	HANDLE  hpParProcessId;   //父進程ID.當前你也可以有進程名字
	BOOLEAN bIsCreateMark;    //表示是創建進程還是銷毀.創建進程回調可以看到

}DEVICE_EXTEN,* PDEVICE_EXTEN;
PDEVICE_OBJECT g_PDeviceObject;

//定義ring3->讀取ring0的數據
typedef struct _PROCESS_LONNK_READDATA
{
	HANDLE hProcessId;
	HANDLE hpParProcessId;
	BOOLEAN bIsCreateMark;
}PROCESS_LONNK_READDATA,*PPROCESS_LONNK_READDATA;

NTSTATUS
DriverEntry(
    _In_ PDRIVER_OBJECT  pDriverObject,
    _In_ PUNICODE_STRING RegistryPath
    )
{
	NTSTATUS ntStatus;
	UNICODE_STRING uDeviceName = { 0 };
	
	UNICODE_STRING uEventName = { 0 };


	//setp 1注冊卸載函數,以及設置通訊方式
	pDriverObject->DriverUnload = DriverUnLoad;
	

	//setp 2 初始化符號鏈接名.設備名. 以及事件對象名字,並且檢驗一下
	ntStatus = RtlUnicodeStringInit(&uDeviceName, IBINARY_DEVICENAME);
	if (!NT_SUCCESS(ntStatus))
	{
		KdPrint(("初始化設備名稱失敗"));
		return ntStatus;
	}
	KdPrint(("初始化設備名稱成功"));

	ntStatus = RtlUnicodeStringInit(&g_uSymbolicLinkName, IBINARY_LINKNAME);
	if (!NT_SUCCESS(ntStatus))
	{
		KdPrint(("初始化符號鏈接名字失敗"));
		return ntStatus;
	}
	KdPrint(("初始化符號鏈接名字成功"));
	ntStatus = RtlUnicodeStringInit(&uEventName, IBINARY_EVENTNAME);
	if (!NT_SUCCESS(ntStatus))
	{
		KdPrint(("初始化全局事件對象失敗"));
		return ntStatus;
	}
	KdPrint(("初始化全局事件對象成功"));

	//setp 3建立一個函數.函數內部進行初始化設備對象.初始化符號鏈接.初始化全局事件對象.
	ntStatus = InitDeviceAnSymbolicLink(
		pDriverObject,
		uDeviceName,
		g_uSymbolicLinkName,
		uEventName);
    return ntStatus;
}

//卸載驅動.關閉符號鏈接
void DriverUnLoad(PDRIVER_OBJECT pDriverObject)
{
	NTSTATUS ntStatus;
	UNICODE_STRING SymboLicLinkStr = { 0 };
	ntStatus = RtlUnicodeStringInit(&SymboLicLinkStr, IBINARY_LINKNAME);
	if (NT_SUCCESS(ntStatus))
	{
		ntStatus = IoDeleteSymbolicLink(&SymboLicLinkStr);
		if (!NT_SUCCESS(ntStatus))
		{
			KdPrint(("刪除符號鏈接失敗"));
		}
	}
	
	IoDeleteDevice(pDriverObject->DeviceObject);
	PsSetCreateProcessNotifyRoutine(pMyCreateRoutine, TRUE);
	KdPrint(("驅動已卸載"));
}

NTSTATUS InitDeviceAnSymbolicLink(
	PDRIVER_OBJECT pDriverObj,
	UNICODE_STRING uDeviceName,
	UNICODE_STRING uSymbolicLinkName,
	UNICODE_STRING uEventName)
{
	NTSTATUS ntStatus;
	PDEVICE_OBJECT pDeviceObject = NULL;
	//使用自定義結構
	ULONG i = 0;

	
	ntStatus = IoCreateDevice(
		pDriverObj,
		sizeof(DEVICE_EXTEN),//使用設備擴展.指定大小.那么設備對象中成員就會指向這塊內存
		&uDeviceName,
		FILE_DEVICE_UNKNOWN,
		FILE_DEVICE_SECURE_OPEN,
		FALSE,                //獨占設備
		&pDeviceObject);
	if (!NT_SUCCESS(ntStatus))
	{
		KdPrint(("創建設備對象失敗"));
		IoDeleteDevice(pDeviceObject);
		return ntStatus;
	}
	pDriverObj->Flags |= DO_BUFFERED_IO;
	//成功之后保存到全局變量中
	KdPrint(("創建設備對象成功"));
	g_PDeviceObject = pDeviceObject;


	//創建事件.ring3->ring0的事件
	PDEVICE_EXTEN pDeviceExten = (PDEVICE_EXTEN)pDeviceObject->DeviceExtension;
	pDeviceExten->pkProcessEvent = IoCreateNotificationEvent(&uEventName, &pDeviceExten->hProcess);
	KeClearEvent(pDeviceExten->pkProcessEvent);

	//創建符號鏈接

	ntStatus = IoCreateSymbolicLink(&g_uSymbolicLinkName, &uDeviceName);
	if (!NT_SUCCESS(ntStatus))
	{
		KdPrint(("創建符號鏈接失敗"));
		IoDeleteDevice(pDeviceObject);
		return ntStatus;
	}
	KdPrint(("創建符號鏈接成功"));

	
	
	
	/*
	因為設備對象擴展我們傳入了DEVICE_EXTEN大小.所以在調用IoCreateDevice的時候.返回的
	設備對象中.設備對象會根據我們傳入的大小創建一塊內存.這塊內存就保存在DeviceExtension
	這個字段中.
	下面調用IoCreateNotificationEvent是創建了一個命名事件.我們將事件放到我們結構體中.
	這個函數創建的事件必須手工設置事件狀態.所以我們首先初始化為無信號狀態.
	總的來說.IoCreateNotificationEvent創建的時候需要一個HANDLE以及一個PKEVENT.
	*/


	//注冊回調控制函數.當進程來了會通知.
	//	PsSetCreateProcessNotifyRoutine
	ntStatus = PsSetCreateProcessNotifyRoutine(pMyCreateRoutine,FALSE); //FASLE為注冊
	if (!NT_SUCCESS(ntStatus))
	{
		KdPrint(("注冊系統回調失敗"));
		IoDeleteDevice(pDeviceObject);
		return ntStatus;
	}
	KdPrint(("注冊系統回調成功"));


	//初始化派遣函數
	for (i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++)
	{
		pDriverObj->MajorFunction[i] = DisPatchComd;
	}
	pDriverObj->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DisPatchIoControl;

	return STATUS_SUCCESS;
}

//當進程來的時候.會通知你.
VOID pMyCreateRoutine(
	IN HANDLE pParentId,
	HANDLE hProcessId,
	BOOLEAN bisCreate)
{
	/*
	進程來的時候會通知我們.所以我們給設備擴展進行賦值.賦值進程ID以及是否創建
	*/
	PDEVICE_EXTEN pDeviceExten =(PDEVICE_EXTEN)g_PDeviceObject->DeviceExtension;
	pDeviceExten->hProcessId = hProcessId;
	pDeviceExten->hpParProcessId = pParentId;
	pDeviceExten->bIsCreateMark = bisCreate;
	//賦值完畢之后.設置信號狀態為有信號. 這樣Ring3就會等待到事件了.
	KeSetEvent(pDeviceExten->pkProcessEvent,0,FALSE);
	//通知ring3可以讀取了.那么還要設置為ring0的事件為無信號.用來保持同步
	//KeClearEvent(pDeviceExten->pkProcessEvent);
	KeResetEvent(pDeviceExten->pkProcessEvent); //跟ClearEvent一樣.上面的快.這個會返回上一個設置的信號狀態.都用一次

}

NTSTATUS DisPatchComd(PDEVICE_OBJECT pDeviceObject, PIRP pIrp)
{
	pIrp->IoStatus.Information = 0;
	pIrp->IoStatus.Status = STATUS_SUCCESS;
	IoCompleteRequest(pIrp, IO_NO_INCREMENT);

	return pIrp->IoStatus.Status;
}
NTSTATUS DisPatchIoControl(PDEVICE_OBJECT pDeviceObject, PIRP pIrp)
{
	/*
	Ring3 -> ring0通訊的控制派遣函數.自定義控制碼.獲取Irp堆棧.獲取緩沖區.

	*/
	NTSTATUS ntStatus;
	PIO_STACK_LOCATION pIrpStack;
	PVOID pUserOutPutBuffer;
	PPROCESS_LONNK_READDATA pReadData;
	ULONG uIoControl = 0;
	ULONG uReadLength;
	ULONG uWriteLeng;
	PDEVICE_EXTEN pDeviceExten;
	/*
	開始解析用戶操作
	*/
	KdPrint(("解析用戶控制碼"));
	pIrpStack = IoGetCurrentIrpStackLocation(pIrp);
	//從堆棧中獲取用戶控制數據
	pUserOutPutBuffer = pIrp->AssociatedIrp.SystemBuffer; //如果控制碼是緩沖區方式.就使用這個.

	//定義讀取的數據
	pReadData = (PPROCESS_LONNK_READDATA)pUserOutPutBuffer;
	//獲取控制碼.長度.進行讀取
	uIoControl = pIrpStack->Parameters.DeviceIoControl.IoControlCode;
	uReadLength = pIrpStack->Parameters.DeviceIoControl.InputBufferLength;
	uWriteLeng = pIrpStack->Parameters.DeviceIoControl.OutputBufferLength;
	
	switch (uIoControl)
	{
	case IOCTL_PROCESS_LOCK_READ:
		//拷貝數據即可.
		pDeviceExten = (PDEVICE_EXTEN)g_PDeviceObject->DeviceExtension;
		pReadData->hProcessId = pDeviceExten->hProcessId;
		pReadData->hpParProcessId = pDeviceExten->hpParProcessId;
		pReadData->bIsCreateMark = pDeviceExten->bIsCreateMark;
		KdPrint(("內核讀取 父ID = %d,子Id = %d,是否創建 = %d", (ULONG)pDeviceExten->hpParProcessId, (ULONG)pDeviceExten->hProcessId, (ULONG)pDeviceExten->bIsCreateMark));

		break;
	default:
		KdPrint(("其它控制碼"));
		ntStatus = STATUS_INVALID_PARAMETER;
		uWriteLeng = 0;
		break;
	}

	pIrp->IoStatus.Information = uWriteLeng; //讀取的字節數
	pIrp->IoStatus.Status = ntStatus;
	IoCompleteRequest(pIrp,IO_NO_INCREMENT);
	return ntStatus;
}

ring3下打開事件對象即可. 注意我自己寫的是打開 全局事件對象\\Global
然后發送控制碼.ring0進行賦值即可.

ring3代碼.

// ProcWatchClientConsole.cpp : Defines the entry point for the console application.
//



#include "windows.h"
#include "winioctl.h"
#include "stdio.h"




#define EVENT_NAME    L"Global\\ProcLook"




#define CTRLCODE_BASE 0x8000
#define MYCTRL_CODE(i) \
CTL_CODE(FILE_DEVICE_UNKNOWN,CTRLCODE_BASE +i,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define IOCTL_PROCESS_LOCK_READ MYCTRL_CODE(1)

#define		IOCTL_PROCESS_LOCK_READ		MYCTRL_CODE(1)

typedef struct _PROCESS_LONNK_READDATA
{
	HANDLE hProcessId;
	HANDLE hpParProcessId;
	BOOLEAN bIsCreateMark;
}PROCESS_LONNK_READDATA, *PPROCESS_LONNK_READDATA;



int main(int argc, char* argv[])
{

	PROCESS_LONNK_READDATA pmdInfoNow = { 0 };
	PROCESS_LONNK_READDATA pmdInfoBefore = { 0 };

	
	// 打開驅動設備對象
	HANDLE hDriver = ::CreateFile(
		"\\\\.\\IBinarySymboliclnk",
		GENERIC_READ | GENERIC_WRITE,
		0,
		NULL,
		OPEN_EXISTING,
		FILE_ATTRIBUTE_NORMAL,
		NULL);
	if (hDriver == INVALID_HANDLE_VALUE)
	{
		printf("Open device failed:%x\n", GetLastError());
		return -1;
	}
	// 打開內核事件對象
	HANDLE hProcessEvent = ::OpenEventW(SYNCHRONIZE, FALSE, EVENT_NAME);

	if (NULL == hProcessEvent)
	{
		OutputDebugString("打開事件對象失敗");
		system("pause");
		return 0;
	}
	OutputDebugString("打開事件對象成功");
	
	while (TRUE)
	{
		::WaitForSingleObject(hProcessEvent, INFINITE); //等待事件
		DWORD    dwRet = 0;
		BOOL     bRet = FALSE;

		bRet = ::DeviceIoControl(
			hDriver,
			IOCTL_PROCESS_LOCK_READ,
			NULL,
			0,
			&pmdInfoNow,
			sizeof(pmdInfoNow),
			&dwRet,
			NULL);
		if (!bRet)
		{
			OutputDebugString("發送控制碼失敗");
			system("pause");
			return 0;
		}

		OutputDebugString("Ring3發送控制碼成功");

		if (bRet)
		{
			if (pmdInfoNow.hpParProcessId != pmdInfoBefore.hpParProcessId || \
				pmdInfoNow.hProcessId != pmdInfoBefore.hProcessId || \
				pmdInfoNow.bIsCreateMark != pmdInfoBefore.bIsCreateMark)
			{
				if (pmdInfoNow.bIsCreateMark)
				{
					printf("進程創建 PID = %d\n", pmdInfoNow.hProcessId);
				}
				else
				{
					printf("進程退出,PID = %d\n", pmdInfoNow.hProcessId);
				}
				pmdInfoBefore = pmdInfoNow;
			}
		}
		else
		{
			printf("Get Proc Info Failed!\n");
			break;
		}
	}

	::CloseHandle(hDriver);
	system("pause");

	return 0;
}

注意,ring0下設置的進程系統回調是用的 PsSetCreateProcessNotifyRoutine 這個內核函數只能監視
進程ID 父進程ID以及一個創建或者結束標記. 我們可以使用Ex系列.這樣的話可以監視到進程的名字.等等.

演示


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM