360安全卫士hookport.sys简单逆向

2019-04-14 21:12发布

360安全卫士hookport.sys简单逆向——MyHookMgr数据结构 2009年10月23日 星期五 17:12 MyHookMgr数据结构 MyHookMgr是一个大小为0x5DDC的巨大结构,是360挂钩中的一个重要数据,它记录了挂钩函数的原地址、代理函数地址及相应函数是否挂钩的标志位。 它的数据结构是这样的:   struct MyHookMgr{          DWORD ssdtSize;                                                  //ssdt大小          DWORD ssdtOriginFunc[1001];                         //ssdt对应的原函数地址 DWORD ssdtFakeFunc[1001];                            //ssdt对应的代理函数地址 DWORD shadowSsdtOriginFunc[1001];           //ssdtShadow原函数地址 DWORD shadowSsdtFakeFunc[1001];              //ssdtShadow代理函数地址 DWORD ssdtSwitch[1001];                                 //ssdt代理函数开关 DWORD shadowSsdtSwitch [1001];                  //ssdtShadow代理函数开关 }; ssdtSize是ssdt的大小,这个域被用的并不多,这里不做过多解释。
SsdtOriginFunc和shadowSsdtOriginFunc数组分别包含了两个表中所有的原函数地址。但这个域在初始化时并没有被填写,在过滤函数真正开始工作时才逐渐被代理函数填写完毕。
ssdtFakeFunc和shadowSsdtFakeFunc是对应的代理函数表,初始化时被填写。
ssdtSwitch和shadowSsdtSwitch是代理函数开关,为1时代表函数被挂载,为0时直接执行原始函数,不进行过滤,初始化时所有开关均置0。
结构中的6个数组都是以ssdt编号作为索引的,由于win7的ssdtShadow表函数最多——827个,所以这里使用了足够大的数组保证稳定,当然这里浪费了一些内存。 关于SSDT编号的获取有两种方法,对于导出函数,因为其形式都如: ntdll!ZwSetEvent: 7c92e570 b8db000000      mov     eax,0DBh             ;0DBh就是ssdt编号 7c92e575 ba0003fe7f      mov     edx,offset SharedUserData!SystemCallStub (7ffe0300) 7c92e57a ff12            call    dword ptr [edx] 7c92e57c c20800          ret     8 7c92e57f 90              nop
从第一条指令中就可以读取ssdt索引了。
对于未导出函数360使用了根据操作系统进行硬编码的方式。(详情见IDB文件) 360安全卫士hookport.sys简单逆向——KiFastCallEntry挂钩 2009年10月23日 星期五 17:15

KiFastCallEntry的挂钩

         360安全卫士并没有使用通常的方式——直接修改SSDT和SSDTShadow进行挂钩。由于两个表中的函数最终是由系统未导出的函数KiFastCallEntry调用的,所以360在适当的位置挂载了KiFastCallEntry函数,达到了过滤的目的。大多数ARK软件只检测了两个表是否被更改,所以不能检测出360的HOOK。          上面介绍的MyHookMgr结构之所以占据近6K的连续内存空间,是因为KiFastCallEntry是一个频繁被系统调用的函数,所以钩子的效率十分重要,使用连续的空间可以节省出大量寻址时间,是一种空间换时间的做法。

KiFastCallEntry内存地址的确定

         KiFastCallEntry并没有被系统模块导出,360采取了一个比较巧妙的方法简介获得其地址。 1.       常规方法挂载ZwSetEvent函数,代理函数为hookprot.sys模块中的_HookKiFastCallEntryKnrl。 2.       之后马上调用ZwSetEvent函数。 可以看到这里的handle为0x288C58F1,显然是一个不合法的句柄。这里起到一个标志,所用,我们的_HookKiFastCallEntryKnrl函数看到这个handle就会做一些特殊处理来寻找KiFastCallEntry的地址啦。 1.       然后会跳转到_HookKiFastCallEntryKnrl中,如果句柄是0x288C58F1则恢复刚才的SSDT钩子。并对KiFastCallEntry进行挂载。 由于_HookKiFastCallEntryKnrl是KiFastCallEntry调用的,所以我们可以从栈针中找到的返回地址也就是KiFastCallEntry所在了。 代码只需要一句 mov eax, [ebp+4] 就可以了。

KiFastCallEntry的挂载

KiFastCallEntry是一个非常复杂的函数,挂载位置很重要。 我们对比一下360安全卫士挂载前和挂载后的函数。 挂载前: 8053d7dc ff0538f6dfff    inc     dword ptr ds:[0FFDFF638h]
8053d7e2 8bf2            mov     esi,edx
8053d7e4 8b5f0c          mov     ebx,dword ptr [edi+0Ch]
8053d7e7 33c9            xor     ecx,ecx
8053d7e9 8a0c18          mov     cl,byte ptr [eax+ebx]
8053d7ec 8b3f            mov     edi,dword ptr [edi]
8053d7ee 8b1c87          mov     ebx,dword ptr [edi+eax*4]
8053d7f1 2be1            sub     esp,ecx
8053d7f3 c1e902          shr     ecx,2
8053d7f6 8bfc            mov     edi,esp
8053d7f8 3b35b48b5580    cmp     esi,dword ptr [nt!MmUserProbeAddress (80558bb4)]
8053d7fe 0f83a8010000    jae     nt!KiSystemCallExit2+0x9f (8053d9ac)
8053d804 f3a5            rep movs dword ptr es:[edi],dword ptr [esi]
8053d806 ffd3            call    ebx
8053d808 8be5            mov     esp,ebp
8053d80a 8b0d24f1dfff    mov     ecx,dword ptr ds:[0FFDFF124h] 挂载后: 8053d7dc ff0538f6dfff    inc     dword ptr ds:[0FFDFF638h]
8053d7e2 8bf2            mov     esi,edx
8053d7e4 8b5f0c          mov     ebx,dword ptr [edi+0Ch]
8053d7e7 33c9            xor     ecx,ecx
8053d7e9 8a0c18          mov     cl,byte ptr [eax+ebx]
8053d7ec 8b3f            mov     edi,dword ptr [edi]
8053d7ee 8b1c87          mov     ebx,dword ptr [edi+eax*4]
8053d7f1 e93289ca01      jmp     821e6128
8053d7f6 8bfc            mov     edi,esp
8053d7f8 3b35b48b5580    cmp     esi,dword ptr [nt!MmUserProbeAddress (80558bb4)]
8053d7fe 0f83a8010000    jae     nt!KiSystemCallExit2+0x9f (8053d9ac)
8053d804 f3a5            rep movs dword ptr es:[edi],dword ptr [esi]
8053d806 ffd3            call    ebx
8053d808 8be5            mov     esp,ebp
8053d80a 8b0d24f1dfff    mov     ecx,dword ptr ds:[0FFDFF124h]
8053d810 8b553c          mov     edx,dword ptr [ebp+3Ch] 根据WRK的代码我们知道,这时所有调用需要的环境都已经准备好了。360这里替换了 8053d7f1 2be1            sub     esp,ecx 8053d7f3 c1e902          shr     ecx,2 这两条指令,实现了jmp,将这两条指令放到Hook后的函数中处理,这里就不详细叙述了。IDB文件中有比较详细的注释。 WinXP系统调用的步骤 sysenter指令:从sysenter_CS_MSR、sysenter_ESP_MSR、sysenter_EIP_MSR寄存器读取数据,填写cs、ss(cs+8)esp、eip。进入ring0特权级 步骤:
0. 进入时eax = 服务号,edx = 当前栈顶 (+0x08为用户参数) sysenter 进入ring0
1. 初始化ds、es为0x23,fs 0x30
2. PCR+0x40(0FFDFF040h)处取得TSS,TSS+0x04取得当前线程内核栈地址,赋值给esp。所以sysenter_ESP_MSR没有意义。
esp = pcr->TSS->Esp0
3. 关中断,建立ThTrapFrame
ntdll!_KTRAP_FRAME
   +0x000 DbgEbp           : Uint4B
   +0x004 DbgEip           : Uint4B
   +0x008 DbgArgMark       : Uint4B
   +0x00c DbgArgPointer    : Uint4B
   +0x010 TempSegCs        : Uint4B
   +0x014 TempEsp          : Uint4B
   +0x018 Dr0              : Uint4B
   +0x01c Dr1              : Uint4B
   +0x020 Dr2              : Uint4B
   +0x024 Dr3              : Uint4B
   +0x028 Dr6              : Uint4B ;如果KTHREAD中DebugActive被置位 则填写上面的值
   +0x02c Dr7              : Uint4B ;置0
   +0x030 SegGs            : Uint4B
   +0x034 SegEs            : Uint4B
   +0x038 SegDs            : Uint4B
   +0x03c Edx              : Uint4B
   +0x040 Ecx              : Uint4B
   +0x044 Eax              : Uint4B
   +0x048 PreviousPreviousMode : Uint4B     ;设置为USERMODE (1), 并设置KTHREAD中的对应字段为USERMODE      +0x04c ExceptionList    : Ptr32 _EXCEPTION_REGISTRATION_RECORD ;从PCR获得,并且设置更新PCR中的ExceptionList为-1
   +0x050 SegFs            : Uint4B
   +0x054 Edi              : Uint4B
   +0x058 Esi              : Uint4B
   +0x05c Ebx              : Uint4B
   +0x060 Ebp              : Uint4B
   +0x064 ErrCode          : Uint4B
   +0x068 Eip              : Uint4B   ;返回地址 从USER_SHARED_DATA+UsSystemCallReturn取得,我的系统上是0x7c92eb94,只是一个ret,所以返回用户态时直接可以返回Zw.....函数
   +0x06c SegCs            : Uint4B
   +0x070 EFlags           : Uint4B
   +0x074 HardwareEsp      : Uint4B
   +0x078 HardwareSegSs    : Uint4B
   +0x07c V86Es            : Uint4B
   +0x080 V86Ds            : Uint4B
   +0x084 V86Fs            : Uint4B
   +0x088 V86Gs            : Uint4B
更新KTHREAD中的TrapFrame 4. 从KTHREAD中取出服务表ServiceTable 
   将用户堆栈中的参数复制到内核堆栈中,如果service no & 0x1000 为ssdtShadow服务,否则为ssdt服务。
   如果是ssdtShadow服务则先Call _KeGdiFlushUserBatch
   call相应服务
5. 取出_KTRAP_FRAME 恢复ExceptionList 等。。。。没看下去。。。 疑问:
恢复ExceptionList 时
        mov     edx, [ebp].TsEdx        ; restore previous trap frame address
        mov     [ecx].ThTrapFrame, edx ;
   [ebp].TsEdx填了么??不应该是[ebp].ExceptionList??