枚举并删除系统上ObRegisterCallbacks回调

DemonGan

发布日期: 2019-03-29 14:19:44 浏览量: 1208
评分:
star star star star star star star star star star_border
*转载请注明来自write-bug.com

背景

我们学习内核 Rootkit 编程,那么肯定会接触到各种无 HOOK 回调函数的设置,这些回调函数都是官方为我们做好的接口,我们直接调用就好。这些回调使用方便,运行在底层,功能强大,而且非常稳定。很多杀软、游戏保护等就是设置这些回调,实现对计算机的监控的。

既然可以设置回调,自然也可以删除回调。如果是自己程序设置的回调,当然可以很容易删除。但是,我们要做的是要枚举系统上存在的回调,不管是不是自己程序创建的,然后,并对这些回调进行删除,使其失效。

本文要介绍的是枚举并删除系统上 ObRegisterCallbacks 回调,支持 32 位和 64 位、Win7 到 Win10 全平台系统。现在,我把实现的过程和原理整理成文档,分享给大家。

实现原理

我们注册的对象回调,会存储在一个表头为 CallbackList 的双向链表里,它存储着系统里所有 ObRegisterCallbacks 对象回调操作前回调函数地址、操作后回调函数地址和对象回调句柄的信息。

经过使用 WinDbg 逆向,总结得出 CallbackList 双向链表指向的数据结构为:

  1. #pragma pack(1)
  2. typedef struct _OB_CALLBACK
  3. {
  4. LIST_ENTRY ListEntry;
  5. ULONGLONG Unknown;
  6. HANDLE ObHandle;
  7. PVOID ObTypeAddr;
  8. PVOID PreCall;
  9. PVOID PostCall;
  10. }OB_CALLBACK, *POB_CALLBACK;
  11. #pragma pack()

经过分析,无论 32 位或 64 位平台,成员 Unknown 数据类型是 8 字节,而不是PVOID!分析方法为,使用 WinDbg 的命令 dd CallbackList(0x9A485632), 获取 CallbackList 双向链表地址及其附近的数据,与使用 ObRegisterCallback 设置的回调信息比对,比对设置的回调地址、句柄等, 从而确定。

ListEntry 中存储着下一个或者上一个 OB_CALLBACK 结构体指针的信息,我们通过遍历这个双向链表,就可以枚举出 ObRegisterCallbacks 对象回调函数地址和句柄的信息。

CallbackList 地址的获取

CallbackList 的地址,可以直接从对象类型中获取。如果要获取进程对象回调函数的双向链表信息,可以从 *PsProcessType 中获取;如果要获取线程对象回调函数的双向链表信息,可以从 *PsThreadType 中获取。

其中,进程对象类型 *PsProcessType 和线程对象类型 *PsThreadType 的数据结构类型为 POBJECT_TYPE,那么数据结构定义为:

  1. typedef struct _OBJECT_TYPE
  2. {
  3. LIST_ENTRY TypeList; // _LIST_ENTRY
  4. UNICODE_STRING Name; // _UNICODE_STRING
  5. PVOID DefaultObject; // Ptr64 Void
  6. UCHAR Index; // UChar
  7. ULONG TotalNumberOfObjects; // Uint4B
  8. ULONG TotalNumberOfHandles; // Uint4B
  9. ULONG HighWaterNumberOfObjects; // Uint4B
  10. ULONG HighWaterNumberOfHandles; // Uint4B
  11. OBJECT_TYPE_INITIALIZER TypeInfo; // _OBJECT_TYPE_INITIALIZER
  12. EX_PUSH_LOCK TypeLock; // _EX_PUSH_LOCK
  13. ULONG Key; // Uint4B
  14. LIST_ENTRY CallbackList; // _LIST_ENTRY
  15. }OBJECT_TYPE, *POBJECT_TYPE;

其中,OBJECT_TYPE_INITIALIZER 数据结构类型的定义为:

  1. typedef struct _OBJECT_TYPE_INITIALIZER
  2. {
  3. USHORT Length; // Uint2B
  4. UCHAR ObjectTypeFlags; // UChar
  5. ULONG ObjectTypeCode; // Uint4B
  6. ULONG InvalidAttributes; // Uint4B
  7. GENERIC_MAPPING GenericMapping; // _GENERIC_MAPPING
  8. ULONG ValidAccessMask; // Uint4B
  9. ULONG RetainAccess; // Uint4B
  10. POOL_TYPE PoolType; // _POOL_TYPE
  11. ULONG DefaultPagedPoolCharge; // Uint4B
  12. ULONG DefaultNonPagedPoolCharge; // Uint4B
  13. PVOID DumpProcedure; // Ptr64 void
  14. PVOID OpenProcedure; // Ptr64 long
  15. PVOID CloseProcedure; // Ptr64 void
  16. PVOID DeleteProcedure; // Ptr64 void
  17. PVOID ParseProcedure; // Ptr64 long
  18. PVOID SecurityProcedure; // Ptr64 long
  19. PVOID QueryNameProcedure; // Ptr64 long
  20. PVOID OkayToCloseProcedure; // Ptr64 unsigned char
  21. #if (NTDDI_VERSION >= NTDDI_WINBLUE) // Win8.1
  22. ULONG WaitObjectFlagMask; // Uint4B
  23. USHORT WaitObjectFlagOffset; // Uint2B
  24. USHORT WaitObjectPointerOffset; // Uint2B
  25. #endif
  26. }OBJECT_TYPE_INITIALIZER, *POBJECT_TYPE_INITIALIZER;

