內存直接加載運行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;
}

 

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM