zoukankan      html  css  js  c++  java
  • 内存直接加载运行DLL文件

    前言:

      将DLL文件作为资源插入到自己程序中的方法,前面已经说过了。附上链接:MFC —— 资源文件释放(为了程序更简洁) 程序需要动态调用DLL文件,内存加载运行技术可以把这些DLL作为资源插入到自己的程序中。此时直接在内存中加载运行即可,不需要再将DLL释放到本地。

    实现原理:

      将资源加载到内存,然后把DLL文件按照映像对齐大小映射到内存中,切不可直接将DLL文件数据存储到内存中。因为根据PE结构的基础知识可知,PE文件有两个对齐字段,一个是映像对齐大小SectionAlignment,另一个是文件对齐大小FileAlignment。其中,映像对齐大小是PE文件加载到内存中所用的对齐大小,而文件对齐大小是PE文件存储在本地磁盘所用的对齐大小。一般文件对齐大小会比映像对齐大小要小,这样文件会变小,以此节省磁盘空间。然而,成功映射内存数据之后,在DLL程序中会存在硬编码数据,硬编码都是以默认的加载基址作为基址来计算的。由于DLL可以任意加载到其他进程空间中,所以DLL的加载基址并非固定不变。当改变加载基址的时候,硬编码也要随之改变,这样DLL程序才会计算正确。但是,如何才能知道需要修改哪些硬编码呢?换句话说,如何知道硬编码的位置?答案就藏在PE结构的重定位表中,重定位表记录的就是程序中所有需要修改的硬编码的相对偏移位置。根据重定位表修改硬编码数据后,这只是完成了一半的工作。DLL作为一个程序,自然也会调用其他库函数,例如MessageBox。那么DLL如何知道MessageBox函数的地址呢?它只有获取正确的调用函数地址后,方可正确调用函数。PE结构使用导入表来记录PE程序中所有引用的函数及其函数地址。在DLL映射到内存之后,需要根据导入表中的导入模块和函数名称来获取调用函数的地址。若想从导入模块中获取导出函数的地址,最简单的方式是通过GetProcAddress函数来获取(此次采用的方法)。但是为了避免调用敏感的WIN32 API函数而被杀软拦截检测,采用直接遍历PE结构导出表的方式来获取导出函数地址。

    实现流程:

      (1).将资源形式的dll文件加载到内存

      (2).根据PE结构获取其文件映像大小

      (3).根据文件映像大小再申请一块可读、可写、可执行的内存

      (4).将内存DLL数据按映像对齐大小(SectionAlignment)映射到刚刚申请的内存中

      (5).根据PE结构的重定位表,对需要重定位的数据进行修正

      (6).根据PE结构的导入表,加载所需的DLL,获取函数地址并写入导入地址表

      (7).修改DLL的加载基址为第(3)步申请的空间的首地址

      (8).获取Dll的入口地址并构造DllMain函数,然后调用DllMain函数

    实现代码:

        //************************************
        // 函数名:  CStartDlg::LoadMyResource
        // 返回类型:   LPVOID
        // 功能: 加载资源到内存
        // 参数1: UINT uiResourceName    资源名
        // 参数1: char * lpszResourceType    资源类型
        //************************************
    LPVOID CStartDlg::LoadMyResource(UINT uiResourceName, char* lpszResourceType)
    {
        //获取指定模块里的资源
        HRSRC hRsrc = FindResource(GetModuleHandle(NULL), MAKEINTRESOURCE(uiResourceName), (LPCWSTR)lpszResourceType);
        if (NULL == hRsrc)
        {
            MessageBox(L"获取资源失败!");
            return FALSE;
        }
        //获取资源大小
        DWORD dwSize = SizeofResource(NULL, hRsrc);
        if (dwSize <= 0)
        {
            MessageBox(L"获取资源大小失败!");
            return FALSE;
        }
        //将资源加载到内存里
        HGLOBAL hGlobal = LoadResource(NULL, hRsrc);
        if (NULL == hGlobal)
        {
            MessageBox(L"资源加载到内存失败!");
            return FALSE;
        }
        //锁定资源
        LPVOID lpVoid = LockResource(hGlobal);
        if (NULL == lpVoid)
        {
            MessageBox(L"锁定资源失败!");
            return FALSE;
        }
        return lpVoid;
    }
    
    
        //************************************
        // 函数名:  CStartDlg::MmLoadLibrary
        // 返回类型:   LPVOID
        // 功能: 模拟LoadLibrary加载内存文件到进程中
        // 参数1: LPVOID lpData    文件基址
        // 参数2: BOOL IsExe    文件属性标志,TRUE表示exe文件,FALSE表示dll文件
        //************************************
    LPVOID CStartDlg::MmLoadLibrary(LPVOID lpData,BOOL IsExe)
    {
        LPVOID lpBaseAddress = NULL;
        // 获取映像大小
        DWORD dwSizeOfImage = GetSizeOfImage(lpData);
        // 在进程中申请一个可读、可写、可执行的内存块
        lpBaseAddress = VirtualAlloc(NULL, dwSizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
        if (NULL == lpBaseAddress)
        {
            MessageBox(L"申请空间失败!");
            return NULL;
        }
        // 将申请的空间的数据全部置0
        RtlZeroMemory(lpBaseAddress, dwSizeOfImage);
    
        // 将内存DLL数据按映像对齐大小(SectionAlignment)映射到刚刚申请的内存中
        if (FALSE == MmMapFile(lpData, lpBaseAddress))
        {
            MessageBox(L"区段映射到内存失败!");
            return NULL;
        }
    
        // 修改PE文件的重定位表信息
        if (FALSE == DoRelocationTable(lpBaseAddress))
        {
            MessageBox(L"修复重定位失败!");
            return NULL;
        }
    
        // 填写PE文件的导入表信息
        if (FALSE == DoImportTable(lpBaseAddress))
        {
            MessageBox(L"导入表填写失败!");
            return NULL;
        }
        // 修改PE文件的加载基址IMAGE_NT_HEADERS.OptionalHeader.ImageBase
        if (FALSE == SetImageBase(lpBaseAddress))
        {
            MessageBox(L"修改加载机制失败!");
            return NULL;
        }
        // 调用DLL的入口函数DllMain,函数地址即为PE文件的入口点AddressOfEntryPoint
        if (FALSE == CallDllMain(lpBaseAddress,IsExe))
        {
            MessageBox(L"调用入口函数失败!");
            return NULL;
        }
        return lpBaseAddress;
    }
    
    
    
        //************************************
        // 函数名:  CStartDlg::GetSizeOfImage
        // 返回类型:   DWORD
        // 功能: 获取文件映像大小
        // 参数1: LPVOID lpData    文件基址
        //************************************
    DWORD CStartDlg::GetSizeOfImage(LPVOID lpData)
    {
        //获取Dos头
        PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)lpData;
        //判断是否是有效的PE文件        0x5A4D
        if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE)
        {
            MessageBox(L"这不是一个PE文件!");
            return 0;
        }
    
        //获取NT头
        PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader+pDosHeader->e_lfanew);
        //判断是否是有效的PE文件        0x00004550
        if (pNtHeader->Signature != IMAGE_NT_SIGNATURE)
        {
            MessageBox(L"这不是一个PE文件!");
            return 0;
        }
    
        //获取文件映像大小
        return pNtHeader->OptionalHeader.SizeOfImage;
    }
    
    
    
        //************************************
        // 函数名:  CStartDlg::MmMapFile
        // 返回类型:   BOOL
        // 功能: 将内存DLL数据按映像对齐大小(SectionAlignment)映射到刚刚申请的内存中
        // 参数1: LPVOID lpData    文件基址
        // 参数2: LPVOID lpBaseAddress    申请的内存的首地址
        //************************************
    BOOL CStartDlg::MmMapFile(LPVOID lpData, LPVOID lpBaseAddress)
    {
        //获取Dos头
        PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)lpData;
        //获取NT头
        PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader + pDosHeader->e_lfanew);
        //获取所有头部+区段表的大小
        DWORD dwSizeOfHeaders = pNtHeader->OptionalHeader.SizeOfHeaders;
        //获取区段数量
        WORD wNumberOfSections = pNtHeader->FileHeader.NumberOfSections;
        //获取区段表数组的首元素
        PIMAGE_SECTION_HEADER pSectionHeader = IMAGE_FIRST_SECTION(pNtHeader);
        //将头部(包括区段表)拷贝到内存
        RtlCopyMemory(lpBaseAddress, lpData, dwSizeOfHeaders);
        LPVOID lpSrcMem = NULL;
        LPVOID lpDestMem = NULL;
        DWORD dwSizeOfRawData = 0;
        //循环加载所有区段
        for (WORD i = 0; i < wNumberOfSections; i++)
        {
            //过滤掉无效区段
            if (0 == pSectionHeader->VirtualAddress || 0 == pSectionHeader->SizeOfRawData)
            {
                pSectionHeader++;
                continue;
            }
            //获取区段在文件中的位置
            lpSrcMem = (LPVOID)((DWORD)lpData + pSectionHeader->PointerToRawData);
            //获取区段映射到内存中的位置
            lpDestMem = (LPVOID)((DWORD)lpBaseAddress + pSectionHeader->VirtualAddress);
            //获取区段在文件中的大小
            dwSizeOfRawData = pSectionHeader->SizeOfRawData;
            //将区段数据拷贝到内存中
            RtlCopyMemory(lpDestMem, lpSrcMem, dwSizeOfRawData);
            //获取下一个区段头(属性)
            pSectionHeader++;
        }
        return TRUE;
    }
    
    
        //************************************
        // 函数名:  CStartDlg::DoRelocationTable
        // 返回类型:   BOOL
        // 功能: 修改PE文件的重定位表信息
        // 参数1: LPVOID lpBaseAddress    映像对齐后的文件基址
        //************************************
    BOOL CStartDlg::DoRelocationTable(LPVOID lpBaseAddress)
    {
        //获取Dos头
        PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)lpBaseAddress;
        //获取NT头
        PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader + pDosHeader->e_lfanew);
        //获取重定位表的地址
        PIMAGE_BASE_RELOCATION pReloc = (PIMAGE_BASE_RELOCATION)((DWORD)pDosHeader + 
            pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);
    
        //注意重定位表的位置可能和硬盘文件中的偏移地址不同,应该使用加载后的地址
    
        //判断是否有重定位表
        if ((PVOID)pReloc == (PVOID)pDosHeader)
        {
            //没有重定位表
            return TRUE;
        }
    
        int nNumberOfReloc = 0;
        WORD* pRelocData = NULL;
        DWORD* pAddress = NULL;
        //开始修复重定位
        while (pReloc->VirtualAddress != 0 && pReloc->SizeOfBlock != 0)
        {
            //计算本区域(每一个描述了4KB大小的区域的重定位信息)需要修正的重定位项的数量
            nNumberOfReloc = (pReloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD);
    
            for (int i = 0; i < nNumberOfReloc; i++)
            {
                //获取IMAGE_BASE_RELOCATION结构后面的数据的地址
                pRelocData = (WORD*)((DWORD)pReloc + sizeof(IMAGE_BASE_RELOCATION));
    
                //每个WORD由两部分组成,高4位指出了重定位的类型,WINNT.H中的一系列IMAGE_REL_BASED_xxx定义了重定位类型的取值。
                //大部分重定位属性值都是0x3
                //低12位是相对于IMAGE_BASE_RELOCATION中第一个元素VirtualAddress描述位置的偏移
                //找出需要修正的地址
                if ((WORD)(pRelocData[i] & 0xF000) == 0x3000)
                {
                    //获取需要修正数据的地址,    按位与计算优先级比加减乘除低
                    pAddress = (DWORD*)((DWORD)pDosHeader + pReloc->VirtualAddress + (pRelocData[i] & 0x0FFF));
                    //进行修改
                    *pAddress += (DWORD)pDosHeader - pNtHeader->OptionalHeader.ImageBase;
                }
            }
    
            //下一个重定位块
            pReloc = (PIMAGE_BASE_RELOCATION)((DWORD)pReloc + pReloc->SizeOfBlock);
        }
        return TRUE;
    }
    
    
    
        //************************************
        // 函数名:  CStartDlg::DoImportTable
        // 返回类型:   BOOL
        // 功能: 填写PE文件的导入表信息
        // 参数1: LPVOID lpBaseAddress    映像对齐后的文件基址
        //************************************
    BOOL CStartDlg::DoImportTable(LPVOID lpBaseAddress)
    {
        //获取Dos头
        PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)lpBaseAddress;
        //获取NT头
        PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader + pDosHeader->e_lfanew);
        //获取导入表地址
        PIMAGE_IMPORT_DESCRIPTOR pImport = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)pDosHeader + 
            pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
    
        char* pDllName = nullptr;
        HMODULE hDll = NULL;
        PIMAGE_THUNK_DATA pIat = NULL;
        FARPROC pFuncAddress = NULL;
        PIMAGE_IMPORT_BY_NAME pImportByName = NULL;
    
        //循环遍历导入表
        while (pImport->Name)
        {
            //获取导入表中的Dll名称
            pDllName = (char*)((DWORD)pDosHeader + pImport->Name);
            //检索Dll模块获取模块句柄
            hDll = GetModuleHandleA(pDllName);
            //获取失败
            if (NULL == hDll)
            {
                //加载Dll模块获取模块句柄
                hDll = LoadLibraryA(pDllName);
                //加载失败
                if (NULL == hDll)
                {
                    pImport++;
                    continue;
                }
            }
            
            //获取IAT
            pIat = (PIMAGE_THUNK_DATA)((DWORD)pDosHeader + pImport->FirstThunk);
    
            //遍历IAT中函数
            while (pIat->u1.Ordinal)
            {
                //判断导入的函数是名称导入还是序号导入
                //判断最高位是否为1,如果是1那么是序号导入
                if (pIat->u1.Ordinal & 0x80000000)
                {
                    //获取函数地址
                    pFuncAddress = GetProcAddress(hDll, (LPCSTR)(pIat->u1.Ordinal & 0x7FFFFFFF));
    
                }
                    //名称导入
                else
                {
                    //获取IMAGE_IMPORT_BY_NAME结构
                    pImportByName = (PIMAGE_IMPORT_BY_NAME)((DWORD)pDosHeader + pIat->u1.AddressOfData);
                    //获取函数地址
                    pFuncAddress = GetProcAddress(hDll, pImportByName->Name);
                }
                //将函数地址填入到IAT中
                pIat->u1.Function = (DWORD)pFuncAddress;
                pIat++;
            }
            pImport++;
        }
    
        return TRUE;
    }
    
    
    
    
        //************************************
        // 函数名:  CStartDlg::SetImageBase
        // 返回类型:   BOOL
        // 功能: 修改PE文件的加载基址IMAGE_NT_HEADERS.OptionalHeader.ImageBase
        // 参数1: LPVOID lpBaseAddress    映像对齐后的文件基址
        //************************************
    BOOL CStartDlg::SetImageBase(LPVOID lpBaseAddress)
    {
        //获取Dos头
        PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)lpBaseAddress;
        //获取NT头
        PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader + pDosHeader->e_lfanew);
        //修改默认加载基址
        pNtHeader->OptionalHeader.ImageBase = (DWORD)lpBaseAddress;
        return TRUE;
    }
    
    
    
        //************************************
        // 函数名:  CStartDlg::CallDllMain
        // 返回类型:   BOOL
        // 功能: 调用PE文件的入口函数
        // 参数1: LPVOID lpBaseAddress    映像对齐后的文件基址
        // 参数2: BOOL IsExe    文件属性标志,TRUE表示exe文件,FALSE表示dll文件
        //************************************
    BOOL CStartDlg::CallDllMain(LPVOID lpBaseAddress,BOOL IsExe)
    {
        //定义函数指针变量
        typedef_DllMain DllMain = NULL;
        typedef_wWinMain MyWinMain = NULL;
    
        //获取Dos头
        PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)lpBaseAddress;
        //获取NT头
        PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader + pDosHeader->e_lfanew);
    
        BOOL bRet = TRUE;
        //如果是exe文件
        if (IsExe)
        {
            MessageBox(_T("有问题,待解决"));
            //MyWinMain = (typedef_wWinMain)((DWORD)pDosHeader + pNtHeader->OptionalHeader.AddressOfEntryPoint+0xF8D);
            //bRet = MyWinMain((HINSTANCE)lpBaseAddress, NULL, NULL, SW_SHOWNORMAL);
        }
        //dll 文件
        else {
            DllMain = (typedef_DllMain)((DWORD)pDosHeader + pNtHeader->OptionalHeader.AddressOfEntryPoint);
            //调用入口函数,附加进程DLL_PROCESS_ATTACH
            bRet = DllMain((HINSTANCE)lpBaseAddress, DLL_PROCESS_ATTACH, NULL);
        }
    
        return bRet;
    }

      

  • 相关阅读:
    hdu 1260 Tickets
    hdu 4738 Caocao's Bridges(桥的最小权值+去重)
    找规律
    C语言快速排序
    数组的初始化方法
    C语言选择排序
    副本机制
    安装完Kali的后续操作
    Bool盲注
    Python中的列表
  • 原文地址:https://www.cnblogs.com/ndyxb/p/12896872.html
Copyright © 2011-2022 走看看