360HOOK表,Hook过滤架构搭建

Posted 菜b

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了360HOOK表,Hook过滤架构搭建相关的知识,希望对你有一定的参考价值。

分析了一下360的HOOK,通过直接hook KiFastCallEntry实现以所有系统调用的过滤。
我分析的版本如下:
主程序版本: 6.0.1.1003
HookPort.sys版本: 1, 0, 0, 1005
HookPort.sys的TimeStamp: 4A8D4AB8

简单说明:360把所有被hook的系统服务的过滤函数放在了一个表里,索引即对应的系统服务在该过滤函数表中的索引。
所有列出来的函数都会被hook掉的,是否处理指某个系统服务有没有相应的过滤函数进行处理,拒绝还是放行就是在过滤函数中完成判断的。
不处理的系统服务,将会直接调用原始服务例程。
函数如下:
服务名称          索引  是否处理  备注
==============================================================================
NtCreateKey          0x00  否
NtQueryValueKey        0x01  是
NtDeleteKey          0x02  是
NtDeleteValueKey      0x03  是
NtRenameKey          0x04  是
NtReplaceKey        0x05  是
NtRestoreKey        0x06  是
NtSetValueKey        0x07  是
NtCreateFile        0x08  是
NtFsControl          0x09  是
NtSetInformationFile     0x0A  是
NtWriteFile          0x0B  是
NtWriteFileGather      0x0B  是    //和NtWriteFile共用一个过滤函数
NtCreateProcess        0x0D  是
NtCreateProcessEx      0x0E  是
NtCreateUserProcess      0x0F  是    //Only on Vista or later
NtCreateThread        0x10  是
NtCreateThreadEx      0x10  是    //和NtCreateThread共用一个过滤函数,for vista or later
NtOpenThread        0x11  是
NtDeleteFile        0x12  是
NtOpenFile          0x13  是
NtReadVirtualMemory      0x14  否
NtTerminateProcess      0x15  是
NtQueueApcThread      0x16  是
NtSetContextThread      0x17  是
NtSetInformationThread    0x18  否
NtProtectVirtualMemory    0x19  否 
NtWriteVirtualMemory    0x1A  是
NtAdjustGroupToken      0x1B  否
NtAdjustPrivilegesToken   0x1C  否
NtRequestWaitReplyPort    0x1D  是
NtCreateSection        0x1E  是
NtOpenSecton        0x1F  是
NtCreateSymbolicLinkObject  0x20  是
NtOpenSymbolicLinkObject  0x21  否
NtLoadDriver        0x22  是
NtUnloadDriver        0x22  是    //和NtLoadDriver共用一个过滤函数
NtQuerySystemInformation  0x23  是
NtSetSystemTime        0x25  否
NtSystemDebugControl    0x26  是
NtUserBuildHwndList      0x27  是
NtUserQueryWindow      0x28  是
NtUserFindWindowEx      0x29  是
NtUserWindowFromPoint    0x2A  是
NtUserMessageCall      0x2B  是
NtUserPostMessage      0x2C  是
NtUserSetWindowsHookEx    0x2D  是
NtUserPostThreadMessage    0x2E  是
NtOpenProcess        0x2F  是
NtDeviceIoControlFile    0x30  是
NtUserSetParent        0x31  是
NtOpenKey          0x32  是
NtDuplicateObject      0x33  是
NtResumeThread        0x34  否
NtUserChildWindowFromPointEx 0x35  是
NtUserDestroyWindow      0x36  是
NtUserInternalGetWindowText  0x37  否
NtUserMoveWindow      0x38  是    //和NtSetParent共用一个过滤函数
NtUserRealChildWindowFromPoint 0x39 是    //和NtUserChildWindowFromPointEx共用一个过滤函数
NtUserSetInformationThread  0x3A  否
NtUserSetInternalWindowPos  0x3B  是    //和NtSetParent共用一个过滤函数
NtUserSetWindowLong      0x3C  是    //和NtSetParent共用一个过滤函数
NtUserSetWindowPlacement  0x3D  是    //和NtSetParent共用一个过滤函数    
NtUserSetWindowPos      0x3E  是    //和NtSetParent共用一个过滤函数
NtUserSetWindowRgn      0x3F  是    //和NtSetParent共用一个过滤函数    
NtUserShowWindow      0x40  是
NtUserShowWindowAsync    0x41  是    //和NtUserShowWindow共用一个过滤函数
NtQueryAttributesFile    0x42  否
NtUserSendInput        0x43  否
NtAlpcSendWaitReceivePort  0x44  是    //for vista or later
NtUnmapViewOfSection    0x46  是
NtUserSetWinEventHook    0x47  否
NtSetSecurityObject      0x48  是
NtUserCallHwndParamLock    0x49  是
NtUserRegisterUserApiHok  0x4A  否

 

