查看: 644753|回复: 3435

dll内存加载的实现

[复制链接]
发表于 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)
回复

使用道具 举报

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

评分

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

查看全部评分

回复 支持 反对

使用道具 举报

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

使用道具 举报

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

使用道具 举报

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

使用道具 举报

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

使用道具 举报

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

使用道具 举报

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

使用道具 举报

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

使用道具 举报

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

使用道具 举报

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

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

旗下站点

邮箱系统

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2025-3-7 02:58 , Processed in 0.034671 second(s), 14 queries , Gzip On, MemCache On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部