在筆者前一篇文章《內核枚舉Registry注冊表回調》
中實現了對注冊表的枚舉,本章將實現對注冊表的監控,不同于32位系統在64位系統中,微軟為我們提供了兩個針對注冊表的專用內核監控函數,通過這兩個函數可以在不劫持內核API的前提下實現對注冊表增加,刪除,創建等事件的有效監控,注冊表監視通常會通過CmRegisterCallback
創建監控事件并傳入自己的回調函數,與該創建對應的是CmUnRegisterCallback
當注冊表監控結束后可用于注銷回調。
CmRegisterCallback和CmUnRegisterCallback是Windows操作系統提供的兩個內核API函數,用于注冊和取消注冊注冊表回調函數。
注冊表回調函數是一種內核回調函數,它可以用于監視和攔截系統中的注冊表操作,例如鍵值的創建、修改和刪除等。當有相關操作發生時,操作系統會調用注冊的注冊表回調函數,并將操作相關的信息傳遞給回調函數。
CmRegisterCallback函數用于注冊注冊表回調函數,而CmUnRegisterCallback函數則用于取消注冊已經注冊的回調函數。開發者可以在注冊表回調函數中執行自定義的邏輯,例如記錄日志、過濾敏感數據、或者阻止某些操作。
需要注意的是,注冊表回調函數的注冊和取消注冊必須在內核模式下進行,并且需要開發者有一定的內核開發經驗。同時,注冊表回調函數也需要遵守一些約束條件,例如不能阻塞或掛起進程或線程的創建或訪問,不能調用一些內核API函數等。
內核監控Register注冊表回調在安全軟件、系統監控和調試工具等領域有著廣泛的應用。開發者可以利用這個機制來監視和攔截系統中的注冊表操作,以保護系統安全。
- CmRegisterCallback 設置注冊表回調
- CmUnRegisterCallback 注銷注冊表回調
默認情況下CmRegisterCallback
需傳入三個參數,參數一回調函數地址,參數二空余,參數三回調句柄,微軟定義如下。
// 參數1:回調函數地址
// 參數2:無作用
// 參數3:回調句柄
NTSTATUS CmRegisterCallback([in] PEX_CALLBACK_FUNCTION Function,[in, optional] PVOID Context,[out] PLARGE_INTEGER Cookie
);
自定義注冊表回調函數MyLySharkCallback
需要保留三個參數,CallbackContext
回調上下文,Argument1
是操作類型,Argument2
定義詳細結構體指針。
NTSTATUS MyLySharkCallback(_In_ PVOID CallbackContext, _In_opt_ PVOID Argument1, _In_opt_ PVOID Argument2)
在自定義回調函數內Argument1
則可獲取到操作類型,類型是一個REG_NOTIFY_CLASS
枚舉結構,微軟對其的具體定義如下所示。
typedef enum _REG_NOTIFY_CLASS {RegNtDeleteKey,RegNtPreDeleteKey = RegNtDeleteKey,RegNtSetValueKey,RegNtPreSetValueKey = RegNtSetValueKey,RegNtDeleteValueKey,RegNtPreDeleteValueKey = RegNtDeleteValueKey,RegNtSetInformationKey,RegNtPreSetInformationKey = RegNtSetInformationKey,RegNtRenameKey,RegNtPreRenameKey = RegNtRenameKey,RegNtEnumerateKey,RegNtPreEnumerateKey = RegNtEnumerateKey,RegNtEnumerateValueKey,RegNtPreEnumerateValueKey = RegNtEnumerateValueKey,RegNtQueryKey,RegNtPreQueryKey = RegNtQueryKey,RegNtQueryValueKey,RegNtPreQueryValueKey = RegNtQueryValueKey,RegNtQueryMultipleValueKey,RegNtPreQueryMultipleValueKey = RegNtQueryMultipleValueKey,RegNtPreCreateKey,RegNtPostCreateKey,RegNtPreOpenKey,RegNtPostOpenKey,RegNtKeyHandleClose,RegNtPreKeyHandleClose = RegNtKeyHandleClose,//// .Net only// RegNtPostDeleteKey,RegNtPostSetValueKey,RegNtPostDeleteValueKey,RegNtPostSetInformationKey,RegNtPostRenameKey,RegNtPostEnumerateKey,RegNtPostEnumerateValueKey,RegNtPostQueryKey,RegNtPostQueryValueKey,RegNtPostQueryMultipleValueKey,RegNtPostKeyHandleClose,RegNtPreCreateKeyEx,RegNtPostCreateKeyEx,RegNtPreOpenKeyEx,RegNtPostOpenKeyEx,//// new to Windows Vista//RegNtPreFlushKey,RegNtPostFlushKey,RegNtPreLoadKey,RegNtPostLoadKey,RegNtPreUnLoadKey,RegNtPostUnLoadKey,RegNtPreQueryKeySecurity,RegNtPostQueryKeySecurity,RegNtPreSetKeySecurity,RegNtPostSetKeySecurity,//// per-object context cleanup//RegNtCallbackObjectContextCleanup,//// new in Vista SP2 //RegNtPreRestoreKey,RegNtPostRestoreKey,RegNtPreSaveKey,RegNtPostSaveKey,RegNtPreReplaceKey,RegNtPostReplaceKey,MaxRegNtNotifyClass //should always be the last enum
} REG_NOTIFY_CLASS;
其中對于注冊表最常用的監控項為以下幾種類型,當然為了實現監控則我們必須要使用之前,如果使用之后則只能起到監視而無法做到監控的目的。
- RegNtPreCreateKey 創建注冊表之前
- RegNtPreOpenKey 打開注冊表之前
- RegNtPreDeleteKey 刪除注冊表之前
- RegNtPreDeleteValueKey 刪除鍵值之前
- RegNtPreSetValueKey 修改注冊表之前
如果需要實現監視則,首先CmRegisterCallback
注冊一個自定義回調,當有消息時則觸發MyLySharkCallback
其內部獲取到lOperateType
操作類型,并通過switch
選擇不同的處理例程,每個處理例程都通過GetFullPath
得到注冊表完整路徑,并打印出來,這段代碼實現如下。
#include <ntifs.h>
#include <windef.h>// 未導出函數聲明 pEProcess -> PID
PUCHAR PsGetProcessImageFileName(PEPROCESS pEProcess);NTSTATUS ObQueryNameString(_In_ PVOID Object,_Out_writes_bytes_opt_(Length) POBJECT_NAME_INFORMATION ObjectNameInfo,_In_ ULONG Length,_Out_ PULONG ReturnLength);// 注冊表回調Cookie
LARGE_INTEGER g_liRegCookie;// 獲取注冊表完整路徑
BOOLEAN GetFullPath(PUNICODE_STRING pRegistryPath, PVOID pRegistryObject)
{// 判斷數據地址是否有效if ((FALSE == MmIsAddressValid(pRegistryObject)) ||(NULL == pRegistryObject)){return FALSE;}// 申請內存ULONG ulSize = 512;PVOID lpObjectNameInfo = ExAllocatePool(NonPagedPool, ulSize);if (NULL == lpObjectNameInfo){return FALSE;}// 獲取注冊表路徑ULONG ulRetLen = 0;NTSTATUS status = ObQueryNameString(pRegistryObject, (POBJECT_NAME_INFORMATION)lpObjectNameInfo, ulSize, &ulRetLen);if (!NT_SUCCESS(status)){ExFreePool(lpObjectNameInfo);return FALSE;}// 復制RtlCopyUnicodeString(pRegistryPath, (PUNICODE_STRING)lpObjectNameInfo);// 釋放內存ExFreePool(lpObjectNameInfo);return TRUE;
}// 注冊表回調函數
NTSTATUS MyLySharkCallback(_In_ PVOID CallbackContext, _In_opt_ PVOID Argument1, _In_opt_ PVOID Argument2)
{NTSTATUS status = STATUS_SUCCESS;UNICODE_STRING ustrRegPath;// 獲取操作類型LONG lOperateType = (REG_NOTIFY_CLASS)Argument1;// 申請內存ustrRegPath.Length = 0;ustrRegPath.MaximumLength = 1024 * sizeof(WCHAR);ustrRegPath.Buffer = ExAllocatePool(NonPagedPool, ustrRegPath.MaximumLength);if (NULL == ustrRegPath.Buffer){return status;}RtlZeroMemory(ustrRegPath.Buffer, ustrRegPath.MaximumLength);// 判斷操作switch (lOperateType){// 創建注冊表之前case RegNtPreCreateKey:{// 獲取注冊表路徑GetFullPath(&ustrRegPath, ((PREG_CREATE_KEY_INFORMATION)Argument2)->RootObject);DbgPrint("[創建注冊表][%wZ][%wZ]\n", &ustrRegPath, ((PREG_CREATE_KEY_INFORMATION)Argument2)->CompleteName);break;}// 打開注冊表之前case RegNtPreOpenKey:{// 獲取注冊表路徑GetFullPath(&ustrRegPath, ((PREG_CREATE_KEY_INFORMATION)Argument2)->RootObject);DbgPrint("[打開注冊表][%wZ][%wZ]\n", &ustrRegPath, ((PREG_CREATE_KEY_INFORMATION)Argument2)->CompleteName);break;}// 刪除鍵之前case RegNtPreDeleteKey:{// 獲取注冊表路徑GetFullPath(&ustrRegPath, ((PREG_DELETE_KEY_INFORMATION)Argument2)->Object);DbgPrint("[刪除鍵][%wZ] \n", &ustrRegPath);break;}// 刪除鍵值之前case RegNtPreDeleteValueKey:{// 獲取注冊表路徑GetFullPath(&ustrRegPath, ((PREG_DELETE_VALUE_KEY_INFORMATION)Argument2)->Object);DbgPrint("[刪除鍵值][%wZ][%wZ] \n", &ustrRegPath, ((PREG_DELETE_VALUE_KEY_INFORMATION)Argument2)->ValueName);// 獲取當前進程, 即操作注冊表的進程PEPROCESS pEProcess = PsGetCurrentProcess();if (NULL != pEProcess){UCHAR *lpszProcessName = PsGetProcessImageFileName(pEProcess);if (NULL != lpszProcessName){DbgPrint("進程 [%s] 刪除了鍵值對 \n", lpszProcessName);}}break;}// 修改鍵值之前case RegNtPreSetValueKey:{// 獲取注冊表路徑GetFullPath(&ustrRegPath, ((PREG_SET_VALUE_KEY_INFORMATION)Argument2)->Object);DbgPrint("[修改鍵值][%wZ][%wZ] \n", &ustrRegPath, ((PREG_SET_VALUE_KEY_INFORMATION)Argument2)->ValueName);break;}default:break;}// 釋放內存if (NULL != ustrRegPath.Buffer){ExFreePool(ustrRegPath.Buffer);ustrRegPath.Buffer = NULL;}return status;
}VOID UnDriver(PDRIVER_OBJECT driver)
{DbgPrint(("Uninstall Driver Is OK \n"));// 注銷當前注冊表回調if (0 < g_liRegCookie.QuadPart){CmUnRegisterCallback(g_liRegCookie);}
}NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{DbgPrint(("hello lyshark \n"));// 設置注冊表回調NTSTATUS status = CmRegisterCallback(MyLySharkCallback, NULL, &g_liRegCookie);if (!NT_SUCCESS(status)){g_liRegCookie.QuadPart = 0;return status;}Driver->DriverUnload = UnDriver;return STATUS_SUCCESS;
}
運行驅動程序,則會輸出當前系統中所有針對注冊表的操作,如下圖所示。
如上的代碼只能實現注冊表項的監視,而如果需要監控則需要在回調函數MyLySharkCallback
判斷,如果指定注冊表項是需要保護的則直接返回status = STATUS_ACCESS_DENIED;
從而達到保護注冊表的目的,核心代碼如下所示。
// 反注冊表刪除回調
NTSTATUS MyLySharkCallback(_In_ PVOID CallbackContext, _In_opt_ PVOID Argument1, _In_opt_ PVOID Argument2)
{NTSTATUS status = STATUS_SUCCESS;UNICODE_STRING ustrRegPath;// 獲取操作類型LONG lOperateType = (REG_NOTIFY_CLASS)Argument1;ustrRegPath.Length = 0;ustrRegPath.MaximumLength = 1024 * sizeof(WCHAR);ustrRegPath.Buffer = ExAllocatePool(NonPagedPool, ustrRegPath.MaximumLength);if (NULL == ustrRegPath.Buffer){return status;}RtlZeroMemory(ustrRegPath.Buffer, ustrRegPath.MaximumLength);// 判斷操作switch (lOperateType){// 刪除鍵值之前case RegNtPreDeleteValueKey:{// 獲取注冊表路徑GetFullPath(&ustrRegPath, ((PREG_DELETE_VALUE_KEY_INFORMATION)Argument2)->Object);DbgPrint("[刪除鍵值][%wZ][%wZ]\n", &ustrRegPath, ((PREG_DELETE_VALUE_KEY_INFORMATION)Argument2)->ValueName);// 如果要刪除指定注冊表項則拒絕PWCH pszRegister = L"\\REGISTRY\\MACHINE\\SOFTWARE\\lyshark";if (wcscmp(ustrRegPath.Buffer, pszRegister) == 0){DbgPrint("[lyshark] 注冊表項刪除操作已被攔截! \n");// 拒絕操作status = STATUS_ACCESS_DENIED;}break;}default:break;}// 釋放內存if (NULL != ustrRegPath.Buffer){ExFreePool(ustrRegPath.Buffer);ustrRegPath.Buffer = NULL;}return status;
}
運行驅動程序,然后我們嘗試刪除\\LyShark\HKEY_LOCAL_MACHINE\SOFTWARE\lyshark
里面的子項,則會提示如下信息。
當然這里的RegNtPreDeleteValueKey
是指的刪除操作,如果將其替換成RegNtPreSetValueKey
,那么只有當注冊表被創建才會攔截,此時就會變成攔截創建。
// 攔截創建操作
NTSTATUS MyLySharkCallback(_In_ PVOID CallbackContext, _In_opt_ PVOID Argument1, _In_opt_ PVOID Argument2)
{NTSTATUS status = STATUS_SUCCESS;UNICODE_STRING ustrRegPath;// 獲取操作類型LONG lOperateType = (REG_NOTIFY_CLASS)Argument1;// 申請內存ustrRegPath.Length = 0;ustrRegPath.MaximumLength = 1024 * sizeof(WCHAR);ustrRegPath.Buffer = ExAllocatePool(NonPagedPool, ustrRegPath.MaximumLength);if (NULL == ustrRegPath.Buffer){return status;}RtlZeroMemory(ustrRegPath.Buffer, ustrRegPath.MaximumLength);// 判斷操作switch (lOperateType){// 修改鍵值之前case RegNtPreSetValueKey:{// 獲取注冊表路徑GetFullPath(&ustrRegPath, ((PREG_DELETE_VALUE_KEY_INFORMATION)Argument2)->Object);// 攔截創建PWCH pszRegister = L"\\REGISTRY\\MACHINE\\SOFTWARE\\lyshark";if (wcscmp(ustrRegPath.Buffer, pszRegister) == 0){DbgPrint("[lyshark] 注冊表項創建操作已被攔截! \n");status = STATUS_ACCESS_DENIED;}break;}default:break;}// 釋放內存if (NULL != ustrRegPath.Buffer){ExFreePool(ustrRegPath.Buffer);ustrRegPath.Buffer = NULL;}return status;
}
加載驅動保護,然后我們嘗試在\\LyShark\HKEY_LOCAL_MACHINE\SOFTWARE\lyshark
里面創建一個子項,則會提示創建失敗。