关于如何使用PsSetCreateProcessNotifyRoutineEx来实现进程监控,请看这篇文章通过PsSetCreateProcessNotifyRoutineEx和PsSetCreateThreadNotifyRoutine实现进程与线程监控
这篇主要是讲解通过逆向PsSetCreateProcessNotifyRoutineEx来查看这个API如何实现的进程监控并以此来实现反进程监控。
这次的实验是在Win7 x86系统上进行,所以如果是其他版本的Windows系统,里面的具体实现会有不同。
首先看下PsSetCreateProcessNotifyRoutine的反汇编结果。
可以看到PsSetCreateProcessNotifyRoutine就干了一件事,就是调用PspSetCreateProcessNotifyRoutine。在对它的调用时候,压入的两个参数除了NotifyRoutine和Remove以外,还压入了一个0。事实上PsSetCreateProcessNotifyRoutine和PsSetCreateProcessNotifyRoutineEx的唯一区别就是,在压入第三个参数的时候,PsSetCreateProcessNotifyRoutine压入的是0,而PsSetCreateProcessNotifyRoutineEx压入的是1。
接着跟进PspSetCreateProcessNotifyRoutine
函数首先判断压入的Remove参数是否为0,是的话则跳转。由于在增加回调的时候,Remove参数会使用FALSE,所以这个时候这个跳转会成立。接着跟进loc_594905看看
在loc_594905中,函数会判断传进的第三个参数是否为0。上面提到过,这个参数其实是区别你调用的是PsSetCreateProcessNotifyRoutine还是PsSetCreateProcessNotifyRoutineEx。如果是后者,这个参数就是1,前者这个参数是0。而两者的区别只是后者会调用sub_56F869。但由于这个函数的作用对今天的主题而言没什么意义,所以这里不分析,直接接着看loc_59491E的内容
函数将要设置的回调函数的地址作为第一个参数,根据调用是否带有Ex的PsSetCreateProcessNotifyRoutine来传入第二个参数。
这里主要是调用AllocateAssign这个函数,当然这个函数名是我重命名以后的函数名。因为这个函数就是为保存回调函数地址申请一块空间,如果申请到了那么会跳转继续执行,如果没申请到就会设置返回值为没有足够的资源并且跳到函数结束执行,接下来看看AllocateAssign的反汇编结果
可以看到这个函数中,首先是申请0xC大小的内存空间,然后将低4为清0,中间4位赋值为要设置的回调函数的地址,最高4位根据传入的参数来设置。
由此我们可以知道,在PsSetCreateProcessNotifyRoutine函数中,系统会分配0xC大小的内存,而内存的中间4位保存的就是要设置的回调函数的地址。根据调用的是PsSetCreateProcessNotifyRoutine还是PsSetCreateProcessNotifyRoutineEx,系统会设置最高的4位的值,如果是前者会设置位0,后者设置位1。
接着看内存看内存分配并且赋值成功以后,会执行的代码,也就是loc_59493C的代码
可以看到,函数首先将一个数组地址赋给esi,然后将0压入栈中并且调用SetArray这个函数,在调用这个函数之前还把esi的地址,也就是这个数组的地址赋给了eax,把ebx的值赋给ecx,这个ebx的值就是在上面调用完AllocateAssign函数以后得到的分配和赋值的内存地址。因为在调用AllocateAssign函数后的返回值eax赋值给了ebx。
调用完SetArray函数以后,会判断返回值是否为0,不为0那就是调用成功,也就是回调函数设置成功,就会跳转到函数调用成功的退出代码执行。如果为0说明并没有设置完成,随后会对edi和esi进行加4的操作并判断edi是否小于0x100,如果小于就跳上去继续执行SetArray函数。由于此时esi指向的是ProcessFuncArray数组的地址,所以加4就是指向数组中下一个元素,edi就是为了避免数组溢出而设置,根据值的大小可以猜测这个数组一共0x40个大小。
分析到这里就可以得出结论,PsSetCreateProcessNotifyRoutine函数设置进程监控的回调函数的办法就是首先申请一块内存来保存要设置的回调函数的地址,然后将这个申请到的内存的地址赋到ProcessFuncArray数组中的某个元素。
那么它是怎么判断要赋值到哪个元素呢,就要继续跟进SetArray函数
函数将数组中的对应的内容赋给eax以后跳转到loc_594759执行,接着看loc_594749处的代码
在这里程序将得到的数组中对应的元素内容和传入的参数进行异或,然后判断是否小于7,如果小于7就跳转到对数组进行赋值的代码进行执行。而0x7对应的二进制是高29位是0,低3位是1,在跟进得到的数组内容要和0进行异或可以知道,这里是判断得到的数组中的内容高29位是不是0。如果高29位中有1个是1,那么异或以后得到的值就会大于7。
接着看loc_59472C中的代码,也就是对数组进行赋值的代码
这里的ecx的值就是前面用来保存回调函数地址所调用的AllocateAssign函数得到的分配的内存地址。因为上面申请到内存以后,将内存地址先赋给eax在赋给ecx,随后的代码中都没对ecx进行改变。
将这个地址赋给eax并且与7或运算,那就是将低3位置1,接着就跳转到loc_594739处执行。执行的时候,这个esi就是要赋值的数组的对应元素的地址,因为在进入SetArray函数前,将数组的对应元素的地址赋给了esi后就没在改变了。而根据cmpxchg的功能可以知道,这里就是将AllocateAssign分配到的内存地址和7或运算以后的地址复制到数组中,随后跳转到loc_594751执行。那么继续看loc_594751执行的代码
在这里,程序首先将原来数组中保存的内容的低三位清0,接着判断得到的数据是否为0。也就是判断原来数组中的内容的高29位是不是0,如果是0说明上面的赋值成功了,程序就会跳转到函数执行成功的地方,也就是loc_5947C0处继续执行,在loc_5947C0,程序会将eax赋值为1,也就是说返回值是1。如果高29位不是0,那上面的赋值就会失败,程序就会跳转到loc_5947C4的地址继续执行,在这个地址中,程序会将eax赋值为0,也就是说返回值是0。
综上,可以得出以下的结论
程序会申请一块0xC大小的内存,内存中低4为是0,中间4为是要设置的回调函数的地址,高4为根据是否是Ex函数来设置为是0还是1。
从整型数组ProcessArray中按顺序依次查看对应的数组的元素的位置是不是可以用来保存分配的内存地址
而这个位置能不能存储这个地址,取决于这个位置中保存的内容的高29位是不是0,如果是0就用来保存申请到的地址。
根据上面的分析,可以得出结论,设置这些回调函数的时候,操作系统首先会申请一个0xC大小的内存,然后将内存地址的中间4位设置为回调函数的地址。随后就会在ArrayProcess数组中存放这个分配的内存地址,当要注意这个内存地址的低3位在复制到数组之前会被置1。
那也就是说当有进程的状态发生变化的时候,操作系统就会去ProcessArray数组中遍历,如果发现对应的元素的高29位不是0就把内容取出并且将低3位置0(因为在复制到数组之中去的时候,分配的内存的低3位会被置为1)。置0以后得到的地址在+4的地方保存的就是回调函数的地址,系统就会根据这个地址在调用相应的回调函数。
所以要进行反进程监控,只需要找到这个数组,并且将这个数组中的回调函数的地址取出然后通过PsSetCreateProcessNotifyRoutine函数删除这个回调函数就可以了。
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2022-3-26 10:59
被1900编辑
,原因: