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 进程与线程

  • 相关阅读:
    CodeForces 681D Gifts by the List (树上DFS)
    UVa 12342 Tax Calculator (水题,纳税)
    CodeForces 681C Heap Operations (模拟题,优先队列)
    CodeForces 682C Alyona and the Tree (树上DFS)
    CodeForces 682B Alyona and Mex (题意水题)
    CodeForces 682A Alyona and Numbers (水题,数学)
    Virtualizing memory type
    页面跳转
    PHP Misc. 函数
    PHP 5 Math 函数
  • 原文地址:https://www.cnblogs.com/theseventhson/p/14401689.html
Copyright © 2011-2022 走看看