Windows內存隱藏技術初探

 <?xml:namespace prefix = w ns = "urn:schemas-microsoft-com:office:word" />

Windows內存隱藏技術初探

<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

 

 

NetRoc

http://www.DbgTech.net/

    最早看到Shadow Walker這種隱藏內存數據的技術的時間忘了,呵呵。大約是一年多或者兩年以前吧。當時還只是提出了理論性的東西,沒有人實現出來。前段時間搞NP玩的時候,對Themida和虛擬機深惡痛絕,遂想,乾脆把內存數據隱藏得了,省得掛個鉤子還得縮手縮腳,找個一勞永逸的辦法。於是google了一把網上的代碼,自己也花了幾天時間實現了一下。雖然不太完美,不支持PAE,目前也只能隱藏ring3的代碼,不過對這項技術的原理和實現也算是摸清楚了。由於對現在的安全軟件具有極度破壞性的殺傷力,開始沒想寫這篇文章,不過最近確實比較無聊,NMAKE的文章又比較難寫,so還是寫寫玩吧,權當練文筆了,呵呵。

關於內存隱藏的概念。有時候我們會面臨這麼一個問題,如果我們有一段內存中的代碼,不想被別人發現,但是又要它能確確實實的執行起來起作用,怎麼辦?或許有很多辦法,比如自我變形之類的。但是,如果可以完完全全把這段代碼的痕跡從內存中“抹消”,豈不是很舒坦的事情?hoho。簡單來說,就是讓內存中的一段數據,執行的時候是一個樣子,但是read/write的時候又是另外一個樣子。是不是有點玄妙了?呵呵。

一切都要從Petium架構CPU提供的TLBS說起。TLBS的全稱是Translation Lookaside Buffers。爲了加速處理器內存在分頁模式下的訪問速度,從P6 familycpu開始就支持這樣一種特性。處理器會將最近訪問過的頁目錄(page-directory)和頁表(page-table)存儲在芯片內部稱爲translation lookaside buffers的緩存中。P6 family處理器分別爲4K頁面和4M頁面分別保留不同的TLBs。絕大多數對分頁的訪問,都可以通過TLBs的內容完成,只有在緩存中找不到所訪問的頁面的信息時,纔會去訪問實際的頁目錄和頁表。Ring0的代碼通過重新裝載CR3或者使用INVLPG指令,可以將TLB裏面某些頁表的入口無效化。而CPU分別爲內存的執行和讀寫保存了不同的TLB,即DTLBITLB。對於DTLB,在執行數據訪問指令的時候,會更新DTLB中被訪問頁面的入口;而對於ITLB,在執行某個頁面代碼的時候,會更新ITLB內的入口。通常情況下,DTLBITLB的內容是同步的,但是我們可以通過操作這兩種TLB,實現對某段內存的讀寫/執行控制。

如何才能在某個內存地址被讀寫或者訪問的時候獲得控制呢?很明顯,當內存訪問出錯的時候,系統會觸發頁面異常,即Trap0E。通過鉤掛Trap0E,並且將我們要控制的內存頁面PTE標記爲不存在,並且通過INVLPG指令清空TLB中該頁的入口,這樣,當對這個頁面進行讀寫/執行訪問的時候,TLB中不存在該頁的入口信息,並且頁面不存在,能夠觸發Trap0E,使得我們可以獲得系統的控制權。

接下來,我們需要區分觸發異常是由於讀寫訪問還是執行訪問。通過比較發生異常的頁面地址,和出現異常的代碼地址,我們可以區分異常類型。如果出現異常的地址就是發生異常時正執行的代碼地址,那麼說明是一次執行訪問;反之則是讀寫訪問。在捕獲到異常並區分出異常類型之後,就可以通過分別手動裝載DTLBITLB使得讀寫/執行數據的過濾。TLB被裝載之後,只要沒有被清除出去,對這些頁面的訪問就通過TLB裏面的內容實現。這樣使得Hook頁面之後的訪問速度,比未Hook之前並不會有明顯的損失。

基本的思想就是這樣了,後面貼出來一些我的代碼實現,其中很多直接“參考”了OllyBone和網上其他公佈出來的代碼。

由於目的是想做對目標進程的Hook,並且隱藏被修改的代碼,僅需要支持對ring3代碼的隱藏即可。我的流程是在驅動中提供了一個接口,將應用層傳進去的一段代碼複製到要hook的地址,並保存原始內容。通過內存僞裝,使得該地址執行的是新代碼,而讀寫操作得到的是舊代碼。爲了在自己Trap0E裏面區分是哪個進程觸發的異常,採用了比較奇怪的辦法,即通過對比CR3寄存器的值來判斷,至於爲什麼這麼做,忘了……。另外,爲了裝載ITLB,需要調用一下僞裝頁面內的代碼,所以我們在被Hook的頁面內需要找到一條retn指令,並把這個地址傳遞給驅動,在裝載ITLB的時候,驅動程序直接call這句retn

