從內存資源中加載DLL: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


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章