请选择 进入手机版 | 继续访问电脑版
查看: 13791|回复: 3435

[C/C++] dll内存加载的实现

[复制链接]
  • TA的每日心情
    奋斗
    2017-1-16 20:03
  • 签到天数: 116 天

    [LV.6]常住居民II

    发表于 2016-5-27 00:34:36 | 显示全部楼层 |阅读模式
            学过C/C++的同学应该都接触过库的加载函数LoadLibrary。这个API函数可以实现动态添加dll的效果。但是有个问题,就是用这个函数加载库之后,可以在模块中找到库的相关信息,这在正常情况下是没啥的。但是在非正常的情况下,这就有较大的缺陷。比如在写外挂的时候,需要往游戏进程中注入一个dll,如果用LoadLibrary函数来加载,加载之后在进程模块中就会看到这个dll的信息,这样只要那些搞游戏开发的有意识的扫下进程模块,你的这个库就立马暴露了。所以在外挂中,隐藏库是很有必要的,而内存加载就可以达到隐藏的目的。
            这里会涉及到PE文件的相关知识,由于篇幅问题,PE格式就不细说,这里只做了解,PE的格式如下图所示
    pe.png
            这图看着挺复杂,其实就是PE头+区块表+区块数据,具体的大家就自己去查资料吧。
            要加载库到内存中,首先我们得先读取库的数据,读取函数如下
    [AppleScript] 纯文本查看 复制代码
    //读取dll数据到内存
    //szLibName		要读取的库文件
    //pBuffer		用来获取数据的缓存(这个值位NULL则只是获取文件大小,文件大小由lSize返回)
    //lSize			读取的数据大小(文件大小)
    //成功返回0,失败返回-1
    int ReadLibrary(wchar_t *szLibName, void *pBuffer, uint64_t &lSize)
    {
    	int nResult = 0;
    	LARGE_INTEGER li = { 0 };
    	HANDLE hFile = INVALID_HANDLE_VALUE;
    
    	hFile = CreateFileW(szLibName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    	if (INVALID_HANDLE_VALUE == hFile){
    
    		return ERR_OPEN_FILE;
    	}
    
    	li.LowPart = GetFileSize(hFile, (LPDWORD)&li.HighPart);
    	//pBuffer为NULL则只是为了获取文件大小
    	if (NULL == pBuffer){
    
    		lSize = li.QuadPart;
    		CloseHandle(hFile);
    		return ERR_SUCCESS;
    	}
    
    	if (ReadFile(hFile, pBuffer, lSize, (LPDWORD)&lSize, NULL)) {
    
    		nResult = ERR_SUCCESS;
    	}
    	else{
    
    		nResult = ERR_READ_FILE;
    	}
    	CloseHandle(hFile);
    
    	return nResult;
    }


            这个函数比较简单,只是打开文件,获取文件大小,然后读取文件,关闭文件等。做的一些处理就是当传入的缓存为NULL时,表示要获取文件大小,这时先不进行文件的读取,而只是将文件大小返回,以便外部开辟相应大小的缓存来装载数据。当数据读取出来之后,我们先要判断这个文件是否是PE格式,代码如下
    [AppleScript] 纯文本查看 复制代码
    //判断当前文件是否是PE格式
    int IsPEFile(void *pBuffer)
    {
    	uint32_t uResult = 0;
    	PIMAGE_DOS_HEADER		pDosHeader = NULL;
    	PIMAGE_FILE_HEADER		pFileHeader = NULL;
    	PIMAGE_NT_HEADERS		pNTHeader = NULL;
    
    	pDosHeader = (PIMAGE_DOS_HEADER)pBuffer;
    	pNTHeader = (PIMAGE_NT_HEADERS)((char*)pBuffer + pDosHeader->e_lfanew);
    
    	//PE格式的文件是以"MZ"开头,在pDosHeader->e_lfanew处的值是"PE\0\0"
    	if (pDosHeader->e_magic == IMAGE_DOS_SIGNATURE && pNTHeader->Signature == IMAGE_NT_SIGNATURE){
    
    		uResult = ERR_PE_FORMAT;
    	}
    	else{
    
    		uResult = ERR_OTHERE_FORMAT;
    	}
    
    	return uResult;
    }


            要想理解上面的代码,我们先来看Dos头的结构,如下
    [AppleScript] 纯文本查看 复制代码
    typedef struct _IMAGE_DOS_HEADER {      // DOS .EXE header
        WORD   e_magic;                     // Magic number
        WORD   e_cblp;                      // Bytes on last page of file
        WORD   e_cp;                        // Pages in file
        WORD   e_crlc;                      // Relocations
        WORD   e_cparhdr;                   // Size of header in paragraphs
        WORD   e_minalloc;                  // Minimum extra paragraphs needed
        WORD   e_maxalloc;                  // Maximum extra paragraphs needed
        WORD   e_ss;                        // Initial (relative) SS value
        WORD   e_sp;                        // Initial SP value
        WORD   e_csum;                      // Checksum
        WORD   e_ip;                        // Initial IP value
        WORD   e_cs;                        // Initial (relative) CS value
        WORD   e_lfarlc;                    // File address of relocation table
        WORD   e_ovno;                      // Overlay number
        WORD   e_res[4];                    // Reserved words
        WORD   e_oemid;                     // OEM identifier (for e_oeminfo)
        WORD   e_oeminfo;                   // OEM information; e_oemid specific
        WORD   e_res2[10];                  // Reserved words
        LONG   e_lfanew;                    // File address of new exe header
    } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
    


                    通过上面的PE文集格式的结构可知,在文件开始处,也就是Dos头有个"MZ"的标志,这个被定义为IMAGE_DOS_SIGNATURE,其值为0x5A4D,其实就是字符'M'和'Z'的ascii码值。在Dos头中,最后一个成员变量e_lfanew是NT头的RVA,即PE文件数据的首地址加上这个值就是NT头的首地址,NT头的格式如下
    [AppleScript] 纯文本查看 复制代码
    typedef struct _IMAGE_NT_HEADERS {
        DWORD Signature;
        IMAGE_FILE_HEADER FileHeader;
        IMAGE_OPTIONAL_HEADER32 OptionalHeader;
    } IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
    


            在NT头中有个Signature,这个成员变量是NT头的标志,被定义为IMAGE_NT_SIGNATURE,其值为0x00004550,其实就是"PE\0\0"的ascii码值。这里只是简单的判断了下这两个标志的值是否为定义的值,如果是,则认为这个文件的数据是PE格式,否则不是。
            判断了文件格式之后,接下来就是加载文件数据到内存了。加载到内存并不是在内存中开辟个空间,然后直接把数据写入就行了,在PE格式中,有加载后的内存偏移和文件偏移的区别,文件偏移就是数据保存在文件中使用的偏移,内存偏移就是dll被加载到内存后的偏移,这些偏移在PE结构中都可以读取出来。因此在加载之前,我们先要计算dll加载到内存后要占用多少空间,然后在开辟相应大小的空间来加载dll。计算空间的代码如下
    [AppleScript] 纯文本查看 复制代码
    //计算加载到内存后的数据大小
    //pImage	读取到的库文件数据
    //返回加载到内存后索要占用的内存大小
    uint64_t GetImageSize(void *pImage)
    {
    	int i = 0;
    	uint32_t uSizeOfOptional = 0;
    	uint32_t uSectionNumber = 0;
    	uint32_t uImagePage = 0;
    	uint64_t lImageSize = 0;
    	PIMAGE_DOS_HEADER		pDosHeader = NULL;
    	PIMAGE_FILE_HEADER		pFileHeader = NULL;
    	PIMAGE_NT_HEADERS		pNTHeader = NULL;
    	PIMAGE_SECTION_HEADER	pSectionHeader = NULL;
    	SYSTEM_INFO  sysInfo = { 0 };
    
    	//判断是不是PE格式的文件
    	if (ERR_PE_FORMAT != IsPEFile(pImage)){
    
    		return ERR_OTHERE_FORMAT;
    	}
    
    	//先获取DOS头
    	pDosHeader = (PIMAGE_DOS_HEADER)pImage;
    	//获取NT头指针
    	pNTHeader = (PIMAGE_NT_HEADERS)((char*)pImage + pDosHeader->e_lfanew);
    	//获取第一个section的指针
    	pSectionHeader = IMAGE_FIRST_SECTION(pNTHeader);
    	//计算映像大小(映像大小 = 最后一个section的RVA + SizeOfRawData)
    	for (i = 0; i < pNTHeader->FileHeader.NumberOfSections; i ++) {
    
    		if (pSectionHeader[i].VirtualAddress + pSectionHeader[i].SizeOfRawData > lImageSize){
    
    			lImageSize = pSectionHeader[i].VirtualAddress + pSectionHeader[i].SizeOfRawData;
    		}
    	}
    
    	//获取当前系统的基本信息
    	GetSystemInfo(&sysInfo);
    	//计算映像要占用的内存页数
    	uImagePage = lImageSize / sysInfo.dwPageSize;
    	//如果当前映像大小不能被内存页大小整除,则表示最后一页数据没有占满
    	//即lImageSize / sysInfo.dwPageSize得出的结果还有余数,所以uImagePage要加1
    	if (0 != lImageSize % sysInfo.dwPageSize){
    
    		uImagePage ++;
    	}
    
    	lImageSize = uImagePage * sysInfo.dwPageSize;
    
    	return lImageSize;
    }


            这段代码用到了NT头中的FileHeader这个成员变量,这是个IMAGE_FILE_HEADER结构,其结构如下
    [AppleScript] 纯文本查看 复制代码
    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;
    


            在这个结构中NumberOfSections成员变量存储的是区块的个数,我们可以用这个值来控制查找各个区块的RVA和大小。
            上面给出的PE结构图可以看出,在NT头之后,跟着就是区块表,区块表后跟着的就是区块数据,最后面的那些COFF啥的一般都为空,可以不用管。所以我们要计算的加载后的大小就是NT头+区块表+区块数据。
            在上面的代码中,先用IMAGE_FIRST_SECTION来得到区块表的指针。既然PE文件后面都是区块数据,那么最后个区块的RVA+该区块的大小,不就是dll加载到内存后的大小了么(这里可能有点绕,大家仔细揣摩)。那怎么找到最后一个区块数据的RVA和它的大小呢?
            这里用到的方法很简单,逐一比较各个区块RVA+该区块大小的值,得到的最大值即可认为是最后一个区块数据的RVA+区块数据长度(因为当前区块的RVA>=前一个区块的RVA+区块大小),而最后一个区块数据的RVA+长度就是dll加载到内存中的大小。
            我们得到大小之后,要把这个大小转成内存页的倍数,所以要先找出内存页的大小,然后用当前得到的值除以内存叶大小,则可得到要占用多少内存页,但是还有不被内存页整除的情况,所以用这个大小对内存页求模,如果不为0表示不能被内存页整除,即所需的内存页数+1.
            得到大小之后,就是分配内存,加载数据到内存中,代码如下
    [AppleScript] 纯文本查看 复制代码
    //分配内存,加载数据到内存中
    //pImage	分配的内存
    //pBuffer	读取到的dll文件数据
    //lSize		需要分配的内存大小
    int LoadLibraryImage(void *&pImage, void *pBuffer, uint64_t lSize)
    {
    	uint32_t uResult = 0;
    	uint32_t uPEHeaderLen = 0;
    	uint32_t uSectionTableSize = 0;
    	PIMAGE_DATA_DIRECTORY	pDataDir = NULL;
    	PIMAGE_DOS_HEADER		pDosHeader = NULL;
    	PIMAGE_FILE_HEADER		pFileHeader = NULL;
    	PIMAGE_NT_HEADERS		pNTHeader = NULL;
    	PIMAGE_OPTIONAL_HEADER	pOptionalHeader = NULL;
    	PIMAGE_SECTION_HEADER	pSectionHeader = NULL;
    
    	pDosHeader = (PIMAGE_DOS_HEADER)pBuffer;
    	pNTHeader = (PIMAGE_NT_HEADERS)((char*)pDosHeader + pDosHeader->e_lfanew);
    	pSectionHeader = IMAGE_FIRST_SECTION(pNTHeader);
    	uPEHeaderLen = (char*)pSectionHeader - (char*)pDosHeader;
    	pImage = VirtualAlloc(NULL, lSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    	if (NULL == pImage){
    
    		return ERR_MALLOC;
    	}
    	ZeroMemory(pImage, lSize);
    	//先写入PE头
    	MoveMemory(pImage, pBuffer, uPEHeaderLen);
    	//写入区块表
    	uSectionTableSize = pNTHeader->FileHeader.NumberOfSections * sizeof(IMAGE_SECTION_HEADER);
    	MoveMemory((char*)pImage + uPEHeaderLen, (char*)pBuffer + uPEHeaderLen, uSectionTableSize);
    	//写入区块数据
    	for (int i = 0; i < pNTHeader->FileHeader.NumberOfSections; i ++) {
    
    		MoveMemory((char*)pImage + pSectionHeader[i].VirtualAddress, (char*)pBuffer + pSectionHeader[i].PointerToRawData, pSectionHeader[i].SizeOfRawData);
    	}
    
    	return uResult;
    }
    


            上面这段代码实现的是分配内存,然后往内存中写入数据。由于PE头和区块表没有文件偏移和内存偏移的问题,所以可以把它们直接写入内存即可。但是区块数据有特定的RVA,所以我们就根据其RVA来加载。这样把所有的数据都加载到内存之后,加载的环节就结束了,接下来就是做加载后的修补工作了。
            加载数据到内存之后,我们要对输入表进行修复,代码如下
    [AppleScript] 纯文本查看 复制代码
    //修复输入表
    //pImage   PE文件加载到内存中的地址
    int ImportRepair(void *pImage)
    {
    	char *szDllName = NULL;
    	uint32_t uImportAddr = 0;
    	uint32_t uImportSize = 0;
    	uint32_t uExportAddr = 0;
    	uint32_t uExportSize = 0;
    	uint32_t *pOrgThunk = NULL;
    	uint32_t *pFirstThunk = NULL;
    	HMODULE  hModule = NULL;
    	PIMAGE_IMPORT_DESCRIPTOR pImportDesc = NULL;
    	PIMAGE_DATA_DIRECTORY	pImport = NULL;
    	PIMAGE_DOS_HEADER		pDosHeader = NULL;
    	PIMAGE_NT_HEADERS		pNTHeader = NULL;
    	PIMAGE_IMPORT_BY_NAME   pImportName = NULL;
    
    	pDosHeader = (PIMAGE_DOS_HEADER)pImage;
    	pNTHeader = (PIMAGE_NT_HEADERS)((char*)pDosHeader + pDosHeader->e_lfanew);
    	pImport = &pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
    	if (NULL == pImport || 0 == pImport->VirtualAddress){
    
    		return ERR_INVALID_IMPORT;
    	}
    	pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)((char*)pDosHeader + pImport->VirtualAddress);
    	uImportSize = pImport->Size;
    	//重写输入表
    	for (;;) {
    
    		//获取dll名
    		szDllName = (char*)pDosHeader + pImportDesc->Name;
    		hModule = LoadLibraryA(szDllName);
    		if (NULL == hModule){
    
    			return ERR_LOAD_LIB;
    		}
    		pOrgThunk = (uint32_t *)((char*)pDosHeader + pImportDesc->OriginalFirstThunk);
    		pFirstThunk = (uint32_t*)((char*)pDosHeader + pImportDesc->FirstThunk);
    
    		while (*pOrgThunk){
    
    			//最高位为1,表示函数以序号方式输入,低31位是函数序号
    			if (*pOrgThunk & 0x80000000){
    
    				*pFirstThunk = (uint32_t)GetProcAddress(hModule, (char*)(*pOrgThunk & 0xffff));
    			}
    			else{
    
    				pImportName = (PIMAGE_IMPORT_BY_NAME)((char*)pDosHeader + *pOrgThunk);
    				*pFirstThunk = (uint32_t)GetProcAddress(hModule, pImportName->Name);
    			}
    
    			//无法获取函数地址则退出
    			if (0 == *pFirstThunk){
    
    				FreeLibrary(hModule);
    				return ERR_PROC_ADDR;
    			}
    
    			pOrgThunk++;
    			pFirstThunk++;
    		}
    
    		pImportDesc++;
    		if (0 == *(uint32_t*)pImportDesc){
    
    			break;
    		}
    	}
    
    	return ERR_SUCCESS;
    }
    


            要修复输入表,就要找到输入表的指针。这个得通过IMAGE_OPTIONAL_HEADER32结构来获取。先来看IMAGE_OPTIONAL_HEADER32结构
    [AppleScript] 纯文本查看 复制代码
    typedef struct _IMAGE_OPTIONAL_HEADER {
        //
        // Standard fields.
        //
    
        WORD    Magic;
        BYTE    MajorLinkerVersion;
        BYTE    MinorLinkerVersion;
        DWORD   SizeOfCode;
        DWORD   SizeOfInitializedData;
        DWORD   SizeOfUninitializedData;
        DWORD   AddressOfEntryPoint;
        DWORD   BaseOfCode;
        DWORD   BaseOfData;
    
        //
        // NT additional fields.
        //
    
        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是个数组,这个数组中存储的是输入表、输出表、重定位、资源等结构的RVA。接下来介绍下输入表的结构,如下
    [AppleScript] 纯文本查看 复制代码
    typedef struct _IMAGE_IMPORT_DESCRIPTOR {
        union {
            DWORD   Characteristics;            // 0 for terminating null import descriptor
            DWORD   OriginalFirstThunk;         // RVA to original unbound IAT (PIMAGE_THUNK_DATA)
        } DUMMYUNIONNAME;
        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;
    typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;
    


            输入表的结构比较简单,只有5个成员(开头是个联合),我们只需要关注其中的3个成员即可。其中Name是该dll中依赖的其他dll的名称的RVA,而OriginalFirstThunk和FirstThunk比较重要,都是IMAGE_THUNK_DATA 结构,如下所示
    [AppleScript] 纯文本查看 复制代码
    typedef struct _IMAGE_THUNK_DATA32 {
        union {
            DWORD ForwarderString;      // PBYTE 
            DWORD Function;             // PDWORD
            DWORD Ordinal;
            DWORD AddressOfData;        // PIMAGE_IMPORT_BY_NAME
        } u1;
    } IMAGE_THUNK_DATA32;
    


            这个结构是个联合,有着多种意义。当该结构的最高位为1时,表示函数以序号的方式输入,则其低31位为函数的序号,当其最高位为0时,表示函数以字符串类型的方式输入,其低31未为指向IMAGE_IMPORT_BY_NAME的RVA。而这里的OriginalFirstThunk和FirstThunk,其中前者是不可改写的,后者是在加载的时候需要往里填入输入函数地址的。即我哦们这里的输入表修复其实就是往这里面填入输入函数地址。填入的方式为先加载库,根据OriginalFirstThunk来得到函数名或者序号,然后用GetProcAddress来获取函数地址,将得到的地址写入FirstThunk,直到加载完所有的输入函数地址即可。输入表修复了之后,我们接下来要修正重定位了。
            先说为啥要做重定位。链接器生成PE文件时有个默认的装载基地址,一般情况下,PE文件都会被加载到这个默认的基地址处,这样就不需要重定位,但是当PE文件被加载到其他地址处时,链接器所登记的地址就不正确了,这样就需要通过重定位来修正。修正重定位的代码如下
    [AppleScript] 纯文本查看 复制代码
    //基址重定位
    //pImage   PE文件加载到内存中的地址
    int BaseRelocation(void *pImage)
    {
    	uint32_t				uSizeOfBlock = 0;
    	uint16_t				*pTypeOffset = NULL;
    	uint32_t				*pRelocAddr = NULL;
    	PIMAGE_DOS_HEADER		pDosHeader = NULL;
    	PIMAGE_NT_HEADERS		pNTHeader = NULL;
    	PIMAGE_BASE_RELOCATION	pBaseRelocation = NULL;
    
    	pDosHeader = (PIMAGE_DOS_HEADER)pImage;
    	pNTHeader = (PIMAGE_NT_HEADERS)((char*)pDosHeader + pDosHeader->e_lfanew);
    	pBaseRelocation = (PIMAGE_BASE_RELOCATION)((char*)pDosHeader + pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);
    	while (pBaseRelocation->VirtualAddress != 0){
    
    		uSizeOfBlock = pBaseRelocation->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION);
    		pTypeOffset = (uint16_t*)((uint32_t)pBaseRelocation + sizeof(IMAGE_BASE_RELOCATION));
    		for (int i = 0; i < uSizeOfBlock / 2; i ++) {
    
    			//类型是IMAGE_REL_BASED_HIGHLOW(高4位表示类型)
    			if (IMAGE_REL_BASED_HIGHLOW == ((*pTypeOffset & 0xf000) >> 12)){
    
    				pRelocAddr = (uint32_t*)((char*)pDosHeader + pBaseRelocation->VirtualAddress + (*pTypeOffset & 0xfff));
    				*pRelocAddr = (uint32_t)((uint32_t)pDosHeader - pNTHeader->OptionalHeader.ImageBase + *pRelocAddr);
    			}
    
    			pTypeOffset++;
    		}
    
    		pBaseRelocation = (PIMAGE_BASE_RELOCATION)((uint32_t)pBaseRelocation + pBaseRelocation->SizeOfBlock);
    	}
    
    	return ERR_SUCCESS;
    }
    


            这里先来了解下重定位的结构
    [AppleScript] 纯文本查看 复制代码
    typedef struct _IMAGE_BASE_RELOCATION {
        DWORD   VirtualAddress;
        DWORD   SizeOfBlock;
    //  WORD    TypeOffset[1];
    } IMAGE_BASE_RELOCATION;
    typedef IMAGE_BASE_RELOCATION UNALIGNED * PIMAGE_BASE_RELOCATION;
    


            这个结构很简单,但是要注意的是,这结构很面跟着一个数组,这个数组每个元素占2字节,其中高4位表示重定位类型,低12位表示重定位地址。我们要做的就是取到重定位数据的指针(和获取输入表指针方式一样),然后计算重定位的项数,最后修改重定位地址,重定位地址的修改方式为库加载到内存的地址+当前重定位地址-默认的基址。
            重定位修正之后,我们只需要得到库的入口地址,然后调用即可。到了这里,库加载的流程算了结束了,加载的代码如下
    [AppleScript] 纯文本查看 复制代码
    HMODULE	 MemLoadLibrary(wchar_t *szLibName)
    {
    	uint32_t nResult = 0;
    	uint64_t lSize = 0;
    	char *pLibBuf = NULL;
    	char *pImage = NULL;
    	DLLMAIN pfnDllMain = NULL;
    
    	nResult = ReadLibrary(szLibName, NULL, lSize);
    
    	if (ERR_SUCCESS != nResult){
    
    		return (HMODULE)ERR_LOAD_LIB;
    	}
    	
    	pLibBuf = (char*)malloc(lSize);
    	if (NULL == pLibBuf) {
    
    		return (HMODULE)ERR_LOAD_LIB;
    	}
    
    	memset(pLibBuf, 0, lSize);
    	nResult = ReadLibrary(szLibName, pLibBuf, lSize);
    	lSize = GetImageSize(pLibBuf);
    	if (LoadLibraryImage((void*&)pImage, pLibBuf, lSize)) {
    
    		free(pLibBuf);
    		if (NULL != pImage){
    
    			VirtualFree(pImage, 0, MEM_RELEASE);
    			pImage = NULL;
    		}
    
    		return (HMODULE)ERR_LOAD_LIB;
    	}
    	ImportRepair(pImage);
    	BaseRelocation(pImage);
    	//获取DllMain函数的地址
    	pfnDllMain = (DLLMAIN)GetDllMain(pImage);
    	//调用DllMain函数
    	pfnDllMain((HMODULE)pImage, DLL_PROCESS_ATTACH, NULL);
    
    	return (HMODULE)pImage;
    }
    


            库加载了之后,我们还可以提供一个获取函数地址的函数接口,这样当库加载了之后,我们可以通过这个接口来获取函数地址。代码如下
    [AppleScript] 纯文本查看 复制代码
    void*	MemGetProcAddress(HMODULE hModule, char *szFuncName)
    {
    	uint32_t uFuncAddr = 0;
    	uint16_t uFuncAddrIndex = 0;
    	char *szExportFuncName = NULL;
    	uint32_t *pAddrOfFunctions = NULL;
    	uint32_t *pAddrOfName = NULL;
    	uint32_t *pAddrOfNameOrdinals = NULL;
    	PIMAGE_DOS_HEADER			pDosHeader = NULL;
    	PIMAGE_NT_HEADERS			pNTHeader = NULL;
    	PIMAGE_EXPORT_DIRECTORY		pExportDir = NULL;
    
    	pDosHeader = (PIMAGE_DOS_HEADER)hModule;
    	pNTHeader = (PIMAGE_NT_HEADERS)((char*)hModule + pDosHeader->e_lfanew);
    	pExportDir = (PIMAGE_EXPORT_DIRECTORY)((char*)hModule + pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
    	pAddrOfFunctions = (uint32_t*)((char *)hModule + pExportDir->AddressOfFunctions);
    	pAddrOfName = (uint32_t*)((char *)hModule + pExportDir->AddressOfNames);
    	pAddrOfNameOrdinals = (uint32_t *)((char *)hModule + pExportDir->AddressOfNameOrdinals);
    	if (NULL == pExportDir){
    
    		return NULL;
    	}
    
    	for (int i = 0; i < pExportDir->NumberOfNames; i ++) {
    
    		if (pAddrOfName[i] != 0){
    
    			szExportFuncName = (char*)((char*)hModule + pAddrOfName[i]);
    			if (!strcmp(szExportFuncName, szFuncName)) {
    
    				uFuncAddrIndex = pAddrOfNameOrdinals[i];
    				uFuncAddr = pAddrOfFunctions[uFuncAddrIndex];
    				break;
    			}
    		}
    	}
    
    	return ((char*)hModule + uFuncAddr);
    }
    


            这个函数的流程是找到输出表,然后在输出表中找到要导出的函数名,根据函数名来取出函数地址,然后将函数地址返回。最后则是释放加载的库,代码如下
    [AppleScript] 纯文本查看 复制代码
    void	MemFreeLibrary(HMODULE hModule)
    {
    	PIMAGE_DOS_HEADER			pDosHeader = NULL;
    	PIMAGE_NT_HEADERS			pNTHeader = NULL;
    	DLLMAIN pfnDllMain = NULL;
    
    	pDosHeader = (PIMAGE_DOS_HEADER)hModule;
    	pNTHeader = (PIMAGE_NT_HEADERS)((char*)hModule + pDosHeader->e_lfanew);
    	//获取DllMain函数的地址
    	pfnDllMain = (DLLMAIN)GetDllMain((char*)hModule);
    	if (NULL != pfnDllMain){
    
    		pfnDllMain(hModule, DLL_PROCESS_DETACH, NULL);
    	}
    
    	VirtualFree((LPVOID)hModule, 0, MEM_RELEASE);
    }
    


            到了这里,关于内存加载的流程和代码也介绍完了,想要把过程弄明白,需要有一定的PE格式的知识,建议大家边看先熟悉PE的知识,再来看这个,效果会更好
            附件里式写好的代码,感兴趣的可以拿去玩玩
    MemLoadLibrary.zip (173.8 KB, 下载次数: 28)
    回复

    使用道具 举报

  • TA的每日心情
    擦汗
    2019-10-17 06:41
  • 签到天数: 182 天

    [LV.7]常住居民III

    发表于 2016-5-27 07:24:31 | 显示全部楼层
    确切的说学完C/C++还不知道LoadLibrary这个函数,要学完'VC++或者Windows系统编程才知道

    评分

    参与人数 1i币 +8 收起 理由
    wuyan + 8 支持原创

    查看全部评分

    回复 支持 反对

    使用道具 举报

    该用户从未签到

    发表于 2016-5-27 10:24:49 | 显示全部楼层
    我是来水经验的……
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    开心
    2019-8-2 09:32
  • 签到天数: 227 天

    [LV.7]常住居民III

    发表于 2016-5-27 11:47:20 | 显示全部楼层
    看懂你的文章还得学几年?告诉我!
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    开心
    2020-1-10 03:16
  • 签到天数: 1 天

    [LV.1]初来乍到

    发表于 2016-5-27 12:01:49 | 显示全部楼层
    回复 支持 反对

    使用道具 举报

    该用户从未签到

    发表于 2016-5-27 12:58:46 | 显示全部楼层
    我是来水经验的……
    回复 支持 反对

    使用道具 举报

    该用户从未签到

    发表于 2016-5-27 14:21:37 | 显示全部楼层
    非常感谢
    回复 支持 反对

    使用道具 举报

    该用户从未签到

    发表于 2016-5-27 15:00:12 | 显示全部楼层
    支持,看起来还是可以的
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    慵懒
    2017-4-9 19:10
  • 签到天数: 149 天

    [LV.7]常住居民III

    发表于 2016-5-27 15:02:41 | 显示全部楼层
    不觉明历
    回复

    使用道具 举报

    该用户从未签到

    发表于 2016-5-27 15:23:09 | 显示全部楼层
    我是来水经验的……
    回复 支持 反对

    使用道具 举报

    您需要登录后才可以回帖 登录 | 注册

    本版积分规则

    快速回复 返回顶部 返回列表