您好,登錄后才能下訂單哦!
這篇文章主要講解了“windows中怎么從內存加載DLL”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“windows中怎么從內存加載DLL”吧!
首先我們先看看pe的結構
DOS headerDOS stub |
---|
PE header |
Section header |
Section 1 |
Section 2 |
. . . |
Section n |
下面給出的所有結構都可以在頭文件winnt.h中找到。
DOS header 僅用于向后兼容。它位于DOS stub 之前。
Microsoft定義DOS標頭如下:
typedef struct _IMAGE_DOS_HEADER {// DOS .EXE標頭 WORD e_magic; //Magic number 字e_cblp; //文件最后一頁上的字節 字e_cp; //文件中的頁面 WORD e_crlc; //Relocations 字e_cparhdr; //段落中header的大小 字e_minalloc; //所需的最少額外段落 字e_maxalloc; //所需的最大額外段落數 WORD e_ss; //初始(相對)SS值 WORD e_sp; //初始SP值 WORD e_csum; //校驗和 WORD e_ip; //初始IP值 WORD e_cs; //初始(相對)CS值 字e_lfarlc; //重定位表的文件地址 WORD e_ovno; //覆蓋數 WORD e_res [4]; //保留字 WORD e_oemid; // OEM標識符(用于e_oeminfo) WORD e_oeminfo; // OEM信息;特定于e_oemid 字e_res2 [10]; //保留字 LONG e_lfanew; //新的exe標頭的文件地址 } IMAGE_DOS_HEADER,* PIMAGE_DOS_HEADER;
PE 頭包含有關可執行文件內不同部分的信息,這些信息用于存儲代碼和數據或定義從其他庫導入或此庫提供的導出。
它的定義如下:
typedef struct _IMAGE_NT_HEADERS { DWORD簽名; IMAGE_FILE_HEADER FileHeader; IMAGE_OPTIONAL_HEADER32可選標題; } 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里包含的信息邏輯庫的格式,包括需要的操作系統版本,內存需求和切入點:
typedef struct _IMAGE_OPTIONAL_HEADER { // //標準字段。 // 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 | 導出功能 |
1 | 導入功能 |
2 | 資源資源 |
3 | 異常信息 |
4 | 安全信息 |
5 | 基地搬遷表 |
6 | 調試信息 |
7 | 特定于架構的數據 |
8 | 全局指針 |
9 | 線程本地存儲 |
10 | 加載配置 |
11 | 綁定進口 |
12 | 導入地址表 |
13 | 延遲加載導入 |
14 | COM運行時描述符 |
對于導入DLL,我們僅需要描述導入和基本重定位表的條目。為了提供對導出功能的訪問,需要導出條目。
段頭存儲在PE頭中的OptionalHeader結構之后。Microsoft提供了宏IMAGE_FIRST_SECTION以基于PE標頭獲得起始地址。
實際上,節頭是文件中每個節的信息列表:
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;
一個部分可以包含代碼,數據,重定位信息,資源,導出或導入定義等。
要模擬PE加載程序,我們必須首先了解,將文件加載到內存并準備結構以便從其他程序中調用它們是必需的。
在發出API調用LoadLibrary時,Windows基本上執行以下任務:
1.打開給定的文件并檢查DOS和PE標頭。
2.嘗試在位置PEHeader.OptionalHeader.ImageBase處分配PEHeader.OptionalHeader.SizeOfImage字節的內存塊。
3.解析節標題并將節復制到其地址。相對于已分配內存塊的基址的每個節的目標地址存儲在IMAGE_SECTION_HEADER結構的VirtualAddress屬性中。
4.如果分配的內存塊與ImageBase不同,則必須調整代碼和/或數據部分中的各種引用。這稱為Base relocation.。
5.必須通過加載相應的庫來解決所需的庫導入。
6.必須根據部分的特性來保護不同部分的存儲區域。有些部分標記為可丟棄,因此此時可以安全釋放。這些部分通常包含僅在導入期間需要的臨時數據,例如基本重定位的信息。
7.現在,庫已完全加載。必須通過使用標志DLL_PROCESS_ATTACH調用入口點來對此進行通知。
在以下段落中,將描述每個步驟。
該庫所需的所有內存必須使用VirtualAlloc保留/分配,因為Windows提供了保護這些內存塊的功能。這是限制訪問存儲器所必需的,例如阻止對代碼或常量數據的寫訪問。
OptionalHeader結構定義該庫所需的內存塊的大小。如果可能,必須在ImageBase指定的地址處保留它:
memory = VirtualAlloc((LPVOID)(PEHeader->OptionalHeader.ImageBase), PEHeader->OptionalHeader.SizeOfImage, MEM_RESERVE, PAGE_READWRITE);
如果保留的內存與ImageBase中指定的地址不同,則必須執行下面的基本重定位。
保留內存后,即可將文件內容復制到系統中。必須對section header 進行評估,以確定文件中的位置和內存中的目標區域。
在復制數據之前,必須先提交內存塊:
dest = VirtualAlloc(baseAddress + section->VirtualAddress, section->SizeOfRawData, MEM_COMMIT, PAGE_READWRITE);
庫的代碼/數據部分中的所有內存地址都相對于ImageBase在OptionalHeader中定義的地址進行存儲。如果無法將庫導入到該內存地址,則必須對引用進行調整=> relocated。文件格式通過在基本重定位表中存儲有關所有這些引用的信息來幫助實現此目的,這些信息可在OptionalHeader中的DataDirectory的目錄條目5中找到。
該表由一系列這種結構組成
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和分配的內存塊之間的增量添加到在偏移處找到的32位。
OptionalHeader中DataDirectory的目錄條目1指定要從中導入符號的庫列表。此列表中的每個條目定義如下:
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條目指向的函數名的引用列表從外部庫中導入。FirstThunk指向地址列表,該地址列表中包含指向導入符號的指針。
解決導入問題時,我們瀏覽兩個列表,將名稱定義的函數導入第一個列表,并將指向符號的指針存儲在第二個列表中:
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; } }
每個部分的“ 特征”條目中都指定了權限標志。這些標志可以是以下之一或組合
IMAGE_SCN_MEM_EXECUTE
本節包含可以執行的數據。
IMAGE_SCN_MEM_READ
本節包含可讀數據。
IMAGE_SCN_MEM_WRITE
本節包含可寫的數據。
這些標志必須映射到保護標志
PAGE_NOACCESS
PAGE_WRITECOPY
PAGE_READONLY
PAGE_READWRITE
PAGE_EXECUTE
PAGE_EXECUTE_WRITECOPY
PAGE_EXECUTE_READ
PAGE_EXECUTE_READWRITE
現在,可以使用函數VirtualProtect限制對內存的訪問。如果程序嘗試以未經授權的方式訪問它,則Windows會引發異常。
除了上面的部分標志之外,還可以添加以下內容:
IMAGE_SCN_MEM_DISCARDABLE
導入后可以釋放此部分中的數據。通常,這是為重定位數據指定的。
IMAGE_SCN_MEM_NOT_CACHED
Windows不得緩存此部分中的數據。將位標志PAGE_NOCACHE添加到上面的保護標志中。
最后要做的是調用DLL入口點(由AddressOfEntryPoint定義),并因此通知庫有關附加到進程的信息。
入口點的功能定義為
typedef BOOL (WINAPI *DllEntryProc)(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved);
所以我們最后需要執行的代碼是
DllEntryProc entry = (DllEntryProc)(baseAddress + PEHeader->OptionalHeader.AddressOfEntryPoint); (*entry)((HINSTANCE)baseAddress, DLL_PROCESS_ATTACH, 0);
之后,我們可以像使用任何普通庫一樣使用導出的函數。
如果要訪問庫導出的函數,則需要找到符號的入口點,即要調用的函數的名稱。
OptionalHeader中DataDirectory的目錄條目0包含有關導出函數的信息。它的定義如下:
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;
首先要做的是將函數名稱映射到導出符號的序號。因此,只需并行遍歷AddressOfNames和AddressOfNameOrdinals定義的數組,直到找到所需的名稱。
現在,您可以使用序號通過評估AddressOfFunctions數組的第n個元素來讀取地址。
要釋放自定義加載的庫,請執行以下步驟
調用入口點以通知庫有關分離的信息:
DllEntryProc entry = (DllEntryProc)(baseAddress + PEHeader->OptionalHeader.AddressOfEntryPoint); (*entry)((HINSTANCE)baseAddress, DLL_PROCESS_ATTACH, 0);
用于解析導入的免費外部庫。
釋放已分配的內存。
MemoryModule是一個C庫,可用于從內存加載DLL。
該接口與加載庫的標準方法非常相似:
typedef void *HMEMORYMODULE; HMEMORYMODULE MemoryLoadLibrary(const void *); FARPROC MemoryGetProcAddress(HMEMORYMODULE, const char *); void MemoryFreeLibrary(HMEMORYMODULE);
感謝各位的閱讀,以上就是“windows中怎么從內存加載DLL”的內容了,經過本文的學習后,相信大家對windows中怎么從內存加載DLL這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。