看流星社区

 找回密码
 注册账号
查看: 2412|回复: 0

遍历创建进程、创建线程、加载模块的回调函数

[复制链接]

该用户从未签到

发表于 2017-6-1 17:25:32 | 显示全部楼层 |阅读模式
今天我们首先来看一下最简单的,关于遍历PspCreateProcessNotifyRoutine数组,PspLoadImageNotifyRoutine也同理

这两个数组保存了两组函数地址,它们将在有进程被创建或销毁,有镜像被装载或映射入内存的时候被依次调用,
当然了这两个符号都是未导出的,而且它们的数量在xp和win7下也有所不同,xp<2k3>下最大数组数量为8,win7下为64

既然要找到两个未导出符号,我们就在已导出的相关函数中看一下有没有线索,
相关函数sSetCreateProcessNotifyRoutine,注册或注销一个进程创建,销毁的通知回调
相关函数sSetLoadImageNotifyRoutine,注册一个镜像装载,映射的通知回调
相关函数sRemoveLoadImageNotifyRoutine,注销一个镜像装载,映射的通知回调
相关函数sSetCreateThreadNotifyRoutine,注册一个线程创建,销毁的通知回调

相关函数sRemoveCreateThreadNotifyRoutine,注销一个线程创建,销毁的通知回调




<S一下:不清楚为什么那么多文章说PsSetCreateProcessNotifyRoutine,或PsSetLoadImageNotifyRoutine注册的例程无法被删除,从而导致相关的驱动无法被卸载,云云..
WDK中已经解释的很清楚了,而且我平时的使用中没出过任何异常,两个回调都可以通过正规函数删掉,当然手动遍历数组删除也没问题>

另外&#20540;得一提的是PsSetLoadImageNotifyRoutine,
该函数注册的回调例程会在镜像被装载或映射入内存之后马上被调用,
注意它是先于DLLMAIN或者DRIVER_ENTRY被调用的,联想到有的驱动会在被加载之后马上删除源文件,我们可以通过这种回调在驱动自动删掉文件本身之前将它copy出去,这种方式比内存dump这种马后炮的方式优雅可靠得多


首先来看一下xp的PsSetCreateProcessNotifyRoutine<2k3下相仿>:




可以看到在入口地址不远处就有PspCreateProcessNotifyRoutine的出现,我们可以很方便地通过搜索特征码的方式得到.

在看一下win7下的PsSetCreateProcessNotifyRoutine:




它首先调用了PspSetCreateProcessNotifyRoutine函数,追踪一下:

PspCreateProcessNotifyRoutine在入口不远处出现


这样,我们就得到了xp<2k3>和win7下的PspCreateProcessNotifyRoutine的地址,
接下来就是遍历一下这个数组看一下每个回调例程都位于哪个模块中了,
找到之后调用PsSetCreateProcessNotifyRoutine,第二个参数设置为TRUE就能摘掉回调例程了


...什么?你遍历了数组但是没有发现目标模块中的回调?好吧,这其中貌似有些猫腻,我们来看一下:
<图中选中的项是我自己的回调例程,当然地址是我已经计算好了的,PspCreateProcessNotifyRoutine数组中并不会出现此地址>




我们来看一下这个驱动文件在内核地址空间中的范围:
<图中选中项是我自己的回调例程所在模块>


可以计算一下该驱动文件的地址范围是0x927F7000至0x927F7000+7000<927FE000>

接下来我们用livekd来看一下,直接找到PspCreateProcessNotifyRoutine:



可以看到8个回调地址中没有0x927F7000-927FE000范围内的地址,直觉上最后一个应该是与我们自己注册的例程有关系的数据,那么我们看一下地址0x9c79061f中又有什么玄机:



这..貌似没有什么有用的数据,那么我自己注册的例程地址保存到了哪里呢?于是我们自然想到翻看一下WRK,
在WRK中找到PsSetCreateProcessNotifyRoutine,
然后发现其中一处调用了ExAllocateCallBack,看名称应该是为回调分配内存,我们看一下:




这里进行了内存分配,并且将传参Function,也就是我们注册回调时提供的例程地址,写入了一个结构中PEX_CALLBACK_ROUTINE_BLOCK,看一下这个结构有什么成员:



