溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

windows中怎么從內(nèi)存加載DLL

發(fā)布時間:2021-11-03 17:45:12 來源:億速云 閱讀:185 作者:iii 欄目:編程語言

這篇文章主要講解了“windows中怎么從內(nèi)存加載DLL”,文中的講解內(nèi)容簡單清晰,易于學(xué)習(xí)與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“windows中怎么從內(nèi)存加載DLL”吧!

Windows可執(zhí)行文件– PE格式

首先我們先看看pe的結(jié)構(gòu)

DOS headerDOS stub
PE header
Section header
Section 1
Section 2
. . .
Section n

下面給出的所有結(jié)構(gòu)都可以在頭文件winnt.h中找到。

DOS header

DOS header 僅用于向后兼容。它位于DOS stub 之前。

Microsoft定義DOS標(biāo)頭如下:

typedef struct _IMAGE_DOS_HEADER {// DOS .EXE標(biāo)頭
    WORD e_magic; //Magic number
    字e_cblp; //文件最后一頁上的字節(jié)
    字e_cp; //文件中的頁面
    WORD e_crlc; //Relocations
    字e_cparhdr; //段落中header的大小
    字e_minalloc; //所需的最少額外段落
    字e_maxalloc; //所需的最大額外段落數(shù)
    WORD e_ss; //初始(相對)SS值
    WORD e_sp; //初始SP值
    WORD e_csum; //校驗和
    WORD e_ip; //初始IP值
    WORD e_cs; //初始(相對)CS值
    字e_lfarlc; //重定位表的文件地址
    WORD e_ovno; //覆蓋數(shù)
    WORD e_res [4]; //保留字
    WORD e_oemid; // OEM標(biāo)識符(用于e_oeminfo)
    WORD e_oeminfo; // OEM信息;特定于e_oemid
    字e_res2 [10]; //保留字
    LONG e_lfanew; //新的exe標(biāo)頭的文件地址
  } IMAGE_DOS_HEADER,* PIMAGE_DOS_HEADER;

PE header

PE 頭包含有關(guān)可執(zhí)行文件內(nèi)不同部分的信息,這些信息用于存儲代碼和數(shù)據(jù)或定義從其他庫導(dǎo)入或此庫提供的導(dǎo)出。

它的定義如下:

typedef struct _IMAGE_NT_HEADERS {
    DWORD簽名;
    IMAGE_FILE_HEADER FileHeader;
    IMAGE_OPTIONAL_HEADER32可選標(biāo)題;
} IMAGE_NT_HEADERS32,* PIMAGE_NT_HEADERS32;

該FileHeader里描述的physical 文件的格式,如目錄符號等信息:

typedef struct _IMAGE_FILE_HEADER {
    WORD    Machine;
    WORD NumberOfSections;
    DWORD TimeDateStamp;
    DWORD PointerToSymbolTable;
    DWORD NumberOfSymbols;
    WORD SizeOfOptionalHeader;
    WORD    Characteristics;
} IMAGE_FILE_HEADER,* PIMAGE_FILE_HEADER;

該OptionalHeader里包含的信息邏輯庫的格式,包括需要的操作系統(tǒng)版本,內(nèi)存需求和切入點:

