第一回合:
Inline Hook 的检测是一个比较古老的话题,一种基本的检测思路是先与模块镜像进行比较,如果不同的话调用反汇编引擎解析内存中这个函数的指令,如果发现跳转指令则计算其跳转目的地,再结合ZwQuerySystemInformation(用户模式下可以用ZwQueryVirtualMemory)定位做HOOK的模块。但是像一些PUSH XXXX + RET之类类似于跳转功能的指令却无法正确其跳转地址。
不过好的是PUSH + RET格式的Inline Hook 被使用得太多了(包括一些AV都在用),我们可以专门针对这样的指令“出台”对应的方案。
当然这种做法没有什么大用,我们可以简单地使用如下指令:(假如我们想跳转到0x12345678)
sub esp,4
mov dword ptr [esp], 12340000H
add dword ptr [esp], 5678H
pop eax
jmp eax
第二回合:
这样,Inline Hook 检测器必须学会一定量的数学计算才行,仅仅从反汇编引擎中直接得不出什么好的效果。况且我们的Inline Hook 可以使用一些花指令来迷惑反汇编器,比方说:
mov eax, 123456H
cmp eax, 654321H
jl code
db e8H; // 这里可以是任意字节,只要不是单字节指令就行
code:
// 这里才是我们的代码
Inline Hook 检测器除了数学计算还得有逻辑判断,还得知道如果搞掉假的E8 CALL。况且这样的花指令代码花样非常之多,可填充的无意义字节每次都会导致不同的反汇编结果。这样一来,最终的结果是Inline Hook 检测器得自己模拟一个CPU来运行各种指令,最终判断是否跳到别的地方。
第三回合:
但是更加令人困惑的是,假设我们花了很多时间真的做出来一个CPU模拟器,可我们却分析静态指令,不可能知道Inline Hook 执行前的堆栈、寄存器的值。所以当有的模块这样做HOOK时我们无能为力了。
// 这是一个内核态钩子
mov eax, [esp + 4]
cmp eax, 80000000H
ja code: // 用户态钩子这里改成jb
db e9H
code:
// 我们的代码
[esp + 4]里面的调用这个函数压入的返回地址,很明显这个地址必须大于 0x80000000,而CPU模拟器面对静态代码时不可能是知道堆栈中有什么,所以无法做出动态判断。
当然把上面的方法“记录在案”可以让Inline Hook 检测器正确检测出它来。可是我们可以造出千万个类似的代码,这样就使检测器囧掉了。
第四回合:
不过检测器也不是没有办法,在检测函数时,使用调试器的方法下断以获取堆栈和寄存器。但是必须等到这个函数被执行才行。如果检测NtLoadDriver,则必须有驱动加载才会被断,这个频率非常之低。
检测器还可以遍历所有可能的条件跳转,即不去判断条件而把所有可能的解释枚举出来再判断其合理性,可是我们精心设计一个根本不会被执行的死循环就可以让检测器挂得很惨……
就像编写病毒和检测病毒的关系一样,编写病毒不需要考虑太多,而检测病毒却是一道非常高深的算法题目,因此大家无需在此方面总是骂AVs的技术还不如病毒。确实,如果让病毒作者去写杀毒软件,能有正确、高效思路的未必很多。
第五回合:
(由大家来说)
没有评论:
发表评论