2010年12月8日 星期三

Hook過濾架構搭建

仿照了下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;

好的宏定義也可以簡化工程,這裡大家可自行考慮。



這樣差不多整個架構就搭建起來了,一個小型的監控系統就可以完成了。優秀的架構的確可以事半功倍,不過過濾函數的規則其實才是重中之重呀……………..。大家可以多討論下,這個過濾規則是很需要仔細研究的。當然你要藏著咱也沒辦法呵…………

最後說下:

由於這個源代碼是我一個大文件裡面的一部分,所以整個也不好給出,其實也沒必要給出來,畢竟沒多少技術含量,大家都可以寫得出的,這裡只是就框架總結下而已。全部發了也沒意思一大堆你也不想看,還不如發點核心的,然後你也可以嘗試搭建下自己的更有樂趣呢!

第一次發這種貼,如果有不當之處敬請諒解


沒有留言:

張貼留言