驅動開發:核心中程序與控制程式碼互轉

2023-06-23 12:00:14

在核心開發中,經常需要進行程序和控制程式碼之間的互相轉換。程序通常由一個唯一的程序識別符號(PID)來標識,而控制程式碼是指對核心物件的參照。在Windows核心中,EProcess結構表示一個程序,而HANDLE是一個控制程式碼。

為了實現程序與控制程式碼之間的轉換,我們需要使用一些核心函數。對於程序PID和控制程式碼的互相轉換,可以使用函數如OpenProcessGetProcessId。OpenProcess函數接受一個PID作為引數,並返回一個控制程式碼。GetProcessId函數接受一個控制程式碼作為引數,並返回該程序的PID。

對於程序PID和EProcess結構的互相轉換,可以使用函數如PsGetProcessIdPsGetCurrentProcess。PsGetProcessId函數接受一個EProcess結構作為引數,並返回該程序的PID。PsGetCurrentProcess函數返回當前程序的EProcess結構。

最後,對於控制程式碼和EProcess結構的互相轉換,可以使用函數如ObReferenceObjectByHandle和PsGetProcessId。ObReferenceObjectByHandle函數接受一個控制程式碼和一個物件型別作為引數,並返回對該物件的參照。PsGetProcessId函數接受一個EProcess結構作為引數,並返回該程序的PID。

掌握這些核心函數的使用,可以方便地實現程序與控制程式碼之間的互相轉換。在進行程序和執行緒的核心開發之前,瞭解這些轉換功能是非常重要的。

程序PID與程序HANDLE之間的互相轉換: 程序PID轉化為HANDLE控制程式碼,可通過ZwOpenProcess這個核心函數,傳入PID傳出程序HANDLE控制程式碼,如果需要將HANDLE控制程式碼轉化為PID則可通過ZwQueryInformationProcess這個核心函數來實現,具體轉換實現方法如下所示;

在核心開發中,經常需要進行程序PID和控制程式碼HANDLE之間的互相轉換。將程序PID轉化為控制程式碼HANDLE的方法是通過呼叫ZwOpenProcess核心函數,傳入PID作為引數,函數返回對應程序的控制程式碼HANDLE。具體實現方法是,定義一個OBJECT_ATTRIBUTES結構體和CLIENT_ID結構體,將程序PID賦值給CLIENT_ID結構體的UniqueProcess欄位,呼叫ZwOpenProcess函數開啟程序,如果函數執行成功,將返回程序控制程式碼HANDLE,否則返回NULL。

將控制程式碼HANDLE轉化為程序PID的方法是通過呼叫ZwQueryInformationProcess核心函數,傳入程序控制程式碼和資訊類別作為引數,函數返回有關指定程序的資訊,包括程序PID。具體實現方法是,定義一個PROCESS_BASIC_INFORMATION結構體和一個NTSTATUS變數,呼叫ZwQueryInformationProcess函數查詢程序基本資訊,如果函數執行成功,將返回程序PID,否則返回0。

其中ZwQueryInformationProcess是一個未被匯出的函數如需使用要通過MmGetSystemRoutineAddress動態獲取到,該函數的原型定義如下:

NTSTATUS ZwQueryInformationProcess(
  HANDLE           ProcessHandle,
  PROCESSINFOCLASS ProcessInformationClass,
  PVOID            ProcessInformation,
  ULONG            ProcessInformationLength,
  PULONG           ReturnLength
);

函數可以接受一個程序控制程式碼ProcessHandle、一個PROCESSINFOCLASS列舉型別的引數ProcessInformationClass、一個用於儲存返回資訊的緩衝區ProcessInformation、緩衝區大小ProcessInformationLength和一個指向ULONG型別變數的指標ReturnLength作為引數。

在呼叫該函數時,ProcessInformationClass引數指定要獲取的程序資訊的型別。例如,如果要獲取程序的基本資訊,則需要將該引數設定為ProcessBasicInformation;如果要獲取程序的映像檔名,則需要將該引數設定為ProcessImageFileName。呼叫成功後,返回的資訊儲存在ProcessInformation緩衝區中。

在呼叫該函數時,如果ProcessInformation緩衝區的大小小於需要返回的資訊大小,則該函數將返回STATUS_INFO_LENGTH_MISMATCH錯誤程式碼,並將所需資訊的大小儲存在ReturnLength指標指向的ULONG型別變數中。