仿照了下360 的过滤架构,搭建了个Hook 框架,360的Hook架构的确很优秀,我觉得很值得我们学习与研究。这里我按照大牛们已经逆向出来的思路实现了下代码(都逆向出来了坐下代码工作不会怎么样吧?….只是学习架构)。不要鄙视我等代码工………,好吧大牛们想BS就BS吧,我表示毫无压力~~~~,我是菜鸟我怕谁!

废话不多说发代码,如果有错误和白痴的地方请指出,我水平有限…….
搭建这个架构大致需要以下几个模块,一是安装KiFastCallEntry的Hook模块,二是FakeKiFastCallEntry代理模块,三是SysCallFilter系统调用是否过滤的判断模块。其余的模块主要是过滤函数了,还有个获取KiFastCallEntry的patch地址的模块,最后是释放模块和初始化模块,这样大致的架构就搭建起来了。
接下来看看每个模块是怎么工作的。

首先是安装KiFastCallEntry的Hook模块,这个原理我就不多说了,大家都懂的
/************************************************************************
* 函数名称:HookKiFastCallEntry
* 功能描述:安装KiFastCallEntry钩子
* 参数列表:

* 返回值:状态
*************************************************************************/
NTSTATUS HookKiFastCallEntry()
{
  NTSTATUS status=STATUS_SUCCESS;
  if (!GetKiFastCallEntryPatchAddr())
  {
    KdPrint(("(HookKiFastCallEntry) GetKiFastCallEntryPatchAddr failed"));
    return STATUS_UNSUCCESSFUL;
  }
  RtlCopyMemory(OriginalHead2,(PVOID)PatchAddr,5);
  *(ULONG *)(ReplaceHead2+1)=(ULONG)FakeKiFastCallEntry-(PatchAddr+5);
  KIRQL Irql;
  Irql=WOFF();
  //写入新的函数头
  RtlCopyMemory((BYTE *)PatchAddr,ReplaceHead2,5);
  WON(Irql);  
  return status;

}
这个模块有个地方就是GetKiFastCallEntryPatchAddr()获取KiFastCallEntry的Patch点这个有点小技巧,大家可以学习下,360是用SetEvent钩子栈回朔实现的,这个大家听了应该都能明白,就是调用函数时候会PUSH 返回到的EIP,这个EIP就是KiFastCallEntry中的了。
要patch的地方是按特征码搜索的,这个是xp sp3的
BOOL GetKiFastCallEntryPatchAddr()
{
  ULONG ulCallNum;
  PULONG pHookAddr;
  PBYTE pCode;
  ULONG i;
  BOOL  bRet=true;
  KIRQL Irql;
  hFakeEvent=(HANDLE)FakeHandle;
  ulCallNum=*(PULONG)((PBYTE)ZwSetEvent+1);
  pHookAddr=(PULONG)(pSysCallFilterInfo->ulSSDTAddr+ulCallNum*4);
  RealNtSetEvent=*pHookAddr;//保存真实地址
  Irql=WOFF();
    *pHookAddr=(ULONG)FakeNtSetEvent; // 写入代理地址
  WON(Irql);
  ZwSetEvent(hFakeEvent,NULL);
  Irql=WOFF();
  *pHookAddr=RealNtSetEvent; // 写回真实地址
  WON(Irql);
  if (MmIsAddressValid((PVOID)BackTrackingAddr))
  {
    pCode=(PBYTE)BackTrackingAddr;
    for (i=0;i<SearchByte;i++)
    {
      if (*(pCode-i)==0xe1&&*(pCode-i-1)==0x2b)
      {
        PatchAddr=(ULONG)(pCode-i-1);
        break;
      }
      if (*(pCode-i)==0xfc&&*(pCode-i-1)==0x8b)
      {
        RetAddress=(ULONG)(pCode-i-1);
      }
      
    }
  }
  if (!PatchAddr||!RetAddress)
  {
    bRet=false;
  }
  return bRet;
}

这个代理函数里面获取EIP
NTSTATUS FakeNtSetEvent (
        __in HANDLE EventHandle,
        __out_opt PLONG PreviousState
        )
{
  NTSTATUS status=STATUS_SUCCESS;
  if (EventHandle!=hFakeEvent||ExGetPreviousMode()==UserMode)// 不是自己调用,或者调用来自UserMode,直接调用原函数
  {
    status=((NTSETEVENT)RealNtSetEvent)(&EventHandle, PreviousState);
  }
  else
  {
    _asm
    {
      mov eax,dword ptr [ebp+4h]
      mov  BackTrackingAddr,eax
    }
  }
  return status;
}

安装好Hook后就是Hook的代理函数了
这段代码 ……..好吧被BS咱也莫有办法,代理函数传入三个参数,这三个参数的含义可以参考内核情景分析一书中有详细介绍,给SysCallFileter来判断是否过滤。
_declspec (naked) NTSTATUS FakeKiFastCallEntry()
{
  _asm
  {
    mov     edi,edi
    pushfd
      pushad
    push    edi
    push    ebx
    push    eax
    call    SysCallfilter
    mov     dword ptr [esp+10h],eax
    popad
    popfd
    sub     esp, ecx
    shr     ecx, 2
    push    RetAddress
    retn

  }
}

接下来就是判断过滤的函数了,这里我略去了SHADOW SSDT,这段代码也……
/************************************************************************
* 函数名称:SysCallfilter
* 功能描述:过滤系统调用
* 参数列表:
ULONG SysCallNum:系统调用号
ULONG FunAddr:系统调用函数入口地址
ULONG ServiceBase:系统调用表指针
* 返回值:过滤则返回代理函数地址,否则返回真实地址
*************************************************************************/
ULONG SysCallfilter(ULONG SysCallNum,ULONG FunAddr,ULONG ServiceBase)
{
    
  if( ServiceBase==pSysCallFilterInfo->ulSSDTAddr&&SysCallNum<=pSysCallFilterInfo->ulSSDTNum)
  {
    if(pSysCallFilterInfo->SSDTSwitchTable[SysCallNum]&&HookOrNot(SysCallNum,FALSE))
    {
      return pSysCallFilterInfo->ProxySSDTTable[SysCallNum];// 
    }
  }
  return FunAddr;  

}

这个模块可以考虑添加适当的过滤规则,但最好效率点,这里我没加什么过滤,主要是搭建框架。

/************************************************************************
* 函数名称:HookOrNot
* 功能描述:判断是否过滤系统调用
* 参数列表:
ULONG SysCallNum:系统调用号
BOOL Flags:SSDT还是SDOWSSDT标志
* 返回值:返回表示不过滤,表示过滤
*************************************************************************/
ULONG HookOrNot(ULONG SysCallNum,BOOL Flags)
{
  if (ExGetPreviousMode()==KernelMode)
  {
    return 0;
  }  
  if (Flags)
  {
    return 1;
  }
  else
    return 1;
}

好了基本功能模块搭建好了,现在就要初始化这些模块内所要使用的数据结构,来运作起来。
初始化里面我直接把savessdttable原始函数表填充为文件获取的原始地址表了,这里大家可以不必这么做。
/************************************************************************
* 函数名称:InitSysCallFilter
* 功能描述:初始化系统调用过滤
* 参数列表:

* 返回值:状态
*************************************************************************/
NTSTATUS InitSysCallFilter()
{
  NTSTATUS status=STATUS_SUCCESS;
  PVOID FileBuffer,FunBuffer;
  ULONG ulSSDTLimit;
  PKSERVICE_TABLE_DESCRIPTOR pServiceDescriptor;
  //init 

  //Init SysCallFilterInfo buffer
  pSysCallFilterInfo=(PSYSCALL_FILTER_INFO_TABLE)ExAllocatePoolWithTag(
    NonPagedPool,
    sizeof(SYSCALL_FILTER_INFO_TABLE),
    MM_TAG_FILT);
  RtlZeroMemory(pSysCallFilterInfo,sizeof(SYSCALL_FILTER_INFO_TABLE));
  //Init SSDT address
  pServiceDescriptor=(PKSERVICE_TABLE_DESCRIPTOR)GetKeServiceDescriptorTable();
  pSysCallFilterInfo->ulSSDTAddr=(ULONG)pServiceDescriptor->Base;
  //Init SSDT Table
  
  FileBuffer=ExAllocatePoolWithTag(NonPagedPool,(SSDT_MAX_NUM)*sizeof(ULONG),MM_TAG_FILT);
  FunBuffer=ExAllocatePoolWithTag(NonPagedPool,(SSDT_MAX_NUM)*sizeof(ULONG),MM_TAG_FILT);
  if (!FileBuffer||!FunBuffer)
  {
    KdPrint(("(InitSysCallFilter) MmBuffer FunBuffer failed"));
    return STATUS_UNSUCCESSFUL;
  }
  status=EnumOriginalSSDT(FileBuffer,FunBuffer,&ulSSDTLimit);
  if (!NT_SUCCESS(status))
  {
    KdPrint(("(InitSysCallFilter) EnumOriginalSSDT failed"));
    ExFreePool(FileBuffer);
    ExFreePool(FunBuffer);
    return STATUS_UNSUCCESSFUL;
}
  memcpy(pSysCallFilterInfo->SavedSSDTTable,FileBuffer,ulSSDTLimit*4);
  ExFreePool(FileBuffer);
  ExFreePool(FunBuffer);
  pSysCallFilterInfo->ulSSDTNum=ulSSDTLimit;
  //Init Proxy SSDT table
  pSysCallFilterInfo->ProxySSDTTable[97]=(ULONG)FakeNtLoadDriver;
  //这里就可以随意添加Hook,相当方便
  //Init SSDT Swicth table
  pSysCallFilterInfo->SSDTSwitchTable[97]=1;
  //记得要开开关
  return status;
}

