使用静态链接生成的可执行文件体积较大,造成浪费
我们常用的printf、memcpy、strcpy等就来自这种静态库

有维护性问题。不方便修改库中实现,修改后所有使用静态库的都要重新链接一遍

因为静态库相当于copy源码,所以如果a.exe 和b.exe 都用的同一套静态库,这两个exe中含有大量重复代码。

先把程序运行起来,再将库链接进来——动态链接

exe在可执行程序中没有相应库的代码,当软件运行起来再把库代码拷到内存。

所以使用动态库的软件不需要重新链接,修改动态库即可修复库的问题(即头疼医头,不用把整个身体链接一遍)。Windows中动态链接库通常为.dll 文件

缺点是启动慢,如果缺库则软件无法正常运行,静态库则相反

1.dll 创建

生成的工程中 dllmain 并不是必须的,可以删掉。不影响使用

dllmain 主要是 dll 被加载时立即执行的一段代码,用于初始化和监听是否被加载或卸载而已。

dll 主要还是作为库,提供函数给人调用的作用。

这里依然把其他多余的全删了,只创建两个, MyDll.h 和 MyDll.cpp

先来最简单的

MyDll.h :

#pragma once
_declspec(dllexport) int Plus(int x, int y);
_declspec(dllexport) int Sub(int x, int y);
_declspec(dllexport) int Mul(int x, int y);
_declspec(dllexport) int Div(int x, int y);

_declspec(dllexport) 关键字 声明函数为导出函数,声明将函数相关放进后面说到的导出表中,作为导出函数给他人使用

MyDll.cpp:

#include "MyDll.h"
int Plus(int x, int y)
{return x + y;
}
int Sub(int x, int y)
{return x - y;
}
int Mul(int x, int y)
{return x * y;
}
int Div(int x, int y)
{return x / y;
}

同样点击平时的编译运行按钮,提示无法运行(本来就无法单独运行),但已成功生成.dll 文件,去到 Debug 或 Release 目录下即可看到,注意同样还有个.lib文件,我们后面会用到

2.dll 调用

分为动态使用(显式调用)和静态使用(隐式调用)

前者可以在调用者代码中任意位置自定义的加载进内存,即如果调用者没运行到那段代码,那 dll 就一直不被加载进内存,可以动态的使用。而这种调用方式需要显式的在代码中写出加载 dll 的代码。

后者就是通过编译选项和设置,使得被调程序只要运行就立即加载 dll 进内存,无法动态灵活的使用。因为调用者程序中没有显式的加载 dll 的代码,因而也叫隐式调用。

2.1 动态使用(显式调用)

同样新建一个带main的控制台项目工程。只需将dll 拷贝进目录下。

#include<stdio.h>
#include<windows.h>int main()
{// ********** dll显式调用的测试 ***********   这里只需将相应dll拷贝到目录下即可,无需拷贝.h,.lib 与 调资源文件//关于无法将参数1从"const char *"转换为"LPCWSTR" 的错误,见 项目->属性->常规->字符集 调整为 未设置 或 多字节//如果还是编译不过 查看项目->属性->C++->预处理器->预处理定义中是否有UNICODE//定义函数指针             typedef int(* lpPlus)(int, int);//这里声明的函数参数类型需与.h中一致lpPlus myPlus;//动态加载dll到内存中HMODULE hMod = LoadLibrary("MyDll.dll");if (hMod == nullptr) {printf("load dll error!");system("pause");return 0;}myPlus = (lpPlus)GetProcAddress(hMod, "?Plus@@YAHHH@Z");    //这里的函数名使用Dependency Walker查看,必须正确if (myPlus == nullptr) {printf("load func error!");system("pause");return 0;}//调用函数int a = myPlus(10, 2);printf("%d", a);return 0;
}

动态调用主要通过 LoadLibrary 获取 加载dll 并获取其地址,后通过 GetProcAddress 通过 dll 句柄和函数名或者函数序号(后面会说)获取真正的函数地址,得到地址,套上和该函数格式一模一样的函数指针。即可成功调用。

首先,编译器通过名称粉碎(Name-Mangling)来达到作用域控制和函数重载的目的。所以,C++中的所有函数在编译后,编译器都会加上返回类型、调用约定、参数类型等来唯一标识一个函数。是编译器的内部规则,所以不具有普适性。所以放入导入表中,这个函数名就变了,这里变成?Plus@@YAHHH@Z,可以用 Dependency Walker 或 CFF Explorer 来查看。显示调用中通过GetProcAddress获取函数地址,参数的函数名必须和这个名称粉碎后的名字相同。由此这样的显示调用非常麻烦,后面有说不用名称粉碎的方法。

因为导出表中不止含有函数名,所以 GetProcAddress 也可以通过函数序号获取到函数地址。

2.2 静态使用(隐式调用)

还记得前面在生存dll 时还自动生成了个.lib 吗,这个lib是动态lib,里面没有实现代码,只有函数名、序号和dll名,同样的lib里的函数名依然是被名称粉碎过的,把这个 .lib 也拷贝到调用者目录下。

然后再把之前写的头文件拷贝到目录下。

然后上代码

#include<stdio.h>
#include<windows.h>// ********** dll隐式调用的测试 ***********
#include "MyDll.h"                //将.h文件复制到目录下则可用该条语句,include了则不需要再声明dll中的函数
#pragma comment(lib,"MyDll.lib")  //这里就是lib没有错,不是.dll 在项目的"资源文件"中添加相应lib与dll则不用这一条int main()
{// ********** dll隐式调用的测试 ***********printf("%d", Plus(2,3));return 0;
}

同样,#pragma comment(lib,"MyDll.lib") 这一条的有无参考上一章lib中使用/不使用资源文件的调用。

注意静态使用(隐式调用)只能导出名字的函数使用,序号的不能使用

运行后可顺利显示结果,证明调用成功。

3.其他变式与拓展答疑

前面提到可以更方面的使用显示调用,无需打开其他工具查看名称粉碎后的结果了

方法一可以通过加入 extern "C" 关键字,意为使用C语言的格式,那么就没有C++的名称粉碎规则了。(但注意,C语言仍然有自己的名称粉碎规则)如下格式声明和定义的函数,在 dll 导出表中名称和原名一致。

声明:

extern "C" _declspec(dllexport) int Plus(int x, int y);

实现:

extern "C" int Plus(int x, int y)
{return x + y;
}

那么这样生成的 dll ,函数名和原名一致。但如果调用规则变化为__stdcall。即实现为:

extern "C" int __stdcall Plus(int x, int y)
{return x + y;
}

声明为

extern "C" _declspec(dllexport) int __stdcall  Plus(int x, int y);

则生成的 dll 函数名依然会被在C语言规则下的名称粉碎弄乱名称。使用工具查看导出函数名,一样是被粉碎过的。

方法二可以通过添加 def 文件,在源文件->添加->新建项->搜索def,即可添加def文件。

或者在文件中直接弄个空文件改成.def,然后在添加到项目的源文件中 也可以。

但第二种方法添加到项目的源文件后,需要在如下项目属性的这里,填入添加的def的文件名才能正常使用

def 文件中写入下列文本:(开头的 LIBRARY 可有可无,这里没写)

EXPORTS  Plus    @12
Sub     @15 NONAME
Mul     @13
Div     @16

即可无视各类名称粉碎规则,输出的导出函数名必然是表上的名字,后面的@12为规定函数名为序号, NONAME 为无名字导出,这个函数就只能以序号方式调用,另外写入Plus=AA 可以更换导出函数名为AA。具体更多字段可自行查阅def文本规则。

由于静态使用(隐式调用)只能以导出名字的函数使用,所以设置为NONAME,就无法静态使用该函数了。