typedef struct _IMAGE_OPTIONAL_HEADER {
    //
    //標(biāo)準(zhǔn)字段。
    //
WORD    Magic;
    BYTE    MajorLinkerVersion;
    BYTE    MinorLinkerVersion;
    DWORD   SizeOfCode;
    DWORD   SizeOfInitializedData;
    DWORD   SizeOfUninitializedData;
    DWORD   AddressOfEntryPoint;
    DWORD   BaseOfCode;
    DWORD   BaseOfData;
    //
    // NT其他字段。
    //
    DWORD   ImageBase;
    DWORD   SectionAlignment;
    DWORD   FileAlignment;
    WORD    MajorOperatingSystemVersion;
    WORD    MinorOperatingSystemVersion;
    WORD    MajorImageVersion;
    WORD    MinorImageVersion;
    WORD    MajorSubsystemVersion;
    WORD    MinorSubsystemVersion;
    DWORD   Win32VersionValue;
    DWORD   SizeOfImage;
    DWORD   SizeOfHeaders;
    DWORD   CheckSum;
    WORD    Subsystem;
    WORD    DllCharacteristics;
    DWORD   SizeOfStackReserve;
    DWORD   SizeOfStackCommit;
    DWORD   SizeOfHeapReserve;
    DWORD   SizeOfHeapCommit;
    DWORD   LoaderFlags;
    DWORD   NumberOfRvaAndSizes;
    IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32,* PIMAGE_OPTIONAL_HEADER32;

所述DataDirectory目錄包含16(IMAGE_NUMBEROF_DIRECTORY_ENTRIES定義庫的邏輯組件)條目:

Index描述
0導(dǎo)出功能
1導(dǎo)入功能
2資源資源
3異常信息
4安全信息
5基地搬遷表
6調(diào)試信息
7特定于架構(gòu)的數(shù)據(jù)
8全局指針
9線程本地存儲
10加載配置
11綁定進口
12導(dǎo)入地址表
13延遲加載導(dǎo)入
14COM運行時描述符

對于導(dǎo)入DLL,我們僅需要描述導(dǎo)入和基本重定位表的條目。為了提供對導(dǎo)出功能的訪問,需要導(dǎo)出條目。

Section header

段頭存儲在PE頭中的OptionalHeader結(jié)構(gòu)之后。Microsoft提供了宏IMAGE_FIRST_SECTION以基于PE標(biāo)頭獲得起始地址。

實際上,節(jié)頭是文件中每個節(jié)的信息列表:

typedef struct _IMAGE_SECTION_HEADER {
    BYTE    Name[IMAGE_SIZEOF_SHORT_NAME];
    union {
            DWORD   PhysicalAddress;
            DWORD   VirtualSize;
    } Misc;
    DWORD   VirtualAddress;
    DWORD   SizeOfRawData;
    DWORD   PointerToRawData;
    DWORD   PointerToRelocations;
    DWORD   PointerToLinenumbers;
    WORD    NumberOfRelocations;
    WORD    NumberOfLinenumbers;
    DWORD   Characteristics;
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;

一個部分可以包含代碼,數(shù)據(jù),重定位信息,資源,導(dǎo)出或?qū)攵x等。

加載庫

要模擬PE加載程序,我們必須首先了解,將文件加載到內(nèi)存并準(zhǔn)備結(jié)構(gòu)以便從其他程序中調(diào)用它們是必需的。

在發(fā)出API調(diào)用LoadLibrary時,Windows基本上執(zhí)行以下任務(wù):

1.打開給定的文件并檢查DOS和PE標(biāo)頭。

2.嘗試在位置PEHeader.OptionalHeader.ImageBase處分配PEHeader.OptionalHeader.SizeOfImage字節(jié)的內(nèi)存塊。

3.解析節(jié)標(biāo)題并將節(jié)復(fù)制到其地址。相對于已分配內(nèi)存塊的基址的每個節(jié)的目標(biāo)地址存儲在IMAGE_SECTION_HEADER結(jié)構(gòu)的VirtualAddress屬性中。

4.如果分配的內(nèi)存塊與ImageBase不同,則必須調(diào)整代碼和/或數(shù)據(jù)部分中的各種引用。這稱為Base relocation.。

5.必須通過加載相應(yīng)的庫來解決所需的庫導(dǎo)入。

6.必須根據(jù)部分的特性來保護不同部分的存儲區(qū)域。有些部分標(biāo)記為可丟棄,因此此時可以安全釋放。這些部分通常包含僅在導(dǎo)入期間需要的臨時數(shù)據(jù),例如基本重定位的信息。

7.現(xiàn)在,庫已完全加載。必須通過使用標(biāo)志DLL_PROCESS_ATTACH調(diào)用入口點來對此進行通知。

在以下段落中,將描述每個步驟。

分配內(nèi)存

該庫所需的所有內(nèi)存必須使用VirtualAlloc保留/分配,因為Windows提供了保護這些內(nèi)存塊的功能。這是限制訪問存儲器所必需的,例如阻止對代碼或常量數(shù)據(jù)的寫訪問。

OptionalHeader結(jié)構(gòu)定義該庫所需的內(nèi)存塊的大小。如果可能,必須在ImageBase指定的地址處保留它:

memory = VirtualAlloc((LPVOID)(PEHeader->OptionalHeader.ImageBase),
    PEHeader->OptionalHeader.SizeOfImage,
    MEM_RESERVE,
    PAGE_READWRITE);

如果保留的內(nèi)存與ImageBase中指定的地址不同,則必須執(zhí)行下面的基本重定位。

復(fù)制sections

保留內(nèi)存后,即可將文件內(nèi)容復(fù)制到系統(tǒng)中。必須對section header 進行評估,以確定文件中的位置和內(nèi)存中的目標(biāo)區(qū)域。

在復(fù)制數(shù)據(jù)之前,必須先提交內(nèi)存塊:

dest = VirtualAlloc(baseAddress + section->VirtualAddress,
    section->SizeOfRawData,
    MEM_COMMIT,
    PAGE_READWRITE);

Base relocation

庫的代碼/數(shù)據(jù)部分中的所有內(nèi)存地址都相對于ImageBase在OptionalHeader中定義的地址進行存儲。如果無法將庫導(dǎo)入到該內(nèi)存地址,則必須對引用進行調(diào)整=> relocated。文件格式通過在基本重定位表中存儲有關(guān)所有這些引用的信息來幫助實現(xiàn)此目的,這些信息可在OptionalHeader中的DataDirectory的目錄條目5中找到。

該表由一系列這種結(jié)構(gòu)組成

typedef struct _IMAGE_BASE_RELOCATION {
    DWORD   VirtualAddress;
    DWORD   SizeOfBlock;
} IMAGE_BASE_RELOCATION;

它包含(SizeOfBlock – IMAGE_SIZEOF_BASE_RELOCATION)/ 2個條目,每個條目16位。高4位定義重定位的類型,低12位定義相對于VirtualAddress的偏移量。

似乎在DLL中使用的唯一類型是

IMAGE_REL_BASED_ABSOLUTE

用于填充。

IMAGE_REL_BASED_HIGHLOW

將ImageBase和分配的內(nèi)存塊之間的增量添加到在偏移處找到的32位。

解決導(dǎo)入部分

OptionalHeader中DataDirectory的目錄條目1指定要從中導(dǎo)入符號的庫列表。此列表中的每個條目定義如下:

typedef struct _IMAGE_IMPORT_DESCRIPTOR {
    union {
        DWORD   Characteristics;            // 0 for terminating null import descriptor
        DWORD   OriginalFirstThunk;         // RVA to original unbound IAT (PIMAGE_THUNK_DATA)
    };
    DWORD   TimeDateStamp;                  // 0 if not bound,
                                            // -1 if bound, and real date\time stamp
                                            //     in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND)
                                            // O.W. date/time stamp of DLL bound to (Old BIND)
    DWORD   ForwarderChain;                 // -1 if no forwarders
    DWORD   Name;
    DWORD   FirstThunk;                     // RVA to IAT (if bound this IAT has actual addresses)
} IMAGE_IMPORT_DESCRIPTOR;

該名條目描述偏移到庫的名稱(例如的空值終止字符串KERNEL32.DLL)。該OriginalFirstThunk條目指向的函數(shù)名的引用列表從外部庫中導(dǎo)入。FirstThunk指向地址列表,該地址列表中包含指向?qū)敕柕闹羔槨?/p>