下面的代碼還有一些問題,只是當時寫的實驗性的代碼,所以也比較零亂。不過大致的流程是清楚的。

下面這段是IoCtrl裏面的

case IOCTL_SHADOWHOOK:

                   //Shadow Hook!!

                   g_ulHookPid = (ULONG)PsGetCurrentProcessId();

                   g_ulHookProcessCr3 = GetCR3();

                   if( g_pbyCode == NULL)

                   {

                            g_pbyCode = ExAllocatePoolWithTag( PagedPool, 4*1024, 'SWHK');

                            g_pMdl = IoAllocateMdl( g_pbyCode, 4*1024, FALSE, FALSE, NULL);

                            if ( g_pMdl == NULL)

                            {

                                     ExFreePool( g_pbyCode);

                                     g_pbyCode = NULL;

                                     break;

                            }

                            else

                            {

                                     __try

                                     {

                                               MmProbeAndLockPages( g_pMdl, KernelMode, IoReadAccess);

                                               g_blIsLocked = TRUE;

                                     }

                                     __except( EXCEPTION_EXECUTE_HANDLER)

                                     {

                                               IoFreeMdl(g_pMdl);

                                               g_pMdl = NULL;

                                               ExFreePool( g_pbyCode);

                                               g_pbyCode = NULL;

                                               g_blIsLocked = FALSE;

                                     }

                            }

                   }

                   RtlZeroMemory( g_pbyCode, 4*1024);

                   pstShadowHookInfo = (PSHADOW_HOOK_INFO)Irp->AssociatedIrp.SystemBuffer;

                   RtlCopyMemory( g_pbyCode, (PVOID)(pstShadowHookInfo->ulStartAddr & 0xFFFFF000), 4*1024);

                   SetCopyOnWrite( (PVOID)pstShadowHookInfo->ulStartAddr);

                   g_ulHookCodeLen = pstShadowHookInfo->ulLength;

 

                   _asm

                   {//關閉內存寫保護

                            cli;

                            mov eax,cr0;

                            and eax,0fffeffffh;

                            mov cr0,eax;

                   }

                   RtlCopyMemory( (PVOID)pstShadowHookInfo->ulStartAddr, (PVOID)pstShadowHookInfo->pbyHookCode, pstShadowHookInfo->ulLength);

                  

                 _asm

                 {//重新打開內存寫保護

                           mov eax,cr0;

                           or eax,0x10000;

                           mov cr0, eax;

                           sti;

                 }

                  

                  

                   CpuCount = *KeNumberProcessors;

                   while( CpuCount > 0)

                   {

                            KeSetAffinityThread( KeGetCurrentThread(), CpuCount);//綁定CPU

                           HookMemoryPage( (PVOID)pstShadowHookInfo->ulStartAddr, g_pbyCode, (PVOID)pstShadowHookInfo->pfnNullSub);

                            CpuCount--;

                   }

                   g_blIsHookedPage = TRUE;

                 ntStatus = HookTrap0E();

                 if( !NT_SUCCESS(ntStatus))

                 {

                           DbgPrint( "HookTrap0E fail./r/n");

                 }

                   break;

下面是鉤掛Trap0E

Hook Trap0E

NTSTATUS HookTrap0E()

{

         CCHAR CpuCount = 0;

         PIDTENTRY    IdtEntry = NULL;

         IDTR stIdtr = {0};

 

         if( g_blIsHookTrapE0)

                   return STATUS_UNSUCCESSFUL;

 

         CpuCount = *KeNumberProcessors;

         while( CpuCount > 0)

         {

                   KeSetAffinityThread( KeGetCurrentThread(), CpuCount);//綁定CPU

                  

                   //得到 IDTR 中得段界限與基地址

                   _asm sidt stIdtr;

                   IdtEntry = (PIDTENTRY)stIdtr.Base;

                  

                   //保存原有得 IDT

                   RtlCopyMemory(&g_IdtEntryOld, &IdtEntry[0x0E], sizeof(g_IdtEntryOld));

 

                   _asm cli;//禁止中斷

                   g_ulOldTrap0E = (ULONG)IdtEntry[0x0E].OffsetLow | ((ULONG)IdtEntry[0x0E].OffsetHigh<<16);          

                   IdtEntry[0x0E].OffsetLow   = (unsigned short)NewTrap0E;                          

                   IdtEntry[0x0E].OffsetHigh  = (unsigned short)((unsigned int)NewTrap0E>>16);

                   _asm sti;//開中斷

 

                   CpuCount--;

         }

         g_blIsHookTrapE0 = TRUE;

         return STATUS_SUCCESS;

}

 

這是Hook一個頁面

void HookMemoryPage( PVOID pExecutePage, PVOID pReadWritePage,

                                               PVOID pfnCallIntoHookedPage)                         