最后是释放清理模块了。
void UnHookKiFastCallEntry()
{
  KIRQL Irql;
  if (*(PULONG)OriginalHead2)
  {
  Irql=WOFF();
  //写回原来的函数头
  RtlCopyMemory((BYTE *)PatchAddr,OriginalHead2,5);
  WON(Irql);
  }
};

NTSTATUS FreeSysCallFilter()
{
  NTSTATUS status=STATUS_SUCCESS;
  UnHookKiFastCallEntry();
  if (pSysCallFilterInfo)
  {
    ExFreePool(pSysCallFilterInfo);
  }
  return status;
}
这里顺带发个过滤函数以及R3通信架构的搭建好了
这个过滤是NtLoadDriver的
NTSTATUS FakeNtLoadDriver( __in PUNICODE_STRING DriverServiceName)
{
  PEPROCESS pCurProcess;
  DRIVER_TRANS_INFO DriverTransInfo;
  if (DriverServiceName==NULL)
  {
    return ((NTLOADDRIVER)pSysCallFilterInfo->SavedSSDTTable[97])(DriverServiceName);
  }
  DriverTransInfo.Size=sizeof(DRIVER_TRANS_INFO);
  pCurProcess=PsGetCurrentProcess();
  if (pCurProcess)
  {
    GetProcessFullPathW((ULONG)pCurProcess,DriverTransInfo.ProcessFullPath);
  }
  RtlStringCchCopyW(DriverTransInfo.WarmReason,MAX_REASON*sizeof(WCHAR),L"尝试加载驱动,一旦加载驱动进程将会获得最高权限,允许此操作将可能导致危险发生,驱动文件为:");
  RtlStringCchCatW(DriverTransInfo.WarmReason,MAX_PATH*sizeof(WCHAR),DriverServiceName->Buffer);
  if (!GoOrNot((PVOID)&DriverTransInfo,TYPE_DRIVER_MONITOR))
  {
    return STATUS_ACCESS_DENIED;
  }
  else
  {
    return ((NTLOADDRIVER)pSysCallFilterInfo->SavedSSDTTable[97])(DriverServiceName);
  }
  
}

然后是GoOrNot与R3通信等待R3命令 
BOOL GoOrNot(__in PVOID pMonitorInfo,__in ULONG Type)
{
  BOOL bRet=false;
  switch (Type)
  {
  case TYPE_DRIVER_MONITOR:
  bRet=GetUserCommand(g_DeviceExtension->DriverMonitorInfo.pNotifyEvent,
      g_DeviceExtension->DriverMonitorInfo.SharedMemInfo.pShareMemory,
      pMonitorInfo,
      sizeof(DRIVER_TRANS_INFO));
    break;
default:
        ;
  }
  return bRet;
    
}
//获取用户层命令 
BOOL GetUserCommand(__in PKEVENT pNotifyEvent,
          __in PVOID pShareMemory,
          __in PVOID pTransInfo,
          __in ULONG pTransLen)
{
  BOOL bRet;
  PDRIVER_TRANS_INFO pDriverTransInfo;
  memcpy(pShareMemory,pTransInfo,pTransLen);
  KeSetEvent(pNotifyEvent,0,false);
  KeWaitForSingleObject(
    pNotifyEvent,
    Executive,
    KernelMode,
    false,
    NULL);
  pDriverTransInfo=(PDRIVER_TRANS_INFO)pShareMemory;
  if (pDriverTransInfo->Command==COMMAND_GO)
  {
    bRet=true;
  }
  else if (pDriverTransInfo->Command==COMMAND_STOP)
  {
    bRet=false;
  }
  return bRet;
}