ZwQueryInformationProcess函數的返回值為NTSTATUS型別,表示函數執行的結果狀態。如果函數執行成功,則返回STATUS_SUCCESS,否則返回其他錯誤程式碼。

掌握這些轉換方法可以方便地在核心開發中進行程序PID和控制程式碼HANDLE之間的互相轉換。

#include <ntifs.h>

// 定義函數指標
typedef NTSTATUS(*PfnZwQueryInformationProcess)(
	__in HANDLE ProcessHandle,
	__in PROCESSINFOCLASS ProcessInformationClass,
	__out_bcount(ProcessInformationLength) PVOID ProcessInformation,
	__in ULONG ProcessInformationLength,
	__out_opt PULONG ReturnLength
);

PfnZwQueryInformationProcess ZwQueryInformationProcess;

// 傳入PID傳出HANDLE控制程式碼
HANDLE PidToHandle(ULONG PID)
{
	HANDLE hProcessHandle;
	OBJECT_ATTRIBUTES obj;
	CLIENT_ID clientid;

	clientid.UniqueProcess = PID;
	clientid.UniqueThread = 0;

	// 屬性初始化
	InitializeObjectAttributes(&obj, 0, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, 0, 0);

	NTSTATUS status = ZwOpenProcess(&hProcessHandle, PROCESS_ALL_ACCESS, &obj, &clientid);
	if (status == STATUS_SUCCESS)
	{
		// DbgPrint("[*] 已開啟 \n");
		ZwClose(&hProcessHandle);
		return hProcessHandle;
	}

	return 0;
}

// HANDLE控制程式碼轉換為PID
ULONG HandleToPid(HANDLE handle)
{
	PROCESS_BASIC_INFORMATION ProcessBasicInfor;

	// 初始化字串,並獲取動態地址
	UNICODE_STRING UtrZwQueryInformationProcessName = RTL_CONSTANT_STRING(L"ZwQueryInformationProcess");
	ZwQueryInformationProcess = (PfnZwQueryInformationProcess)MmGetSystemRoutineAddress(&UtrZwQueryInformationProcessName);

	// 呼叫查詢
	ZwQueryInformationProcess(
		handle,
		ProcessBasicInformation,
		(PVOID)&ProcessBasicInfor,
		sizeof(ProcessBasicInfor),
		NULL);

	// 返回程序PID
	return ProcessBasicInfor.UniqueProcessId;
}

VOID UnDriver(PDRIVER_OBJECT driver)
{
	DbgPrint("[-] 驅動解除安裝 \n");
}

NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
	DbgPrint("Hello LyShark \n");

	// 將PID轉換為HANDLE
	HANDLE ptr = PidToHandle(6932);
	DbgPrint("[*] PID  --> HANDLE = %p \n", ptr);

	// 控制程式碼轉為PID
	ULONG pid = HandleToPid(ptr);

	DbgPrint("[*] HANDLE  --> PID = %d \n", pid);

	Driver->DriverUnload = UnDriver;
	return STATUS_SUCCESS;
}

編譯並執行如上這段程式碼片段,將把程序PID轉為HANDLE控制程式碼,再通過控制程式碼將其轉為PID,輸出效果圖如下所示;

程序PID轉換為EProcess結構: 通過PsLookUpProcessByProcessId函數,該函數傳入一個PID則可獲取到該PID的EProcess結構體,具體轉換實現方法如下所示;

本段程式碼展示瞭如何使用Windows核心API函數PsLookupProcessByProcessId將一個PID(Process ID)轉換為對應的EProcess結構體,EProcess是Windows核心中描述程序的資料結構之一。

程式碼段中定義了一個名為PidToObject的函數,該函數的輸入引數是一個PID,輸出引數是對應的EProcess結構體。

在函數中,通過呼叫PsLookupProcessByProcessId函數來獲取對應PID的EProcess結構體,如果獲取成功,則呼叫ObDereferenceObject函數來減少EProcess物件的參照計數,並返回獲取到的EProcess指標;否則返回0。

DriverEntry函數中,呼叫了PidToObject函數將PID 6932轉換為對應的EProcess結構體,並使用DbgPrint函數輸出了轉換結果。最後設定了驅動程式解除安裝函數為UnDriver,當驅動程式被解除安裝時,UnDriver函數會被呼叫。

#include <ntifs.h>
#include <windef.h>

// 將Pid轉換為Object or EProcess
PEPROCESS PidToObject(ULONG Pid)
{
	PEPROCESS pEprocess;

	NTSTATUS status = PsLookupProcessByProcessId((HANDLE)Pid, &pEprocess);

	if (status == STATUS_SUCCESS)
	{
		ObDereferenceObject(pEprocess);
		return pEprocess;
	}

	return 0;
}

