动态复用(DLL)
- 将函数库编译成 二进制dll
- 每次更改只需替换成品的dll
- 不需要重新编译
- 现有函数接口(函数声明/约定) 不能改变
- dll会与lib一起生成(这个lib是动态库,用于链接DLL,本身没有任何代码)
- class 导出成DLL 时注意在
class
名称后声明__declspec(dllexport)
LoadLibrary
(有引用计数)将DLL加载到程序内存,最后需要释放句柄(FreeLibrary)GetModuleHandle
可以在某个DLL通过LoadLibrary
载入后根据文件名称获取该DLL句柄,没有引用计数,不需要释放
生成DLL
- 新建一个没有
main
的项目 常规
->配置类型
->DLL
项目任选一种方式导出DLL
- vs: 在 函数声明/类声明/变量声明 前 增加 导出前缀(
__declspec(dllexport)
),建议extern "C"
- 推荐 vs: 在生成 DLL 时,创建一个模块定义 (.def) 文件并使用该 .def 文件
vs: 使用
comment
的linker
选项一共四种方式
https://msdn.microsoft.com/zh-cn/library/hyx1zcd3.aspx
- vs: 在 函数声明/类声明/变量声明 前 增加 导出前缀(
两种加载DLL的方式
隐式/静态 加载
- 隐式加载DLL由操作系统完成
- 生成的exe中有程序需要的DLL名称以及函数名称
- 操作系统自动搜索DLL然后展开,最后运行程序
- 隐式加载 需要有配套的lib(lib记录了 函数名称与 DLL中地址的对应)
- 可以通过函数名 直接使用
- 导出时 添加
PRIVATE
是说明只能显式引用 - 可以正常使用class
显示/动态 加载
- 不需要lib,只要有dll文件即可
- 不需要头文件,但是需要 函数的 函数指针的定义
- 程序运行时加载DLL(可以使任意位置的DLL)到内存中
- 函数在DLL在文件中的地址叫RA
- 函数在DLL展开后在内存中的地址叫RVA
- 每个函数在DLL中都有唯一的编号(最大65535,编号由2B来保存)
- 动态加载只能定位到每个class::函数地址,不能直接操作class
- 只能通过 定制 工厂函数来生成class对象的指针
- 如果要操作 对象的 函数,那么要修改 函数 为虚函数
隐式加载DLL
- 项目中导入LIB->参考 静态复用(LIB)的导入
- 导入dll 的函数/变量时 ,建议在 DLL的函数声明前 增加说明符(编译器可提供代码优化)
__declspec(dllimport)
- exe加载时会寻找DLL(优先寻找同级目录)
显示/动态加载DLL
- 加载DLL到内存中
- 获取函数地址RVA
- 通过函数指针运行
通过函数名称查找RVA
winapi:通过函数名称搜索到函数地址
- 如果c++代码的话,需要搜索名称粉碎后的名称
- 如果没有
extern C
那么可能会找不到
- 第三方工具:直接获取函数在内存中的偏移。DLL首内存地址+ 偏移地址=RVA
通过函数编号查找RVA
编号的获取
- 在导出DLL时声明编号
- 通过软件查看函数的编号
- 通过编号获取函数的RAV地址
动态加载案例
/*
导出DLL的项目
我们将MyCreateFileA函数转发到 kernel32.CreateFile123A上
这里使用方案三linker生命导出函数,def文件方法也可以实现相同功能
*/
//函数导出
//#pragma comment(linker, "/EXPORT:_SuperAdd")
//设置函数别名/劫持
//#pragma comment(linker, "/EXPORT:MyCreateFileA=kernel32.CreateFile123A")
/*
使用DLl的项目
*/
//定义函数指针
typedef int (* my_dll_export_fucation)(int,int);
int main()
{
//加载dll到内存中
HMODULE hSuper = LoadLibraryA("dll_export.dll");
//准备函数指针
my_dll_export_fucation p_fx1, p_fx2,p_fx3,p_fx4;
//通过函数名称获取地址
p_fx1 = (my_dll_export_fucation)GetProcAddress(hSuper, "dll_export");
/*
我们这里原计划调用的函数是MyCreateFileA
但是我们在程序加载的DLL中劫持MyCreateFileA转发到kernel32.CreateFile123A
因为给这些函数我们不想处理所以转发出去
如果这是我们要劫持的函数那么久劫持到我们自己的函数上处理
*/
p_fx4 = (my_dll_export_fucation)GetProcAddress(hSuper, "MyCreateFileA");
//通过偏移计算出地址
p_fx2 = (my_dll_export_fucation)((char*)hSuper + 70102);
//通过def文件中定义的函数的编号获取地址
DWORD dwOrdId = 6;
p_fx3 = (my_dll_export_fucation)GetProcAddress(hSuper, (LPCSTR)dwOrdId);
std::cout
<< "函数地址:" << (int)p_fx1 << std::endl
<< "函数偏移:" << (int)p_fx1 - (int)hSuper << std::endl
<< "执行函数名称出来的函数指针" << p_fx1(1, 2) << std::endl
<< "执行手动计算出来的函数指针" << p_fx2(1, 2) << std::endl
<< "搜索函数编号出来的函数指针" << p_fx3(1, 2) << std::endl
<< std::endl;
//释放DLL句柄
freeResult = FreeLibrary(hSuper);
}