close

仿照了下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;
好的宏定義也可以簡化工程,這裡大家可自行考慮。

這樣差不多整個架構就搭建起來了,一個小型的監控系統就可以完成了。優秀的架構的確可以事半功倍,不過過濾函數的規則其實才是重中之重呀……………..。大家可以多討論下,這個過濾規則是很需要仔細研究的。當然你要藏著咱也沒辦法呵…………
最後說下:
由於這個源代碼是我一個大文件裡面的一部分,所以整個也不好給出,其實也沒必要給出來,畢竟沒多少技術含量,大家都可以寫得出的,這裡只是就框架總結下而已。全部發了也沒意思一大堆你也不想看,還不如發點核心的,然後你也可以嘗試搭建下自己的更有樂趣呢!
第一次發這種貼,如果有不當之處敬請諒解

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 殘月影 的頭像
    殘月影

    Rootkit --逆向工程技術--

    殘月影 發表在 痞客邦 留言(0) 人氣()