通过上面可以知道,C++的文件通过声明 extern "C" 即可生成双方都能调用的库:C++使用仍然要加上extern "C",C语言直接使用即可。而C生成的库,C可以直接调用,C++需加上extern "C",但C++生成的库若不加该关键字,则只能C++使用。所以一般做成C的库,两者都能通用。但 extern "C" 只在C++有,C是没有这个用法的。所以为了通用,给使用者的头文件可以写成以下形式:

#ifdef __cplusplusextern "C"
{
#endifvoid fun1();
void fun2();extern int g_num;#ifdef __cplusplus
}
#endif

如果需要在上述框架下加入 全局变量,也可以,但要在变量前加上extern关键字,就像上面的g_num;  全局变量的用法和函数一致,本质都只是一个地址,并且也会受到名称粉碎的影响。

还有一点,如果需要再导出C++类。由于头文件写的类是含有__declspec(dllexport)的,而这是个导出类,放到调用者那调用会报错,因此需要在调用者的.cpp中拷贝一份.h中的类定义并把__declspec(dllexport) 改成 __declspec(dllimport) 。但这样会报重定义错误。因此需要在头文件中定义宏:

#ifdef _USRDLL#define CLASS_DEF_declspec(dllexport)
#else#define CLASS_DEF _declspec(dllimport)
#endif
class CLASS_DEF Ctest
{  …
}

这样两边就都能用了,dll 编译时头文件中的这个看作导出类,但.h头文件到了dll使用者那里又变成了导入

另外,由于.lib文件中只记录了.dll的名字,但并没有记录dll的路径,而是通过一个默认的搜索规则,先实验室当前路径,再搜索系统目录,再搜索环境变量的。所以存在 dll 劫持的安全问题。即dll 可以按照这个规则被人为替换掉。而里面所有函数都可能会被动手脚。

※※※※

都说有了 def 就不用extern "C" 了,两种方法都只改变了函数名,使用起来应该是一致的。

但注意到一个问题,dll 导出函数使用 extern "C"  与 使用 def 文件而不使用 extern "C" 相比。两种方法在 dll 中的导出函数名一致,extern "C" 在调用者处使用extern "C" 声明就能正常使用。

但是DEF文件弄的导出函数,C++隐式调用起来声明extern "C" 会报链接错误(可见C使用者也会报错,实验证明的确这样)。证明这两者还是有区别的。

是因为 DLL文件里的函数名是 def 文件指定的,但是它对应的 lib 文件则不是。既 lib 文件中的函数名仍然被名称粉碎了!

所以 无extern "C" 且用 def 文件弄出的动态库,无法给C语言调用者 静态使用。因为C语言认不出 lib 中被粉碎的名字。(C++使用者在声明中不加 extern "C" 即可)

但因为只涉及到 lib 文件,所以C语言使用者仍然可以通过动态使用(显式调用)的方式使用 dll 的