我们感兴趣的只有结构中的第二个成员Function.
回想一下,既然系统为回调分配了内存,那么PspCreateProcessNotifyRoutine中保存的应该是每个结构的地址了,但是回过头我们再看一下0x9c79061f地址处的数据:



这里还是没有我们要寻找的地址!
而继续在PsSetCreateProcessNotifyRoutine中寻找线索也没有什么收获

于是我们开始转换了思路,既然我们找不到这个回调的地址,那么系统是如何找到的呢?
嗯,貌似这是一个新的突破口,可是我们不知道系统是在什么时候通过什么方式调用PspCreateProcessNotifyRoutine中的例程的啊!
额,再想一下,系统要在某个函数中调用PspCreateProcessNotifyRoutine中的例程,那么应该会对PspCreateProcessNotifyRoutine进行引用的吧,我们在WRK中搜索PspCreateProcessNotifyRoutine,看一看都有哪里对其进行了引用:



于是我们找到了这里:



想想也对,因为PspCreateProcessNotifyRoutine是在进程创建,销毁都进行调用的
跟踪一下CallBack=ExReferenceCallBackBlock(&ampspCreateProcessNotifyRoutine[i]);这行代码,这里应该是取得回调结构的调用



仔细看看该函数的函数头,我们才发现原来PspCreateProcessNotifyRoutine中保存的是PEX_CALLBACK结构:




我们看这一行代码
CallBackBlock=ExFastRefGetObject(CallBack->RoutineBlock);
CallBackBlock被定义为PEX_CALLBACK_ROUTINE_BLOCK也就是PsSetCreateProcessNotifyRoutine分配的内存数据结构

它的意思很明确,就是得到这个结构的地址,看一下ExFastRefGetObject:



只是简单地和一个常量进行了与运算

好了,我们理清思路:
PspCreateProcessNotifyRoutine数组中保存的是PEX_CALLBACK类型的结构地址数组;
EX_CALLBACK结构中只有一个成员,就是RoutineBlock,它是一个EX_FAST_REF结构;
EX_FAST_REF是一个联合,我们这里简单地将之视为一个指针;
省略掉中间步骤,PspCreateProcessNotifyRoutine保存的就是指针数组;
系统将数组中的指针传递给ExFastRefGetObject,就得到了PEX_CALLBACK_ROUTINE_BLOCK结构地址,这个结构中保存这我们想要的回调例程地址;
ExFastRefGetObject将传进来的指针进行了与运算得到了PEX_CALLBACK_ROUTINE_BLOCK结构地址.

好了,回想一下我们自己寻找回调地址的过程,我们没有进行最后的与运算而是直接将PspCreateProcessNotifyRoutine中的数据当成了结构的地址

最后看一下那个神秘的常量:



win32下被定义为7,先取反再与运算,也就是低3位清零

我们回过头在用windbg看一下:


对0x9c79061f进行运算得到了0x9C790618,看一下这里的数据:


成功得到了注册的例程地址,我们在PT中验证一下:



如过想通过PsSetCreateProcessNotifyRoutine摘掉其他进程的回调,传参必须经过以上运算

当然了,如果你直接将PspCreateProcessNotifyRoutine数组暴力清空也行,不过既然有相对优雅的方式,何乐而不为呢?


---------------------------------------------------------------------------------------------------------------------------
练习实例

看了上边的教程 以下是练习的过程 代码参考来源于WRK

windbg中创建进程的回调函数数组实例如下:
3: kd> dd nt!PspCreateProcessNotifyRoutine
83f789a0 8d808a3f 8fe950b7 8fea577f 8fe81daf
83f789b0 8fe3bff7 9c08feb7 a9345397 00000000
以 a9345397 为例


1.在PsSetCreateProcessNotifyRoutine函数中有如下代码
PEX_CALLBACK_ROUTINE_BLOCK CallBack = ExReferenceCallBackBlock (&ampspCreateProcessNotifyRoutine[i]); //这里传入的是 83f789b8
通过代码知道 ExReferenceCallBackBlock将回调数组的&#20540;转换为 我们需要的 PEX_CALLBACK_ROUTINE_BLOCK 结构 从而可以得到函数地址
其中PEX_CALLBACK_ROUTINE_BLOCK结构定义如下:
typedef struct _EX_CALLBACK_ROUTINE_BLOCK {
EX_RUNDOWN_REF RundownProtect;
PEX_CALLBACK_FUNCTION Function;
PVOID Context;
} EX_CALLBACK_ROUTINE_BLOCK, *PEX_CALLBACK_ROUTINE_BLOCK;
其中Function成员就是我们要的函数地址

