第20章 DLL高级技术
一、DLL模块的显示载入和符号链接
在任何时候,进程中的一个线程可以调用下面两个函数来将一个DLL映射到进程的地址空间中:
HMODULE LoadLibrary(PCTSTR pszDLLPathName);
HMODULE LoadLibraryEx(PCTSTR pszDLLPathName, HANDLE hFile, DWORD dwFlags);
这两个函数会(根据19章提到的搜索顺序)在用户的系统中对DLL的文件映像进行定位,并试图将该文件映像映射到调用进程的地址空间中。两个函数返回的HMODULE表示文件映像被映射到的虚拟内存地址。注意,这两个函数返回的是HMOUDULE值,这个类型等价于HINSTANCE,两者可以换用。
LoadLibraryEx函数有两个额外的参数:hFile和dwFlags。参数hFile是为将来扩充所保留的,现在必须将它设为NULL。参数dwFlags可以被设为0,或下列标志的组合:
1、DONT_RESOLVE_DLL_REFERENCES标志,告诉系统只需将DLL映射到调用进程的地址空间。让系统只映射文件映像,不要调用DllMain。同时,系统不会讲那些额外的DLL(该DLL模块所需要包含的其他DLL)自动载入到进程的地址空间中。这样调用DLL到处的任何函数时,会面临很大的风险:代码所依赖的内部数据结构可能尚未初始化,或者代码所引用的DLL尚未载入。
2、LOAD_LIBRARY_AS_DATAFILE标志,告诉系统将DLL作为数据文件映射到进程的地址空间中。就只对文件进行映射这点而言,和上一个标志相似,系统不会花费额外的时间来准备执行文件中的任何代码。当系统将一个DLL映射到进程的地址空间中的时候,会检查DLL中的一些信息来决定应该给文件中不同的段指定何种页面保护属性。如果不指定这个标志,那么系统会认为需要执行文件中的代码,并用相应的方式来设置页面保护属性。举个例子,例如一个DLL使用这个标志载入的,那么对这个DLL调用GetProcessAddress的时候,返回值将是NULL,而GetLastError将会返回ERROR_MOD_NOT_FOUND。
该标志非常有用,可以加载资源文件使用,从DLL中,或者另一个EXE文件中。
3、LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE标志,和上个标志相似,唯一不同之处在于DLL文件是以独占访问模式打开的,从而禁止任何其他应用程序在当前应用程序使用该DLL文件的时候对其进行修改。该标志能提供更好的安全性。
4、LAOD_LIBRARY_AS_IMAGE_RESOURCE标志,与标志2相似,但也有一点略有不同:当系统载入DLL的时候,会对相对虚拟地址(relative virtual address,简称RVA)进行修复。这样,RVA就可以直接使用,而不必再根据DLL载入到的内存地址来对它们进程转换了。当需要对DLL进行解析来遍历其中的PE(protable executable)段时,这个标志特别有用。
5、LOAD_WITH_ALTERED_SEARCH_PATH标志,用来改变LoadLibraryEx在对指定的DLL进行定位时所使用的搜索算法。通常LoadLibraryEx会更具运行可执行模块中列出的顺序来搜索文件。但是指定了这个标志后,函数会根据传给pszDLLPathName参数的值,用三种不同的算法来搜索文件。
a、不包含\字符,那么使用19章的标准搜索路径来对DLL进行搜索。
b、如果pszDLLPathName包含\字符,那么取决于该路径是全路径还是相对路径。全路径或网络共享路径(\\server\share\BetaBinLibrary.dll)的话,函数会试图直接载入该DLL文件。如果对应的文件不存在,那么直接返回NULL,这时错误码为ERROR_MOD_NOT_FOUND。如果是相对路径的话,会把下面文件夹与pszDLLPathName连接起来:进程的当前目录、Windows的系统目录(system32)、16位Windows系统目录(system)、Windows目录、PATH环境变量总列出的目录。如果参数中出现“.”或“..”,那恶魔在搜索过程中的每一个步骤都会将它们考虑在内来构建一个相对路径。例如,如果将TEXT("..\\BetaBinLibrary.dll")作为参数传入,那么函数会在下列位置搜索BetaBinLibrary.dll:保护当前目录的文件夹、包含Windows的系统目录的文件夹(即Windows目录)、包含16位Windows系统目录的文件夹、保护Windows目录的文件夹(通常是磁盘的根目录)、PATH环境变量中列出的每个目录的上一层文件夹。
c、在构建应用程序的时候,如果不希望用LOAD_WITH_ALTERED_SERACH_PATH标志来调用LoadLibraryEx,或者不希望改变英语欧冠呢程序的当前目录,而是希望让它从一个众所周知的文件夹中动态地载入DLL,那么应该调用SetDllDirectory,并将程序库所在的文件夹作为参数传入。这个函数告诉LoadLibrary和LoadLibraryEx在搜素的时候使用下面的算法:进程的当前目录、通过SetDllDirectory所设置的文件夹、Windows的系统目录、16位Windows系统目录、Windows目录和PATH环境变量中列出的目录。该搜索算法允许我们将应用程序和共享的DLL保存在一个预先定义好的目录中,由于应用程序的当前目录是可以设置的,比如在快捷方式中,因此该算法可以避免从应用程序的当前目录中意外地载入同名DLL的风险。
6、LOAD_IGNORE_CODE_AUTHZ_LEVEL标志,用来关闭WinSafer所提供的验证,是在XP中引入的,其设计目的是为了对代码在执行过程中可以拥有的特权加以控制。UAC特性已经取代了这项特性。
通过FreeLibrary函数可以显式地将DLL从进程的地址空间中卸载,传入的HMODULE值用来标识我们想要卸载的DLL。
还有个FreeLibraryAndExitThread函数,在Kernel32.dll中实现如下:
VOID FreeLibraryAndExitThread(HMODULE hInstDll, DWORD dwExitCode)
{
FreeLibrary(hInstDll);
一、DLL模块的显示载入和符号链接
在任何时候,进程中的一个线程可以调用下面两个函数来将一个DLL映射到进程的地址空间中:
HMODULE LoadLibrary(PCTSTR pszDLLPathName);
HMODULE LoadLibraryEx(PCTSTR pszDLLPathName, HANDLE hFile, DWORD dwFlags);
这两个函数会(根据19章提到的搜索顺序)在用户的系统中对DLL的文件映像进行定位,并试图将该文件映像映射到调用进程的地址空间中。两个函数返回的HMODULE表示文件映像被映射到的虚拟内存地址。注意,这两个函数返回的是HMOUDULE值,这个类型等价于HINSTANCE,两者可以换用。
LoadLibraryEx函数有两个额外的参数:hFile和dwFlags。参数hFile是为将来扩充所保留的,现在必须将它设为NULL。参数dwFlags可以被设为0,或下列标志的组合:
1、DONT_RESOLVE_DLL_REFERENCES标志,告诉系统只需将DLL映射到调用进程的地址空间。让系统只映射文件映像,不要调用DllMain。同时,系统不会讲那些额外的DLL(该DLL模块所需要包含的其他DLL)自动载入到进程的地址空间中。这样调用DLL到处的任何函数时,会面临很大的风险:代码所依赖的内部数据结构可能尚未初始化,或者代码所引用的DLL尚未载入。
2、LOAD_LIBRARY_AS_DATAFILE标志,告诉系统将DLL作为数据文件映射到进程的地址空间中。就只对文件进行映射这点而言,和上一个标志相似,系统不会花费额外的时间来准备执行文件中的任何代码。当系统将一个DLL映射到进程的地址空间中的时候,会检查DLL中的一些信息来决定应该给文件中不同的段指定何种页面保护属性。如果不指定这个标志,那么系统会认为需要执行文件中的代码,并用相应的方式来设置页面保护属性。举个例子,例如一个DLL使用这个标志载入的,那么对这个DLL调用GetProcessAddress的时候,返回值将是NULL,而GetLastError将会返回ERROR_MOD_NOT_FOUND。
该标志非常有用,可以加载资源文件使用,从DLL中,或者另一个EXE文件中。
3、LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE标志,和上个标志相似,唯一不同之处在于DLL文件是以独占访问模式打开的,从而禁止任何其他应用程序在当前应用程序使用该DLL文件的时候对其进行修改。该标志能提供更好的安全性。
4、LAOD_LIBRARY_AS_IMAGE_RESOURCE标志,与标志2相似,但也有一点略有不同:当系统载入DLL的时候,会对相对虚拟地址(relative virtual address,简称RVA)进行修复。这样,RVA就可以直接使用,而不必再根据DLL载入到的内存地址来对它们进程转换了。当需要对DLL进行解析来遍历其中的PE(protable executable)段时,这个标志特别有用。
5、LOAD_WITH_ALTERED_SEARCH_PATH标志,用来改变LoadLibraryEx在对指定的DLL进行定位时所使用的搜索算法。通常LoadLibraryEx会更具运行可执行模块中列出的顺序来搜索文件。但是指定了这个标志后,函数会根据传给pszDLLPathName参数的值,用三种不同的算法来搜索文件。
a、不包含\字符,那么使用19章的标准搜索路径来对DLL进行搜索。
b、如果pszDLLPathName包含\字符,那么取决于该路径是全路径还是相对路径。全路径或网络共享路径(\\server\share\BetaBinLibrary.dll)的话,函数会试图直接载入该DLL文件。如果对应的文件不存在,那么直接返回NULL,这时错误码为ERROR_MOD_NOT_FOUND。如果是相对路径的话,会把下面文件夹与pszDLLPathName连接起来:进程的当前目录、Windows的系统目录(system32)、16位Windows系统目录(system)、Windows目录、PATH环境变量总列出的目录。如果参数中出现“.”或“..”,那恶魔在搜索过程中的每一个步骤都会将它们考虑在内来构建一个相对路径。例如,如果将TEXT("..\\BetaBinLibrary.dll")作为参数传入,那么函数会在下列位置搜索BetaBinLibrary.dll:保护当前目录的文件夹、包含Windows的系统目录的文件夹(即Windows目录)、包含16位Windows系统目录的文件夹、保护Windows目录的文件夹(通常是磁盘的根目录)、PATH环境变量中列出的每个目录的上一层文件夹。
c、在构建应用程序的时候,如果不希望用LOAD_WITH_ALTERED_SERACH_PATH标志来调用LoadLibraryEx,或者不希望改变英语欧冠呢程序的当前目录,而是希望让它从一个众所周知的文件夹中动态地载入DLL,那么应该调用SetDllDirectory,并将程序库所在的文件夹作为参数传入。这个函数告诉LoadLibrary和LoadLibraryEx在搜素的时候使用下面的算法:进程的当前目录、通过SetDllDirectory所设置的文件夹、Windows的系统目录、16位Windows系统目录、Windows目录和PATH环境变量中列出的目录。该搜索算法允许我们将应用程序和共享的DLL保存在一个预先定义好的目录中,由于应用程序的当前目录是可以设置的,比如在快捷方式中,因此该算法可以避免从应用程序的当前目录中意外地载入同名DLL的风险。
6、LOAD_IGNORE_CODE_AUTHZ_LEVEL标志,用来关闭WinSafer所提供的验证,是在XP中引入的,其设计目的是为了对代码在执行过程中可以拥有的特权加以控制。UAC特性已经取代了这项特性。
通过FreeLibrary函数可以显式地将DLL从进程的地址空间中卸载,传入的HMODULE值用来标识我们想要卸载的DLL。
还有个FreeLibraryAndExitThread函数,在Kernel32.dll中实现如下:
VOID FreeLibraryAndExitThread(HMODULE hInstDll, DWORD dwExitCode)
{
FreeLibrary(hInstDll);