• 『转载』从内存资源中加载C++程序集:CMemLoadDll


    MemLoadDll.h

    #if !defined(Q_OS_LINUX)  
    #pragma once  
      
    typedef   BOOL (__stdcall *ProcDllMain)(HINSTANCE, DWORD,  LPVOID );  
      
    class CMemLoadDll  
    {  
    public:  
        CMemLoadDll();  
        ~CMemLoadDll();  
        BOOL    MemLoadLibrary( void *lpFileData , int DataLength);  // Dll file data buffer  
        FARPROC MemGetProcAddress(LPCSTR lpProcName);  
    private:  
        BOOL isLoadOk;  
        BOOL CheckDataValide(void *lpFileData, int DataLength);  
        int  CalcTotalImageSize();  
        void CopyDllDatas(void *pDest, void *pSrc);  
        BOOL FillRavAddress(void *pBase);  
        void DoRelocation(void *pNewBase);  
        int  GetAlignedSize(int Origin, int Alignment);  
    private:  
        ProcDllMain pDllMain;  
      
    private:  
        DWORD  pImageBase;  
        PIMAGE_DOS_HEADER pDosHeader;  
        PIMAGE_NT_HEADERS pNTHeader;  
        PIMAGE_SECTION_HEADER pSectionHeader;  
      
    };  
    #endif  

    MemLoadDll.cpp

    #if !defined(Q_OS_LINUX)  
      
    #include <windows.h>  
    #include <assert.h>  
    #include "MemLoadDll.h"  
      
    #include "QDebug"  
      
      
    CMemLoadDll::CMemLoadDll()  
    {  
        isLoadOk = FALSE;  
        pImageBase = NULL;  
        pDllMain = NULL;  
    }  
      
    CMemLoadDll::~CMemLoadDll()  
    {  
        if(isLoadOk)  
        {  
            assert(pImageBase != NULL);  
            assert(pDllMain != NULL);  
            //脱钩,准备卸载dll  
            pDllMain((HINSTANCE)pImageBase,DLL_PROCESS_DETACH,0);  
            VirtualFree((LPVOID)pImageBase, 0, MEM_RELEASE);  
        }  
    }  
      
    //MemLoadLibrary函数从内存缓冲区数据中加载一个dll到当前进程的地址空间,缺省位置0x10000000  
    //返回值: 成功返回TRUE , 失败返回FALSE  
    //lpFileData: 存放dll文件数据的缓冲区  
    //DataLength: 缓冲区中数据的总长度  
    BOOL CMemLoadDll::MemLoadLibrary(void *lpFileData, int DataLength)  
    {  
        if (pImageBase != NULL)  
        {  
            return FALSE;  //已经加载一个dll,还没有释放,不能加载新的dll  
        }  
      
        //检查数据有效性,并初始化  
        if (!CheckDataValide(lpFileData, DataLength))  
        {  
            return FALSE;  
        }  
      
        //计算所需的加载空间  
        int ImageSize = CalcTotalImageSize();  
        if (ImageSize == 0)  
        {  
            return FALSE;  
        }  
      
        // 分配虚拟内存  
        void *pMemoryAddress = VirtualAlloc((LPVOID)NULL, ImageSize,  
                                            MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);  
        if (pMemoryAddress == NULL)  
        {  
            return FALSE;  
        }  
        else  
        {  
            CopyDllDatas(pMemoryAddress, lpFileData); //复制dll数据,并对齐每个段  
      
            //重定位信息  
            if (pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress > 0  
                    && pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size > 0)  
            {  
                DoRelocation(pMemoryAddress);  
            }  
      
            //填充引入地址表  
            if (!FillRavAddress(pMemoryAddress))  //修正引入地址表失败  
            {  
                VirtualFree(pMemoryAddress, 0, MEM_RELEASE);  
                return FALSE;  
            }  
      
            //修改页属性。应该根据每个页的属性单独设置其对应内存页的属性。这里简化一下。  
            //统一设置成一个属性PAGE_EXECUTE_READWRITE  
            unsigned long old;  
            VirtualProtect(pMemoryAddress, ImageSize, PAGE_EXECUTE_READWRITE, &old);  
        }  
      
        //修正基地址  
        pNTHeader->OptionalHeader.ImageBase = (DWORD)pMemoryAddress;  
      
        //接下来要调用一下dll的入口函数,做初始化工作。  
        pDllMain = (ProcDllMain)(pNTHeader->OptionalHeader.AddressOfEntryPoint + (DWORD) pMemoryAddress);  
      
        BOOL InitResult = pDllMain((HINSTANCE)pMemoryAddress, DLL_PROCESS_ATTACH, 0);  
        if (!InitResult)  //初始化失败  
        {  
            pDllMain((HINSTANCE)pMemoryAddress, DLL_PROCESS_DETACH, 0);  
            VirtualFree(pMemoryAddress, 0, MEM_RELEASE);  
            pDllMain = NULL;  
            return FALSE;  
        }  
      
        isLoadOk = TRUE;  
        pImageBase = (DWORD)pMemoryAddress;  
        return TRUE;  
    }  
      
    //MemGetProcAddress函数从dll中获取指定函数的地址  
    //返回值: 成功返回函数地址 , 失败返回NULL  
    //lpProcName: 要查找函数的名字或者序号  
    FARPROC  CMemLoadDll::MemGetProcAddress(LPCSTR lpProcName)  
    {  
        if (pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress == 0 ||  
                pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size == 0)  
        {  
            return NULL;  
        }  
      
        if (!isLoadOk)  
        {  
            return NULL;  
        }  
      
        DWORD OffsetStart = pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;  
        DWORD Size = pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size;  
      
        PIMAGE_EXPORT_DIRECTORY pExport = (PIMAGE_EXPORT_DIRECTORY)((DWORD)pImageBase + pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);  
        int iBase = pExport->Base;  
        int iNumberOfFunctions = pExport->NumberOfFunctions;  
        int iNumberOfNames = pExport->NumberOfNames; //<= iNumberOfFunctions  
        LPDWORD pAddressOfFunctions = (LPDWORD)(pExport->AddressOfFunctions + pImageBase);  
        LPWORD  pAddressOfOrdinals = (LPWORD)(pExport->AddressOfNameOrdinals + pImageBase);  
        LPDWORD pAddressOfNames  = (LPDWORD)(pExport->AddressOfNames + pImageBase);  
      
        int iOrdinal = -1;  
      
        if (((DWORD)lpProcName & 0xFFFF0000) == 0)  //IT IS A ORDINAL!  
        {  
            iOrdinal = (DWORD)lpProcName & 0x0000FFFF - iBase;  
        }  
        else     //use name  
        {  
            int iFound = -1;  
      
            for (int i = 0; i < iNumberOfNames; i++)  
            {  
                char *pName = (char * )(pAddressOfNames[i] + pImageBase);  
                if (strcmp(pName, lpProcName) == 0)  
                {  
                    iFound = i;  
                    break;  
                }  
            }  
            if (iFound >= 0)  
            {  
                iOrdinal = (int)(pAddressOfOrdinals[iFound]);  
            }  
        }  
      
        if (iOrdinal < 0 || iOrdinal >= iNumberOfFunctions )  
        {  
            return NULL;  
        }  
        else  
        {  
            DWORD pFunctionOffset = pAddressOfFunctions[iOrdinal];  
            if (pFunctionOffset > OffsetStart && pFunctionOffset < (OffsetStart + Size)) //maybe Export Forwarding  
            {  
                return NULL;  
            }  
            else  
            {  
                return (FARPROC)(pFunctionOffset + pImageBase);  
            }  
        }  
      
    }  
      
    // 重定向PE用到的地址  
    void CMemLoadDll::DoRelocation( void *NewBase)  
    {  
        /* 重定位表的结构: 
        // DWORD sectionAddress, DWORD size (包括本节需要重定位的数据) 
        // 例如 1000节需要修正5个重定位数据的话,重定位表的数据是 
        // 00 10 00 00   14 00 00 00      xxxx xxxx xxxx xxxx xxxx 0000 
        // -----------   -----------      ---- 
        // 给出节的偏移  总尺寸=8+6*2     需要修正的地址           用于对齐4字节 
        // 重定位表是若干个相连,如果address 和 size都是0 表示结束 
        // 需要修正的地址是12位的,高4位是形态字,intel cpu下是3 
        */  
        //假设NewBase是0x600000,而文件中设置的缺省ImageBase是0x400000,则修正偏移量就是0x200000  
        DWORD Delta = (DWORD)NewBase - pNTHeader->OptionalHeader.ImageBase;  
      
        //注意重定位表的位置可能和硬盘文件中的偏移地址不同,应该使用加载后的地址  
        PIMAGE_BASE_RELOCATION pLoc = (PIMAGE_BASE_RELOCATION)((unsigned long)NewBase  
                                      + pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);  
        while ((pLoc->VirtualAddress + pLoc->SizeOfBlock) != 0)  //开始扫描重定位表  
        {  
            WORD *pLocData = (WORD *)((int)pLoc + sizeof(IMAGE_BASE_RELOCATION));  
            //计算本节需要修正的重定位项(地址)的数目  
            int NumberOfReloc = (pLoc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD);  
            for ( int i = 0 ; i < NumberOfReloc; i++)  
            {  
                if ( (DWORD)(pLocData[i] & 0xF000) == 0x00003000)  //这是一个需要修正的地址  
                {  
                    // 举例:  
                    // pLoc->VirtualAddress = 0x1000;  
                    // pLocData[i] = 0x313E; 表示本节偏移地址0x13E处需要修正  
                    // 因此 pAddress = 基地址 + 0x113E  
                    // 里面的内容是 A1 ( 0c d4 02 10)  汇编代码是: mov eax , [1002d40c]  
                    // 需要修正1002d40c这个地址  
                    DWORD *pAddress = (DWORD *)((unsigned long)NewBase + pLoc->VirtualAddress + (pLocData[i] & 0x0FFF));  
                    *pAddress += Delta;  
                }  
            }  
            //转移到下一个节进行处理  
            pLoc = (PIMAGE_BASE_RELOCATION)((DWORD)pLoc + pLoc->SizeOfBlock);  
        }  
    }  
      
    //填充引入地址表  
    BOOL CMemLoadDll::FillRavAddress(void *pImageBase)  
    {  
        // 引入表实际上是一个 IMAGE_IMPORT_DESCRIPTOR 结构数组,全部是0表示结束  
        // 数组定义如下:  
        //  
        // DWORD   OriginalFirstThunk;         // 0表示结束,否则指向未绑定的IAT结构数组  
        // DWORD   TimeDateStamp;  
        // DWORD   ForwarderChain;             // -1 if no forwarders  
        // DWORD   Name;                       // 给出dll的名字  
        // DWORD   FirstThunk;                 // 指向IAT结构数组的地址(绑定后,这些IAT里面就是实际的函数地址)  
      
        int i;  
      
        unsigned long Offset = pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress ;  
        if (Offset == 0)  
        {  
            return TRUE;    //No Import Table  
        }  
        PIMAGE_IMPORT_DESCRIPTOR pID = (PIMAGE_IMPORT_DESCRIPTOR)((unsigned long) pImageBase + Offset);  
        while (pID->Characteristics != 0 )  
        {  
            PIMAGE_THUNK_DATA pRealIAT = (PIMAGE_THUNK_DATA)((unsigned long)pImageBase + pID->FirstThunk);  
            PIMAGE_THUNK_DATA pOriginalIAT = (PIMAGE_THUNK_DATA)((unsigned long)pImageBase + pID->OriginalFirstThunk);  
            //获取dll的名字  
            WCHAR buf[256]; //dll name;  
            BYTE *pName = (BYTE *)((unsigned long)pImageBase + pID->Name);  
            for (i = 0; i < 256; i++)  
            {  
                if (pName[i] == 0)  
                {  
                    break;  
                }  
                buf[i] = pName[i];  
            }  
            if (i >= 256)  
            {  
                return FALSE;    // bad dll name  
            }  
            else  
            {  
                buf[i] = 0;  
            }  
            HMODULE hDll = GetModuleHandle(buf);  
            if (hDll == NULL)  
            {  
                hDll = LoadLibrary(buf);  
            }  
            if (hDll == NULL)  
            {  
                return FALSE;    //NOT FOUND DLL  
            }  
            //获取DLL中每个导出函数的地址,填入IAT  
            //每个IAT结构是 :  
            // union { PBYTE  ForwarderString;  
            //   PDWORD Function;  
            //   DWORD Ordinal;  
            //   PIMAGE_IMPORT_BY_NAME  AddressOfData;  
            // } u1;  
            // 长度是一个DWORD ,正好容纳一个地址。  
            for (i = 0; ; i++)  
            {  
                if (pOriginalIAT[i].u1.Function == 0)  
                {  
                    break;  
                }  
                FARPROC lpFunction = NULL;  
                if (pOriginalIAT[i].u1.Ordinal & IMAGE_ORDINAL_FLAG)  //这里的值给出的是导出序号  
                {  
                    lpFunction = GetProcAddress(hDll, (LPCSTR)(pOriginalIAT[i].u1.Ordinal & 0x0000FFFF));  
                }  
                else     //按照名字导入  
                {  
                    //获取此IAT项所描述的函数名称  
                    PIMAGE_IMPORT_BY_NAME pByName = (PIMAGE_IMPORT_BY_NAME)  
                                                    ((DWORD)pImageBase + (DWORD)(pOriginalIAT[i].u1.AddressOfData));  
                    //    if(pByName->Hint !=0)  
                    //     lpFunction = GetProcAddress(hDll, (LPCSTR)pByName->Hint);  
                    //    else  
                    lpFunction = GetProcAddress(hDll, (char *)pByName->Name);  
                }  
      
                if (lpFunction != NULL)  //找到了!  
                {  
                    pRealIAT[i].u1.Function = (DWORD)lpFunction;//(PDWORD) lpFunction;  
                }  
                else  
                {  
                    return FALSE;  
                }  
            }  
      
            //move to next  
            pID = (PIMAGE_IMPORT_DESCRIPTOR)( (DWORD)pID + sizeof(IMAGE_IMPORT_DESCRIPTOR));  
        }  
      
        return TRUE;  
    }  
      
    //CheckDataValide函数用于检查缓冲区中的数据是否有效的dll文件  
    //返回值: 是一个可执行的dll则返回TRUE,否则返回FALSE。  
    //lpFileData: 存放dll数据的内存缓冲区  
    //DataLength: dll文件的长度  
    BOOL CMemLoadDll::CheckDataValide(void *lpFileData, int DataLength)  
    {  
        //检查长度  
        if (DataLength < sizeof(IMAGE_DOS_HEADER))  
        {  
            return FALSE;  
        }  
        pDosHeader = (PIMAGE_DOS_HEADER)lpFileData;  // DOS头  
        //检查dos头的标记  
        if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE)  
        {  
            return FALSE;    //0x5A4D : MZ  
        }  
      
        //检查长度  
        if ((DWORD)DataLength < (pDosHeader->e_lfanew + sizeof(IMAGE_NT_HEADERS)) )  
        {  
            return FALSE;  
        }  
        //取得pe头  
        pNTHeader = (PIMAGE_NT_HEADERS)( (unsigned long)lpFileData + pDosHeader->e_lfanew); // PE头  
        //检查pe头的合法性  
        if (pNTHeader->Signature != IMAGE_NT_SIGNATURE)  
        {  
            return FALSE;    //0x00004550 : PE00  
        }  
        if ((pNTHeader->FileHeader.Characteristics & IMAGE_FILE_DLL) == 0) //0x2000  : File is a DLL  
        {  
            return FALSE;  
        }  
        if ((pNTHeader->FileHeader.Characteristics & IMAGE_FILE_EXECUTABLE_IMAGE) == 0) //0x0002 : 指出文件可以运行  
        {  
            return FALSE;  
        }  
        if (pNTHeader->FileHeader.SizeOfOptionalHeader != sizeof(IMAGE_OPTIONAL_HEADER))  
        {  
            return FALSE;  
        }  
      
        //取得节表(段表)  
        pSectionHeader = (PIMAGE_SECTION_HEADER)((int)pNTHeader + sizeof(IMAGE_NT_HEADERS));  
        //验证每个节表的空间  
        for (int i = 0; i < pNTHeader->FileHeader.NumberOfSections; i++)  
        {  
            if ((pSectionHeader[i].PointerToRawData + pSectionHeader[i].SizeOfRawData) > (DWORD)DataLength)  
            {  
                return FALSE;  
            }  
        }  
        return TRUE;  
    }  
      
    //计算对齐边界  
    int CMemLoadDll::GetAlignedSize(int Origin, int Alignment)  
    {  
        return (Origin + Alignment - 1) / Alignment * Alignment;  
    }  
      
    //计算整个dll映像文件的尺寸  
    int CMemLoadDll::CalcTotalImageSize()  
    {  
        int Size;  
        if (pNTHeader == NULL)  
        {  
            return 0;  
        }  
        int nAlign = pNTHeader->OptionalHeader.SectionAlignment; //段对齐字节数  
      
        // 计算所有头的尺寸。包括dos, coff, pe头 和 段表的大小  
        Size = GetAlignedSize(pNTHeader->OptionalHeader.SizeOfHeaders, nAlign);  
        // 计算所有节的大小  
        for (int i = 0; i < pNTHeader->FileHeader.NumberOfSections; ++i)  
        {  
            //得到该节的大小  
            int CodeSize = pSectionHeader[i].Misc.VirtualSize ;  
            int LoadSize = pSectionHeader[i].SizeOfRawData;  
            int MaxSize = (LoadSize > CodeSize) ? (LoadSize) : (CodeSize);  
      
            int SectionSize = GetAlignedSize(pSectionHeader[i].VirtualAddress + MaxSize, nAlign);  
            if (Size < SectionSize)  
            {  
                Size = SectionSize;    //Use the Max;  
            }  
        }  
        return Size;  
    }  
    //CopyDllDatas函数将dll数据复制到指定内存区域,并对齐所有节  
    //pSrc: 存放dll数据的原始缓冲区  
    //pDest:目标内存地址  
    void CMemLoadDll::CopyDllDatas(void *pDest, void *pSrc)  
    {  
        // 计算需要复制的PE头+段表字节数  
        int  HeaderSize = pNTHeader->OptionalHeader.SizeOfHeaders;  
        int  SectionSize = pNTHeader->FileHeader.NumberOfSections * sizeof(IMAGE_SECTION_HEADER);  
        int  MoveSize = HeaderSize + SectionSize;  
        //复制头和段信息  
        memmove(pDest, pSrc, MoveSize);  
      
        //复制每个节  
        for (int i = 0; i < pNTHeader->FileHeader.NumberOfSections; ++i)  
        {  
            if (pSectionHeader[i].VirtualAddress == 0 || pSectionHeader[i].SizeOfRawData == 0)  
            {  
                continue;  
            }  
            // 定位该节在内存中的位置  
            void *pSectionAddress = (void *)((unsigned long)pDest + pSectionHeader[i].VirtualAddress);  
            // 复制段数据到虚拟内存  
            memmove((void *)pSectionAddress,  
                    (void *)((DWORD)pSrc + pSectionHeader[i].PointerToRawData),  
                    pSectionHeader[i].SizeOfRawData);  
        }  
      
        //修正指针,指向新分配的内存  
        //新的dos头  
        pDosHeader = (PIMAGE_DOS_HEADER)pDest;  
        //新的pe头地址  
        pNTHeader = (PIMAGE_NT_HEADERS)((int)pDest + (pDosHeader->e_lfanew));  
        //新的节表地址  
        pSectionHeader = (PIMAGE_SECTION_HEADER)((int)pNTHeader + sizeof(IMAGE_NT_HEADERS));  
        return ;  
    }  
    #endif  
  • 相关阅读:
    基于ffmpeg 直播推流和播放rtmp (IOS源码)
    Hadoop 安装指南
    机器学习算法( 七、AdaBoost元算法)
    机器学习算法( 五、Logistic回归算法)
    机器学习算法( 四、朴素贝叶斯算法)
    机器学习算法( 三、决策树)
    机器学习算法( 二、K
    Solr 多字段、打分规则、权重和实时索引同步
    Android Activity生命周期
    .net , java webSocket 连接 Socket.io (1.4.4版本) 问题
  • 原文地址:https://www.cnblogs.com/shuxiaolong/p/CMemLoadDll_CPPLib.html
Copyright © 2020-2023  润新知