您好,登錄后才能下訂單哦!
這篇文章主要講解了“windows中怎么從內(nèi)存加載DLL”,文中的講解內(nèi)容簡單清晰,易于學(xué)習(xí)與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“windows中怎么從內(nèi)存加載DLL”吧!
首先我們先看看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 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 頭包含有關(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)入 |
14 | COM運行時描述符 |
對于導(dǎo)入DLL,我們僅需要描述導(dǎo)入和基本重定位表的條目。為了提供對導(dǎo)出功能的訪問,需要導(dǎo)出條目。
段頭存儲在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)存必須使用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í)行下面的基本重定位。
保留內(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);
庫的代碼/數(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位。
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; } }
每個部分的“ 特征”條目中都指定了權(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)志中。
最后要做的是調(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)出的函數(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)存。
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)注!
免責(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)容。