其中,进程对象类型 *PsProcessType 和线程对象类型 *PsThreadType 系统已经在头文件 wdm.h 中为我们导入:

  1. // #include <wdm.h>
  2. extern POBJECT_TYPE *CmKeyObjectType;
  3. extern POBJECT_TYPE *IoFileObjectType;
  4. extern POBJECT_TYPE *ExEventObjectType;
  5. extern POBJECT_TYPE *ExSemaphoreObjectType;
  6. extern POBJECT_TYPE *TmTransactionManagerObjectType;
  7. extern POBJECT_TYPE *TmResourceManagerObjectType;
  8. extern POBJECT_TYPE *TmEnlistmentObjectType;
  9. extern POBJECT_TYPE *TmTransactionObjectType;
  10. extern POBJECT_TYPE *PsProcessType;
  11. extern POBJECT_TYPE *PsThreadType;
  12. extern POBJECT_TYPE *SeTokenObjectType;

所以,可直接从 *PsProcessType、 *PsThreadType 等对象类型 POBJECT_TYPE 中获取对应类型的 CallbackList 双向链表的地址。注意,不同的对象类型,都有一个属于自己类型的 CallbackList 双向链表。

我们只要对 CallbackList 双向链表进行遍历,就可以获取对应对象类型的回调信息。

获取进程对象类型 *PsProcessType 的 CallbackList 地址代码为:

  1. ((POBJECT_TYPE)(*PsProcessType))->CallbackList;

获取线程对象类型 *PsThreadType 的 CallbackList 地址代码为:

  1. ((POBJECT_TYPE)(*PsThreadType))->CallbackList;

删除回调

我们可以通过上述介绍的方法,枚举系统中的回调函数。那么,要删除回调函数可以有 3 种方式:

  • 可以直接调用 ObUnRegisterCallbacks 函数,传入回调句柄,即可删除回调

  • 修改 CallbackList 双向链表中的数据,使其指向我们自己定义的空回调函数地址。这样,当触发回调函数的时候,执行的是我们自己的空回调函数

  • 修改回调函数的前几字节内存数据,写入直接返回指令 RET,不进行任何操作

编码实现

获取进程对象类型回调

  1. // 获取进程对象类型回调
  2. BOOLEAN EnumProcessObCallback()
  3. {
  4. POB_CALLBACK pObCallback = NULL;
  5. // 直接获取 CallbackList 链表
  6. LIST_ENTRY CallbackList = ((POBJECT_TYPE)(*PsProcessType))->CallbackList;
  7. // 开始遍历
  8. pObCallback = (POB_CALLBACK)CallbackList.Flink;
  9. do
  10. {
  11. if (FALSE == MmIsAddressValid(pObCallback))
  12. {
  13. break;
  14. }
  15. if (NULL != pObCallback->ObHandle)
  16. {
  17. // 显示
  18. DbgPrint("[PsProcessType]pObCallback->ObHandle = 0x%p\n", pObCallback->ObHandle);
  19. DbgPrint("[PsProcessType]pObCallback->PreCall = 0x%p\n", pObCallback->PreCall);
  20. DbgPrint("[PsProcessType]pObCallback->PostCall = 0x%p\n", pObCallback->PostCall);
  21. }
  22. // 获取下一链表信息
  23. pObCallback = (POB_CALLBACK)pObCallback->ListEntry.Flink;
  24. } while (CallbackList.Flink != (PLIST_ENTRY)pObCallback);
  25. return TRUE;
  26. }

获取线程对象类型回调

  1. // 获取线程对象类型回调
  2. BOOLEAN EnumThreadObCallback()
  3. {
  4. POB_CALLBACK pObCallback = NULL;
  5. // 直接获取 CallbackList 链表
  6. LIST_ENTRY CallbackList = ((POBJECT_TYPE)(*PsThreadType))->CallbackList;
  7. // 开始遍历
  8. pObCallback = (POB_CALLBACK)CallbackList.Flink;
  9. do
  10. {
  11. if (FALSE == MmIsAddressValid(pObCallback))
  12. {
  13. break;
  14. }
  15. if (NULL != pObCallback->ObHandle)
  16. {
  17. // 显示
  18. DbgPrint("[PsThreadype]pObCallback->ObHandle = 0x%p\n", pObCallback->ObHandle);
  19. DbgPrint("[PsThreadType]pObCallback->PreCall = 0x%p\n", pObCallback->PreCall);
  20. DbgPrint("[PsThreadType]pObCallback->PostCall = 0x%p\n", pObCallback->PostCall);
  21. }
  22. // 获取下一链表信息
  23. pObCallback = (POB_CALLBACK)pObCallback->ListEntry.Flink;
  24. } while (CallbackList.Flink != (PLIST_ENTRY)pObCallback);
  25. return TRUE;
  26. }

移除回调

  1. // 移除回调
  2. NTSTATUS RemoveObCallback(PVOID RegistrationHandle)
  3. {
  4. ObUnRegisterCallbacks(RegistrationHandle);
  5. return STATUS_SUCCESS;
  6. }

程序测试

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

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

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

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

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

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

总结

要理解清楚获取 CallbackList 双向链表地址的流程,其中,不同系统的数据结构定义是不同的,要注意区分。大家也不用记忆这些特征码,如果需要用到,可以随时使用 WinDbg 来进行逆向查看就好。

删除回调常用就有 3 种方式,自己根据需要选择一种使用即可。

参考

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

上传的附件 cloud_download ObRegisterCallbacks_Enum_Remove_Test.7z ( 9.40kb, 2次下载 )

发送私信

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

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