首页
论坛
课程
招聘
雪    币: 985
活跃值: 活跃值 (81)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝

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

2019-5-9 16:17 6503

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

2019-5-9 16:17
6503
在开始正文之前, 首先说一些题外话。
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
);

参数结构体相关
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;


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



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

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


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

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


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


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



看到这个就有点辣眼睛了, 为了让IDA 能够完全发挥功能,定义一波用到的结构体先。


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:                                         申请内存的大小
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 刚刚申请的内存首地址
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 成员指向的位置
}


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


下面是上面图片的完整代码
  else
  {
    v23 = 0;
    p_oft_48 = (char *)p_alloc_buf1 + 48;

    while ( 1 )
    {
      p_oper_reg = (_OB_OPERATION_REGISTRATION *)(v23 + p_call_back_reg->p_OperationRegistration);
      p_oper_reg1 = (_OB_OPERATION_REGISTRATION *)(v23 + p_call_back_reg->p_OperationRegistration);// 
       
      // 检测数据成员是否合法
      if ( !p_oper_reg->Operations || !(*(_BYTE *)(*(_DWORD *)p_oper_reg->p_ObjectType + 42) & 0x40) )
        break;                               
 
       // 检测回调函数 调用前 指针是否合法
      if ( p_oper_reg->PreOperation )
      {
        if ( !MmVerifyCallbackFunction(p_oper_reg->PreOperation) )
          goto LABEL_21;
        p_oper_reg = p_oper_reg1;
      }                                       
    
      // 检测调用后指针是否合法
      else if ( !p_oper_reg->PostOperation )
      {
        break;
      }
      if ( p_oper_reg->PostOperation )
      {
        if ( !MmVerifyCallbackFunction(p_oper_reg->PostOperation) )
        {
LABEL_21:
          u_ret = -1073741790;
          goto LABEL_22;
        }
        p_oper_reg = p_oper_reg1;
      }
    .......
    .......
}

else 块中  进行了一波检测   检测了成员的合法性
只要有一个条件不满足就跳出。


首先我把 上面的图 一步步进行拆解分析
首先我们看到了一波赋值  这里赋值了 36个字节的数据  
36
36
36
这个数字是不是很熟悉  

申请内存大小 = 36 *  要注册的回调函数条目数量 +  参数结构体中unicode字符串的长度 + 16;

结合上下文的汇编  得出这是个单独的结构体  具体分析代码稍后写出来
当前结构体在缓冲区 oft_16 的位置处
p_oft_48 - sizeof(DWORD) * 8 =  p_oft_48 - 32 =   p_oft_16

      //这里是对缓冲区的又一波赋值  p_oft_48 表示在缓冲区首地址 + 48 的位置
      // 这里的 p_oft_48定义为一个 DWORD* 指针 所以 - 1 就是 - sizeof(DWORD) 请知悉
       //p_oft_48 - 8 是表示 p_oft_48 - sizeof(DWORD) * 8 

      *p_oft_48 = 0;                     //固定为0
      *(p_oft_48 - 7) = p_oft_48 - 8;    //指向 本结构首地址
      *(p_oft_48 - 8) = p_oft_48 - 8;    //指向 本结构首地址
      *(p_oft_48 - 6) = p_oper_reg->Operations;                // _OB_OPERATION_REGISTRATION   Operations成员
      *(p_oft_48 - 4) = p_alloc_buf1;                         //缓冲区首地址
      *(p_oft_48 - 3) = *(_DWORD *)p_oper_reg->p_ObjectType;  // _OB_OPERATION_REGISTRATION  ObjectType 成员
      *(p_oft_48 - 2) = p_oper_reg->PreOperation;             //_OB_OPERATION_REGISTRATION  PreOperation 成员
      *(p_oft_48 - 1) = p_oper_reg->PostOperation;            // _OB_OPERATION_REGISTRATION PostOperation成员

仔细观察 这里有一个成员没有被赋值  *(p_oft_48 - 5)   也就是 缓冲区 oft_28  在这里没有出现
前面if 块中有对这个成员的操作  *(p_oft_48 - 5)  就是 if 块里面的变量   p_oft_28  稍后分析

我把这个结构命名为 St_Reg_Info
//结构体大小36字节 9个成员 有些成员的功能会在下面进一步分析

struct St_Reg_Info
{
	oft_0:	PUNCHAR p_self0;			      //指向本结构首地址  怀疑跟LIST_ENTRY有关
	oft_4:	PUNCHAR p_self1;			      //指向本结构首地址
	oft_8:  OB_OPERATION Operations;                       //_OB_OPERATION_REGISTRATION   Operations成员
	oft_12: DWORD dw_flag;		                      //待分析
	oft_16:	PUCHAR p_call_back_self;		       //指向缓冲区 St_Call_Back_Table 首地址
	oft_20:	POBJECT_TYPE ObjectType;                       //_OB_OPERATION_REGISTRATION   ObjectType成员
	oft_24:	POB_PRE_OPERATION_CALLBACK PreOperation;       //_OB_OPERATION_REGISTRATION PreOperation成员 
	oft_28:	POB_POST_OPERATION_CALLBACK PostOperation;     //_OB_OPERATION_REGISTRATION PostOperation成员
	oft_32:	DWORD zero_flag;                               //直接赋值为0 含义暂时不知道
}

赋值完一波结构体后 调用了一个参数  ObpInsertCallbackByAltitude 
顾名思义是一个插入链表的函数 
arg1   St_Reg_Info::p_self0
arg2  *(St_Reg_Info:: p_ObjectType)
//插入链表 
u_ret = ObpInsertCallbackByAltitude(p_oft_48 - 8, *(p_oft_48 - 3));

 if ( u_ret < 0 )
 {
     goto LABEL_22;
 }
        
进去函数看看 


看到了一个教科书般的 链表插入动作
由此可以判定  St_Reg_Info 结构体中 p_self 实际上是个 LIST_ENTRY  结构体

更新一下分析结果
//结构体大小36字节 9个成员 有些成员的功能会在下面进一步分析

struct St_Reg_Info
{
        oft_0: LIST_ENTRY   list_entry;                        //回调函数链表项
	oft_8:  OB_OPERATION Operations;                       //_OB_OPERATION_REGISTRATION   Operations成员
	oft_12: DWORD dw_flag;		                     //待分析
	oft_16:	PUCHAR p_call_back_self;		       //指向缓冲区 St_Call_Back_Table 首地址
	oft_20:	POBJECT_TYPE ObjectType;                       //_OB_OPERATION_REGISTRATION   ObjectType成员
	oft_24:	POB_PRE_OPERATION_CALLBACK PreOperation;       //_OB_OPERATION_REGISTRATION PreOperation成员 
	oft_28:	POB_POST_OPERATION_CALLBACK PostOperation;     //_OB_OPERATION_REGISTRATION PostOperation成员
	oft_32:	DWORD zero_flag;                               //直接赋值为0 含义暂时不知道
}

链表成功插入后  oft_2 的位置进行了一个递增操作
猜测是表示成功  因为缓冲区在开始的时候被赋值为0 所以这个值是从0开始累积的
   ++*((_WORD *)p_alloc_buf1 + 1);           // 缓冲区偏移为2的数递增



接下来 指针向后轮询 开始新一轮的插入操作



此刻 在结构体中还有一个成员未分析
St_Reg_Info 偏移为12位置的  dw_flag;

根据 if 块的代码分析
可以知道  dw_flag 成员在一个循环里  只要注册成功 这个值都会 最低位置1 
所以 猜测是一个 标志位 表示是否有效

更新一波分析到的结构体
struct St_Call_Back_Table
{
	oft_0:	USHORT   Version;              // _OB_CALLBACK_REGISTRATION结构体Version成员
	oft_2:	USHORT   u_valid_cnt;		//有效注册条目 注册失败的条目不算
	oft_4:	PVOID    RegistrationContext;  //_OB_CALLBACK_REGISTRATION结构体 RegistrationContext
	
	//MaximumLength 和 Length 数值一样 都被赋值为字符串长度
	oft_8:  UNICODE_STRING Altitude   	//_OB_CALLBACK_REGISTRATION结构体 Altitude 成员 
	
        //以下结构主要看 注册条目有几个 结构大小为36个字节  
        //n_reg_cnt 表示注册条目 注意这里不是 n_valid_cnt 有效条目
	oft_16:	St_Reg_Info st_reg_info[u_reg_cnt]; 
	
	//在缓冲区最后 sizeof(p_alloc_buf1) - altitude.length的位置
	oft_last: wchar_t[xx];	               //unicode字符串  当前结构体中 Altitude.buffer 成员指向的位置
}

struct St_Reg_Info
{
        oft_0: LIST_ENTRY   list_entry;                        //回调函数链表项
	oft_8:  OB_OPERATION Operations;                     //_OB_OPERATION_REGISTRATION   Operations成员
	oft_12: DWORD dw_flag;		                      //标志位 最低位为1 表示有效
	oft_16:	PUCHAR p_call_back_self;		       //指向缓冲区 St_Call_Back_Table 首地址
	oft_20:	POBJECT_TYPE ObjectType;                       //_OB_OPERATION_REGISTRATION   ObjectType成员
	oft_24:	POB_PRE_OPERATION_CALLBACK PreOperation;       //_OB_OPERATION_REGISTRATION PreOperation成员 
	oft_28:	POB_POST_OPERATION_CALLBACK PostOperation;     //_OB_OPERATION_REGISTRATION PostOperation成员
	oft_32:	DWORD dw_zero_flag;                            //直接赋值为0 含义暂时不知道
}

最后做一波验证
申请内存大小 = 36 *  要注册的回调函数条目数量 +  参数结构体中unicode字符串的长度 + 16;
这里 36 是结构体  St_Reg_Info的大小
        16  是结构体 St_Call_Back_Table中 前4个成员占用的总字节数

Bingo!!!! 验证成功
虽然下面还有一些 针对成员的操作 但是时间原因 不多分析了(主要原因是懒)
本来是想上传idb 文件的 但是 压缩了后还是有14M多 附件最多8M 所以表示遗憾了
如果帖子有什么错误也请各位大佬指出明示, 毕竟我自己水平有限
希望本贴可以抛砖引玉 能有更多的人加入到对这个表的分析也是好的。


autor:刘一刀
2019.5.9 于武汉

HWS计划·2020安全精英夏令营来了!我们在华为松山湖欧洲小镇等你

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