Windows内存隐藏技术初探

 

Windows内存隐藏技术初探

 

 

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 )

易语言辅助必备驱动保护模块 代码公开 透明 绝无暗装之类 ------------------------------ .版本 2 .子程序 关闭保护辅助进程, 逻辑型, 公开, 取消禁止结束并保护程序 .参数 进程ID, 整数型, 可空, 可空,默认取消自身,可用的进程_名取ID()获取进程ID, .子程序 关闭防各类调试, 逻辑型, 公开, 取消结束并保护程序 .参数 进程ID, 整数型, 可空, 可空,默认取消自身,可用的进程_名取ID()获取进程ID, .子程序 开启保护辅助进程, 逻辑型, 公开, 可禁止他人有意结束某程序,并保护程序不被注入,打开程序,支持所有系统,32,WIN764位都可以 .参数 进程ID, 整数型, 可空, 可空,默认保护自身,可用的进程_名取ID()获取进程ID, .子程序 开启防各类调试, 逻辑型, 公开, 可禁止他人有意,用CE,VE,ME,GE,内存工具和WPE等程序,打开程序,支持所有系统,32,WIN764位都可以 .参数 进程ID, 整数型, 可空, 可空,默认保护自身,可用的进程_名取ID()获取进程ID, .子程序 隐藏模块, 逻辑型, 公开, 隐藏模块 (GetModuleHandle (“隐藏.dll”)) .参数 模块基地址, 整数型 .子程序 郁金香取消隐藏进程, 逻辑型, 公开, 取消隐藏进程 暂时无法取消隐藏 .参数 进程ID, 整数型, 可空, 可空,默认取消自身,可用进程_名取ID()获取进程ID, .子程序 郁金香隐藏进程, 逻辑型, 公开, 隐藏进程,,支持32位,和64位WIn7,与所有系统请自行测试 .参数 进程ID, 整数型, 可空, 可空,默认隐藏自身,可用进程_名取ID()获取进程ID, .子程序 置格盘陷阱, 逻辑型, 公开 .子程序 置蓝屏陷阱, 逻辑型, 公开, 利用蓝屏代码 绝对值蓝屏 .子程序 置死机陷阱, 逻辑型, 公开 .子程序 置重启陷阱, 逻辑型, 公开, 绝对值重启 .DLL命令 GetModuleHandle, 整数型, "kernel32", "GetModuleHandleA", 公开 .参数 lpModuleName, 文本型 .DLL命令 RtlMoveMemory, 整数型, , "RtlMoveMemory", 公开, _写内存3 .参数 dest, 整数型, 传址 .参数 Source, 整数型 .参数 len, 整数型, , 4
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值