2.
再来看看ExReferenceCallBackBlock函数定义
PEX_CALLBACK_ROUTINE_BLOCK
ExReferenceCallBackBlock (
IN OUT PEX_CALLBACK CallBack  //CallBack是83f789b8
)
通过参数可以知道&ampspCreateProcessNotifyRoutine[i]为 PEX_CALLBACK 类型
这样就知道了回调函数数组中的元素PspCreateProcessNotifyRoutine[i]为 EX_CALLBACK 类型
EX_CALLBACK结构定义如下:
typedef struct _EX_CALLBACK {
EX_FAST_REF RoutineBlock;
} EX_CALLBACK, *PEX_CALLBACK;


3.
ExReferenceCallBackBlock函数中有以下代码:
PEX_CALLBACK_ROUTINE_BLOCK CallBackBlock = ExFastRefGetObject (CallBack->RoutineBlock);  //CallBack->RoutineBlock的&#20540;是 a9345397
可以看到通过ExFastRefGetObject得到了 我们想要的最终结果 PEX_CALLBACK_ROUTINE_BLOCK 结构
传入的参数是RoutineBlock成员 也就是EX_FAST_REF结构
EX_FAST_REF结构定义如下:
typedef struct _EX_FAST_REF {
union {
PVOID Object;
#if defined (_WIN64)
ULONG_PTR RefCnt : 4;
#else
ULONG_PTR RefCnt : 3;
#endif
ULONG_PTR Value;
};
} EX_FAST_REF, *PEX_FAST_REF;


4.再来看看ExFastRefGetObject函数中具体的转换过程
NTKERNELAPI
PVOID
FORCEINLINE
ExFastRefGetObject (
__in EX_FAST_REF FastRef
)
{
return (PVOID) (FastRef.Value &amp; ~MAX_FAST_REFS); //FastRef.Value的&#20540;是 a9345397
}
仅仅只有一句代码 简单的逻辑运算
其中MAX_FAST_REFS定义是
#if defined (_WIN64)
#define MAX_FAST_REFS 15
#else
#define MAX_FAST_REFS 7
#endif
这里是WIN7 32位 所以MAX_FAST_REFS的&#20540;为7


5.MAX_FAST_REFS为7 二进制是111 ~MAX_FAST_REFS二进制则是000 所以代码相当于 FastRef.Value &amp; 000 把FastRef.Value二进制的最后三位置为0
a9345397 &amp; ~7 = A9345390 这里的结果是一个PEX_CALLBACK_ROUTINE_BLOCK结构 是我们一直想要的 然后看看他的第二个成员 Function
windbg运行结果如下:
3: kd> dd A9345390
a9345390 00000010 9cd63f40 00000000 000ca0e8
可以看到是 9cd63f40 和 工具里边的&#20540;对比是一样的 由此得到了真正的函数地址


6.总结一下 过程感觉很漫长 其实就2步 要想得到PspCreateProcessNotifyRoutine 回调数组的某个函数地址
实例:
3: kd> dd nt!PspCreateProcessNotifyRoutine
83f789a0 8d808a3f 8fe950b7 8fea577f 8fe81daf
83f789b0 8fe3bff7 9c08feb7 a9345397 00000000
比如这里的 9c08feb7 或 8fe3bff7 或 8fe81daf 等等。。这里用XXX来代替

第一步:YYY=XXX &amp; ~7
第二步: *((PULONG)YYY+1) 就是函数地址了
当然这里是伪代码 而且是32位系统
点击按钮快速添加回复内容: 支持 高兴 激动 给力 加油 苦寻 生气 回帖 路过 感恩
您需要登录后才可以回帖 登录 | 注册账号

本版积分规则

小黑屋|手机版|Archiver|看流星社区 |网站地图

GMT+8, 2024-3-19 13:39

Powered by Kanliuxing X3.4

© 2010-2019 kanliuxing.com

快速回复 返回顶部 返回列表