学过内核的大佬们知道,内核有一些表是全局的数组, 因为是全局的,所以对表项数目会有要求,比如最多只能注册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编辑
,原因: