zoukankan      html  css  js  c++  java
  • (xxxx)七:3环下注入的dll模块隐藏(一)

         1、做外挂,本质上就改变目标软件的执行流程。为了达到目的,肯定要给目标软件额外添加代码才能达到自己的目的。3环下为了添加代码实现自己的逻辑,要么写shellcode,要么加载dll;两种方式对比如下:

         

         相比之下,dll的优点非常明显,尤其是第2点,可以用C实现复杂的逻辑,这能极大加快外挂、补丁等的开发;缺点只有1条,本文就介绍一个隐藏dll的办法!

         2、windwos作为一个复杂的操作系统,需要运营和管理的模块太多了,大家最熟悉的莫过于进程了。 打开任务管理器,就能查到所有的进程;相应的,通过OD、x32dbg这些调试器打开进程,也能查到进程所有的模块,比如dll、exe等。任务管理器、调试器是怎么知道这些模块详细信息的了?

              个人观点:进程本质上是个资源分配的最小单位。用户双击exe的时候,windwos会在内存分配空间,然后把exe从磁盘加载到内存执行。为了提高效率,windwos是“同时”运行多个进程的。为了管理这些进程所使用的资源,windows使用了一个叫做PEB的结构体! 进程并不真正执行exe的代码,线程才是。1个进程至少有1个主线程。进程和线程是1对多的关系。为了管理这些线程,windwos又使用了一个TEB的结构体。关于PEB和TEB的介绍,网上已经烂大街了,建议各位小伙伴google一下,有很多比较成熟的讲解(这里推荐一下滴水逆向海哥的windows逆向课程,讲解很详细,B站的链接下面有)。

              每个进程都有PEB,每个线程也都有TEB,操作系统该怎么管理这么多的结构体了? 比如用户点击磁盘上的exe,此时需要分配物理内存存放exe的数据和代码;用户退出程序后,操作系统需要回收这个程序的内存并释放,相应的各种元数据(内存地址、内存大小、读写属性、栈、模块地址、模块大小、各种sector大小基址等等)也都要释放,操作系统怎么精准找到这些需要释放的PEB和TEB的位置了(一旦找错,把还在运行进程的PEB、TEB释放掉,岂不是错杀了)?

             微软的研发人员在开发操作系统时,是不可能预测到用户到底会启动多少进程的,所以这么多PEB和TEB的实例是不可能用数组来存储和检索的。由于PEB之间、TEB之间、PEB内部的模块之间都是平行关系,不是上下游或父子关系,所以也不会用树形结构来存储,现在只剩链表了;限于篇幅,本文介绍PEB内部模块之间存储的数据结构——链表,如下:

           

          每个模块都有自己的属性,比较重要的有模块名称、内存基址、大小、磁盘上的路径、入口点等,这些数据都存放在结构体内。结构体之间通过链表首尾相接,操作系统就可以通过遍历链表查到每个模块的属性。当exe执行loadlibrary时,需要从磁盘加载dll,这时新生成这样的一个结构体,然后加入链表(一般是尾部);当exe执行freelibrary时,操作系统释放dll占据的内存,同时也释放这个结构体,然后从现有的这个链表摘除(业界俗称“断链”),这样下次遍历这个链表时就找不到了。根据这个原理,目前网上99%的dll隐藏教程都是根据fullDllName找到目标dll,然后把三个关键字段(如上)的上下游链表分别连接,自己从链表中断开,代码如下:

    #include "stdafx.h"
    #include "windows.h"
    
    typedef struct _UNICODE_STRING {
        USHORT Length;
        USHORT MaximumLength;
        PWSTR  Buffer;
    }UNICODE_STRING, *PUNICODE_STRING;
    
    typedef struct _PEB_LDR_DATA
    {
        ULONG Length; // +0x00
        BOOLEAN Initialized; // +0x04
        PVOID SsHandle; // +0x08
        LIST_ENTRY InLoadOrderModuleList; // +0x0c
        LIST_ENTRY InMemoryOrderModuleList; // +0x14
        LIST_ENTRY InInitializationOrderModuleList;// +0x1c
        PVOID EntryInProgress;            // +0x24
    } PEB_LDR_DATA,*PPEB_LDR_DATA; 
    
    typedef struct _LDR_DATA_TABLE_ENTRY
    {
        LIST_ENTRY InLoadOrderLinks;
        LIST_ENTRY InMemoryOrderLinks;
        LIST_ENTRY InInitializationOrderLinks;
        PVOID DllBase;
        PVOID EntryPoint;
        ULONG SizeOfImage;
        UNICODE_STRING FullDllName;
        UNICODE_STRING BaseDllName;        //后面不写了,用不到
    } LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;
    
    int STRCMP(unsigned short *str1,unsigned short *str2)    //自己实现一个UNICODE字符串比较
    {
        int flag = 0;
        while (*str1)
        {
            if (*str1 != *str2)
            {
                flag = 1;
                break;
            }
            str1++;
            str2++;
        }
        return flag;
    }
    
    void HideModule(UNICODE_STRING DllName)
    {
        PPEB_LDR_DATA ldr;    
        PLDR_DATA_TABLE_ENTRY Node;
        PLIST_ENTRY Head,Temp;
        __asm
        {
            mov eax,fs:[0x30]   //只有在ring3_TEB的值为fs[0],偏移0x30到_PEB
            mov ecx,[eax+0xC]    //_PEB偏移0xC到PEB_LDR
            mov ldr,ecx
        }
    
        // 分别将三个链表断链处理
        //1
        Head = &(ldr->InLoadOrderModuleList);    //第一项是自己的exe,windbg显示不出来
        Temp = Head->Flink;
        printf("以下为所有模块名:
    ");
        do
        {
            //CONTAINING_RECORD宏的作用就是根据结构体类型和结构体中成员变量地址和名称,则可求出该变量所在结构体的指针
            Node = (PLDR_DATA_TABLE_ENTRY)Temp;    //InLoadOrderLinks就是结构体第一个成员,不必CONTAINING_RECORD
            printf("%ls
    ",Node->BaseDllName.Buffer);    //打印所有模块名
            if (!STRCMP(Node->BaseDllName.Buffer,DllName.Buffer))
            {        
                Node->InLoadOrderLinks.Blink->Flink = Node->InLoadOrderLinks.Flink;  
                Node->InLoadOrderLinks.Flink->Blink = Node->InLoadOrderLinks.Blink;         
            }
            Temp = Temp->Flink;
        } while(Head != Temp);
        //2
        Head = &(ldr->InMemoryOrderModuleList);    
        Temp = Head->Flink;
        do
        {
            //CONTAINING_RECORD宏的作用就是根据结构体类型和结构体中成员变量地址和名称,则可求出该变量所在结构体的指针
            Node = CONTAINING_RECORD(Temp, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks);
             if (!STRCMP(Node->BaseDllName.Buffer,DllName.Buffer))
            {        
                Node->InMemoryOrderLinks.Blink->Flink = Node->InMemoryOrderLinks.Flink;  
                Node->InMemoryOrderLinks.Flink->Blink = Node->InMemoryOrderLinks.Blink;         
            }
            Temp = Temp->Flink;
        } while(Head != Temp);
        //3
        Head = &(ldr->InInitializationOrderModuleList);    
        Temp = Head->Flink;
        do
        {
            //CONTAINING_RECORD宏的作用就是根据结构体类型和结构体中成员变量地址和名称,则可求出该变量所在结构体的指针
            Node = CONTAINING_RECORD(Temp, LDR_DATA_TABLE_ENTRY, InInitializationOrderLinks);
            if (!STRCMP(Node->BaseDllName.Buffer,DllName.Buffer))
            {        
                Node->InInitializationOrderLinks.Blink->Flink = Node->InInitializationOrderLinks.Flink;  
                Node->InInitializationOrderLinks.Flink->Blink = Node->InInitializationOrderLinks.Blink;         
            }
            Temp = Temp->Flink;
        } while(Head != Temp);
    }
    int main()
    {
        printf("未断链
    ");
        getchar();
        WCHAR MoudleName[] = L"ntdll.dll";        //要隐藏的dll名称
        UNICODE_STRING YourMoudle;
        YourMoudle.MaximumLength = strlen((char*)MoudleName) + 1;
        YourMoudle.Length = strlen((char*)MoudleName);
        YourMoudle.Buffer = MoudleName;
        HideModule(YourMoudle);
        printf("断链后
    ");
        printf("隐藏的模块名为:%ls",YourMoudle.Buffer);
        getchar();
        return 0;
    }

      网上99%的教程都到此为止了;运行这段代码后,调用windwos提供的CreateToolhelp32Snapshot确实已经查不到了,用OD、x32dbg这些调试器也查不到了,自此已经完事大吉了?

           我们双击exe的时候,操作系统会先逐个检查,看看这个到底是不是可执行文件;在windwos下就是PE格式(linux和android时ELF)。PE格式文件的教程网上也烂大街了,感兴趣的小伙伴自己google一下吧;如果不是PE格式,windwos直接报错,是不会加载执行的(可以把其他格式,比如txt、jpg等常见改成exe试试)。

            Dll、exe、sys本质上都是PE格式的文件,所以windwos操作系统遇到这类文件,会根据特定的格式找到代码段的入口加载到内存后开始执行。PE大体的构成如下:有各种头部和sector;头部的作用主要是描述文件的类型、各个sector的偏移等,相当于整个文件的“元数据”;text、data等才是核心的代码、数据段;一旦加载到内存开始执行,说明windwos已经认识到了这个文件就是PE,那么头部这些描述整个文件的信息就没用了(就像厕纸一样,上完厕所后用之即弃,完全没必要继续保留)

             

              根据以上的推理,可以进一步抹掉我们自己dll的PE头信息,只留关键的sector,比上面仅仅断链的隐藏更进了一步!核心代码如下:

        IMAGE_DOS_HEADER   dosH{};
        IMAGE_NT_HEADERS   ntH{};
        PIMAGE_DOS_HEADER  dosHeader = (PIMAGE_DOS_HEADER)_hMod;
        PIMAGE_NT_HEADERS  ntHeader = (PIMAGE_NT_HEADERS)(dosHeader->e_lfanew + (unsigned)_hMod);
        DWORD dOld;
        VirtualProtect(dosHeader, sizeof(dosH), PAGE_EXECUTE_READWRITE, &dOld);
        VirtualProtect(ntHeader, sizeof(ntH), PAGE_EXECUTE_READWRITE, &dOld);
        memcpy(dosHeader, &dosH, sizeof(dosH));
        memcpy(ntHeader, &ntH, sizeof(ntH));

          断链前用CreateToolhelp32Snapshot查询到所有的模块,其中有我们拦截消息的dll,在0x78DF0000处,大小是0x1EA000。断链并且抹掉dll的PE头后,x32dbg在原dll处还是能看到有一块内存的属性是ERWC,但这时已经看不到dll的任何名字了;但此时我们拦截消息的功能依然能正常使用,说明dll的代码本身还是正常运行的!

     

      其他dll模块在内存视图能看到明显的PE头特征:

     

      我们注入的dll中,已经看不到MZ这些PE头的特征了。当然下面还有this is program..... 也有可能被检测到,所以可以进一步去掉这些信息;

     

    到这里完了么? dll是不是隐藏地很好,再也无法明了地找到了?这么想就too yong, too simple了,用process hacker照样能找到我们自己注入的dll,如下:

      

       最新的PCHUNTER(V1.57)也能查到:而且居然还标红提示,说明察觉到了异常!

       

    注意的地方:

    1、不同版本的windwos中,PEB结构体有细微差异,建议自己用windbg查查,再写代码

    2、各种结构体建议根据windbg查到的重新定义,不要使用windwos原始的结构体

    3、PChunter停更了1年多后终于再次更新,能在1909版本继续使用,但试用旗截至2021.2.1,今天2021.2.14,已经过期。但更改本地的时间还能正常使用,不知道后续会不会联网比对时间;

    参考:

    1、https://blog.csdn.net/Mikasys/article/details/109260609  PEB断链隐藏模块

    2、https://www.bilibili.com/video/BV1m7411p7TM?p=30 进程与线程

  • 相关阅读:
    JS加强学习-BOM学习03
    JS加强学习-BOM学习02
    JS加强学习-BOM学习01
    JS加强学习-DOM学习总结
    JS加强学习-DOM学习05
    JS加强学习-DOM学习04
    JS加强学习-DOM学习03
    《雨霖铃》——柳永
    《青玉案·元夕》——辛弃疾
    《沁园春·雪》——毛 泽东
  • 原文地址:https://www.cnblogs.com/theseventhson/p/14401689.html
Copyright © 2011-2022 走看看