首页
社区
课程
招聘
[原创]逆向ObRegisterCallbacks 分析回调表结构
发表于: 2019-5-9 16:17 10448

[原创]逆向ObRegisterCallbacks 分析回调表结构

2019-5-9 16:17
10448



  学过内核的大佬们知道,内核有一些表是全局的数组, 因为是全局的,所以对表项数目会有要求,比如最多只能注册8项之类的。

  那么, 看到微软官方的解释, 并没有限制注册的数量,而且, 注册条目这里 类型是 USHORT。


  也就是说,理论上我们可以注册 65536个回调函数。如果用全局数组,并不现实,推测使用了链表之类的数据结构。

1.这是本人在论坛注册后的第一次发帖,如果有格式错误, 发布的版块错误, 请各位明示指出。管理员也可以删帖。
2.因为本人水平有限, 某些地方写的可能是错的, 也请各位大佬指正, 不胜感激。
3.为什么要发这个帖子?
       1).因为后期如果我们想对进程线程搞一些事情的话,需要了解 ObRegisterCallbacks表的结构.
       2).当我去查 ObRegisterCallbacks表结构资料的时候, 全网搜了半小时,也没找到啥能用的,当然这可能是我搜索的姿势不对。

闲话不说,开始正文。

用到的文件:win7 sp1 x64 内核文件ntoskrnl.exe 
用的的工具:IDA 7.0 

阅读提醒
1 以下所有被命名为 p_oft_xx 都表示在缓冲区 offset 为 xx位置的变量
2 为了更清晰的表示结构体 在结构体成员前面 也加了 oft_xx 表示 偏移

ObRegisterCallbacks函数 故名思意,是一个注册对象钩子的函数。
可以让用户定义多个自定义的回调函数, 在某些事件即将发生, 或者已经发生的时候被触发。
比如进程线程句柄的创建,句柄的拷贝。在时下, 经常被用作进程监测,进程防杀。

函数定义

NTSTATUS ObRegisterCallbacks(
  POB_CALLBACK_REGISTRATION   CallbackRegistration,
  PVOID                       *RegistrationHandle
);

NTSTATUS ObRegisterCallbacks(
  POB_CALLBACK_REGISTRATION   CallbackRegistration,
  PVOID                       *RegistrationHandle
);

参数结构体相关
typedef struct _OB_CALLBACK_REGISTRATION {
  USHORT                    Version;
  USHORT                    OperationRegistrationCount;
  UNICODE_STRING            Altitude;
  PVOID                     RegistrationContext;
  OB_OPERATION_REGISTRATION *OperationRegistration;
} OB_CALLBACK_REGISTRATION, *POB_CALLBACK_REGISTRATION;

typedef struct _OB_CALLBACK_REGISTRATION {
  USHORT                    Version;
  USHORT                    OperationRegistrationCount;
  UNICODE_STRING            Altitude;
  PVOID                     RegistrationContext;
  OB_OPERATION_REGISTRATION *OperationRegistration;
} OB_CALLBACK_REGISTRATION, *POB_CALLBACK_REGISTRATION;



typedef struct _OB_OPERATION_REGISTRATION {
  POBJECT_TYPE                *ObjectType;
  OB_OPERATION                Operations;
  POB_PRE_OPERATION_CALLBACK  PreOperation;
  POB_POST_OPERATION_CALLBACK PostOperation;
} OB_OPERATION_REGISTRATION, *POB_OPERATION_REGISTRATION;

typedef struct _OB_OPERATION_REGISTRATION {
  POBJECT_TYPE                *ObjectType;
  OB_OPERATION                Operations;
  POB_PRE_OPERATION_CALLBACK  PreOperation;
  POB_POST_OPERATION_CALLBACK PostOperation;
} OB_OPERATION_REGISTRATION, *POB_OPERATION_REGISTRATION;


        在_OB_CALLBACK_REGISTRATION结构体中, 有一个需要注意的成员 OperationRegistrationCount
        这个成员表示注册回调函数的个数



  学过内核的大佬们知道,内核有一些表是全局的数组, 因为是全局的,所以对表项数目会有要求,比如最多只能注册8项之类的。

  那么, 看到微软官方的解释, 并没有限制注册的数量,而且, 注册条目这里 类型是 USHORT。


  也就是说,理论上我们可以注册 65536个回调函数。如果用全局数组,并不现实,推测使用了链表之类的数据结构。

  所以,逆向的时候,我从这个角度入手, 重点找内存申请的相关操作, 确定表结构。


定位到关键位置,开始逆向


首先看一下 整个函数的规模, 也不是特别大。挺好挺好。
F5一下,看看反汇编的结果。




00000000 _OB_CALLBACK_REGISTRATION struc ; (sizeof=0x14, mappedto_1310)
00000000 Version         dw ?
00000002 OperationRegistrationCount dw ?
00000004 Altitude        UNICODE_STRING ?
0000000C p_RegistrationContext dd ?
00000010 p_OperationRegistration dd ?
00000014 _OB_CALLBACK_REGISTRATION ends
00000014

00000000 _OB_OPERATION_REGISTRATION struc ; (sizeof=0x10, mappedto_1309)
00000000 p_ObjectType    dd ?
00000004 Operations      dd ?
00000008 PreOperation    dd ?
0000000C PostOperation   dd ?
00000010 _OB_OPERATION_REGISTRATION ends

继续分析函数功能
00000000 _OB_CALLBACK_REGISTRATION struc ; (sizeof=0x14, mappedto_1310)
00000000 Version         dw ?
00000002 OperationRegistrationCount dw ?
00000004 Altitude        UNICODE_STRING ?
0000000C p_RegistrationContext dd ?
00000010 p_OperationRegistration dd ?
00000014 _OB_CALLBACK_REGISTRATION ends
00000014

00000000 _OB_OPERATION_REGISTRATION struc ; (sizeof=0x10, mappedto_1309)
00000000 p_ObjectType    dd ?
00000004 Operations      dd ?
00000008 PreOperation    dd ?
0000000C PostOperation   dd ?
00000010 _OB_OPERATION_REGISTRATION ends

继续分析函数功能

和大部分函数一样,上来就是一波参数检查。
检测Version成员,OperationRegistrationCount成员的合法性。
//===============================================
接下来我们看到了一波申请内存的操作。
接下来我们看到了一波申请内存的操作。
接下来我们看到了一波申请内存的操作。

n_alloc_size = 36 * u_reg_cnt + CallbackRegistration->Altitude.Length + 16;
n_alloc_size = 36 * u_reg_cnt + CallbackRegistration->Altitude.Length + 16;
结合上面的观点, 整个表很可能是一个链表之类的数据结构,那么这块内存很可能就是数据成员的大小了。
变量解释 
n_alloc_size:                                         申请内存的大小
u_reg_cnt:                                             要注册的回调函数条目数量
CallbackRegistration->Altitude.Length  参数结构体中unicode字符串的长度

所以上面这个式子 用文字来表达就是
申请内存大小 = 36 *  要注册的回调函数条目数量 +  参数结构体中unicode字符串的长度 + 16
这个很可能是数据结构中,单个数据成员占用内存空间,我们先继续向后看。


可以看到上面来了一波赋值 而且都是针对刚申请的缓冲区的赋值  
为了方便阅读 我把上图的代码 整理了下
如下
  *(_WORD *)p_alloc_buf1 = 256;        //OB_FLT_REGISTRATION_VERSION 这里是version成员
  *((_DWORD *)p_alloc_buf1 + 1) = CallbackRegistration->p_RegistrationContext;

  //这里是一个 UNICODE_STRING 结构体
  n_str_len = CallbackRegistration->Altitude.Length;        
  *((_WORD *)p_alloc_buf1 + 4) = n_str_len;  // UNICODE_STRING Length   成员
  *((_WORD *)p_alloc_buf1 + 5) = n_str_len;  // UNICODE_STRING MaximumLength成员  
   p_u_str = (char *)p_alloc_buf1 + n_alloc_size - n_str_len;    // 在缓冲区的最后部分存字符串
  *((_DWORD *)p_alloc_buf1 + 3) = p_u_str;   // UNICODE_STRING Buffer 成员

  n_str_len1 = *((unsigned __int16 *)p_alloc_buf1 + 4);
  memcpy(p_u_str, CallbackRegistration->Altitude.Buffer, n_str_len1);  // 拷贝字符串到内存
变量解释:
p_alloc_buf1 刚刚申请的内存首地址
  *(_WORD *)p_alloc_buf1 = 256;        //OB_FLT_REGISTRATION_VERSION 这里是version成员
  *((_DWORD *)p_alloc_buf1 + 1) = CallbackRegistration->p_RegistrationContext;

  //这里是一个 UNICODE_STRING 结构体
  n_str_len = CallbackRegistration->Altitude.Length;        
  *((_WORD *)p_alloc_buf1 + 4) = n_str_len;  // UNICODE_STRING Length   成员
  *((_WORD *)p_alloc_buf1 + 5) = n_str_len;  // UNICODE_STRING MaximumLength成员  
   p_u_str = (char *)p_alloc_buf1 + n_alloc_size - n_str_len;    // 在缓冲区的最后部分存字符串
  *((_DWORD *)p_alloc_buf1 + 3) = p_u_str;   // UNICODE_STRING Buffer 成员

  n_str_len1 = *((unsigned __int16 *)p_alloc_buf1 + 4);
  memcpy(p_u_str, CallbackRegistration->Altitude.Buffer, n_str_len1);  // 拷贝字符串到内存
变量解释:
p_alloc_buf1 刚刚申请的内存首地址
n_str_len       参数中unicode字符串长度
p_u_str          unicode字符串缓冲区的地址

