分析360安全卫士HOOK

分析了一下360安全卫士的HOOK(一) 
分析了一下360的HOOK,通过直接hook KiFastCallEntry实现对所有系统调用的过滤。 
我分析的版本如下: 
HookPort.sys版本: 1, 0, 0, 1005 
HookPort.sys的TimeStamp: 4A8D4AB8 
简单说明:360把所有被hook的系统服务的过滤函数放在了一个表里,索引即对应的系统服务在该过滤函数表中的索引。 
所有列出来的函数都会被hook掉的,是否处理指某个系统服务有没有相应的过滤函数进行处理,拒绝还是放行就是在过滤函数中完成判断的。 
不处理的系统服务,将会直接调用原始服务例程。 
函数如下: 
服务名称索引是否处理备注 
============================================================================== 
NtCreateKey0x00否 
NtQueryValueKey0x01是 
NtDeleteKey0x02是 
NtDeleteValueKey0x03是 
NtRenameKey0x04是 
NtReplaceKey0x05是 
NtRestoreKey0x06是 
NtSetValueKey0x07是 
NtCreateFile0x08是 
NtFsControl0x09是 
NtSetInformationFile 0x0A是 
NtWriteFile0x0B是 
NtWriteFileGather0x0B是//和NtWriteFile共用一个过滤函数 
NtCreateProcess0x0D是 
NtCreateProcessEx0x0E是 
NtCreateUserProcess0x0F是//Only on Vista or later 
NtCreateThread0x10是 
NtCreateThreadEx0x10是//和NtCreateThread共用一个过滤函数,for vista or later 
NtOpenThread0x11是 
NtDeleteFile0x12是 
NtOpenFile0x13是 
NtReadVirtualMemory0x14否 
NtTerminateProcess0x15是 
NtQueueApcThread0x16是 
NtSetContextThread0x17是 
NtSetInformationThread0x18否 
NtProtectVirtualMemory0x19否 
NtWriteVirtualMemory0x1A是 
NtAdjustGroupToken0x1B否 
NtAdjustPrivilegesToken 0x1C否 
NtRequestWaitReplyPort0x1D是 
NtCreateSection0x1E是 
NtOpenSecton0x1F是 
NtCreateSymbolicLinkObject0x20是 
NtOpenSymbolicLinkObject0x21否 
NtLoadDriver0x22是 
NtUnloadDriver0x22是//和NtLoadDriver共用一个过滤函数 
NtQuerySystemInformation0x23是 
NtSetSystemTime0x25否 
NtSystemDebugControl0x26是 
NtUserBuildHwndList0x27是 
NtUserQueryWindow0x28是 
NtUserFindWindowEx0x29是 
NtUserWindowFromPoint0x2A是 
NtUserMessageCall0x2B是 
NtUserPostMessage0x2C是 
NtUserSetWindowsHookEx0x2D是 
NtUserPostThreadMessage0x2E是 
NtOpenProcess0x2F是 
NtDeviceIoControlFile0x30是 
NtUserSetParent0x31是 
NtOpenKey0x32是 
NtDuplicateObject0x33是 
NtResumeThread0x34否 
NtUserChildWindowFromPointEx 0x35是 
NtUserDestroyWindow0x36是 
NtUserInternalGetWindowText0x37否 
NtUserMoveWindow0x38是//和NtSetParent共用一个过滤函数 
NtUserRealChildWindowFromPoint 0x39 是//和NtUserChildWindowFromPointEx共用一个过滤函数 
NtUserSetInformationThread0x3A否 
NtUserSetInternalWindowPos0x3B是//和NtSetParent共用一个过滤函数 
NtUserSetWindowLong0x3C是//和NtSetParent共用一个过滤函数 
NtUserSetWindowPlacement0x3D是//和NtSetParent共用一个过滤函数
NtUserSetWindowPos0x3E是//和NtSetParent共用一个过滤函数 
NtUserSetWindowRgn0x3F是//和NtSetParent共用一个过滤函数
NtUserShowWindow0x40是 
NtUserShowWindowAsync0x41是//和NtUserShowWindow共用一个过滤函数 
NtQueryAttributesFile0x42否 
NtUserSendInput0x43否 
NtAlpcSendWaitReceivePort0x44是//for vista or later 
NtUnmapViewOfSection0x46是 
NtUserSetWinEventHook0x47否 
NtSetSecurityObject0x48是 
NtUserCallHwndParamLock0x49是 
NtUserRegisterUserApiHok0x4A否
分析了一下360安全卫士的HOOK(二) 
上一篇的分析中漏掉了三个函数,现补上: 
NtSetSystemInformation0x24
ProcessNotify0x45 //这个并非Hook,只是HookPort安装的一个Notify 
KeUserModeCallback0x4B 
这样一共是从0到0x4B,共0x4C个过滤函数,齐了~~ 
上次先列出了360所hook的系统服务,让大家对它做了什么有了一些了解。这次分析的重点是360的KiFastCallEntry钩子安装全过程,更为重点的是360这样一个安全软件,是如何理好地处理好这众全多的Hook,即我所谓的“架构”问题。 
一、准备工作 
(1)准备要Hook的系统服务的服务号ServiceIndex,对于导出的服务,采用获取Zw*函数地址后再取服务号的方法,这个想必大家都很熟悉。未导出的,则根据不同系统版本,采用硬编码的方法。 
(2)准备缓冲区,存放原始服务例程地址、过滤开关、代理函数地址等,记为ServiceFilterInfoTable,这些都是HOOK中要使用到的数据。 
HookPort.sys申请了一块很大的内存用于存放这些数据,该内存大小为0x5DDC=6007*4=(6006+1)*4,为什么这么写呢?因为它实际上是下面一个结构: 
复制代码
typedef struct _SERVICE_FILTER_INFO_TABLE{
ULONG SSDTCnt;
ULONG SavedSSDTServiceAddress[1001];//起始偏移0001*4,保存被Hook的SSDT函数的地址
ULONG ProxySSDTServiceAddress[1001];//起始偏移1002*4,保存被Hook的SSDT函数对应的代理函数的地址
ULONG SavedShadowSSDTServiceAddress[1001];//起始偏移2003*4,保存被Hook的ShadowSSDT函数的地址
ULONG ProxyShadowSSDTServiceAddress[1001];//起始偏移3004*4,保存被Hook的ShadowSSDT函数对应的代理函数的地址
ULONG SwitchTableForSSDT[1001];//起始偏移4005*4,保存SSDT Hook开关,决定该函数是否会被Hook
ULONG SwitchTableForShadowSSDT[1001];//起始偏移5006*4,保存ShadowSSDT Hook开关,决定该函数是否会被Hook
}SERVICE_FILTER_INFO_TABLE; 
考虑到服务数最多的就是Win7的ShadowSSDT服务了,共827个,所以这里使用1001个项。显然,每个表之间都有空隙,每个表内部也有大量空隙,觉得浪费内存了是吧?确实是这样的,但是不要忘了有一条法则,叫做“以时间换空间,以空间换时间”,这里虽然浪费了内存,但是保证了效率最高,因为被Hook的地方是调用非常非常频繁的KiFastCallEntry. 
(3)准备缓冲区,存放过滤函数的地址和规则表,记为FilterFunRuleTable。这个部分的实现虽然是在HookPort.sys中,但是它对外留出了这个接口,实际上却是由360SelfProtect.sys调用完成的。 
这个缓冲区的结构如下: 
复制代码
#define FILTERFUNCNT 0x4C //过滤函数的个数
typedef struct _FILTERFUN_RULE_TABLE{
ULONG bSize; //本结构的大小,为0x144=0x51*4=(0x4C+5)*4=(FILTERFUNCNT+5)*4
ULONG Unknown1; //未明确
ULONG IsFilterFunFilledReady; //标志,表明过滤函数表是否准备好
ULONG FakeServiceRoutine[FILTERFUNCNT]//偏移为0xC,过滤函数数组,共有过滤函数0x4C个
PULONG SSDTRuleTableBase;//偏移为0x13C,是SSDT函数的过滤规则表,表的大小为SSDTCnt*4
PULONG ShadowSSDTRuleTableBase; //偏移为0x140,是ShadowSSDT函数的过滤规则表,表的大小为ShadowSSDTCnt*4
}FILTERFUN_RULE_TABLE;
这些只是一些准备工作,具体有些是什么时候完成的,在后面会讲到。 
二、KiFastCallEntry的Hook 
(1)目标KiFastCallEntry,但具体Hook在哪里最好? 
KiFastCallEntry是ring3经sysenter进入内核后的第一个必经之地(不考虑sysenter hook),但是KiFastCallEntry所作的工作也很多,比如设置ds,es,fs段寄存器的值,从ETHREAD中取ServiceTable,判断ServiceIndex是否合法,若合法则判断应使用哪张表(SSDT还是ShadowSSDT),然后从表中取出服务例程地址,从用户栈复制参数到内核栈,然后调用服务例程,调用完之后再做一点准备工作然后就由KiServiceExit再飞回ring3.大致的流程就是这样的.我贴一下关键的一段: 
复制代码
kd>
8053d7e4 8b5f0cmovebx,dword ptr [edi+0Ch] //edi指向SSDT或ShadowSSDT
8053d7e7 33c9xorecx,ecx //ecx清零
8053d7e9 8a0c18movcl,byte ptr [eax+ebx]//cl得到参数的长度,即参数个数*4
8053d7ec 8b3fmovedi,dword ptr [edi] //edi指向KiServiceTable或W32pServiceTable
8053d7ee 8b1c87movebx,dword ptr [edi+eax*4] //eax是服务号,然后ebx得到服务函数地址
8053d7f1 2be1subesp,ecx //ecx得到参数的总长度,这里是开辟栈空间
8053d7f3 c1e902shrecx,2 //除以4,得参数个数
8053d7f6 8bfcmovedi,esp //准备复制参数
8053d7f8 3b35b48b5580cmpesi,dword ptr [nt!MmUserProbeAddress (80558bb4)] //判断参数地址是否有效
8053d7fe 0f83a8010000jaent!KiSystemCallExit2+0x9f (8053d9ac)
8053d804 f3a5rep movs dword ptr es:[edi],dword ptr [esi] //复制参数
8053d806 ffd3callebx //调用系统服务
我们不能hook在开头,这样太多的准备工作需要自己来完成,而且涉及到的操作太多的话,兼容性和稳定性就很成问题。显然我们又不能hook在call服务例程之后,这时该办的事都办完了,我们再接手就已经晚了,所以必须在call服务例程之前。KiFastCallEntry中的指令,真是有点寸士寸金的感觉,寄存器也不能随意改变,挑哪儿下手呢?来看看360是怎么做的: 
Hook之后: 
复制代码
kd>
nt!KiFastCallEntry+0xcc:
8053d7dc ff0538f6dfffincdword ptr ds:[0FFDFF638h]
8053d7e2 8bf2movesi,edx
8053d7e4 8b5f0cmovebx,dword ptr [edi+0Ch]
8053d7e7 33c9xorecx,ecx
8053d7e9 8a0c18movcl,byte ptr [eax+ebx]
8053d7ec 8b3fmovedi,dword ptr [edi]
8053d7ee 8b1c87movebx,dword ptr [edi+eax*4]
8053d7f1 e94a49e901jmp823d2140//这里被改成了一个跳转
8053d7f6 8bfcmovedi,esp
8053d7f8 3b35b48b5580cmpesi,dword ptr [nt!MmUserProbeAddress (80558bb4)]
8053d7fe 0f83a8010000jaent!KiSystemCallExit2+0x9f (8053d9ac)
8053d804 f3a5rep movs dword ptr es:[edi],dword ptr [esi]
8053d806 ffd3callebx
8053d808 8be5movesp,ebp
8053d80a 8b0d24f1dfffmovecx,dword ptr ds:[0FFDFF124h]
8053d810 8b553cmovedx,dword ptr [ebp+3Ch]
这个Hook的位置确实选得非常好啊,自己省去了很多准备工作,都由系统准备好了,此时edi指向服务表的基址(KiServiceTable或W32pServiceTable),ebx是刚刚取出的原始服务例程的地址,eax是服务号,这时再做处理不是就很容易了嘛,重要的几个信息都有了。 
来看看跳转的地址,又是个跳转,看来这里只是个中间跳 
复制代码
kd> u 823d2140
823d2140 e95d982a76jmpHookport+0x79a2 (f867b9a2)
f867b9a2这里才是真正的目的地,在HookPort.sys中. 
复制代码
kd> u f867b9a2
Hookport+0x79a2:
f867b9a2 8bffmovedi,edi
f867b9a4 9cpushfd
f867b9a5 60pushad
f867b9a6 57pushedi //edi指向KiServiceTable或W32pServiceTable
f867b9a7 53pushebx //ebx是原始的KiFastCallEntry从SSDT中取到的服务函数地址
f867b9a8 50pusheax //eax是服务号
f867b9a9 e840ffffffcallHookport+0x78ee (f867b8ee) //KiFastCallEntryFilterFunc
f867b9ae 89442410movdword ptr [esp+10h],eax
f867b9b2 61popad
f867b9b3 9dpopfd
f867b9b4 2be1subesp,ecx
f867b9b6 c1e902shrecx,2
f867b9b9 ff3574d767f8pushdword ptr [Hookport+0x9774 (f867d774)]//这里是回跳的地址,push/ret方式跳回去
f867b9bf c3ret
(2)360是如何具体地去安装这个Hook的? 
360首先获取ZwSetEvent的服务号,然后安装了一个SSDT Hook,目标就是NtSetEvent. 
然后,自己调用了一下ZwSetEvent,触发自己安装的Hook,代码如下: 
复制代码
HANDLE g_FakeEventHandle = (HANDLE)0x288C58F1;
ZwSetEvent(g_FakeEventHandle, NULL); 
0x288C58F1,好奇怪,你看过谁家的句柄长成这个样子嘛,显然这是个假句柄了,它的作用就是在hook函数Fake_NtSetEvent中辨别一下是不是自己人,充当了暗号。 
Fake_NtSetEvent中确实也是这样写的: 
复制代码
if ( EventHandle != g_FakeEventHandle || ExGetPreviousMode()==UserMode )// 不是我们自己调用,或者调用来自UserMode,直接调用原函数
{
result = OriginalNtSetEvent(EventHandle, PreviousState);
}
不是自己人就调用原函数去了,等对上暗号,是自己人了,才真正开始干事. 
先申请一个5字节的buffer,写入一个跳转指令,这个就是上面看到的中间跳,这里还有一个细节,判断了一下系统版本,Vista前后处理上有稍许不同,根据当前系统版本选择合适的JmpStub. 
再准备另外一个跳转指令,跳转目标就是刚才准备的中间跳的地址,这个跳转指令将要被写入KiFastCallEntry中。那么360是如何找到要写入的位置呢?继续看. 
跳转指令准备好之后,先还原刚才SSDT中安装的NtSetEvent钩子,然后从栈中回溯返回地址,也就是取[ebp+4]的值,并保存起来.上面对KiFastCallEntry的分析知道,SSDT中的函数都是从KiFastCallEntry中call过来的,那么返回地址肯定也在KiFastCallEntry中,具体地讲,就是8053d806处call ebx的下一条指令处。取得这个地址后,往上匹配寻找 
8053d7f3 c1e902shrecx,2 
8053d7f6 8bfcmovedi,esp 
找到之后,就确定了要Hook的位置。接下来怎么做写过hook的都知道,就是关写保护然后把刚才准备好的跳转指令写入再打开保护而已,不多说。 
(3)KiFastCallEntry被hook后,360是如何处理的? 
KiFastCallEntry被hook后,经二级跳跳到了JmpStub里。来看看: 
复制代码
kd> u f867b9a2
Hookport+0x79a2:
f867b9a2 8bffmovedi,edi
f867b9a4 9cpushfd
f867b9a5 60pushad
f867b9a6 57pushedi //edi指向KiServiceTable或W32pServiceTable
f867b9a7 53pushebx //ebx是原始的KiFastCallEntry从SSDT中取到的服务函数地址
f867b9a8 50pusheax //eax是服务号
f867b9a9 e840ffffffcallHookport+0x78ee (f867b8ee) //KiFastCallEntryFilterFunc
f867b9ae 89442410movdword ptr [esp+10h],eax
f867b9b2 61popad
f867b9b3 9dpopfd
f867b9b4 2be1subesp,ecx
f867b9b6 c1e902shrecx,2
f867b9b9 ff3574d767f8pushdword ptr [Hookport+0x9774 (f867d774)]//这里是回跳的地址,push/ret方式跳回去
f867b9bf c3ret
可以看到,edi,ebx,eax三个重要数据入栈后,调用了另一个判断函数。该函数逆向如下: 
复制代码
ULONG __stdcall KiFastCallEntryFilterFunc(ULONG ServiceIndex, ULONG OriginalServiceRoutine, ULONG ServiceTable)
{
//判断是否是SSDT中的调用
if ( ServiceTable == g_KiServiceTable && ServiceIndex SwitchTableForSSDT[ServiceIndex] && HookOrNot(ServiceIndex, FALSE))
{
ServiceFilterInfoTable->SavedSSDTServiceAddress[ServiceIndex]=OriginalServiceRoutine;//保存原始例程,以便后面调用
return ServiceFilterInfoTable-> roxySSDTServiceAddress[ServiceIndex];//返回我们代理函数的地址
}
}
//判断是否是ShadowSSDT中的调用,过程同上
if ( ServiceTable == g_GUIServiceTable && ServiceIndex SwitchTableForShadowSSDT[ServiceIndex] && HookOrNot(ServiceIndex, TRUE) )
{
ServiceFilterInfoTable->SavedShadowSSDTServiceAddress[ServiceIndex]=OriginalServiceRoutine;
return ServiceFilterInfoTable-> roxyShadowSSDTServiceAddress[ServiceIndex]; 

}
return OriginalServiceRoutine; // 不明调用,就直接返回原始例程
}
结合上面逆出来的代码和数据结构,相信不难看懂。对于一个调用,通过判断ServiceTable确定是SSDT调用还是Shadow调用,两者过程基本一样,以SSDT为例: 
首先根据ServiceIndex判断SwitchTable中的Hook开关是否打开,是则调用HookOrNot函数根据FilterFunRuleTable表中的Rule((根据PreviousMode有进一步判断))来判断是否需要Hook。经过这两重检查和判断,最终若需要Hook,就保存原始服务例程地址并返回我们的代理函数,若不需要hook,就直接返回原始例程。由于使用了良好的数据结构,这里的效率是非常高的。 
KiFastCallEntryFilterFunc的返回结果,要么是原始例程,要么是代理函数,返回至Jmpstub后,这个结果被保存在了[esp+0x10]处。不要忘了刚才有个pushad,所以[esp+0x10]处实际保存的是ebx的值,这样修改了栈中的ebx的值,再popad时,ebx的值就被修改为了KiFastCallEntryFilterFunc的返回值,再一个popfd恢复刚才保存的标志寄存器,然后执行被jmp指令覆盖掉的那两句指令,最后将刚才回溯到的返回地址压栈,一个ret就又飞回到了KiFastCallEntry中。若已Hook,此时ebx的值就已经被修改了,再下来call ebx时调用的就是刚才返回的代理函数了。很巧妙的处理啊。 
三、代理函数如何处理? 
Hook的细节搞清楚了,本来就已经差不多了,但是看了360代理函数中的处理,又发现一些有趣的东西。 
每一个代理函数,都会首先调用一个CallFilterFunByIndex,调用时传入的第一个参数就是过滤函数在过滤函数表中的索引,也就是我上一篇文章中所列出来的那些函数名称后面的索引。第二个参数则是栈中的参数数组,相当于一下把所有参数都传过去了。 
CallFilterFunByIndex会先根据FilterFunRuleTable->IsFilterFunFilledReady判断该表是否已经准备好(这个过滤函数表实际上是由360SelfProtect.sys调用HookPort.sys提供的接口填充的,所有过滤函数的实现也都在360SelfProtect.sys中),根据传入的过滤函数的索引在FilterFunRuleTable表(也就是我前面提到的第二张表)中查找对应的过滤函数,若过滤函数存在,就调用过滤函数,传入的参数同样有过滤函数索引和参数数组,在这个过滤函数中才真正实现了对参数的判断。 
判断完毕之后,若检查通过,就予以放行,此时再调用JmpStub中保存的原始服务例程的地址,调用完原始例程之后,若调用成功,还会有一个循环的检查,检查目标是调用原始服务例程后返回的结果。这些循环检查的函数哪儿来的呢?就是在CallFilterFunByIndex调用过滤函数时返回的,由于返回的这些函数是在调用原始服务例程之后对结果进行检查,所以称之为CheckResult系列函数,而过滤函数表中的那些函数则称为CheckArguments函数。 
为什么要有CheckArguments函数,又要有CheckResult函数? 
这里来个小小的科普,hook某函数后,检查参数的时机是怎么样的呢?参数是分IN、OUT的,一般来说,传入的参数要在调用原始函数前检查,传出的参数要在调用原始函数后检查,而有些则在调用前后检查都可以,但效果不同。大致可以分为四种情况: 
第一种以(Nt)TerminateProcess为例,它的原型是这样的: 
NTSTATUS 
NtTerminateProcess( 
__in_opt HANDLE Proce***andle, 
__in NTSTATUS ExitStatus 
); 
除了状态码,它没有有效的返回值,它的作用更多的在于结束进程这个“过程”,等该函数返回的时候,进程已经被结束掉了,再来检查有个P用。所以这种强调过程而且重点不在返回值(即使它有)的函数,必须要在调用原函数前检查。 
第二种,一个例子是recv,原型如下: 
int recv ( 
SOCKET s,
char FAR* buf,
int len,
int flags
); 
显然该函数的重点在于第二个参数中返回的数据,但是在调用原始函数之前,缓冲区里什么都没有,检查,怎么检查?像这种函数也是过程性的,但是它的第二个参数是OUT型的,就必须在调用原函数之后检查。 
第三类,比如CreateFile,OpenProcess,OpenThread,OpenEvent等函数,它们有一个共同的特征,就是传入特性相关的数据(文件名,pid等),返回一个句柄,重点不在过程而在于返回值。这类函数即可以在调用原函数前检查传入的参数,也可以在调用原函数后检查返回的句柄。但是一个是对象的名称等外在信息,一个直接指向对象,显然检查后者更为可靠一些,因为它更贴近对象本身。 
对于那些异步方式调用的函数更要注意了,比如异步方式调用的ReadFile或ReadFileEx,调用原函数之前显然缓冲区没有数据,调用了原函数缓冲区也不见得有数据,但是异步有个特征就是它的通知机制,可能是Apc,也可能是Event,这时就必须替换或其它方式处理它的通知机制才能在合适的时候拿到数据。 
科普就先到这儿。现在来解释为什么要有CheckResult系列函数的存在,相信大家就该明白了。以NtOpenSection这个服务的hook为例,我们知道有一个重点照顾对象叫做\\Device\\PhysicalMemory,经常被大家用于各种XX中,一段典型的打开该对象的代码是: 
复制代码
RtlInitUnicodeString(&physmemString, L\"\\\\Device\\\\PhysicalMemory\");
attributes.Length= sizeof(OBJECT_ATTRIBUTES);
attributes.RootDirectory= NULL;
attributes.ObjectName= &physmemString;
attributes.Attributes= 0;
attributes.SecurityDescriptor= NULL;
attributes.SecurityQualityOfService= NULL;
status = ZwOpenSection(&g_hMPM, SECTION_MAP_READ|SECTION_MAP_WRITE, &attributes);
怎么检查?检查第三个参数attributes.ObjectName是否是\\\\Device\\\\PhysicalMemory ?显然是不行的,看看MJ的《续PhysicalMemory攻击》你就知道检查这个参数有多困难。显然NtOpenSection属于我上面提到的第三类函数,看看360是怎么做的: 
代理函数Proxy_NtOpenSection首先调用CallFilterFunByIndex,CallFilterFunByIndex根据所传入的过滤函数索引在FilterFunRuleTable中找到Fake_NtOpenSection并调用之,然而Fake_NtOpenSection除了简单判断下调用者之外不做任何检查,只是返回了一个函数的地址给CallFilterFunIndex,记为CheckResult_After_NtOpenSection,CallfilterFunIndex再把这个地址返回给代理函数Proxy_NtOpenSection,此时Proxy_NtOpenSection调用原函数NtOpenSection,若返回不成功,就直接返回这个状态值,不成功当然就不管啦。若成功,就会调用刚才返回的CheckResult_After_NtOpenSection来检查返回的句柄所指向的对象是不是\\Device\\PhysicalMemory,若是,就关掉该句柄,并返回一个禁止的状态码(STATUS_ACCESS_DENIED),若不是,绿灯大开,放行之.有时,FakeXXX函数返回的CheckResultXXX函数可能不止一个,此时ProxyXXX会根据返回的函数个数循环调用这些CheckResultXXX,有一个不通过即为不通过,只有所有CheckResultXXX都检查通过了,ProxyXXX函数才会返回原始结果给调用者,真是把关极严啊。 
值得一提的是,所有的过滤函数对于来自KernelMode的调用都不做处理,所以使用驱动来破坏360是轻而易举的,但是完全没有意义。按照MJ一贯的理念,如果你都能加载驱动了,那么所有ring0的保护也就失去了意义,再来保护完全是无用功,事实上也根本起不到保护效果了。
四、总结回顾 
现在让我们站得高一点,略去一些细节,来总结一下360整个hook架构: 
1.HookPort.sys准备了ServiceFilterInfoTable(上面的表一),里面保存了SSDT函数的代理函数地址、原始例程地址、Hook开关,ShadowSSDT也是。 
2.HookPort.sys对外留出了三个接口(保存在了DEVICE_EXTENSION中),第一个用于准备FilterFunRuleTable(上面的表二),第二个用于向FilterFunRuleTable中注册过滤函数,第三个用于设置FilterFunRuleTable中的过滤规则。 
3.HookPort.sys安装KiFastCallEntry hook. 
4.HookPort.sys!KiFastCallEntryFilterFunc根据Hook开关和RuleTable中的过滤规则来决定某个系统服务是否会被Hook,被hook后将会调用Proxy函数。 
5.360SelfProtect.sys使用HookPort.sys提供的接口初始化RuleTable,并向RuleTable中注册过滤函数、设置过滤规则。 
6.每个Proxy函数使用CallFilterFunByIndex调用RuleTable中相应的FilterFun进行参数检查,通过后调用原函数,若调用成功再调用FilterFun函数提供的CheckResult函数检查结果(非SSDT、ShadowSSDT函数的Hook也采用同样结构)。 
很显然地: 
如果要增加一个系统服务的Hook,只需要打开ServiceFilterInfoTable中该服务Index对应的开关,并提供一个过滤函数就可以了。 
如果要去掉一个系统服务的hook,只需要关闭ServiceFilterInfoTable中该服务Index对应的开关,立刻生效,而无须其它改变。 
如果要修改一个系统服务的过滤函数,只需要使用HookPort的接口设置新的过滤函数就可以了,无须其它改变。 
所以说,这个架构设计非常好,易于修改,易于扩充,易于分工,无愧于优秀二字。 
结束语:单独写一个或几个函数的Hook,很多人都会,但是要实现这样一个产品级的优秀架构,就不是谁都能完成的了。分析了360这样一个安全产品所使用的Hook架构,让我收获颇多,也让大家领略了360安全卫士的技术魅力。文中如有错误,还请MJ和大家指出。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值