基于ObRegisterCallbacks实现的线程和进程监控及其保护

DemonGan

发布日期: 2019-02-19 13:04:00 浏览量: 953
评分:
star star star star star star star star star star_border
*转载请注明来自write-bug.com

背景

要实现监控系统线程和进程,并实现对指定线程和进程的保护,在 32 位系统上可以使用 HOOK 技术,HOOK 相关的函数来实现。但是,到了 64 位平台上,就不能继续按常规的 HOOK 方法去实现了。

好在 Windows 给我们提供了 ObRegisterCallbacks 内核函数来注册系统回调,可以用来注册系统线程回调,监控系统的线程创建、退出等情况,而且还能进行控制;也可以用来注册系统进程回调,可以监控系统的进程创建、退出等情况,而且也能进行控制。这使得我们实现保护指定线程、进程不被结束,提供了可能。

现在,我就对程序的实现过程和原理进行整理,形成文档,分享给大家。

函数介绍

ObRegisterCallbacks 函数

注册线程、进程和桌面句柄操作的回调函数。

函数声明

  1. NTSTATUS ObRegisterCallbacks(
  2. _In_ POB_CALLBACK_REGISTRATION CallBackRegistration,
  3. _Out_ PVOID *RegistrationHandle
  4. );

参数

  • CallBackRegistration [in]
    指向指定回调例程列表和其他注册信息的OB_CALLBACK_REGISTRATION结构的指针。
  • RegistrationHandle [out]
    指向变量的指针,该变量接收一个标识已注册的回调例程集合的值。 调用者将此值传递给ObUnRegisterCallbacks例程以注销该回调集。

返回值

  • 成功,则返回 STATUS_SUCCESS,否则,返回其它 NTSTATUS 错误码。

备注

  • 驱动程序必须在卸载之前注销所有回调例程。 您可以通过调用ObUnRegisterCallbacks例程来注销回调例程。

OB_CALLBACK_REGISTRATION 结构体

  1. typedef struct _OB_CALLBACK_REGISTRATION {
  2. USHORT Version;
  3. USHORT OperationRegistrationCount;
  4. UNICODE_STRING Altitude;
  5. PVOID RegistrationContext;
  6. OB_OPERATION_REGISTRATION *OperationRegistration;
  7. } OB_CALLBACK_REGISTRATION, *POB_CALLBACK_REGISTRATION;

成员

  • Version
    请求的对象回调注册版本。 驱动程序应指定OB_FLT_REGISTRATION_VERSION。
  • OperationRegistrationCount
    OperationRegistration数组中的条目数。
  • Altitude
    指定驱动程序Altitude的Unicode字符串。一定要存在,不能置为空 NULL,可以任意指定。
  • RegistrationContext
    当回调例程运行时,系统将RegistrationContext值传递给回调例程。 该值的含义是由驱动程序自定义的。
  • OperationRegistration
    指向OB_OPERATION_REGISTRATION结构数组的指针。 每个结构指定ObjectPreCallback和ObjectPostCallback回调例程以及调用例程的操作类型。

OB_OPERATION_REGISTRATION 结构体

  1. typedef struct _OB_OPERATION_REGISTRATION {
  2. POBJECT_TYPE *ObjectType;
  3. OB_OPERATION Operations;
  4. POB_PRE_OPERATION_CALLBACK PreOperation;
  5. POB_POST_OPERATION_CALLBACK PostOperation;
  6. } OB_OPERATION_REGISTRATION, *POB_OPERATION_REGISTRATION;

成员

  • ObjectType
    指向触发回调例程的对象类型的指针。 指定以下值之一:
    用于进程句柄操作的PsProcessType
    线程句柄操作的PsThreadType
    用于桌面句柄操作的ExDesktopObjectType。 此值在Windows 10中受支持,而不在早期版本的操作系统中。
  • Operations
    指定以下一个或多个标志:
    OB_OPERATION_HANDLE_CREATE
    一个新的进程,线程或桌面句柄已被打开或将被打开。
    OB_OPERATION_HANDLE_DUPLICATE
    进程,线程或桌面句柄已被或将被复制。
  • PreOperation
    指向ObjectPreCallback例程的指针。 在请求的操作发生之前,系统调用此例程。
  • PostOperation
    指向ObjectPostCallback例程的指针。 在请求的操作发生后,系统调用此例程。

IoThreadToProcess 函数

返回指向指定线程的进程的指针。

函数声明

  1. PEPROCESS IoThreadToProcess(
  2. _In_ PETHREAD Thread
  3. );

参数

  • Thread[in]
  • 要返回进程的指定线程对象。

返回值

  • 返回线程对象对应的进程对象的指针。

PsGetProcessId 函数

返回与指定进程关联的进程标识符(进程ID)。

函数声明

  1. HANDLE PsGetProcessId(
  2. _In_ PEPROCESS Process
  3. );

参数

  • Process[in]

    指向进程对象结构的指针。

返回值

  • 返回进程ID。

实现原理

破解 ObRegisterCallbacks 函数的使用限制

第一种方法

在讲解怎么使用 ObRegisterCallbacks 函数来注册系统线程、进程回调的之前,先来讲解下 Windows 对这个函数做的限制:驱动程序必须有数字签名才能使用此函数。不过国外的黑客对此限制很不满,通过逆向 ObRegisterCallbacks,找到了
破解这个限制的方法。经研究,内核通过 MmVerifyCallbackFunction 验证此回调
是否合法, 但此函数只是简单的验证了一下 DriverObject->DriverSection->Flags 的值是不是为 0x20:

  1. nt!MmVerifyCallbackFunction+0x75:
  2. fffff800`01a66865 f6406820 test byte ptr [rax+68h],20h
  3. fffff800`01a66869 0f45fd cmovne edi,ebp

所以破解方法非常简单,只要把 DriverObject->DriverSection->Flags 的值按位或 0x20 即可。其中,DriverSection 是指向 LDR_DATA_TABLE_ENTRY 结构的值,要注意该结构在 32 位和 64 位系统下的定义。

  1. // 注意32位与64位的对齐大小
  2. #ifndef _WIN64
  3. #pragma pack(1)
  4. #endif
  5. typedef struct _LDR_DATA_TABLE_ENTRY
  6. {
  7. LIST_ENTRY InLoadOrderLinks;
  8. LIST_ENTRY InMemoryOrderLinks;
  9. LIST_ENTRY InInitializationOrderLinks;
  10. PVOID DllBase;
  11. PVOID EntryPoint;
  12. ULONG SizeOfImage;
  13. UNICODE_STRING FullDllName;
  14. UNICODE_STRING BaseDllName;
  15. ULONG Flags;
  16. USHORT LoadCount;
  17. USHORT TlsIndex;
  18. union
  19. {
  20. LIST_ENTRY HashLinks;
  21. struct
  22. {
  23. PVOID SectionPointer;
  24. ULONG CheckSum;
  25. };
  26. };
  27. union
  28. {
  29. ULONG TimeDateStamp;
  30. PVOID LoadedImports;
  31. };
  32. PVOID EntryPointActivationContext;
  33. PVOID PatchInformation;
  34. LIST_ENTRY ForwarderLinks;
  35. LIST_ENTRY ServiceTagLinks;
  36. LIST_ENTRY StaticLinks;
  37. } LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;
  38. #ifndef _WIN64
  39. #pragma pack()
  40. #endif

第二种方法

使用此函数, 一定要设置 IMAGE_OPTIONAL_HEADER 中的 DllCharacterisitics 字段设置为:IMAGE_DLLCHARACTERISITICS_FORCE_INTEGRITY 属性,该属性是一个驱动强制签名属性。
使用 VS2013 开发环境设置方式是:

  1. 右击项目,选择属性;
  2. 选中配置属性中的链接器,点击命令行;
  3. 在其它选项中输入: /INTEGRITYCHECK 表示设置; /INTEGRITYCHECK:NO 表示不设置。

这样,设置之后,驱动程序必须要进行驱动签名才可正常运行!

调用 ObRegisterCallbacks 注册线程回调以及进程回调

我们从上面的函数介绍中,可以知道大概的实现流程:

  1. 首先,在调用 ObRegisterCallbacks 函数注册系统回调之前,我们要先对结构体 OB_CALLBACK_REGISTRATION 进行初始化。设置回调的版本 Version;设置回调的 Altitude,任意指定;设置回调函数的数量 OperationRegistrationCount;设置回调函数 OperationRegistration。其中,OperationRegistration 是一个 OB_OPERATION_REGISTRATION 结构体数组,里面存储着回调对象的类型、操作类型以及回调函数,它的数量要和 OperationRegistrationCount 对应。
  2. 然后,再调用 ObRegisterCallbacks 进行注册,并保留系统回调对象句柄。
  3. 最后,在不使用回调的时候,调用 ObUnRegisterCallbacks 函数传入系统回调对象句柄,删除回调。

其中,线程回调和进程回调的注册,只有 OB_OPERATION_REGISTRATION 结构体的成员 ObjectType 不同。对于线程,ObjectType 为 PsThreadType;对于进程,ObjectType 为 PsProcessType。其它的操作及其含义,均相同。

线程、进程回调函数中实现线程、进程保护

由于在注册系统回调的时候,我们设置监控线程以及进程的操作类型为:OB_OPERATION_HANDLE_CREATE 和 OB_OPERATION_HANDLE_DUPLICATE。要想实现,拒绝结束线程或者进程的操作,我们只需从操作类型句柄信息中去掉相应的结束线程或者进程的权限即可:

  1. // OB_OPERATION_HANDLE_CREATE 操作类型
  2. pObPreOperationInfo->Parameters->CreateHandleInformation.DesiredAccess = 0;
  1. // OB_OPERATION_HANDLE_DUPLICATE 操作类型
  2. pObPreOperationInfo->Parameters->DuplicateHandleInformation.DesiredAccess = 0;

那么,我们怎么从线程对象或者进程对象 pObPreOperationInfo->Object 中判断是否是保护线程或者进程呢。
对于进程,我们可以调用 PsGetProcessImageFileName 函数,从进程结构对象获取进程名称进行判断。
对于线程,我们可以通过 IoThreadToProcess 函数,根据线程结构对象获取相应的进程结构对象,再根据 PsGetProcessImageFileName 函数,从进程结构对象获取进程名称进行判断。

编码实现

破解 ObRegisterCallbacks 的使用限制

  1. // 编程方式绕过签名检查
  2. BOOLEAN BypassCheckSign(PDRIVER_OBJECT pDriverObject)
  3. {
  4. #ifdef _WIN64
  5. typedef struct _KLDR_DATA_TABLE_ENTRY
  6. {
  7. LIST_ENTRY listEntry;
  8. ULONG64 __Undefined1;
  9. ULONG64 __Undefined2;
  10. ULONG64 __Undefined3;
  11. ULONG64 NonPagedDebugInfo;
  12. ULONG64 DllBase;
  13. ULONG64 EntryPoint;
  14. ULONG SizeOfImage;
  15. UNICODE_STRING path;
  16. UNICODE_STRING name;
  17. ULONG Flags;
  18. USHORT LoadCount;
  19. USHORT __Undefined5;
  20. ULONG64 __Undefined6;
  21. ULONG CheckSum;
  22. ULONG __padding1;
  23. ULONG TimeDateStamp;
  24. ULONG __padding2;
  25. } KLDR_DATA_TABLE_ENTRY, *PKLDR_DATA_TABLE_ENTRY;
  26. #else
  27. typedef struct _KLDR_DATA_TABLE_ENTRY
  28. {
  29. LIST_ENTRY listEntry;
  30. ULONG unknown1;
  31. ULONG unknown2;
  32. ULONG unknown3;
  33. ULONG unknown4;
  34. ULONG unknown5;
  35. ULONG unknown6;
  36. ULONG unknown7;
  37. UNICODE_STRING path;
  38. UNICODE_STRING name;
  39. ULONG Flags;
  40. } KLDR_DATA_TABLE_ENTRY, *PKLDR_DATA_TABLE_ENTRY;
  41. #endif
  42. PKLDR_DATA_TABLE_ENTRY pLdrData = (PKLDR_DATA_TABLE_ENTRY)pDriverObject->DriverSection;
  43. pLdrData->Flags = pLdrData->Flags | 0x20;
  44. return TRUE;
  45. }

注册线程回调

  1. // 设置线程回调函数
  2. NTSTATUS SetThreadCallbacks()
  3. {
  4. NTSTATUS status = STATUS_SUCCESS;
  5. OB_CALLBACK_REGISTRATION obCallbackReg = { 0 };
  6. OB_OPERATION_REGISTRATION obOperationReg = { 0 };
  7. RtlZeroMemory(&obCallbackReg, sizeof(OB_CALLBACK_REGISTRATION));
  8. RtlZeroMemory(&obOperationReg, sizeof(OB_OPERATION_REGISTRATION));
  9. // 设置 OB_CALLBACK_REGISTRATION
  10. obCallbackReg.Version = ObGetFilterVersion();
  11. obCallbackReg.OperationRegistrationCount = 1;
  12. obCallbackReg.RegistrationContext = NULL;
  13. RtlInitUnicodeString(&obCallbackReg.Altitude, L"321001");
  14. obCallbackReg.OperationRegistration = &obOperationReg;
  15. // 设置 OB_OPERATION_REGISTRATION
  16. // Thread 和 Process 的区别所在
  17. obOperationReg.ObjectType = PsThreadType;
  18. obOperationReg.Operations = OB_OPERATION_HANDLE_CREATE | OB_OPERATION_HANDLE_DUPLICATE;
  19. // Thread 和 Process 的区别所在
  20. obOperationReg.PreOperation = (POB_PRE_OPERATION_CALLBACK)(&ThreadPreCall);
  21. // 注册回调函数
  22. status = ObRegisterCallbacks(&obCallbackReg, &g_obThreadHandle);
  23. if (!NT_SUCCESS(status))
  24. {
  25. DbgPrint("ObRegisterCallbacks Error[0x%X]\n", status);
  26. return status;
  27. }
  28. return status;
  29. }

注册进程回调

  1. // 设置进程回调函数
  2. NTSTATUS SetProcessCallbacks()
  3. {
  4. NTSTATUS status = STATUS_SUCCESS;
  5. OB_CALLBACK_REGISTRATION obCallbackReg = { 0 };
  6. OB_OPERATION_REGISTRATION obOperationReg = { 0 };
  7. RtlZeroMemory(&obCallbackReg, sizeof(OB_CALLBACK_REGISTRATION));
  8. RtlZeroMemory(&obOperationReg, sizeof(OB_OPERATION_REGISTRATION));
  9. // 设置 OB_CALLBACK_REGISTRATION
  10. obCallbackReg.Version = ObGetFilterVersion();
  11. obCallbackReg.OperationRegistrationCount = 1;
  12. obCallbackReg.RegistrationContext = NULL;
  13. RtlInitUnicodeString(&obCallbackReg.Altitude, L"321000");
  14. obCallbackReg.OperationRegistration = &obOperationReg;
  15. // 设置 OB_OPERATION_REGISTRATION
  16. // Thread 和 Process 的区别所在
  17. obOperationReg.ObjectType = PsProcessType;
  18. obOperationReg.Operations = OB_OPERATION_HANDLE_CREATE | OB_OPERATION_HANDLE_DUPLICATE;
  19. // Thread 和 Process 的区别所在
  20. obOperationReg.PreOperation = (POB_PRE_OPERATION_CALLBACK)(&ProcessPreCall);
  21. // 注册回调函数
  22. status = ObRegisterCallbacks(&obCallbackReg, &g_obProcessHandle);
  23. if (!NT_SUCCESS(status))
  24. {
  25. DbgPrint("ObRegisterCallbacks Error[0x%X]\n", status);
  26. return status;
  27. }
  28. return status;
  29. }

线程回调函数

  1. // 线程回调函数
  2. OB_PREOP_CALLBACK_STATUS ThreadPreCall(PVOID RegistrationContext, POB_PRE_OPERATION_INFORMATION pObPreOperationInfo)
  3. {
  4. PEPROCESS pEProcess = NULL;
  5. // 判断对象类型
  6. if (*PsThreadType != pObPreOperationInfo->ObjectType)
  7. {
  8. return OB_PREOP_SUCCESS;
  9. }
  10. // 获取线程对应的进程 PEPROCESS
  11. pEProcess = IoThreadToProcess((PETHREAD)pObPreOperationInfo->Object);
  12. // 判断是否市保护PID, 若是, 则拒绝结束线程
  13. if (IsProtectProcess(pEProcess))
  14. {
  15. // 操作类型: 创建句柄
  16. if (OB_OPERATION_HANDLE_CREATE == pObPreOperationInfo->Operation)
  17. {
  18. if (1 == (1 & pObPreOperationInfo->Parameters->CreateHandleInformation.OriginalDesiredAccess))
  19. {
  20. pObPreOperationInfo->Parameters->CreateHandleInformation.DesiredAccess = 0;
  21. }
  22. }
  23. // 操作类型: 复制句柄
  24. else if (OB_OPERATION_HANDLE_DUPLICATE == pObPreOperationInfo->Operation)
  25. {
  26. if (1 == (1 & pObPreOperationInfo->Parameters->DuplicateHandleInformation.OriginalDesiredAccess))
  27. {
  28. pObPreOperationInfo->Parameters->DuplicateHandleInformation.DesiredAccess = 0;
  29. }
  30. }
  31. }
  32. return OB_PREOP_SUCCESS;
  33. }

进程回调函数

  1. // 进程回调函数
  2. OB_PREOP_CALLBACK_STATUS ProcessPreCall(PVOID RegistrationContext, POB_PRE_OPERATION_INFORMATION pObPreOperationInfo)
  3. {
  4. PEPROCESS pEProcess = NULL;
  5. // 判断对象类型
  6. if (*PsProcessType != pObPreOperationInfo->ObjectType)
  7. {
  8. return OB_PREOP_SUCCESS;
  9. }
  10. // 获取进程结构对象
  11. pEProcess = (PEPROCESS)pObPreOperationInfo->Object;
  12. // 判断是否市保护PID, 若是, 则拒绝结束进程
  13. if (IsProtectProcess(pEProcess))
  14. {
  15. // 操作类型: 创建句柄
  16. if (OB_OPERATION_HANDLE_CREATE == pObPreOperationInfo->Operation)
  17. {
  18. if (1 == (1 & pObPreOperationInfo->Parameters->CreateHandleInformation.OriginalDesiredAccess))
  19. {
  20. pObPreOperationInfo->Parameters->CreateHandleInformation.DesiredAccess = 0;
  21. }
  22. }
  23. // 操作类型: 复制句柄
  24. else if (OB_OPERATION_HANDLE_DUPLICATE == pObPreOperationInfo->Operation)
  25. {
  26. if (1 == (1 & pObPreOperationInfo->Parameters->DuplicateHandleInformation.OriginalDesiredAccess))
  27. {
  28. pObPreOperationInfo->Parameters->DuplicateHandleInformation.DesiredAccess = 0;
  29. }
  30. }
  31. }
  32. return OB_PREOP_SUCCESS;
  33. }

程序测试

在 Win7 32 位系统下,驱动程序正常执行:

在 Win10 64 位系统下,驱动程序正常执行:

总结

其中要注意两个问题:

一是,破解 ObRegisterCallbacks 函数的使用限制有两种方式,一种是通过编程来解决;一种是通过 VS 开发环境和数字签名来解决。在使用编程方式解决限制的时候,一定要注意 32 位与 64 位下,LDR_DATA_TABLE_ENTRY 结构体的对齐差别。

二是,OB_CALLBACK_REGISTRATION 中的 Altitude 成员一定要存在,不能置为空 NULL,可以任意指定。

参考

参考自《Windows黑客编程技术详解》一书

上传的附件 cloud_download ObRegisterCallbacks_Test.7z ( 10.16kb, 9次下载 )

keyboard_arrow_left上一篇 : 枚举并删除系统上Minifilter回调 基于MuPDF库实现PDF文件转换成PNG格式图片 : 下一篇keyboard_arrow_right



DemonGan
2019-02-19 13:04:14
基于ObRegisterCallbacks实现的线程和进程监控及其保护
Tommorow
2019-02-19 18:18:34
WoW ~ ⊙o⊙ 谢谢分享
只想睡个好觉
2019-05-17 15:27:47
在win10下 若开启了防护某一进程,同时会影响该程序进程的创建

发送私信

这一切都不是我的,但总有一天,会是我的

73
文章数
67
评论数
最近文章
eject