那么 如果这个缓冲区存储的是结构体数据的话 部分成员已经被分析出来了
我把这个这个结构体命名为 St_Call_Back_Table
如下
struct St_Call_Back_Table
{
	oft_0:	USHORT   Version;              // _OB_CALLBACK_REGISTRATION结构体Version成员
	oft_2:	unknow                         //成员未知
	oft_4:	PVOID    RegistrationContext;  //_OB_CALLBACK_REGISTRATION结构体 RegistrationContext
	
	//MaximumLength 和 Length 数值一样 都是字符串长度
	oft_8:  UNICODE_STRING Altitude   	//_OB_CALLBACK_REGISTRATION结构体 Altitude 成员 
	
        oft_16 --- oft_last  unknow             //成员未知
	
	//在缓冲区最后 sizeof(p_alloc_buf1) - altitude.length的位置
	oft_last: wchar_t[xx];	               //unicode字符串  当前结构体中 Altitude.buffer 成员指向的位置
}

struct St_Call_Back_Table
{
	oft_0:	USHORT   Version;              // _OB_CALLBACK_REGISTRATION结构体Version成员
	oft_2:	unknow                         //成员未知
	oft_4:	PVOID    RegistrationContext;  //_OB_CALLBACK_REGISTRATION结构体 RegistrationContext
	
	//MaximumLength 和 Length 数值一样 都是字符串长度
	oft_8:  UNICODE_STRING Altitude   	//_OB_CALLBACK_REGISTRATION结构体 Altitude 成员 
	
        oft_16 --- oft_last  unknow             //成员未知
	
	//在缓冲区最后 sizeof(p_alloc_buf1) - altitude.length的位置
	oft_last: wchar_t[xx];	               //unicode字符串  当前结构体中 Altitude.buffer 成员指向的位置
}


上面一波赋值完之后 有一个if 语句块  
针对 oft_2  oft_28 两个成员进行了操作
暂时先不理  看下面 else块的代码



[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2020-1-31 17:19 被kanxue编辑 ,原因:
收藏
免费 4
支持
分享
最新回复 (11)
雪    币: 1736
活跃值: (45)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
真的是认真搜了半小时么?这东西N年前网上就有了,google出来的结构体更明了。
2019-5-9 17:45
0
雪    币: 977
活跃值: (435)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
3
sylingyy 真的是认真搜了半小时么?这东西N年前网上就有了,google出来的结构体更明了。
没翻墙 没用谷歌 一直在逆向到 ObpInsertCallbackByAltitude 函数之前 没找到任何可用资料  搜索这个函数的时候发现一些资料 但是 仔细看的话 某些成员我和他们分析的并不一样  或者是我错了  我只是把整个分析流程写出来 比起现成的结构体 思路或者更重要
2019-5-9 18:38
1
雪    币: 3738
活跃值: (3872)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
4
顶!!!
2019-5-9 21:51
0
雪    币: 3190
活跃值: (1816)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
顶,自己动手丰衣足食
2019-5-10 09:33
0
雪    币: 1736
活跃值: (45)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
上海刘一刀 没翻墙 没用谷歌 一直在逆向到 ObpInsertCallbackByAltitude 函数之前 没找到任何可用资料 搜索这个函数的时候发现一些资料 但是 仔细看的话 某些成员我和他们分析的并不一样 ...
不用翻墙啊,百度就有,csdn上有,至少是5年前都有了。讲真这真没啥思路,但凡ark,开源或者不开的都是这样下手。
2019-5-10 09:42
0
雪    币: 977
活跃值: (435)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
7
sylingyy 不用翻墙啊,百度就有,csdn上有,至少是5年前都有了。讲真这真没啥思路,但凡ark,开源或者不开的都是这样下手。
嗯 大哥说的有道理
2019-5-10 11:38
0
雪    币: 300
活跃值: (2477)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
mark
2019-5-10 14:53
0
雪    币: 1055
活跃值: (412)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
9
 
最后于 2020-4-14 10:43 被provence编辑 ,原因:
2019-5-12 17:18
0
雪    币: 1736
活跃值: (45)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
provence 可是也没见你发什么有用的帖子呀
什么是有用,直接让你拿去抄么?东西是有不少,没必要发帖子啊。有问题还可以讨论啊,看雪的精华远没有7,8年前质量高了,高手好几个真心质量帖,什么都没有。这个帖子能给精,我觉得就是因为最后一句“2019.5.9 于武汉”。
2019-5-13 09:45
0
雪    币: 181
活跃值: (50)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
sylingyy 什么是有用,直接让你拿去抄么?东西是有不少,没必要发帖子啊。有问题还可以讨论啊,看雪的精华远没有7,8年前质量高了,高手好几个真心质量帖,什么都没有。这个帖子能给精,我觉得就是因为最后一句“2019. ...
最后于 2019-6-6 02:36 被cloudwindby编辑 ,原因:
2019-5-14 16:44
0
雪    币: 185
活跃值: (292)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
2020-6-28 21:21
0
游客
登录 | 注册 方可回帖
返回
//