• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            S.l.e!ep.¢%

            像打了激速一樣,以四倍的速度運轉(zhuǎn),開心的工作
            簡單、開放、平等的公司文化;尊重個性、自由與個人價值;
            posts - 1098, comments - 335, trackbacks - 0, articles - 1
              C++博客 :: 首頁 :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理
            分析了一下360安全衛(wèi)士的HOOK(一)

            分析了一下360的HOOK,通過直接hook KiFastCallEntry實現(xiàn)對所有系統(tǒng)調(diào)用的過濾。

            我分析的版本如下:
            主程序版本: 6.0.1.1003
            HookPort.sys版本: 1, 0, 0, 1005
            HookPort.sys的TimeStamp: 4A8D4AB8

            簡單說明:360把所有被hook的系統(tǒng)服務的過濾函數(shù)放在了一個表里,索引即對應的系統(tǒng)服務在該過濾函數(shù)表中的索引。
            所有列出來的函數(shù)都會被hook掉的,是否處理指某個系統(tǒng)服務有沒有相應的過濾函數(shù)進行處理,拒絕還是放行就是在過濾函數(shù)中完成判斷的。


            不處理的系統(tǒng)服務,將會直接調(diào)用原始服務例程。

            函數(shù)如下:
            服務名稱? ? ? ? ? ? ? ? ? ? 索引? ? 是否處理? ? 備注
            ==============================================================================
            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共用一個過濾函數(shù)
            NtCreateProcess? ? ? ? ? ? ? ? 0x0D? ? 是
            NtCreateProcessEx? ? ? ? ? ? 0x0E? ? 是
            NtCreateUserProcess? ? ? ? ? ? 0x0F? ? 是? ? ? ? //Only on Vista or later
            NtCreateThread? ? ? ? ? ? ? ? 0x10? ? 是
            NtCreateThreadEx? ? ? ? ? ? 0x10? ? 是? ? ? ? //和NtCreateThread共用一個過濾函數(shù),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共用一個過濾函數(shù)
            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共用一個過濾函數(shù)
            NtUserRealChildWindowFromPoint 0x39 是? ? ? ? //和NtUserChildWindowFromPointEx共用一個過濾函數(shù)
            NtUserSetInformationThread? ? 0x3A? ? 否
            NtUserSetInternalWindowPos? ? 0x3B? ? 是? ? ? ? //和NtSetParent共用一個過濾函數(shù)
            NtUserSetWindowLong? ? ? ? ? ? 0x3C? ? 是? ? ? ? //和NtSetParent共用一個過濾函數(shù)
            NtUserSetWindowPlacement? ? 0x3D? ? 是? ? ? ? //和NtSetParent共用一個過濾函數(shù)? ? ? ?
            NtUserSetWindowPos? ? ? ? ? ? 0x3E? ? 是? ? ? ? //和NtSetParent共用一個過濾函數(shù)
            NtUserSetWindowRgn? ? ? ? ? ? 0x3F? ? 是? ? ? ? //和NtSetParent共用一個過濾函數(shù)? ? ? ?
            NtUserShowWindow? ? ? ? ? ? 0x40? ? 是
            NtUserShowWindowAsync? ? ? ? 0x41? ? 是? ? ? ? //和NtUserShowWindow共用一個過濾函數(shù)
            NtQueryAttributesFile? ? ? ? 0x42? ? 否
            NtUserSendInput? ? ? ? ? ? ? ? 0x43? ? 否
            NtAlpcSendWaitReceivePort? ? 0x44? ? 是? ? ? ? //for vista or later
            NtUnmapViewOfSection? ? ? ? 0x46? ? 是
            NtUserSetWinEventHook? ? ? ? 0x47? ? 否
            NtSetSecurityObject? ? ? ? ? ? 0x48? ? 是
            NtUserCallHwndParamLock? ? ? ? 0x49? ? 是
            NtUserRegisterUserApiHok? ? 0x4A? ? 否


            分析了一下360安全衛(wèi)士的HOOK(二)

            上一篇的分析中漏掉了三個函數(shù),現(xiàn)補上:
            NtSetSystemInformation? ? ? ? 0x24? ?
            ProcessNotify? ? ? ? ? ? ? ? 0x45 //這個并非Hook,只是HookPort安裝的一個Notify
            KeUserModeCallback? ? ? ? ? ? 0x4B
            這樣一共是從0到0x4B,共0x4C個過濾函數(shù),齊了~~

            上次先列出了360所hook的系統(tǒng)服務,讓大家對它做了什么有了一些了解。這次分析的重點是360的KiFastCallEntry鉤子安裝全過程,更為重點的是360這樣一個安全軟件,是如何理好地處理好這眾全多的Hook,即我所謂的“架構(gòu)”問題。

            一、準備工作


            (1)準備要Hook的系統(tǒng)服務的服務號ServiceIndex,對于導出的服務,采用獲取Zw*函數(shù)地址后再取服務號的方法,這個想必大家都很熟悉。未導出的,則根據(jù)不同系統(tǒng)版本,采用硬編碼的方法。

            (2)準備緩沖區(qū),存放原始服務例程地址、過濾開關、代理函數(shù)地址等,記為ServiceFilterInfoTable,這些都是HOOK中要使用到的數(shù)據(jù)。

            HookPort.sys申請了一塊很大的內(nèi)存用于存放這些數(shù)據(jù),該內(nèi)存大小為0x5DDC=6007*4=(6006+1)*4,為什么這么寫呢?因為它實際上是下面一個結(jié)構(gòu):

            復制代碼

            1. typedef struct _SERVICE_FILTER_INFO_TABLE{
              ULONG SSDTCnt;
              ULONG SavedSSDTServiceAddress[1001];? ? ? ? //起始偏移0001*4,保存被Hook的SSDT函數(shù)的地址
              ULONG ProxySSDTServiceAddress[1001];? ? ? ? //起始偏移1002*4,保存被Hook的SSDT函數(shù)對應的代理函數(shù)的地址
              ULONG SavedShadowSSDTServiceAddress[1001];? ? //起始偏移2003*4,保存被Hook的ShadowSSDT函數(shù)的地址
              ULONG ProxyShadowSSDTServiceAddress[1001];? ? //起始偏移3004*4,保存被Hook的ShadowSSDT函數(shù)對應的代理函數(shù)的地址
              ULONG SwitchTableForSSDT[1001];? ? ? ? ? ? ? ? //起始偏移4005*4,保存SSDT Hook開關,決定該函數(shù)是否會被Hook
              ULONG SwitchTableForShadowSSDT[1001];? ? ? ? //起始偏移5006*4,保存ShadowSSDT Hook開關,決定該函數(shù)是否會被Hook
              }SERVICE_FILTER_INFO_TABLE;?

            ?
            考慮到服務數(shù)最多的就是Win7的ShadowSSDT服務了,共827個,所以這里使用1001個項。顯然,每個表之間都有空隙,每個表內(nèi)部也有大量空隙,覺得浪費內(nèi)存了是吧?確實是這樣的,但是不要忘了有一條法則,叫做“以時間換空間,以空間換時間”,這里雖然浪費了內(nèi)存,但是保證了效率最高,因為被Hook的地方是調(diào)用非常非常頻繁的KiFastCallEntry.

            (3)準備緩沖區(qū),存放過濾函數(shù)的地址和規(guī)則表,記為FilterFunRuleTable。這個部分的實現(xiàn)雖然是在HookPort.sys中,但是它對外留出了這個接口,實際上卻是由360SelfProtect.sys調(diào)用完成的。
            這個緩沖區(qū)的結(jié)構(gòu)如下:

            復制代碼

            1. #define FILTERFUNCNT 0x4C //過濾函數(shù)的個數(shù)
              typedef struct _FILTERFUN_RULE_TABLE{
              ULONG bSize; //本結(jié)構(gòu)的大小,為0x144=0x51*4=(0x4C+5)*4=(FILTERFUNCNT+5)*4
              ULONG Unknown1; //未明確
              ULONG IsFilterFunFilledReady; ? ? ? ? ? ? //標志,表明過濾函數(shù)表是否準備好
              ULONG FakeServiceRoutine[FILTERFUNCNT]? ? //偏移為0xC,過濾函數(shù)數(shù)組,共有過濾函數(shù)0x4C個
              PULONG SSDTRuleTableBase;? ? ? ? ? ? ? ? ? //偏移為0x13C,是SSDT函數(shù)的過濾規(guī)則表,表的大小為SSDTCnt*4
              PULONG ShadowSSDTRuleTableBase; ? ? ? ? //偏移為0x140,是ShadowSSDT函數(shù)的過濾規(guī)則表,表的大小為ShadowSSDTCnt*4
              }FILTERFUN_RULE_TABLE;

            ?
            這些只是一些準備工作,具體有些是什么時候完成的,在后面會講到。
            ?
            二、KiFastCallEntry的Hook


            (1)目標KiFastCallEntry,但具體Hook在哪里最好?

            KiFastCallEntry是ring3經(jīng)sysenter進入內(nèi)核后的第一個必經(jīng)之地(不考慮sysenter hook),但是KiFastCallEntry所作的工作也很多,比如設置ds,es,fs段寄存器的值,從ETHREAD中取ServiceTable,判斷ServiceIndex是否合法,若合法則判斷應使用哪張表(SSDT還是ShadowSSDT),然后從表中取出服務例程地址,從用戶棧復制參數(shù)到內(nèi)核棧,然后調(diào)用服務例程,調(diào)用完之后再做一點準備工作然后就由KiServiceExit再飛回ring3.大致的流程就是這樣的.我貼一下關鍵的一段:

            復制代碼

            1. kd>
              8053d7e4 8b5f0c? ? ? ? ? mov? ? ebx,dword ptr [edi+0Ch] //edi指向SSDT或ShadowSSDT
              8053d7e7 33c9? ? ? ? ? ? xor? ? ecx,ecx //ecx清零
              8053d7e9 8a0c18? ? ? ? ? mov? ? cl,byte ptr [eax+ebx]? //cl得到參數(shù)的長度,即參數(shù)個數(shù)*4
              8053d7ec 8b3f? ? ? ? ? ? mov? ? edi,dword ptr [edi] //edi指向KiServiceTable或W32pServiceTable
              8053d7ee 8b1c87? ? ? ? ? mov? ? ebx,dword ptr [edi+eax*4] //eax是服務號,然后ebx得到服務函數(shù)地址
              8053d7f1 2be1? ? ? ? ? ? sub? ? esp,ecx //ecx得到參數(shù)的總長度,這里是開辟??臻g
              8053d7f3 c1e902? ? ? ? ? shr? ? ecx,2 ? ? ? ? //除以4,得參數(shù)個數(shù)
              8053d7f6 8bfc? ? ? ? ? ? mov? ? edi,esp ? ? //準備復制參數(shù)
              8053d7f8 3b35b48b5580? ? cmp? ? esi,dword ptr [nt!MmUserProbeAddress (80558bb4)] //判斷參數(shù)地址是否有效
              8053d7fe 0f83a8010000? ? jae? ? nt!KiSystemCallExit2+0x9f (8053d9ac)
              8053d804 f3a5? ? ? ? ? ? rep movs dword ptr es:[edi],dword ptr [esi] //復制參數(shù)
              8053d806 ffd3? ? ? ? ? ? call? ? ebx //調(diào)用系統(tǒng)服務

            ?
            我們不能hook在開頭,這樣太多的準備工作需要自己來完成,而且涉及到的操作太多的話,兼容性和穩(wěn)定性就很成問題。顯然我們又不能hook在call服務例程之后,這時該辦的事都辦完了,我們再接手就已經(jīng)晚了,所以必須在call服務例程之前。KiFastCallEntry中的指令,真是有點寸士寸金的感覺,寄存器也不能隨意改變,挑哪兒下手呢?來看看360是怎么做的:
            Hook之后:

            復制代碼

            1. kd>
              nt!KiFastCallEntry+0xcc:
              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 e94a49e901? ? ? jmp? ? 823d2140? //這里被改成了一個跳轉(zhuǎn)
              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]

            ?
            這個Hook的位置確實選得非常好啊,自己省去了很多準備工作,都由系統(tǒng)準備好了,此時edi指向服務表的基址(KiServiceTable或W32pServiceTable),ebx是剛剛?cè)〕龅脑挤绽痰牡刂?eax是服務號,這時再做處理不是就很容易了嘛,重要的幾個信息都有了。

            來看看跳轉(zhuǎn)的地址,又是個跳轉(zhuǎn),看來這里只是個中間跳

            復制代碼

            1. kd> u 823d2140
              823d2140 e95d982a76? ? ? jmp? ? Hookport+0x79a2 (f867b9a2)

            ?
            f867b9a2這里才是真正的目的地,在HookPort.sys中.

            復制代碼

            1. kd> u f867b9a2
              Hookport+0x79a2:
              f867b9a2 8bff? ? ? ? ? ? mov? ? edi,edi
              f867b9a4 9c? ? ? ? ? ? ? pushfd
              f867b9a5 60? ? ? ? ? ? ? pushad
              f867b9a6 57? ? ? ? ? ? ? push? ? edi //edi指向KiServiceTable或W32pServiceTable
              f867b9a7 53? ? ? ? ? ? ? push? ? ebx //ebx是原始的KiFastCallEntry從SSDT中取到的服務函數(shù)地址
              f867b9a8 50? ? ? ? ? ? ? push? ? eax //eax是服務號
              f867b9a9 e840ffffff? ? ? call? ? Hookport+0x78ee (f867b8ee) //KiFastCallEntryFilterFunc
              f867b9ae 89442410? ? ? ? mov? ? dword ptr [esp+10h],eax
              f867b9b2 61? ? ? ? ? ? ? popad
              f867b9b3 9d? ? ? ? ? ? ? popfd
              f867b9b4 2be1? ? ? ? ? ? sub? ? esp,ecx
              f867b9b6 c1e902? ? ? ? ? shr? ? ecx,2
              f867b9b9 ff3574d767f8? ? push? ? dword ptr [Hookport+0x9774 (f867d774)]? //這里是回跳的地址,push/ret方式跳回去
              f867b9bf c3? ? ? ? ? ? ? ret

            ?
            (2)360是如何具體地去安裝這個Hook的?

            360首先獲取ZwSetEvent的服務號,然后安裝了一個SSDT Hook,目標就是NtSetEvent.
            然后,自己調(diào)用了一下ZwSetEvent,觸發(fā)自己安裝的Hook,代碼如下:
            ?
            復制代碼

            1. HANDLE g_FakeEventHandle = (HANDLE)0x288C58F1;
              ZwSetEvent(g_FakeEventHandle, NULL);?

            ?
            0x288C58F1,好奇怪,你看過誰家的句柄長成這個樣子嘛,顯然這是個假句柄了,它的作用就是在hook函數(shù)Fake_NtSetEvent中辨別一下是不是自己人,充當了暗號。

            Fake_NtSetEvent中確實也是這樣寫的:
            ?
            復制代碼

            1. if ( EventHandle != g_FakeEventHandle || ExGetPreviousMode()==UserMode )// 不是我們自己調(diào)用,或者調(diào)用來自UserMode,直接調(diào)用原函數(shù)
              {
              ? ? result = OriginalNtSetEvent(EventHandle, PreviousState);
              }

            ?
            不是自己人就調(diào)用原函數(shù)去了,等對上暗號,是自己人了,才真正開始干事.

            先申請一個5字節(jié)的buffer,寫入一個跳轉(zhuǎn)指令,這個就是上面看到的中間跳,這里還有一個細節(jié),判斷了一下系統(tǒng)版本,Vista前后處理上有稍許不同,根據(jù)當前系統(tǒng)版本選擇合適的JmpStub.

            再準備另外一個跳轉(zhuǎn)指令,跳轉(zhuǎn)目標就是剛才準備的中間跳的地址,這個跳轉(zhuǎn)指令將要被寫入KiFastCallEntry中。那么360是如何找到要寫入的位置呢?繼續(xù)看.

            跳轉(zhuǎn)指令準備好之后,先還原剛才SSDT中安裝的NtSetEvent鉤子,然后從棧中回溯返回地址,也就是取[ebp+4]的值,并保存起來.上面對KiFastCallEntry的分析知道,SSDT中的函數(shù)都是從KiFastCallEntry中call過來的,那么返回地址肯定也在KiFastCallEntry中,具體地講,就是8053d806處call ebx的下一條指令處。取得這個地址后,往上匹配尋找
            ?
            8053d7f3 c1e902????????shr? ? ecx,2
            8053d7f6 8bfc? ? ? ? ? ? mov? ? edi,esp

            找到之后,就確定了要Hook的位置。接下來怎么做寫過hook的都知道,就是關寫保護然后把剛才準備好的跳轉(zhuǎn)指令寫入再打開保護而已,不多說。

            (3)KiFastCallEntry被hook后,360是如何處理的?

            KiFastCallEntry被hook后,經(jīng)二級跳跳到了JmpStub里。來看看:
            ?
            復制代碼

            1. kd> u f867b9a2
              Hookport+0x79a2:
              f867b9a2 8bff? ? ? ? ? ? mov? ? edi,edi
              f867b9a4 9c? ? ? ? ? ? ? pushfd
              f867b9a5 60? ? ? ? ? ? ? pushad
              f867b9a6 57? ? ? ? ? ? ? push? ? edi //edi指向KiServiceTable或W32pServiceTable
              f867b9a7 53? ? ? ? ? ? ? push? ? ebx //ebx是原始的KiFastCallEntry從SSDT中取到的服務函數(shù)地址
              f867b9a8 50? ? ? ? ? ? ? push? ? eax //eax是服務號
              f867b9a9 e840ffffff? ? ? call? ? Hookport+0x78ee (f867b8ee) //KiFastCallEntryFilterFunc
              f867b9ae 89442410? ? ? ? mov? ? dword ptr [esp+10h],eax
              f867b9b2 61? ? ? ? ? ? ? popad
              f867b9b3 9d? ? ? ? ? ? ? popfd
              f867b9b4 2be1? ? ? ? ? ? sub? ? esp,ecx
              f867b9b6 c1e902? ? ? ? ? shr? ? ecx,2
              f867b9b9 ff3574d767f8? ? push? ? dword ptr [Hookport+0x9774 (f867d774)]? //這里是回跳的地址,push/ret方式跳回去
              f867b9bf c3? ? ? ? ? ? ? ret

            ?
            可以看到,edi,ebx,eax三個重要數(shù)據(jù)入棧后,調(diào)用了另一個判斷函數(shù)。該函數(shù)逆向如下:
            ?
            復制代碼

            1. ULONG __stdcall KiFastCallEntryFilterFunc(ULONG ServiceIndex, ULONG OriginalServiceRoutine, ULONG ServiceTable)
              {

              ? ? //判斷是否是SSDT中的調(diào)用
              ? ? if ( ServiceTable == g_KiServiceTable && ServiceIndex <= g_SSDTServiceLimit )// 如果是正常的win32調(diào)用,則
              ? ? {
              ? ?? if (ServiceFilterInfoTable->SwitchTableForSSDT[ServiceIndex] && HookOrNot(ServiceIndex, FALSE))
              ? ?? {
              ? ? ? ?? ServiceFilterInfoTable->SavedSSDTServiceAddress[ServiceIndex]=OriginalServiceRoutine;//保存原始例程,以便后面調(diào)用
              ? ? ? ?? return ServiceFilterInfoTable->ProxySSDTServiceAddress[ServiceIndex];//返回我們代理函數(shù)的地址
              ? ?? }
              ? ? }
              ? ? //判斷是否是ShadowSSDT中的調(diào)用,過程同上
              ? ? if ( ServiceTable == g_GUIServiceTable && ServiceIndex <= g_GUIServiceTableLimit )
              ? ? {
              ? ? ? ? if ( ServiceFilterInfoTable->SwitchTableForShadowSSDT[ServiceIndex] && HookOrNot(ServiceIndex, TRUE) )
              ? ? ? ? {
              ? ? ? ? ? ? ServiceFilterInfoTable->SavedShadowSSDTServiceAddress[ServiceIndex]=OriginalServiceRoutine;
              ? ? ? ? ? ? return ServiceFilterInfoTable->ProxyShadowSSDTServiceAddress[ServiceIndex];?
              ? ? ? ? }? ? ? ? ? ? ? ? ? ? ? ? ?
              ? ? }
              ?
              ? ? return OriginalServiceRoutine; // 不明調(diào)用,就直接返回原始例程
              }

            ?
            結(jié)合上面逆出來的代碼和數(shù)據(jù)結(jié)構(gòu),相信不難看懂。對于一個調(diào)用,通過判斷ServiceTable確定是SSDT調(diào)用還是Shadow調(diào)用,兩者過程基本一樣,以SSDT為例:

            首先根據(jù)ServiceIndex判斷SwitchTable中的Hook開關是否打開,是則調(diào)用HookOrNot函數(shù)根據(jù)FilterFunRuleTable表中的Rule((根據(jù)PreviousMode有進一步判斷))來判斷是否需要Hook。經(jīng)過這兩重檢查和判斷,最終若需要Hook,就保存原始服務例程地址并返回我們的代理函數(shù),若不需要hook,就直接返回原始例程。由于使用了良好的數(shù)據(jù)結(jié)構(gòu),這里的效率是非常高的。

            KiFastCallEntryFilterFunc的返回結(jié)果,要么是原始例程,要么是代理函數(shù),返回至Jmpstub后,這個結(jié)果被保存在了[esp+0x10]處。不要忘了剛才有個pushad,所以[esp+0x10]處實際保存的是ebx的值,這樣修改了棧中的ebx的值,再popad時,ebx的值就被修改為了KiFastCallEntryFilterFunc的返回值,再一個popfd恢復剛才保存的標志寄存器,然后執(zhí)行被jmp指令覆蓋掉的那兩句指令,最后將剛才回溯到的返回地址壓棧,一個ret就又飛回到了KiFastCallEntry中。若已Hook,此時ebx的值就已經(jīng)被修改了,再下來call ebx時調(diào)用的就是剛才返回的代理函數(shù)了。很巧妙的處理啊。
            ?
            三、代理函數(shù)如何處理?


            Hook的細節(jié)搞清楚了,本來就已經(jīng)差不多了,但是看了360代理函數(shù)中的處理,又發(fā)現(xiàn)一些有趣的東西。

            每一個代理函數(shù),都會首先調(diào)用一個CallFilterFunByIndex,調(diào)用時傳入的第一個參數(shù)就是過濾函數(shù)在過濾函數(shù)表中的索引,也就是我上一篇文章中所列出來的那些函數(shù)名稱后面的索引。第二個參數(shù)則是棧中的參數(shù)數(shù)組,相當于一下把所有參數(shù)都傳過去了。
            ?
            CallFilterFunByIndex會先根據(jù)FilterFunRuleTable->IsFilterFunFilledReady判斷該表是否已經(jīng)準備好(這個過濾函數(shù)表實際上是由360SelfProtect.sys調(diào)用HookPort.sys提供的接口填充的,所有過濾函數(shù)的實現(xiàn)也都在360SelfProtect.sys中),根據(jù)傳入的過濾函數(shù)的索引在FilterFunRuleTable表(也就是我前面提到的第二張表)中查找對應的過濾函數(shù),若過濾函數(shù)存在,就調(diào)用過濾函數(shù),傳入的參數(shù)同樣有過濾函數(shù)索引和參數(shù)數(shù)組,在這個過濾函數(shù)中才真正實現(xiàn)了對參數(shù)的判斷。

            判斷完畢之后,若檢查通過,就予以放行,此時再調(diào)用JmpStub中保存的原始服務例程的地址,調(diào)用完原始例程之后,若調(diào)用成功,還會有一個循環(huán)的檢查,檢查目標是調(diào)用原始服務例程后返回的結(jié)果。這些循環(huán)檢查的函數(shù)哪兒來的呢?就是在CallFilterFunByIndex調(diào)用過濾函數(shù)時返回的,由于返回的這些函數(shù)是在調(diào)用原始服務例程之后對結(jié)果進行檢查,所以稱之為CheckResult系列函數(shù),而過濾函數(shù)表中的那些函數(shù)則稱為CheckArguments函數(shù)。

            為什么要有CheckArguments函數(shù),又要有CheckResult函數(shù)?

            這里來個小小的科普,hook某函數(shù)后,檢查參數(shù)的時機是怎么樣的呢?參數(shù)是分IN、OUT的,一般來說,傳入的參數(shù)要在調(diào)用原始函數(shù)前檢查,傳出的參數(shù)要在調(diào)用原始函數(shù)后檢查,而有些則在調(diào)用前后檢查都可以,但效果不同。大致可以分為四種情況:

            第一種以(Nt)TerminateProcess為例,它的原型是這樣的:

            NTSTATUS
            NtTerminateProcess(
            ? ? __in_opt HANDLE ProcessHandle,
            ? ? __in NTSTATUS ExitStatus
            ? ? );

            除了狀態(tài)碼,它沒有有效的返回值,它的作用更多的在于結(jié)束進程這個“過程”,等該函數(shù)返回的時候,進程已經(jīng)被結(jié)束掉了,再來檢查有個P用。所以這種強調(diào)過程而且重點不在返回值(即使它有)的函數(shù),必須要在調(diào)用原函數(shù)前檢查。
            第二種,一個例子是recv,原型如下:

            int recv (
            ? SOCKET s,? ? ?
            ? char FAR* buf,?
            ? int len,? ? ? ?
            ? int flags? ? ?
            );

            顯然該函數(shù)的重點在于第二個參數(shù)中返回的數(shù)據(jù),但是在調(diào)用原始函數(shù)之前,緩沖區(qū)里什么都沒有,檢查,怎么檢查?像這種函數(shù)也是過程性的,但是它的第二個參數(shù)是OUT型的,就必須在調(diào)用原函數(shù)之后檢查。

            第三類,比如CreateFile,OpenProcess,OpenThread,OpenEvent等函數(shù),它們有一個共同的特征,就是傳入特性相關的數(shù)據(jù)(文件名,pid等),返回一個句柄,重點不在過程而在于返回值。這類函數(shù)即可以在調(diào)用原函數(shù)前檢查傳入的參數(shù),也可以在調(diào)用原函數(shù)后檢查返回的句柄。但是一個是對象的名稱等外在信息,一個直接指向?qū)ο?顯然檢查后者更為可靠一些,因為它更貼近對象本身。
            對于那些異步方式調(diào)用的函數(shù)更要注意了,比如異步方式調(diào)用的ReadFile或ReadFileEx,調(diào)用原函數(shù)之前顯然緩沖區(qū)沒有數(shù)據(jù),調(diào)用了原函數(shù)緩沖區(qū)也不見得有數(shù)據(jù),但是異步有個特征就是它的通知機制,可能是Apc,也可能是Event,這時就必須替換或其它方式處理它的通知機制才能在合適的時候拿到數(shù)據(jù)。

            科普就先到這兒?,F(xiàn)在來解釋為什么要有CheckResult系列函數(shù)的存在,相信大家就該明白了。以NtOpenSection這個服務的hook為例,我們知道有一個重點照顧對象叫做\Device\PhysicalMemory,經(jīng)常被大家用于各種XX中,一段典型的打開該對象的代碼是:
            ?
            復制代碼

            1. 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);

            ?
            怎么檢查?檢查第三個參數(shù)attributes.ObjectName是否是\\Device\\PhysicalMemory??顯然是不行的,看看MJ的《續(xù)PhysicalMemory攻擊》你就知道檢查這個參數(shù)有多困難。顯然NtOpenSection屬于我上面提到的第三類函數(shù),看看360是怎么做的:

            代理函數(shù)Proxy_NtOpenSection首先調(diào)用CallFilterFunByIndex,CallFilterFunByIndex根據(jù)所傳入的過濾函數(shù)索引在FilterFunRuleTable中找到Fake_NtOpenSection并調(diào)用之,然而Fake_NtOpenSection除了簡單判斷下調(diào)用者之外不做任何檢查,只是返回了一個函數(shù)的地址給CallFilterFunIndex,記為CheckResult_After_NtOpenSection,CallfilterFunIndex再把這個地址返回給代理函數(shù)Proxy_NtOpenSection,此時Proxy_NtOpenSection調(diào)用原函數(shù)NtOpenSection,若返回不成功,就直接返回這個狀態(tài)值,不成功當然就不管啦。若成功,就會調(diào)用剛才返回的CheckResult_After_NtOpenSection來檢查返回的句柄所指向的對象是不是\Device\PhysicalMemory,若是,就關掉該句柄,并返回一個禁止的狀態(tài)碼(STATUS_ACCESS_DENIED),若不是,綠燈大開,放行之.有時,FakeXXX函數(shù)返回的CheckResultXXX函數(shù)可能不止一個,此時ProxyXXX會根據(jù)返回的函數(shù)個數(shù)循環(huán)調(diào)用這些CheckResultXXX,有一個不通過即為不通過,只有所有CheckResultXXX都檢查通過了,ProxyXXX函數(shù)才會返回原始結(jié)果給調(diào)用者,真是把關極嚴啊。

            值得一提的是,所有的過濾函數(shù)對于來自KernelMode的調(diào)用都不做處理,所以使用驅(qū)動來破壞360是輕而易舉的,但是完全沒有意義。按照MJ一貫的理念,如果你都能加載驅(qū)動了,那么所有ring0的保護也就失去了意義,再來保護完全是無用功,事實上也根本起不到保護效果了。
            ?
            四、總結(jié)回顧


            現(xiàn)在讓我們站得高一點,略去一些細節(jié),來總結(jié)一下360整個hook架構(gòu):

            1.HookPort.sys準備了ServiceFilterInfoTable(上面的表一),里面保存了SSDT函數(shù)的代理函數(shù)地址、原始例程地址、Hook開關,ShadowSSDT也是。

            2.HookPort.sys對外留出了三個接口(保存在了DEVICE_EXTENSION中),第一個用于準備FilterFunRuleTable(上面的表二),第二個用于向FilterFunRuleTable中注冊過濾函數(shù),第三個用于設置FilterFunRuleTable中的過濾規(guī)則。

            3.HookPort.sys安裝KiFastCallEntry hook.

            4.HookPort.sys!KiFastCallEntryFilterFunc根據(jù)Hook開關和RuleTable中的過濾規(guī)則來決定某個系統(tǒng)服務是否會被Hook,被hook后將會調(diào)用Proxy函數(shù)。

            5.360SelfProtect.sys使用HookPort.sys提供的接口初始化RuleTable,并向RuleTable中注冊過濾函數(shù)、設置過濾規(guī)則。

            6.每個Proxy函數(shù)使用CallFilterFunByIndex調(diào)用RuleTable中相應的FilterFun進行參數(shù)檢查,通過后調(diào)用原函數(shù),若調(diào)用成功再調(diào)用FilterFun函數(shù)提供的CheckResult函數(shù)檢查結(jié)果(非SSDT、ShadowSSDT函數(shù)的Hook也采用同樣結(jié)構(gòu))。

            很顯然地:
            如果要增加一個系統(tǒng)服務的Hook,只需要打開ServiceFilterInfoTable中該服務Index對應的開關,并提供一個過濾函數(shù)就可以了。
            如果要去掉一個系統(tǒng)服務的hook,只需要關閉ServiceFilterInfoTable中該服務Index對應的開關,立刻生效,而無須其它改變。
            如果要修改一個系統(tǒng)服務的過濾函數(shù),只需要使用HookPort的接口設置新的過濾函數(shù)就可以了,無須其它改變。

            所以說,這個架構(gòu)設計非常好,易于修改,易于擴充,易于分工,無愧于優(yōu)秀二字。

            結(jié)束語:單獨寫一個或幾個函數(shù)的Hook,很多人都會,但是要實現(xiàn)這樣一個產(chǎn)品級的優(yōu)秀架構(gòu),就不是誰都能完成的了。分析了360這樣一個安全產(chǎn)品所使用的Hook架構(gòu),讓我收獲頗多,也讓大家領略了360安全衛(wèi)士的技術魅力。文中如有錯誤,還請MJ和大家指出。

            分析了一下360安全衛(wèi)士的HOOK(三)

            Feedback

            # re: 分析了一下360安全衛(wèi)士的HOOK(一)   回復  更多評論   

            2012-04-30 00:26 by 沒注冊
            有點槍手的味道!連小菜都明白的一個事,卻被大牛熱捧……單開關、雙開關……開關組、開關控制樞紐……事少怎么干、事多怎么干……一個統(tǒng)籌管理的道理而已……
            精品久久人人做人人爽综合| 国产午夜精品久久久久九九| 久久本道久久综合伊人| 久久精品国产亚洲AV无码偷窥| 久久精品免费观看| 四虎国产精品成人免费久久| 亚洲中文久久精品无码| 久久Av无码精品人妻系列| 久久99国产精品久久久| 草草久久久无码国产专区| 精品多毛少妇人妻AV免费久久| 国产99久久久国产精免费| 中文字幕精品久久久久人妻| 88久久精品无码一区二区毛片| 2021国产精品午夜久久| 精品久久久久久久久久中文字幕 | 88久久精品无码一区二区毛片 | 欧美精品九九99久久在观看| 久久天天躁狠狠躁夜夜2020一| 欧美黑人又粗又大久久久| 国产精品久久影院| 亚洲AV无码久久寂寞少妇| 久久久这里只有精品加勒比| av无码久久久久不卡免费网站| 少妇熟女久久综合网色欲| 国产成人综合久久精品尤物| 俺来也俺去啦久久综合网| 久久人人爽人人爽人人片av麻烦| 99久久精品免费观看国产| 久久精品人人做人人爽97 | 久久午夜夜伦鲁鲁片免费无码影视| 久久中文字幕一区二区| 久久久久无码精品国产| 囯产精品久久久久久久久蜜桃 | 久久精品国产亚洲av水果派| 欧美日韩精品久久久免费观看| 久久婷婷色综合一区二区| 久久久SS麻豆欧美国产日韩| 久久成人小视频| 伊人色综合久久天天网| 久久久久久久女国产乱让韩|