浅谈动态链接库(DLL)的显式链接和隐式链接

0 动态链接库

简介

动态链接库(dynamic-link library,DLL)是Windows操作系统的基石,Windows系统所有的应用程序编程接口都包含在DLL中,其中最重要的三个DLL是:

  1. Kernel.dll:包含的函数用来管理内存、进程、线程
  2. User32.dll:包含的函数用来执行与用户界面相关任务,比如创建窗口和发送消息
  3. GDI32.dll:负责绘制图像和显示文字

DLL通常由一组独立的函数组成,DLL可以认为是一组源代码模块,每个模块包含一些可供PE程序或者其他DLL调用的函数。

一般会把DLL称为导出模块,因为他主要是导出一些函数和变量供其他程序使用(当然DLL可以导入DLL来用),而把可执行程序(PE程序)称为导入模块,因为他导入dll的符号(符号是指函数和变量)来使用。


在PE程序或其他DLL调用一个DLL函数之前,要先将这个DLL文件映像映射到程序进程的地址空间中,这就是链接过程,有两种方法:

  1. 隐式载入时链接(implicit load-time linking)
  2. 显式运行时链接(explicit run-time linking)

当DLL载入进程的地址空间之后,那就可以看成程序的一部分了,也就是一堆外部提供的函数或数据。

从某种意义上说,一个地址空间是由一个可执行模块和多个DLL模块组成的


1 隐式链接过程

以下内容都是Windows核心编程的,看那个图就能懂得7788了。

隐式载入dll: 直接让PE程序的源代码中引用dll中的符号(函数、变量等),这样加载PE程序的时候会自动链接,这里需要借助生成dll文件时产生的.lib文件。


2 显式载入dll

强烈建议看这个视频前半部分

极安御信网络安全系列课程-Windows系统编程-动态链接库基础与远程线程注入

当PE程序正在执行的时候,通过一些API,可以将DLL调入进程地址空间使用,所以这里用不到.lib文件

//借助的就是这个API
//这两个函数会在系统中对DLL文件映像进行定位,然后调入进程地址空间

HMODULE LoadLibrary(PCTSTR  pszDLLPathName);

HMODULE LoadLibraryEx(
    PCTSTR pszDLLPathName;
    HANDLE hFile;  //将来扩充保留的参数,一般设为NULL就行
    DWORD dwFlags; //可以设为0,也有一些其他的关键字
)

//HMODULE: 表示文件映像被映射到的虚拟内存地址,HMODULE等价于HINSTANCE,失败则返回NULL

显式和隐式的区别就在于,显式是在程序运行时完成的

2.1 显式卸载DLL

当进程不再需要使用DLL中的符号时,可以调用下面的函数显式的卸载DLL

BOOL FreeLibrary(HMODULE hInstDll);

//或者用这个函数(这是函数声明)
VOID FreeLibraryAndExitThread(
    HMODULE hInstDll,
    DWORD dwExitCode);

//函数原型,在Kernel32.dll中有
//这是Microsoft特意封装的一个函数
VOID FreeLibraryAndExitThread(HMODULE hInstDll, DWORD dwExitCode)
{
    FreeLibrary(hInstDll);
    ExitThread(dwExitCode);
}

2.2 进程中的dll计数

每一个进程在首次调入dll时,会对其进行计数。同一进程的不同线程再次调入dll,只会计数器自增,而不会把dll再调入内存。也就是说同一进程内的dll是共享的

不同进程间的dll计数是独立的。、

当计数器为0的时候,说明dll已经调出进程地址了。

//用GetModuleHandle来检测一个DLL是否映射到进程地址中
//比如当Mylib.dll未调入进程时,下面代码会把他调入
HMODULE hInstDll = GetModuleHandle (TEXT("MyLib"))
if (hInstDll == NULL)
{
    hInstDll = LoadLibrary(TEXT("MyLib"));
}

2.3 显式的链接到导出符号

在导入dll之后,想要使用dll中的符号,我们可以用下面的函数进行显式的链接

//线程通过调用这个函数获得想要应用的符号地址
FARPROC GetProcAddress(
    HMODULE hInstDll,
    PCSTR pszSymbolName);
"""
参数说明:
hInstDll:指定包含我们想要符号的dll句柄
pszSymbolName:有两种形式:
            1. 符号名:一个ANSI字符串,以“\0”结尾的,比如“MyDll”
            2. 序号:但是不建议使用!!!
返回值是一个函数指针,但是使用这个指针前要做转换
"""

上述函数返回值是一个函数指针,使用时要先转换为匹配的函数类型

//比如我想用MyDll中的DumpModule函数

//DumpModule函数声明如下
void DumpModule(HMODULE hModule);
//函数指针的定义,这里callback是函数调用约定(wyl跟我说的)
typedef void (CALLBACK *PFN_DUMPMODULE)(HMODULE hModule);

//那么显式链接可以这么写,记得做类型转换
PFN_DUMPMODULE pfnDumpModule = (PFN_DUMPMODULE)GetProcAddress(MyDll, "DumpModule");
if(pfnDumpModule != NULL)
{
    pfnDumpModule (MyDll);  //调用dll中的符号
}
posted @ 2023-01-03 14:28  wenli7363  阅读(479)  评论(0编辑  收藏  举报