1、一种保护应用程序的方法 模拟 Windows PE 加载器,从内存资源中加载 DLL1、前言目前很多敏感和重要的 DLL(Dynamic-link library) 都没有提供静态版本供编译器进行静态连接(.lib 文件),即使提供了静态版本也因为兼容性问题导致无法使用,而只提供 DLL 版本,并且很多专业软件的授权部分的 API,都是单独提供一个 DLL 来完成,而主模块通过调用 DLL 中的接口来完成授权功能。虽然这些软件一般都采用了加壳和反调试等保护,但是一旦这些功能失去作用,比如脱壳,反反调试,HOOK API 或者干脆写一个仿真的授权 DLL(模拟授权 DLL 的所有导出函数接口)
2、,然后仿真的 DLL 再调用授权 DLL,这样所有的输入首先被仿真DLL 截获再传递给授权 DLL,而授权 DLL 的输出也首先传递给仿真 DLL 再传递给主程序,这样就可以轻易的监视二者之间的输入输出之间的关系,从而轻易的截获 DLL 中的授权信息进行修改再返回给主程序。 2、目前隐式调用敏感 DLL 中可能存在的安全隐患以下通过两个软件的授权 DLL 来说明这种问题的严重性。如下是两个软件中授权 DLL 的部分信息,如下图所示:(图)通过工具 OllyICE 可以轻易的看出 IoMonitor.exe 调用授权 DLL(XKeyAPI.DLL),这样就很容易在调用这些 API 的地方设置断
3、点,然后判断输入输出的关系,从而达到破解的目的。 (图)通过工具 OllyICE 可以轻易的看出 sfeng.DLL 中导出了很多函数,其中含义也很明显。GetHDID 获取硬盘的ID,GetCpuId 获取 cpu 的 ID,WinAntiDebug 反调试接口。而这些都是主程序需要调用的,比如:主程序通过GetHDID 来获取硬盘编码,以这个硬盘 ID 的伪码来生成授权码,破解者很容易修改这些接口的输出值或者干脆写一个 sfeng.DLL 来导出跟目标 sfeng.DLL 一模一样的导出函数,而主程序却完全不知晓。只要用户有一套授权码就可以让 GetHDID 不管什么机器都返回一样的值,从
4、而达到任何机器都可以使用同一套授权码。 (图) 如上图所示,直接修改 DLL 中函数 GetHDID(RVA 地址:0093FF3C 开始)的实现,让它直接返回固定的硬盘 ID 就可以达到一个授权到处使用的目的。其中:”WD-Z=AM9N086529ksaiy” 为需要返回的已经授权的硬盘 ID,我们直接返回这个值即可。把原来 0093FF3C 部分的代码用 nop 替换掉,添加 Call 008FFF60,后面添加字符串”WD-Z=AM9N086529ksaiy”,Call 008FFF60 之后,ESP=Call 后的返回地址(Call 指令的下一行),也就是字符串”WD-Z=AM9N08
5、6529ksaiy”的首地址,然后 pop EAX 后,返回值就是字符串的首地址,通过这种简单的修改就可以达到破解的目的,说明这种隐式的调用是非常危险的。 3、模拟 Windows PE 加载器,从资源中加载 DLL本文主要介绍将 DLL 文件进行加密压缩后存放在程序的资源段,然后在程序中读取资源段数据进行解压和解密工作后,从内存中加载这个 DLL,然后模拟 PE 加载器完成 DLL 的加载过程。本文主要以 Visual C+ 6.0 为工具进行介绍,其它开发工具实现过程与此类似。 这样作的好处也很明显,DLL 文件存放在主程序的资源段,而且经过了加密压缩处理,破解者很难找到下断点的地方,也不
6、能轻易修改资源 DLL,因为只有主程序完成解压和解密工作,完成 PE 加载工作后此 DLL 才开始工作。 我们知道,要显式加载一个 DLL,并取得其中导出的函数地址一般是通过如下步骤:(1) 用 LoadLibrary 加载 DLL 文件,获得该 DLL 的模块句柄;(2) 定义一个函数指针类型,并声明一个变量;(3) 用 GetProcAddress 取得该 DLL 中目标函数的地址,赋值给函数指针变量;(4) 调用函数指针变量。这个方法要求 DLL 文件位于硬盘上面 ,而我们的 DLL 现在在内存中。现在假设我们的 DLL 已经位于内存中,比如通过脱壳、解密或者解压缩得到,能不能不把它写入
7、硬盘文件,而直接从内存加载呢?答案是肯定的,方法就是完成跟 Windows PE 加载器同样的工作即可。 加载过程大致包括以下几个部分: 1、调用 API 读取 DLL 资源数据拷贝到内存中 2、调用解压和解密函数对内存中的 DLL 进行处理 3、检查 DOS 头和 PE 头判断是否为合法的 PE 格式 4、计算加载该 DLL 所需的虚拟地址空间大小 5、向操作系统申请指定大小的虚拟地址空间并提交 6、将 DLL 数据复制到所分配的虚拟内存块中,注意文件段对齐方式和内存段对齐方式 7、对每个 DLL 文件来说都存在一个重定位节(.reloc) ,用于记录 DLL 文件的重定位信息,需要处理重定
8、位信息8、读取 DLL 的引入表部分,加载引入表部分需要的 DLL,并填充需要的函数入口的真实地址 9、根据 DLL 每个节的属性设置其对应内存页的读写属性 10、调用入口函数 DLLMain,完成初始化工作 11、保存 DLL 的基地址(即分配的内存块起始地址) ,用于查找 DLL 的导出函数 12、不需要 DLL 的时候,释放所分配的虚拟内存,释放所有动态申请的内存 以下部分分别介绍这几个步骤,以改造过的网上下载的 CMemLoadDLL 类为例程(原类存在几个错误的地方) A. 调用 API 读取 DLL 资源数据拷贝到内存中 /加载资源 DLL #define strKey (char
9、)0x15 char DLLtype4=D strKey ,l strKey,l strKey,0x00; HINSTANCE hinst=AfxGetInstanceHandle(); HRSRC hr=NULL; HGLOBAL hg=NULL; /对资源名称字符串进行简单的异或操作,达到不能通过外部字符串参考下断点 for(int i=0;ie_magic != IMAGE_DOS_SIGNATURE) return FALSE; /0*5A4D : MZ /检查长度 if(DWORD)DataLength e_lfanew + sizeof(IMAGE_NT_HEADERS) ) re
10、turn FALSE; /取得 pe 头 pNTHeader = (PIMAGE_NT_HEADERS)( (unsigned long)lpFileData + pDosHeader-e_lfanew); / PE 头 /检查 pe 头的合法性 if(pNTHeader-Signature != IMAGE_NT_SIGNATURE) return FALSE; /0*00004550 : PE00 if(pNTHeader-FileHeader.Characteristics if(pNTHeader-FileHeader.Characteristics if(pNTHeader-File
11、Header.SizeOfOptionalHeader != sizeof(IMAGE_OPTIONAL_HEADER) return FALSE; /取得节表(段表) pSectionHeader = (PIMAGE_SECTION_HEADER)(int)pNTHeader + sizeof(IMAGE_NT_HEADERS); /验证每个节表的空间 for(int i=0; iFileHeader.NumberOfSections; i+) if(pSectionHeaderi.PointerToRawData + pSectionHeaderi.SizeOfRawData) (DWOR
12、D)DataLength)return FALSE; return TRUE; D. 计算加载该 DLL 所需的虚拟地址空间大小计算整个 DLL 映像文件的尺寸,最大映像尺寸应该为 VOffset 最大的一个段的 VOffset+VSize,然后补齐段对齐即可。如下图中,最大映像尺寸应该为 0x0000D000+0x00000DA6,然后按段对齐(如为:0x1000 对齐)则结果为0x0000E000。其中 DOS Header 和 PE Header 就占用 0x1000 字节,代码段.text 从 0x1000 开始占用了 0x7000字节。 段名称 虚拟地址 虚拟大小 物理地址 物理大小
13、 标志 int CMemLoadDLL:CalcTotalImageSize() int Size; if(pNTHeader = NULL)return 0; int nAlign = pNTHeader-OptionalHeader.SectionAlignment; /段对齐字节数 / 计算所有头的尺寸。包括 dos, coff, pe 头 和 段表的大小 Size = GetAlignedSize(pNTHeader-OptionalHeader.SizeOfHeaders, nAlign); / 计算所有节的大小 for(int i=0; i FileHeader.NumberOfS
14、ections; +i) /得到该节的大小 int CodeSize = pSectionHeaderi.Misc.VirtualSize ; int LoadSize = pSectionHeaderi.SizeOfRawData; int MaxSize = (LoadSize CodeSize)?(LoadSize):(CodeSize); int SectionSize = GetAlignedSize(pSectionHeaderi.VirtualAddress + MaxSize, nAlign); if(Size OptionalHeader.SizeOfHeaders; int
15、 SectionSize = pNTHeader-FileHeader.NumberOfSections * sizeof(IMAGE_SECTION_HEADER); int MoveSize = HeaderSize + SectionSize; /复制头和段信息 memmove(pDest, pSrc, MoveSize); /复制每个节 for(int i=0; i FileHeader.NumberOfSections; +i) if(pSectionHeaderi.VirtualAddress = 0 | pSectionHeaderi.SizeOfRawData = 0) con
16、tinue; / 定位该节在内存中的位置 void *pSectionAddress = (void *)(unsigned long)pDest + pSectionHeaderi.VirtualAddress); / 复制段数据到虚拟内存 memmove(void *)pSectionAddress, (void *)(DWORD)pSrc + pSectionHeaderi.PointerToRawData), pSectionHeaderi.SizeOfRawData); /修正指针,指向新分配的内存 /新的 dos 头 pDosHeader = (PIMAGE_DOS_HEADER)
17、pDest; /新的 pe 头地址 pNTHeader = (PIMAGE_NT_HEADERS)(int)pDest + (pDosHeader-e_lfanew); /新的节表地址 pSectionHeader = (PIMAGE_SECTION_HEADER)(int)pNTHeader + sizeof(IMAGE_NT_HEADERS); return ; G. 每个 DLL 文件来说都存在一个重定位节(.reloc) ,用于记录 DLL 文件的重定位信息,需要处理重定位信息 Windows 加载 DLL 时就可以按照该节的信息对需要重定位的地址进行修正,在 32 位代码中,凡涉及到
18、直接寻址的指令都是需要重定位的,而 PE 文件的的(.reloc)段则是可选的,因为 PE 文件一般都可以加载到默认地址(如:0x00400000)。当然系统的 DLL 其默认加载地址都能满足要求,因为这些 DLL 都在系统加载其它程序前首先被加载(如:Kernel32.DLL,User32.DLL)等。 对于操作系统来说,其任务就是在对可执行程序透明的情况下完成重定位操作,在现实中,重定位信息是在编译的时候由编译器生成并被保留在可执行文件中的,在程序被执行前由操作系统根据重定位信息修正代码,这样在开发程序的时候就不用考虑重定位问题了。 重定位信息在 DLL 文件中被存放在重定位表中,重定位的
19、算法可以描述为:将直接寻址指令中的双字地址加上模块实际装入地址与模块建议装入地址之差。为了进行这个运算,需要有 3 个数据,首先是需要修正的机器码地址;其次是模块的建议装入地址;最后是模块的实际装入地址。 在这 3 个数据中,模块的建议装入地址已经在 PE 文件头中定义了(编译后就已经确定) ,而模块的实际装入地址是 Windows 装载器确定的,到装载文件的时候自然会知道,所以被保存在重定位表中的仅仅是需要修正的代码的地址。 事实上正是如此,DLL 文件的重定位表中保存的就是一大堆需要修正的代码的地址。 重定位表一般会被单独存放在一个可丢弃的以“.reloc”命名的节中,但是这并不是必然的,
20、因为重定位表放在其他节中也是合法的,惟一可以肯定的是,假如重定位表存在的话,它的地址肯定可以在 DLL 文件头中的数据目录中找到。重定位表的位置和大小可以从数据目录中的第 6 个 IMAGE_DATA_DIRECTORY 结构中获取,虽然重定位表中的有用数据是那些需要重定位机器码的地址指针,但为了节省空间,DLL 文件对存放的方式做了一些优化。 在正常的情况下,每个 32 位的指针占用 4 个字节,假如有 n 个重定位项,那么重定位表的总大小是 4n 字节大小。 直接寻址指令在程序中还是比较多的,在比较靠近的重定位表项中,32 位指针的高位地址总是相同的,假如把这些相近表项的高位地址统一表示,
21、那么就可以省略一部分的空间,当按照一个内存页来分割时,在一个页面中寻址需要的指针位数是 12 位(一页等于 4096 字节,等于 2 的 12 次方) ,假如将这 12 位凑齐 16 位放入一个字类型的数据中,并用一个附加的双字来表示页的起始指针,另一个双字来表示本页中重定位项数的话,那么占用的总空间会是 442n 字节大 小,计算一下就可以发现,当某个内存页中的重定位项多于 4 项的时候,后一种方法的占用空间就会比前面的方法要小。 / 重定向 PE 用到的地址 void CMemLoadDLL:DoRelocation( void *NewBase) /* 重定位表的结构: / DWORD
22、sectionAddress, DWORD size (包括本节需要重定位的数据) / 例如 1000 节需要修正 5 个重定位数据的话,重定位表的数据是 / 00 10 00 00 14 00 00 00 xxxx xxxx xxxx xxxx xxxx 0000 / - / 给出节的偏移 总尺寸=8+6*2 需要修正的地址 用于对齐 4 字节 / 重定位表是若干个相连,如果 address 和 size 都是 0 表示结束 / 需要修正的地址是 12 位的,高 4 位是形态字,intel cpu 下是 3 */ /假设 NewBase 是 0600000,而文件中设置的缺省 ImageBa
23、se 是 0400000,则修正偏移量就是 0200000 DWORD Delta = (DWORD)NewBase - pNTHeader-OptionalHeader.ImageBase; /注意重定位表的位置可能和硬盘文件中的偏移地址不同,应该使用加载后的地址 PIMAGE_BASE_RELOCATION pLoc = (PIMAGE_BASE_RELOCATION)(unsigned long)NewBase + pNTHeader-OptionalHeader.DataDirectoryIMAGE_DIRECTORY_ENTRY_BASERELOC.VirtualAddress);
24、while(pLoc-VirtualAddress + pLoc-SizeOfBlock) != 0) /开始扫描重定位表 WORD *pLocData = (WORD *)(int)pLoc + sizeof(IMAGE_BASE_RELOCATION); /计算本节需要修正的重定位项(地址)的数目 int NumberOfReloc = (pLoc-SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)/sizeof(WORD); for( int i=0 ; i VirtualAddress = 01000; / pLocDatai = 0313E; 表示
25、本节偏移地址 013E 处需要修正 / 因此 pAddress = 基地址 + 0113E / 里面的内容是 A1 ( 0c d4 02 10) 汇编代码是: mov eax , 1002d40c / 需要修正 1002d40c 这个地址 DWORD * pAddress = (DWORD *)(unsigned long)NewBase + pLoc-VirtualAddress + (pLocDatai *pAddress += Delta; /转移到下一个节进行处理 pLoc = (PIMAGE_BASE_RELOCATION)(DWORD)pLoc + pLoc-SizeOfBlock
26、); H. 读取 DLL 的引入表部分,加载引入表部分需要的 DLL,并填充需要的函数入口的真实地址 对引入表中的 DLL,通过 GetModuleHandle 获得其加载基地址,如果这些 DLL 在加载本 DLL 之前还没有加载,那么先调用 LoadLibrary 进行加载,如果加载失败则不能继续处理直接报错,说明找不到依赖的 DLL。 /填充引入地址表 BOOL CMemLoadDLL:FillRavAddress(void *pImageBase) / 引入表实际上是一个 IMAGE_IMPORT_DESCRIPTOR 结构数组,全部是 0 表示结束 / 数组定义如下: / / DWOR
27、D originalFirstThunk; / 0 表示结束,否则指向未绑定的 IAT 结构数组 / DWORD TimeDateStamp; / DWORD ForwarderChain; / -1 if no forwarders / DWORD Name; / 给出 DLL 的名字 / DWORD FirstThunk; / 指向 IAT 结构数组的地址(绑定后,这些 IAT 里面就是实际的函数地址) unsigned long Offset = pNTHeader-OptionalHeader.DataDirectoryIMAGE_DIRECTORY_ENTRY_IMPORT.Virt
28、ualAddress ; if(Offset = 0) return TRUE; /No Import Table PIMAGE_IMPORT_DESCRIPTOR pID = (PIMAGE_IMPORT_DESCRIPTOR)(unsigned long) pImageBase + Offset); while(pID-Characteristics != 0 ) PIMAGE_THUNK_DATA pRealIAT = (PIMAGE_THUNK_DATA)(unsigned long)pImageBase + pID-FirstThunk); PIMAGE_THUNK_DATA pOr
29、iginalIAT = (PIMAGE_THUNK_DATA)(unsigned long)pImageBase + pID-OriginalFirstThunk); /获取 DLL 的名字 char buf256; /DLL name; /修改,需要将 buf 清零,否则 DLL 名称不对 memset(buf,0,sizeof(buf); BYTE* pName = (BYTE*)(unsigned long)pImageBase + pID-Name); for(int i=0;iHint !=0) / lpFunction = GetProcAddress(hDLL, (LPCSTR)
30、pByName-Hint); / else lpFunction = GetProcAddress(hDLL, (char *)pByName-Name); if(lpFunction != NULL) /找到了! pRealIATi.u1.Function = (PDWORD) lpFunction; else return FALSE; /move to next pID = (PIMAGE_IMPORT_DESCRIPTOR)( (DWORD)pID + sizeof(IMAGE_IMPORT_DESCRIPTOR); return TRUE; I. 根据 DLL 每个节的属性设置其对应
31、内存页的读写属性 修改段属性。应该根据每个段的属性单独设置其对应内存页的属性。这里简化一下。 统一设置成一个属性 PAGE_EXECUTE_READWRITE,如果代码段没有执行属性,调用的时候会产生异常,页属性的设置单位至少为一个页。 unsigned long old; VirtualProtect(pMemoryAddress, ImageSize, PAGE_EXECUTE_READWRITE, J. 调用入口函数 DLLMain,完成初始化工作 接下来要调用一下 DLL 的入口函数,做初始化工作 ,每个 PE 文件都有一个 OEP, 它就是 AddressOfEntryPoint,一
32、切代码都是从这里开始,OEP+DLL 基地址就是其真实入口地址,当然这个入口地址一般都不是你所写的main 或者 DLLMain,而是运行库提供的一段代码,先完成全局变量的一些初始化和库函数相关的初始化等,而这段代码最后会调用真正的 main 或者 DLLMain。 pDLLMain = (ProcDLLMain)(pNTHeader-OptionalHeader.AddressOfEntryPoint +(DWORD) pMemoryAddress); BOOL InitResult = pDLLMain(HINSTANCE)pMemoryAddress,DLL_PROCESS_ATTACH
33、,0); if(!InitResult) /初始化失败 pDLLMain(HINSTANCE)pMemoryAddress,DLL_PROCESS_DETACH,0); VirtualFree(pMemoryAddress,0,MEM_RELEASE); pDLLMain = NULL; return FALSE; K. 保存 DLL 的基地址(即分配的内存块起始地址),用于查找 DLL 的导出函数 /修正基地址 pNTHeader-OptionalHeader.ImageBase = (DWORD)pMemoryAddress; /MemGetProcAddress 函数从 dll 中获取指
34、定函数的地址 /返回值: 成功返回函数地址 , 失败返回 NULL /lpProcName: 要查找函数的名字或者序号 FARPROC CMemLoadDll:MemGetProcAddress(LPCSTR lpProcName) if(pNTHeader-OptionalHeader.DataDirectoryIMAGE_DIRECTORY_ENTRY_EXPORT.VirtualAddress = 0 | pNTHeader-OptionalHeader.DataDirectoryIMAGE_DIRECTORY_ENTRY_EXPORT.Size = 0) return NULL; if
35、(!isLoadOk) return NULL; DWORD OffsetStart = pNTHeader-OptionalHeader.DataDirectoryIMAGE_DIRECTORY_ENTRY_EXPORT.VirtualAddress; DWORD Size = pNTHeader-OptionalHeader.DataDirectoryIMAGE_DIRECTORY_ENTRY_EXPORT.Size; PIMAGE_EXPORT_DIRECTORY pExport = (PIMAGE_EXPORT_DIRECTORY)(DWORD)pImageBase + pNTHead
36、er-OptionalHeader.DataDirectoryIMAGE_DIRECTORY_ENTRY_EXPORT.VirtualAddress); int iBase = pExport-Base; int iNumberOfFunctions = pExport-NumberOfFunctions; int iNumberOfNames = pExport-NumberOfNames; /AddressOfFunctions + pImageBase); LPWORD pAddressOfOrdinals = (LPWORD)(pExport-AddressOfNameOrdinals
37、 + pImageBase); LPDWORD pAddressOfNames = (LPDWORD)(pExport-AddressOfNames + pImageBase); int iOrdinal = -1; if(DWORD)lpProcName else /use name int iFound = -1; for(int i=0;i= 0) iOrdinal = (int)(pAddressOfOrdinalsiFound); if(iOrdinal = iNumberOfFunctions ) return NULL; else DWORD pFunctionOffset =
38、pAddressOfFunctionsiOrdinal; if(pFunctionOffset OffsetStart class CMemLoadDll public: CMemLoadDll(); virtual CMemLoadDll(); BOOL MemLoadLibrary( void* lpFileData , int DataLength); / Dll file data buffer FARPROC MemGetProcAddress(LPCSTR lpProcName); private: BOOL isLoadOk; BOOL CheckDataValide(void*
39、 lpFileData, int DataLength); int CalcTotalImageSize(); void CopyDllDatas(void* pDest, void* pSrc); BOOL FillRavAddress(void* pBase); void DoRelocation(void* pNewBase); int GetAlignedSize(int origin, int Alignment); private: ProcDllMain pDllMain; private: DWORD pImageBase; PIMAGE_DOS_HEADER pDosHead
40、er; PIMAGE_NT_HEADERS pNTHeader; PIMAGE_SECTION_HEADER pSectionHeader; ; #endif / !defined(AFX_MEMLOADDLL_H_E1F5150A_B534_4940_9FBF_1E6CA0E50576_INCLUDED_) / MemLoadDll.cpp: implementation of the CMemLoadDll class. / / #include “stdafx.h“ #include “MemLoadDll.h“ #ifdef _DEBUG #undef THIS_FILE static
41、 char THIS_FILE=_FILE_; #define new DEBUG_NEW #endif / / Construction/Destruction / CMemLoadDll:CMemLoadDll() isLoadOk = FALSE; pImageBase = NULL; pDllMain = NULL; CMemLoadDll:CMemLoadDll() if(isLoadOk) ASSERT(pImageBase != NULL); ASSERT(pDllMain != NULL); /脱钩,准备卸载 dll pDllMain(HINSTANCE)pImageBase,
42、DLL_PROCESS_DETACH,0); VirtualFree(LPVOID)pImageBase, 0, MEM_RELEASE); /MemLoadLibrary 函数从内存缓冲区数据中加载一个 dll 到当前进程的地址空间,缺省位置 010000000 /返回值: 成功返回 TRUE , 失败返回 FALSE /lpFileData: 存放 dll 文件数据的缓冲区 /DataLength: 缓冲区中数据的总长度 BOOL CMemLoadDll:MemLoadLibrary(void* lpFileData, int DataLength) if(pImageBase != NU
43、LL) return FALSE; /已经加载一个 dll,还没有释放,不能加载新的 dll /检查数据有效性,并初始化 if(!CheckDataValide(lpFileData, DataLength)return FALSE; /计算所需的加载空间 int ImageSize = CalcTotalImageSize(); if(ImageSize = 0) return FALSE; / 分配虚拟内存 /void *pMemoryAddress = VirtualAlloc(LPVOID)0x10000000, ImageSize,MEM_COMMIT|MEM_RESERVE, PA
44、GE_EXECUTE_READWRITE); /修改,不指定 dll 基址申请内存 void *pMemoryAddress = VirtualAlloc(LPVOID)NULL, ImageSize,MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE); if(pMemoryAddress = NULL) return FALSE; else CopyDllDatas(pMemoryAddress, lpFileData); /复制 dll 数据,并对齐每个段 /重定位信息 if(pNTHeader-OptionalHeader.DataDirect
45、oryIMAGE_DIRECTORY_ENTRY_BASERELOC.VirtualAddress 0 /填充引入地址表 if(!FillRavAddress(pMemoryAddress) /修正引入地址表失败 VirtualFree(pMemoryAddress,0,MEM_RELEASE); return FALSE; /修改页属性。应该根据每个页的属性单独设置其对应内存页的属性。这里简化一下。 /统一设置成一个属性 PAGE_EXECUTE_READWRITE unsigned long old; VirtualProtect(pMemoryAddress, ImageSize, PA
46、GE_EXECUTE_READWRITE, /修正基地址 pNTHeader-OptionalHeader.ImageBase = (DWORD)pMemoryAddress; /接下来要调用一下 dll 的入口函数,做初始化工作。 pDllMain = (ProcDllMain)(pNTHeader-OptionalHeader.AddressOfEntryPoint +(DWORD) pMemoryAddress); BOOL InitResult = pDllMain(HINSTANCE)pMemoryAddress,DLL_PROCESS_ATTACH,0); if(!InitResu
47、lt) /初始化失败 pDllMain(HINSTANCE)pMemoryAddress,DLL_PROCESS_DETACH,0); VirtualFree(pMemoryAddress,0,MEM_RELEASE); pDllMain = NULL; return FALSE; isLoadOk = TRUE; pImageBase = (DWORD)pMemoryAddress; return TRUE; /MemGetProcAddress 函数从 dll 中获取指定函数的地址 /返回值: 成功返回函数地址 , 失败返回 NULL /lpProcName: 要查找函数的名字或者序号 F
48、ARPROC CMemLoadDll:MemGetProcAddress(LPCSTR lpProcName) if(pNTHeader-OptionalHeader.DataDirectoryIMAGE_DIRECTORY_ENTRY_EXPORT.VirtualAddress = 0 | pNTHeader-OptionalHeader.DataDirectoryIMAGE_DIRECTORY_ENTRY_EXPORT.Size = 0) return NULL; if(!isLoadOk) return NULL; DWORD OffsetStart = pNTHeader-OptionalHeader.DataDirectoryI