这里发段R3和R0共享内存的,用的是内核创建pool在建MDL映射到用户空间的方法。
BOOL CreateSharedMemory(__out  PSHARE_MEMORY_INFO pShareMemInfo,
            __in  ULONG MemorySize)
{
   BOOL bRet=true;
   PMDL pMdl;
   PVOID UserVAToReturn;
   PIO_STACK_LOCATION piostackLocation;
   ULONG ulBufferLengthOut;
   PVOID pSharedBuffer;
   pSharedBuffer=ExAllocatePoolWithTag(NonPagedPool,MemorySize,MM_TAG_ANTI);
   if (!pSharedBuffer)
   {
    KdPrint(("(IrpCreateSharedMemory) pSharedBuffer allocate failed"));
    return false;
   }
   pMdl=IoAllocateMdl(pSharedBuffer,MemorySize,false,false,NULL);
   if (!pMdl)
   {
    KdPrint(("(IrpCreateSharedMemory) IoAllocateMdl( failed"));
    ExFreePool(pSharedBuffer);
    return false;
   }
   MmBuildMdlForNonPagedPool(pMdl);
   UserVAToReturn=MmMapLockedPagesSpecifyCache(pMdl,
      UserMode,
      MmCached,
      NULL,
      false,
      NormalPagePriority);
   if (!UserVAToReturn)
   {
    IoFreeMdl(pMdl);
    ExFreePool(pSharedBuffer);
    return false;
   }
   RtlZeroMemory(pSharedBuffer,MemorySize);
   KdPrint(("UserVAToReturn:0x%08x",UserVAToReturn));
   //输出
   pShareMemInfo->pShareMemory=pSharedBuffer;
   pShareMemInfo->pSharedMdl=pMdl;
   pShareMemInfo->UserVA=(ULONG)UserVAToReturn;
   return bRet;
}
R3的创建事件和开线程我就不发了,很简单大家可以自己尝试下。
最后附下整个架构的部分数据结构

//GoOrNot Type宏定义
#define TYPE_DRIVER_MONITOR 0x01
//GoOrNot Command宏定义
#define COMMAND_GO 0x01
#define COMMAND_STOP 0x02
//危险拦截提示语句
#define WARM_DRI_LOAD      L"尝试加载驱动,一旦加载驱动进程将会获得系统最高权限,允许此操作将可能导致危险发生,驱动文件路径:"
//************数据定义***************************************************
typedef struct _SYSCALL_FILTER_INFO_TABLE
{
  ULONG ulSSDTAddr;
  ULONG ulSHADOWSSDTAddr;
  ULONG ulSSDTNum;
  ULONG ulSHADOWSSDTNum;
  ULONG SavedSSDTTable[SSDT_FILTER_NUM];                //SSDT原始函数地址表
  ULONG ProxySSDTTable[SHADOWSSDT_FILTER_NUM];          //SSDT代理函数地址表
  ULONG SavedShadowSSDTTable[SSDT_FILTER_NUM];    //ShadowSSDT原始函数地址表
  ULONG ProxyShadowSSDTTable[SHADOWSSDT_FILTER_NUM];   //ShadowSSDT代理函数地址表
  ULONG SSDTSwitchTable[SSDT_FILTER_NUM];              //SSDT Hook开关表
  ULONG ShadowSSDTSwitchTable[SHADOWSSDT_FILTER_NUM];//ShadowSSDT Hook开关表
}SYSCALL_FILTER_INFO_TABLE,*PSYSCALL_FILTER_INFO_TABLE;
好的宏定义也可以简化工程,这里大家可自行考虑。

这样差不多整个架构就搭建起来了,一个小型的监控系统就可以完成了。优秀的架构的确可以事半功倍,不过过滤函数的规则其实才是重中之重呀……………..。大家可以多讨论下,这个过滤规则是很需要仔细研究的。当然你要藏着咱也没办法呵…………

以上是关于360HOOK表,Hook过滤架构搭建的主要内容,如果未能解决你的问题,请参考以下文章

iptables 深入分析

WordPress 插件机制的简单用法和原理(Hook 钩子)

WordPress中函数钩子hook的作用及基本用法

transform插件

旧文章搬运分析了一下360安全卫士的HOOK

MinHook库的使用 64位下,过滤LoadLibraryExW