解決導(dǎo)入問題時,我們?yōu)g覽兩個列表,將名稱定義的函數(shù)導(dǎo)入第一個列表,并將指向符號的指針存儲在第二個列表中:

nameRef = (DWORD *)(baseAddress + importDesc->OriginalFirstThunk);
symbolRef = (DWORD *)(baseAddress + importDesc->FirstThunk);
for (; *nameRef; nameRef++, symbolRef++)
{
    PIMAGE_IMPORT_BY_NAME thunkData = (PIMAGE_IMPORT_BY_NAME)(codeBase + *nameRef);
    *symbolRef = (DWORD)GetProcAddress(handle, (LPCSTR)&thunkData->Name);
    if (*funcRef == 0)
    {
        handleImportError();
        return;
    }
}

內(nèi)存保護

每個部分的“ 特征”條目中都指定了權(quán)限標(biāo)志。這些標(biāo)志可以是以下之一或組合

IMAGE_SCN_MEM_EXECUTE

本節(jié)包含可以執(zhí)行的數(shù)據(jù)。

IMAGE_SCN_MEM_READ

本節(jié)包含可讀數(shù)據(jù)。

IMAGE_SCN_MEM_WRITE

本節(jié)包含可寫的數(shù)據(jù)。

這些標(biāo)志必須映射到保護標(biāo)志

