admincenter 发表于 2022-6-20 16:37:39

DLL加载过程初探

# 前言
前两天刷手机的时候,无意间想到了一些问题:存储DLL信息的_LDR_DATA_TABLE_ENTRY结构体是在什么时候填充的?是在Dllmain运行之前还是之后呢?这个结构体的作用是什么?本文记录一下探究的过程,如有不对望师傅们指出!

> 实验环境:物理机Windows10 x64 21H2
> 实验工具:IDA
> 测试程序:32位/64位

## 32位分析
动态加载通常使用LoadLibrary进行,一般VS2019默认是W后缀,就从W来分析吧(下文64位同)

### LoadLibraryW
LoadLibraryW函数位于kernelBase.dll里


这个函数是LoadLibraryExW的再次封装,这里直接凑齐参数调用ExW函数了,这里有三个参数:

* DLL路径,
* hFile,保留参数,必须是NULL
* dwFlag,可以是NULL,会完整加载DLL,通过设定Flag可以进行部分加载,具体值在MSDN上有

> 参考自:(https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibraryexw)

### LoadLibraryExW
LoadLibraryExW函数位于kernelBase.dll里


刚开始先判断hFile参数和dwFlag参数,如果是不合法的值,就跳走返回


接下来将Ring3的字符串转换成R0的UNICODE_STRING结构体,转换失败就跳转返回,转换成功则进行末尾去0操作,接下来一切正常的话,会跳转走


接下来是Flag值的判断,如果指定位有值,就跳去执行相应的操作,最后会来到LdrLoadDll函数(下文会介绍),第四个参数传址接收加载模块的模块地址,执行完之后,会返回一个值到eax,然后跳转


如果返回值不是负数,也就是执行成功了,就再次跳转


这里从地址里获取得到的模块地址,然后返回

### LdrLoadDll
LdrLoadDll函数位于ntdll.dll里


这个函数刚开始用局部变量接收了传入的BaseAddress地址


往下走有一个函数调用,传入了空的LdrEntry结构体(通过动态调试发现这是LdrEntry结构体的)来接收数据,函数(下文介绍)执行完成之后,这个结构体会被填充好


再之后,从这个结构体的18h偏移处(偏移0x18是Dllbase,就是模块地址)取出值设置到BaseAddress的地址里返回

### LdrpLoadDllInternal
LdrpLoadDllInternal函数位于ntdll.dll里


进入函数以后,会先判断dll是不是已经加载了,已经加载就获取其模块信息结构并返回,如果不是,会走到这里这里的var_20是局部变量,也是个LdrEntry结构(通过动态调试发现的),把var_20的地址给了eax,然后调用了这个函数


然后把var_20的地址给了参数的LdrEntry赋值,之后操作var_20就是实际操作参数了,程序运行到LdrpPrepareModuleForExecution函数之前,LdrEntry结构体已经是填充完毕的状态了,LdrpPrepareModuleForExecution函数会进一步的进行DLL相关初始化操作,并执行DllMain的`DLL_PROCESS_ATTACH`分支程序,然后函数差不多就结束了,本次的分析目标已经达成了,就不往下分析了

## 64位分析

### LoadLibraryW


64位程序简洁好多啊

### LoadLibraryExW


类比32位的看吧,流程是一样的,这里进入LdrLoadDll,传入了BaseAddress的指针,作为第四个参数(r9)

### LdrLoadDll

开始先把BaseAddress指针从r9存到了r14,然后就是这里:


把空的LdrEntry(准确来说,不是空的,里面前10h字节有值,可能DllBase也有,也可能没有)给r9传参,通过LdrpLoadDll填充了该结构,然后通过偏移取得其中的DllBase,并返回

### LdrpLoadDll


这里把结构体地址给到了局部变量上保存,通过rsi传参调用了LdrpLoadDllInternal函数

### LdrpLoadDllInternal


首先进来后,先把结构体存在了rsp+20的位置上,然后入栈5个参数,rsp抬高50h字节,这个时候结构体存在了rsp+78h的位置


顺利的话,后面会进到这一块来,继续进行初始化操作,这个函数跟32位的一样,继续初始化DLL运行DllMain函数

到此,64位的分析也到此结束了

# 总结

LoadLibrary API动态加载DLL的时候,会先进行LdrEntry结构体的填充,然后再继续执行DLL初始化操作和执行DllMain函数,该API返回的模块句柄其实就是模块地址,是在LdrLoadDll这一层从LdrEntry中找到的值

据查阅资料,进程会维护一个“模块数据库”,用来存储模块信息,模块信息结构是`LDR_DATA_TABLE_ENTRY`,所有的模块信息通过双向链表连接在一起,进程寻找模块中的内容的时候,会基于该结构获取的模块基址进行寻址

> 参考资料:《Windows 核心编程》《深入解析Windows操作系统》
页: [1]
查看完整版本: DLL加载过程初探