VOID UnDriver(PDRIVER_OBJECT driver)
{
	DbgPrint("[-] 驅動解除安裝 \n");
}

NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
	DbgPrint("Hello LyShark \n");

	// 將PID轉換為PEPROCESS
	PEPROCESS ptr = PidToObject(6932);
	DbgPrint("[*] PID  --> PEPROCESS = %p \n", ptr);

	Driver->DriverUnload = UnDriver;
	return STATUS_SUCCESS;
}

編譯並執行如上這段程式碼片段,將把程序PID轉為EProcess結構,輸出效果圖如下所示;

程序HANDLE與EPROCESS互相轉換:Handle轉換為EProcess結構可使用核心函數ObReferenceObjectByHandle實現,反過來EProcess轉換為Handle控制程式碼可使用ObOpenObjectByPointer核心函數實現,具體轉換實現方法如下所示;

首先,將Handle轉換為EProcess結構體,可以使用ObReferenceObjectByHandle核心函數。該函數接受一個Handle引數,以及對應的物件型別(這裡為EProcess),並返回對應物件的指標。此函數會對返回的物件增加參照計數,因此在使用完畢後,需要使用ObDereferenceObject將參照計數減少。

其次,將EProcess結構體轉換為Handle控制程式碼,可以使用ObOpenObjectByPointer核心函數。該函數接受一個指向物件的指標(這裡為EProcess結構體的指標),以及所需的存取許可權和物件型別,並返回對應的Handle控制程式碼。此函數會將返回的控制程式碼新增到當前程序的控制程式碼表中,因此在使用完畢後,需要使用CloseHandle函數將控制程式碼關閉,以避免資源洩漏。

綜上所述,我們可以通過這兩個核心函數實現HandleEProcess之間的相互轉換,轉換程式碼如下所示;

#include <ntifs.h>
#include <windef.h>

// 傳入PID傳出HANDLE控制程式碼
HANDLE PidToHandle(ULONG PID)
{
	HANDLE hProcessHandle;
	OBJECT_ATTRIBUTES obj;
	CLIENT_ID clientid;

	clientid.UniqueProcess = PID;
	clientid.UniqueThread = 0;

	// 屬性初始化
	InitializeObjectAttributes(&obj, 0, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, 0, 0);

	NTSTATUS status = ZwOpenProcess(&hProcessHandle, PROCESS_ALL_ACCESS, &obj, &clientid);
	if (status == STATUS_SUCCESS)
	{
		// DbgPrint("[*] 已開啟 \n");
		ZwClose(&hProcessHandle);
		return hProcessHandle;
	}

	return 0;
}

// 將Handle轉換為EProcess結構
PEPROCESS HandleToEprocess(HANDLE handle)
{
	PEPROCESS pEprocess;

	NTSTATUS status = ObReferenceObjectByHandle(handle, GENERIC_ALL, *PsProcessType, KernelMode, &pEprocess, NULL);
	if (status == STATUS_SUCCESS)
	{
		return pEprocess;
	}

	return 0;
}

// EProcess轉換為Handle控制程式碼
HANDLE EprocessToHandle(PEPROCESS eprocess)
{
	HANDLE hProcessHandle = (HANDLE)-1;

	NTSTATUS status = ObOpenObjectByPointer(
		eprocess,
		OBJ_KERNEL_HANDLE,
		0,
		0,
		*PsProcessType,
		KernelMode,
		&hProcessHandle
		);

	if (status == STATUS_SUCCESS)
	{
		return hProcessHandle;
	}

	return 0;
}

VOID UnDriver(PDRIVER_OBJECT driver)
{
	DbgPrint("[-] 驅動解除安裝 \n");
}

NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
	DbgPrint("Hello LyShark \n");

	// 將Handle轉換為EProcess結構
	PEPROCESS eprocess = HandleToEprocess(PidToHandle(6932));
	DbgPrint("[*] HANDLE --> EProcess = %p \n", eprocess);

	// 將EProcess結構轉換為Handle
	HANDLE handle = EprocessToHandle(eprocess);
	DbgPrint("[*] EProcess --> HANDLE = %p \n", handle);

	Driver->DriverUnload = UnDriver;
	return STATUS_SUCCESS;
}

編譯並執行如上這段程式碼片段,將把程序HANDLEEProcess結構互轉,輸出效果圖如下所示;