{                          

         PPTE ExecutePte;

         g_pExecutePage = pExecutePage;                            

         g_pReadWritePage = pReadWritePage;                       

         g_pfnCallIntoHookedPage = pfnCallIntoHookedPage;   

         __asm cli; //關中斷                                                                                         

         ExecutePte = GetPteAddress( pExecutePage);

         g_pReadWritePTE = GetPteAddress( pReadWritePage);

         g_ExecutePTE = ExecutePte;

 

         //這裏因爲我們是Hook ring3頁面,所以不用EnableGlobalPageFeature

    //EnableGlobalPageFeature( ExecutePte);

 

    //標記頁面爲不存在                                       

    MarkPageNotPresent( ExecutePte);

 

    //清空TLB入口

    __asm invlpg pExecutePage

 

         __asm sti //reenable interrupts                            

}//end HookMemoryPage 

 

下面是鉤掛的Trap0E的代碼了。關鍵就在這裏。

#pragma optimize( "", off )

void __declspec (naked) NewTrap0E(void)

{

         __asm                                                  

        {                                                      

                pushad                                         

                mov edx, dword ptr [esp+0x20] //PageFault.ErrorCode

          test edx, 1     //不是缺頁錯誤

                   jne PassDown

 

                   //通過CR3判斷當前進程

                   mov eax, cr3

                   cmp eax, g_ulHookProcessCr3

                   jnz PassDown

 

                mov eax,cr2     //faulting virtual address

                ////////////////////////////////////////

                //判斷是否是Hook掉的page

                /////////////////////////////////////////

                   mov ebx, g_pExecutePage

                  and  ebx, 0xFFFFF000

                 and  eax, 0xFFFFF000

                cmp eax, ebx

                   mov eax, cr2 

                jnz PassDown  //不是,傳下去

 

                ///////////////////////////////////////        

                //下面處理Hook掉的頁面了

                /////////////////////////////////////          

                mov eax, cr2                                                                 

                push eax

                   push eax

                   call GetPteAddress

                   mov ebx, eax //ebx = pPte

                   pop eax          

                cmp [esp+0x24], eax     //判斷是執行出錯還是讀寫時出錯?

                je LoadITLB 

 

                  

               //判斷是否是Hook的那些字節                            

                   cmp eax, g_pExecutePage

                   jb  LoadDTLB

                   sub eax, g_pExecutePage

                   cmp eax, g_ulHookCodeLen

                   jg  LoadDTLB

                   jmp LoadFakeFrame

LoadITLB:

                ////////////////////////////////               

                //是一次執行操作,所以Load ITLB

                ///////////////////////////////                

                cli                      

                or dword ptr [ebx], 0x01         //標誌頁面爲存在

                call g_pfnCallIntoHookedPage //通過調用一下那個頁面內的代碼,裝載ITLB

                and dword ptr [ebx], 0xFFFFFFFE  //重新標記爲不存在

                //sti                                            

                jmp ReturnWithoutPassdown                       

                                                               

                ////////////////////////////////               

                // 這是讀寫造成的異常,並且不在我們要隱藏的代碼範圍內,Load DTLB

                ///////////////////////////////                 

LoadDTLB:

                cli   

                   mov eax,cr2

                or dword ptr [ebx], 0x01           //mark the page present

                mov eax, dword ptr [eax]           //load the DTLB       

                and dword ptr [ebx], 0xFFFFFFFE    //mark page not present

                //sti                                            

                jmp ReturnWithoutPassdown                      

 

                /////////////////////////////////              

                //需要隱藏這段代碼,所以Load僞裝的頁面

                /////////////////////////////////  

 

LoadFakeFrame:    

                   mov eax, cr2

                mov esi, g_pReadWritePTE

                mov ecx, dword ptr [esi]            //ecx = PTE of the   

                                                                     //read / write page  

                //把頁面替換爲假的

                mov edi, [ebx]                                 

                and edi, 0x00000FFF //preserve the lower 12 bits of the  

                                    //faulting page's PTE                

                and ecx, 0xFFFFF000 //isolate the physical address in    

                                    //the "fake" page's PTE              

                or ecx, edi                                     

                mov edx, [ebx]     //save the old PTE so we can replace it

                cli      

                mov [ebx], ecx    //replace the faulting page's phys frame

                                  //address w/ the fake one

                //load DTLB

                or dword ptr [ebx], 0x01   //標誌爲存在

                mov eax, cr2               //faulting virtual address    

                mov eax, dword ptr[eax]    //訪問一次頁面的數據,Load DTLB

                and dword ptr [ebx], 0xFFFFFFFE //重新標誌爲不存在

                                                               

                //Finally, restore the original PTE

                mov [ebx], edx                                 

                //sti                                             

                                                               

ReturnWithoutPassDown:                                         

                popad                                          

                add esp,4

                   sti

                iretd                                          

                                                               

PassDown:                                                      

                popad                                           

                jmp g_ulOldTrap0E

                                                               

        }//end asm                         

}       

#pragma optimize( "", on )

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章