动态复用(DLL)

  1. 将函数库编译成 二进制dll
  2. 每次更改只需替换成品的dll
  3. 不需要重新编译
  4. 现有函数接口(函数声明/约定) 不能改变
  5. dll会与lib一起生成(这个lib是动态库,用于链接DLL,本身没有任何代码)
  6. class 导出成DLL 时注意在 class 名称后声明__declspec(dllexport)
  7. LoadLibrary(有引用计数)将DLL加载到程序内存,最后需要释放句柄(FreeLibrary)
  8. GetModuleHandle可以在某个DLL通过LoadLibrary载入后根据文件名称获取该DLL句柄,没有引用计数,不需要释放

生成DLL

  1. 新建一个没有 main的项目
  2. 常规->配置类型->DLL项目
  3. 任选一种方式导出DLL

    1. vs: 在 函数声明/类声明/变量声明 前 增加 导出前缀(__declspec(dllexport)),建议 extern "C"
    2. 推荐 vs: 在生成 DLL 时,创建一个模块定义 (.def) 文件并使用该 .def 文件
    3. vs: 使用commentlinker 选项

      一共四种方式https://msdn.microsoft.com/zh-cn/library/hyx1zcd3.aspx

两种加载DLL的方式

隐式/静态 加载

  1. 隐式加载DLL由操作系统完成
  2. 生成的exe中有程序需要的DLL名称以及函数名称
  3. 操作系统自动搜索DLL然后展开,最后运行程序
  4. 隐式加载 需要有配套的lib(lib记录了 函数名称与 DLL中地址的对应)
  5. 可以通过函数名 直接使用
  6. 导出时 添加 PRIVATE是说明只能显式引用
  7. 可以正常使用class

显示/动态 加载

  1. 不需要lib,只要有dll文件即可
  2. 不需要头文件,但是需要 函数的 函数指针的定义
  3. 程序运行时加载DLL(可以使任意位置的DLL)到内存中
  4. 函数在DLL在文件中的地址叫RA
  5. 函数在DLL展开后在内存中的地址叫RVA
  6. 每个函数在DLL中都有唯一的编号(最大65535,编号由2B来保存)
  7. 动态加载只能定位到每个class::函数地址,不能直接操作class
  8. 只能通过 定制 工厂函数来生成class对象的指针
  9. 如果要操作 对象的 函数,那么要修改 函数 为虚函数

隐式加载DLL

  1. 项目中导入LIB->参考 静态复用(LIB)的导入
  2. 导入dll 的函数/变量时 ,建议在 DLL的函数声明前 增加说明符(编译器可提供代码优化) __declspec(dllimport)
  3. exe加载时会寻找DLL(优先寻找同级目录)

显示/动态加载DLL

  1. 加载DLL到内存中
  2. 获取函数地址RVA
  3. 通过函数指针运行

通过函数名称查找RVA

  1. winapi:通过函数名称搜索到函数地址

    1. 如果c++代码的话,需要搜索名称粉碎后的名称
    2. 如果没有extern C那么可能会找不到
  2. 第三方工具:直接获取函数在内存中的偏移。DLL首内存地址+ 偏移地址=RVA

通过函数编号查找RVA

  1. 编号的获取

    1. 在导出DLL时声明编号
    2. 通过软件查看函数的编号
  2. 通过编号获取函数的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);       
}


Last modification:October 26, 2018
如果觉得我的文章对你有用,请随意赞赏