滴水逆向三期实践10:动态链接库相关推荐

  1. 滴水逆向三期实践1:PE头字段解析,附PE结构下载

    视频资源详见网盘搜索 或 在线的B站滴水逆向三期 其课件也能在CSDN或百度搜索到,以下部分为课件内容摘要,部分为自己的理解 最后附上详细注释的自写代码 PE(Portable Executable) ...

  2. 滴水逆向三期实践16:IAT表和导入表

    IAT表 在我们调用 dll 函数的时候,发现代码中的汇编,是通过间接寻址的.也就是不直接 call 函数地址,而是通过一个中间地址再跳转的. 比如调用MessageBox这类系统函数的时候(这也是个 ...

  3. 滴水逆向三期实践15:重定位表修正

    我们知道,重定位表是由于在代码中写入的绝对地址,而 DLL 不能按照设想的 ImageBase 作起始加载位置去了别的地方占坑,那么需要根据重定位表记录的这些绝对地址在内存中的位置(RVA),逐一去到 ...

  4. 滴水逆向三期实践6:扩大节

    扩大节: 1.拉伸到内存(只是逻辑上,实际代码操作并不需要这一步),需要注入的代码为 ShellCode 2.分配一块新的空间:SizeOfImage + sizeof ( ShellCode) 3. ...

  5. 静态链接库,动态链接库【滴水逆向三期48笔记】

    在开发过程中,我们通常会有很多函数,需要多次使用或在不同的程序中使用该函数,也有可能我们会将我们写好的函数给别人使用,但是我们又不想给他源代码,毕竟代码是我们花了很多功夫写出来的,那么我们如何不发给其 ...

  6. 滴水逆向三期笔记与作业——02C语言——02数据类型

    海哥牛逼 这里写自定义目录标题 一.C语言如何变成汇编 1.裸函数 二.调用约定 1.常见的几种调用约定 三.程序的真正入口 四.数据类型 4.1 C语言中的数据类型 作业 一.C语言如何变成汇编 1 ...

  7. 滴水逆向三期 win10 ASLR UnmapViewOfSection傀儡进程 加密壳项目

    特意加了一个win10标题, 碰到0xc0000005的朋友就不要再浪费时间了, 我在这个问题上花了整整一天 网上全是xp的代码, 搜到了了几个win10的也是同样的错误没法解决, 唯一一个有用的答案 ...

  8. IAT表入门简析【滴水逆向三期52笔记】

    在讲IAT表之前,我们来回忆一下之前学习的知识: 如果我们将函数写在程序的源文件中,那么该函数就会被编译器直接编译到程序的二进制文件中,在程序调用该函数的时候,E8后跟的地址是直接写死的,程序直接在e ...

  9. 滴水逆向三期和中级和高期教程

    联系本人QQ747554937 原版视频,海哥教学视频,不是网页上的介绍视频

最新文章

  1. 优秀的Java程序员应具备哪些编程技术?
  2. OKR让伟大的企业愿景成为可能
  3. linux7 yum安装rabbitmq,CentOS7linux下yum安装RabbitMQ以及使用顶
  4. SQL*PLUS常用命令
  5. 每日英语:Delayed Development: 20-Somethings Blame The Brain
  6. [转]java中byte转换int时为何与0xff进行与运算
  7. python输入10个学生的成绩储存在列表中_获得10名学生的平均成绩python
  8. java中精确地小数_在Java等于方法中进行精确比较
  9. [css] 在sass中可以执行布尔运算吗?
  10. QT学习笔记(二):QT MinGW 和 MSVC 编译方式
  11. spirng cloud docker部署
  12. JDK动态代理的使用,以及可以解决哪些问题和优点,什么是动态代理
  13. 卡巴斯基7.0简体中文下载【有2010年的授权文件】
  14. 【keytool】keytool查看jks证书详情
  15. imageAI基本使用
  16. Android对接蓝牙打印机
  17. PythonNote036---python中字典合并
  18. 2019世界互联网大会 聚焦网络安全发展新动能新要求 最新等保测评机构名录发布10月版
  19. 微博研发实习阶段性总结及知识点整理
  20. win8连接wifi成功但受限制_用于 手机热点或WIFI网络的IM143DTU使用手册

热门文章

  1. 超级简单分享:快乐数字
  2. python二维数组的行和列_python 定义N行2列二维数组与赋值
  3. 月光族开始反消费主义:58万年轻人攒钱攒到“丧心病狂”
  4. 【JAVA】-JAVA简介
  5. 什么是RAIN RFID?
  6. 关于用户生命周期分析的总结
  7. 汽车UDS诊断详解及Vector相关工具链使用说明——2.1.3 初步了解CDD(以10服务为例)
  8. L1-2 h0053. 游戏时间 (5 分)
  9. mysql 微信 jsp_SpringMVC+Spring+mybatis+mysql+jsp微信商城系统,有后台管理系统
  10. 2020年JAVA最常见面试题汇总