PAGE_NOACCESS

PAGE_WRITECOPY

PAGE_READONLY

PAGE_READWRITE

PAGE_EXECUTE

PAGE_EXECUTE_WRITECOPY

PAGE_EXECUTE_READ

PAGE_EXECUTE_READWRITE

現(xiàn)在,可以使用函數(shù)VirtualProtect限制對內(nèi)存的訪問。如果程序嘗試以未經(jīng)授權(quán)的方式訪問它,則Windows會引發(fā)異常。

除了上面的部分標(biāo)志之外,還可以添加以下內(nèi)容:

IMAGE_SCN_MEM_DISCARDABLE

導(dǎo)入后可以釋放此部分中的數(shù)據(jù)。通常,這是為重定位數(shù)據(jù)指定的。

IMAGE_SCN_MEM_NOT_CACHED

Windows不得緩存此部分中的數(shù)據(jù)。將位標(biāo)志PAGE_NOCACHE添加到上面的保護標(biāo)志中。

Notify library

最后要做的是調(diào)用DLL入口點(由AddressOfEntryPoint定義),并因此通知庫有關(guān)附加到進程的信息。

入口點的功能定義為

typedef BOOL (WINAPI *DllEntryProc)(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved);

所以我們最后需要執(zhí)行的代碼是

DllEntryProc entry = (DllEntryProc)(baseAddress + PEHeader->OptionalHeader.AddressOfEntryPoint);
(*entry)((HINSTANCE)baseAddress, DLL_PROCESS_ATTACH, 0);

之后,我們可以像使用任何普通庫一樣使用導(dǎo)出的函數(shù)。

導(dǎo)出功能

如果要訪問庫導(dǎo)出的函數(shù),則需要找到符號的入口點,即要調(diào)用的函數(shù)的名稱。

OptionalHeader中DataDirectory的目錄條目0包含有關(guān)導(dǎo)出函數(shù)的信息。它的定義如下:

typedef struct _IMAGE_EXPORT_DIRECTORY {
    DWORD   Characteristics;
    DWORD   TimeDateStamp;
    WORD    MajorVersion;
    WORD    MinorVersion;
    DWORD   Name;
    DWORD   Base;
    DWORD   NumberOfFunctions;
    DWORD   NumberOfNames;
    DWORD   AddressOfFunctions;     // RVA from base of image
    DWORD   AddressOfNames;         // RVA from base of image
    DWORD   AddressOfNameOrdinals;  // RVA from base of image
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;

首先要做的是將函數(shù)名稱映射到導(dǎo)出符號的序號。因此,只需并行遍歷AddressOfNames和AddressOfNameOrdinals定義的數(shù)組,直到找到所需的名稱。

現(xiàn)在,您可以使用序號通過評估AddressOfFunctions數(shù)組的第n個元素來讀取地址。

釋放

要釋放自定義加載的庫,請執(zhí)行以下步驟

調(diào)用入口點以通知庫有關(guān)分離的信息:

DllEntryProc entry = (DllEntryProc)(baseAddress + PEHeader->OptionalHeader.AddressOfEntryPoint);
(*entry)((HINSTANCE)baseAddress, DLL_PROCESS_ATTACH, 0);

用于解析導(dǎo)入的免費外部庫。

釋放已分配的內(nèi)存。

內(nèi)存模塊

MemoryModule是一個C庫,可用于從內(nèi)存加載DLL。

該接口與加載庫的標(biāo)準(zhǔn)方法非常相似:

typedef void *HMEMORYMODULE;
HMEMORYMODULE MemoryLoadLibrary(const void *);
FARPROC MemoryGetProcAddress(HMEMORYMODULE, const char *);
void MemoryFreeLibrary(HMEMORYMODULE);

感謝各位的閱讀,以上就是“windows中怎么從內(nèi)存加載DLL”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對windows中怎么從內(nèi)存加載DLL這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關(guān)知識點的文章,歡迎關(guān)注!

向AI問一下細節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI