滴水三期:day35.1-静态、动态链接库

本文详细介绍了静态链接库和动态链接库的概念、创建与使用方法。静态链接库在编译时将函数代码直接整合到exe文件中,方便代码复用,但更新需要重新编译;动态链接库(DLL)则在运行时加载,提供模块化,更新不影响exe,且节省内存。通过.def文件可以隐藏函数名,增强安全性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在学习导入表、导出表之前,由于导入导出表需要用到静态链接库或者动态链接库,所以先来学习一下

一、代码复用的实现

  • 我们学过如果一个功能的代码需要被我们自己反复使用,即复用,那么可以定义成函数
  • 现在如果要多人实现代码的复用,比如我写的一段代码要给别人用,不可能直接把代码复制一份发给别人,这样如果很多人要使用,就不方便,所以可以通过下面的三种方式实现:
    1. 静态链接库
    2. 动态链接库
    3. def导出

二、静态链接库

1.创建静态链接库

  • 在VC6中创建项目:Win32 Static Library;由于目前还不需要什么支持,所以不用选选项,直接点finish即可

    image-20220104185801548
  • 在项目中创建两个文件:xxx.h 和 xxx.cpp(点击ClassView–右键刚创建的项目–New Class,取一个名字test,则现在就生成了对应的源文件和头文件)

    image-20220104190023112 屏幕截图 2022-01-04 190055

  • 在cpp文件中写要给别人复用的代码:(定义)

    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;
    }
    
    image-20220104190251746
  • 在.h头文件中声明:(声明)

    int Plus(int x, int y);
    int Sub(int x, int y);
    int Mul(int x, int y);
    int Div(int x, int y);
    
    image-20220104190349632
  • 最后按F7编译,那么在创建的项目文件夹中,有一个**.h的头文件**;再打开当中的Debug文件夹,就会发现有一个**.lib文件**。这两个文件就是使用静态连接库需要的两个文件

    image-20220104194837624 image-20220104194915538

2.使用静态链接库

  • 通过使用静态链接库,实现代码复用:

  • 方法一:

    • 将xxx.h 和 xxx.lib复制到需要使用静态链接库的.cpp文件所在的控制台项目文件夹中(就是我们平时写代码用的项目WorkSpace)

      注意:不是Debug中!注意和动态链接库区分开

      image-20220104195224991
    • 在需要使用静态链接库的.cpp文件中包含头文件:

      #include "xxx.h"
      

      先包含.lib的.h头文件,告诉编译器一会要使用的函数在哪

    • 在需要使用的.cpp文件中包含Lib文件:

      #pragma comment(lib, "xxx.lib")
      

      再告诉编译器要使用的函数是什么,即告诉编译器此函数的硬编码,因为静态链接库函数的硬编码就在.lib文件中上面的.h中只是申明、告诉编译器函数在哪

    • 接着就可以使用.lib中写好的函数了

      image-20220104200020727
  • 方法二:

    • 将xxx.h 和 xxx.lib复制到要使用的项目文件夹中

    • 在需要使用的文件中包含:#include "xxx.h"

    • 在文件所在的Project右键–setting–Link–在Library modules中添加导出的静态链接库TestLib.lib即可

      image-20220104200311662 image-20220104200405494

      我们可以发现我们平时就一直在使用静态链接库:memsize和memcopy这些函数,为什么这些函数我们并没有定义过,只是#include 包含了头文件,就可以直接使用了呢?因为include头文件中只是申明了这些函数,而真正告诉编译器这个函数具体是什么的,是上面的link中已经包含了这个函数所在的.lib文件;包括我们平时用的printf函数,它所在lib也已经默认写到Library modules中了

    • 导入头文件和lib文件后就可以直接使用当中的函数了

      image-20220104201335585

3.静态链接库的特点

  • 我们发现静态链接库是一种假的模块化,我们前面学过,一个PE中可以包含很多模块(.dll),可以通过OD中的E来查看。但是此时使用静态链接库,虽然一个程序中使用了别的文件中的函数,有点类似于模块化的感觉,但是我们却发现其实这个程序是没有使用其他模块的。所以静态链接库和模块化是没有什么关系的

    image-20220104201823874
  • 那么为什么使用静态链接库的程序,就可以使用库中的函数了呢?

    • 我们找一下我们使用的Plus函数在哪里?

      image-20220104202130993
    • 可以发现使用的函数所在的内存地址为0x00401190,我们前面学过PE文件了,一般一个.exe文件的代码块差不多就是从偏移地址为1000多的地方开始的(即ImageBase0x400000 + 偏移0x1000),所以此时这个函数就属于.exe文件中的一部分了,而不是使用其他模块.dll中的函数

  • 总结:静态链接库的特点就是使用静态链接库的.exe程序直接将要用到的函数在编译的时候就直接放到.exe文件中代码块中去了

  • 这恰恰也是静态链接库的缺点,比如如果我们现在修改了.lib中的函数,那么此时使用此函数的.exe文件必须要重新编译一次,将修改过的函数重写编译到.exe文件中才能使用修改过后的函数

    但是如果一个.exe文件包含.dll,使用了.dll中的函数,此时如果修改函数,.exe文件就不用动,即不用再重新编译一次。这就是模块化的好处!!

三、动态链接库

1.创建DLL

  • 创建一个新的项目–选择Win32 Dynamic-Link Library–起名–选择A empty DLL project(空的)即可

    image-20220104203454964
  • 同样点Class View–在创建的项目右键–New Class–输入名字,即可创建好.cpp文件和.h文件

    image-20220104203533247 image-20220104203615651

  • 在.cpp源文件中:定义函数

    int __stdcall Plus(int x,int y){	
    	return x+y;
    }	
    int __stdcall Sub(int x,int y){	
    	return x-y;
    }	
    int __stdcall Mul(int x,int y){	
    	return x*y;
    }
    int __stdcall Div(int x,int y){
    	return x/y;
    }
    
    image-20230402180328124
  • 在.h头文件中声明(与静态链接库不同)

    extern "C" _declspec(dllexport) __stdcall int Plus (int x,int y);  
    extern "C" _declspec(dllexport) __stdcall int Sub (int x,int y);
    extern "C" _declspec(dllexport) __stdcall int Mul (int x,int y);
    extern "C" _declspec(dllexport) __stdcall int Div (int x,int y);
    
    image-20230402193458470
    • extern:表示这是一个全局函数,可以供各个其他函数调用

    • “C”:指的是此函数按照C语言的方式进行编译、链接。为什么要按照C的方式导出呢?因为不指定的话,编译器可能会理解成通过C++的方式导出,但是C++允许函数的重载(可以定义相同名字的函数,但参数不同),而假如此时编译器导出后有相同名字的函数,但是此时有一个C程序要使用,但C不支持重载,那么此程序就不知道用哪个函数。①故如果没有指定"C",编译器会自动在导出.dll的时候把当中定义的所有函数名都改了,保证不出现同名函数,这样任何程序使用此动态链接库中函数时就不会出现同名的情况。②如果加上"C",因为C中不能出现同名函数,所以你在动态链接库中定义的函数名是啥就导出啥,不会改名。

      • 假如现在不加"C",我们看看导出的.dll文件中的函数名是什么样的:

        image-20220104204610530
      • 使用VC6自带的工具:depends.exe,查看编译后导出的dllTest.dll(可以查看PE文件依赖于哪些动态链接库以及使用了动态链接库中的哪些接口)

        image-20220104204849579
      • 假如现在按照C语言的方式导出(也不加__stdcall),就不会改名字,定义的函数是什么名字导出的.dll中就是什么名字。如果C、和stdcall都加"C" __stdcall,则在原来函数名的基础上,前面加_,后面加@8,8表示函数有两个4字节宽度的参数。

        image-20230402164057042
    • _declspec(dllexport):指告诉编译器此函数为导出函数,可以供别人使用(一定要写的,固定格式)

    • __stdcall:就是函数的调用约定使用stdcall,我们前面学过,stdcall调用约定的函数会使用内平栈,如果不加VC默认使用cdcall,外平栈。建议如果在Windows下使用动态链接库最后都使用stdcall的调用约定导出.dll

2.使用DLL

1)隐式链接
  • xxx.dllxxx.lib 放到需要使用动态链接库的工程目录(创建的WorkSpace下面)下面(前面如果有静态链接库的.lib和.h记得先删掉)

    image-20230402182737799

    说明:after_day34是我平时写代码的WorkSpace,有些VC版本直接将after_day34.exe放到工程目录下,有些会创建一个Debug文件夹,after_day34.exe在Debug中。只要把dll和lib跟after_day34.exe放到一个文件夹下即可;静态链接库的.h和.lib文件要放到工程目录下面,不是Debug下面

    此时.lib中不像静态链接库会存放函数的硬编码,对于动态链接库,.lib只是告诉编译器要使用的函数在哪里;真正存放代码(函数)的地方是.dll中

  • #pragma comment(lib,"xxx.lib") 添加到调用文件中(告诉编译器要使用动态链接库的函数)

  • 加入函数的声明

    extern "C" __declspec(dllimport) __stdcall int Plus (int x,int y);  
    extern "C" __declspec(dllimport) __stdcall int Sub (int x,int y);
    extern "C" __declspec(dllimport) __stdcall int Mul (int x,int y);
    extern "C" __declspec(dllimport) __stdcall int Div (int x,int y);
    

    __declspec(dllimport):告诉编译器此函数为导入函数,说明这是使用动态链接库的函数

    与上面导出时规定的要一致:导出时有extern "C",导入时也要有

    image-20230402180103595
  • 为什么叫隐式链接:.dll提供了函数,.exe使用函数,隐式链接即.exe找函数是通过编译器去找,跟程序本事没关系

2)显示链接
  • 定义函数指针

    typedef int (__stdcall *lpPlus)(int,int);
    typedef int (__stdcall *lpSub)(int,int);
    typedef int (__stdcall *lpMul)(int,int);
    typedef int (__stdcall *lpDiv)(int,int);
    

    函数在导出时定义了__stdcall,这里定义函数指针时也需要加上__stdcall。导出导入格式要对应

  • 动态加载dll到内存中

    #include <Windows.h>   //LoadLibrary函数调用需要包含Windows.h头文件
    HMODULE hModule = LoadLibrary("xxx.dll");  //你的dll取的什么名字就写什么
    

    特别说明:

    • HINSTANCE:在win32下与HMODULE是相同的东西(Win16 遗留)
    • HMODULE:代表应用程序载入的模块(本例中hModule的值就是给定dll的ImageBase,相当于dll在内存中拉伸后的起始地址
    • HANDLE:代表系统的内核对象,如文件句柄,线程句柄,进程句柄
    • HWND:是窗口句柄

    这三个本质上就是一种类型—无符号整型;windows之所以设计成三个不一样的名字,是因为可读性更好,也可以避免在无意中对这个类型的变量进行运算

  • 给函数指针赋值,即指向具体函数的地址

    lpPlus myPlus = (lpPlus)GetProcAddress(hModule,"_Plus@8"); //这里的函数名要注意!
    lpSub mySub = (lpSub)GetProcAddress(hModule,"_Sub@8");
    lpMul myMul = (lpMul)GetProcAddress(hModule,"_Mul@8");
    lpDiv myDiv = (lpDiv)GetProcAddress(hModule,"_Div@8");
    

    如果导入时格式为"C" __stdcall,那么这里的函数名就是_函数名@参数大小这种格式。如果只有"C",但是没有__stdcall,那么这里的函数名就是在dll中定义的函数名本身。要对应好!

    • ==GetProcAddress()==函数说明:
      • 功能:显式链接时使用,用于获取DLL中导出函数的地址
      • 参数:①先动态加载dll–HINSTANCE hModule = LoadLibrary("xxx.dll");,这里就是把你的dll载入后赋给HINSTANCE类型的变量hModule,那么此时hModule就代表了你的dll载入的模块;②所以GetProcAddress()函数第一个参数就表示已经载入的DLL;第二个参数是DLL当中导出函数的名称
      • 返回值:将DLL中导出函数的地址赋给相应的函数指针,接下来就可以通过函数指针来调用函数了
  • 调用函数

    int a = myPlus(10,2);  //10+2
    int b = mySub(10,2);  //10-2
    int c = myMul(10,2);  //10*2
    int d = myDiv(10,2);  //10/2
    
    image-20230402192259846

3.动态链接库的特点

  • 我们发现如果使用动态链接库的方式,编译的.exePE文件中包含了模块,而不像静态链接库,动态链接库是真正的模块化,.exe文件中包含了.dll(即模块),所以才能使用.dll中的函数。我们使用OD查看一下使用了.dll中的函数的C程序

    183050
  • 我们再通过反汇编查看一下这个C程序使用的Plus的函数地址是多少?发现是0x10001060这个地址明显不属于.exePE文件的某个节,而是.exe文件去调用dllTest.dll模块中的函数

    image-20230402182207301
  • 正因为.exe中使用的函数不在.exe中,而是在.dll模块中,所以如果对.dll中的函数进行优化改动,就不用让.exe也重新编译,直接在.dll中修改完后,.exe文件中使用的函数就是修改后的。

  • 所以==模块化==的好处就是,哪里有问题就改哪个模块。比如做一个项目时,把整个应用按照不同的功能分成不同的dll文件,即分成不同的模块,有的.dll负责通信,有的.dll负责处理等等,此时想修改哪一个功能,不用动整个项目,而是对.dll文件做修改即可

四、使用.def导出

序号的方式导出dll中的函数;一定要好好看,和导出表的内容联系起来

1.为什么使用.def导出

  • 我们如果通过上面的方法,将我们写的代码导出,对方可以通过逆向分析得到函数名字,就可以猜到函数的功能,为了不让别人分析我们的.dll,可以通过.def导出的方法,即通过序列号代替函数名的方式,可以达到隐藏的目的

2…def导出方法

需要在动态链接库的项目WorkSpace中的.h,.cpp,.def中操作!

image-20230402192918768
  • 在.h头文件中:

    int Plus(int x,int y);
    int Sub(int x,int y);
    int Mul(int x,int y);
    int Div(int x,int y);
    

    不用dllexport这种形式定义了,直接定义成普通函数的样子就可以

    image-20230402193735350
  • 在.cpp源文件中:

    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;
    }	
    
    image-20230402193755552
  • 创建.def文件,内容如下:

    EXPORTS	
    	
    Plus @12
    Sub @15 NONAME
    Mul @13
    Div @16
    
    • Export表示导出

    • Plus @12:表示Plus函数的导出序号为12

    • Sub @15 NONAME:表示Sub函数的导出序号为15;NONAME关键字表示Sub函数只有序号,没有名字,这样做就可以将Sub函数名字隐藏(学完导出表后,就可以知道怎么调用这种没有名字的函数)

    • 动态链接库的那种以名字的导出方式也会给函数默认生成一个导出序号;.def导出的方式的序号是自己随便定义的

    • 在动态链接库项目WorkSpace下,点击Filenew–选择Text File,取名字,后缀名为.def

      image-20230402194030919
    • 接着在创建的.def中添加代码

      image-20230402194134874
    • 现在编译一下,使用DEPEND.exe查看一下函数导出后的情况:发现Plus函数前面的序号为12,Mul和Div函数前面的序号分别是13、16,但是序号为15的Sub函数名字并没有显示出来,即函数没有名字!就起到了在dll中隐藏函数名的作用

      image-20230402194253092

3.序号和函数的关系

  • 我们只定义了4个函数导出,但是由于四个函数的序号最小是@12,最大是@16,而系统计算导出函数个数的方式是通过最大序号 - 最小序号 + 1,所以会发现有五个导出函数,那多出来的@14怎么办?就用全0表示即可,但是序号位置是预留出来的

    day36学导出表时,注意与AddressOfFunctions表、AddressOfNameOrdinals表和Base字段联系!!!

  • 另外添加了NONAME关键字导出的序号为@15的函数,会发现名字也和@14一样为空,但是是有地址的

4.导出函数的方式

记住两种方式,导出表的时候会用到

  1. 名字的方式导出(编译器会默认给函数添加序号)

  2. 序号的方式导出(以序号的方式导出,再加上NONAME关键字,就只以序号的方式导出,函数没有名字)

1讲:2015-01-12(进制01) 第2讲:2015-01-13(进制02) 第3讲:2015-01-14(数据宽度-逻辑运算03) 第4讲:2015-01-15(通用寄存器-内存读写04) 第5讲:2015-01-16(内存寻址-堆栈05) 第6讲:2015-01-19(EFLAGS寄存器06) 第7讲:2015-01-20(JCC) 第8讲:2015-01-21(堆栈图) 第8讲:2015-01-21(宝马问题) 第9讲:2015-01-22(堆栈图2) 第10讲:2015-01-23(C语言01_后半段) 第10讲:2015-01-23(C语言完整版) 第11讲:2015-01-26(C语言02_数据类型) 第12讲:2015-01-27(C语言03_数据类型_IF语句) 第13讲:2015-01-28(C语言04_IF语句逆向分析上) 第14讲:2015-01-28(C语言04_IF语句逆向分析下) 第15讲:2015-01-29(C语言04_正向基础) 第16讲:2015-01-30(C语言05_循环语句) 第17讲:2015-02-02(C语言06_参数_返回值_局部变量_数组反汇编) 第18讲:2015-02-02(2015-01-30课后练习) 第19讲:2015-02-03(C语言07_多维数组) 第20讲:2015-02-03(2015-02-02课后练习) 第21讲:2015-02-04(C语言08_结构体) 第22讲:2015-02-05(C语言09_字节对齐_结构体数组) 第23讲:2015-02-06(C语言10_Switch语句反汇编) 第24讲:2015-02-26(C语言11_指针1) 第25讲:2015-02-27(C语言11_指针2) 第26讲:2015-02-28(C语言11_指针3) 第27讲:2015-02-28(C语言11_指针4) 第28讲:2015-03-02(C语言11_指针5) 第29讲:2015-03-03(C语言11_指针6) 第30讲:2015-03-04(C语言11_指针7) 第31讲:2015-03-06(C语言11_指针8) 第32讲:2015-03-09(位运算) 第33讲:2015-03-10(内存分配_文件读写) 第34讲:2015-03-11(PE头解析_手动) 第35讲:2015-03-12(PE头字段说明) 第36讲:2015-03-13(PE节表) 第37讲:2015-03-16(FileBuffer转ImageBuffer) 第38讲:2015-03-17(代码节空白区添加代码) 第39讲:2015-03-18(任意节空白区添加代码) 第40讲:2015-03-19(新增节添加代码) 第41讲:2015-03-20(扩大节-合并节-数据目录) 第42讲:2015-03-23(静态连接库-动态链接库) 第43讲:2015-03-24(导出表) 第44讲:2015-03-25(重定位表) 第45讲:2015-03-26(移动导出表-重定位表) 第46讲:2015-03-27(IAT表) 第47讲:2015-03-27(导入表) 第48讲:2015-03-30(绑定导入表) 第49讲:2015-03-31(导入表注入) 第50讲:2015-04-01(C++ this指针 类 上) 第51讲:2015-04-01(C++ this指针 类 下) 第52讲:2015-04-02(C++ 构造-析构函数 继承) 第53讲:2015-04-03(C++ 权限控制) 第54讲:2015-04-07(C++ 虚函数表) 第55讲:2015-04-08(C++ 动态绑定-多态-上) 第56讲:2015-04-08(C++ 动态绑定-多态-下) 第57讲:2015-04-09(C++ 模版) 第58讲:2015-04-10(C++ 引用-友元-运算符重载) 第59讲:2015-04-13(C++ new-delete-Vector) 第60讲:2015-04-14(C++Vector实现) 第61讲:2015-04-15(C++链表) 第62讲:2015-04-16(C++链表实现) 第63讲:2015-04-16(C++二叉树) 第64讲:2015-04-17(C++二叉树实现) 第65讲:2015-04-20(Win32 宽字符) 第66讲:2015-04-21(Win32 事件-消息-消息处理函数) 第67讲:2015-04-22(Win32 ESP寻址-定位回调函数-条件断点) 第68讲:2015-04-23(Win32 子窗口-消息处理函数定位) 第69讲:2015-04-24(Win32 资源文件-消息断点) 第70讲:2015-04-27(Win32 提取图标-修改标题) 第71讲:2015-04-28(Win32 通用控件-VM_NOTIFY) 第72讲:2015-04-29(Win32 PE查看器-项目要求) 项目一:PE查看器 开发周期(5天) 需求文档 第73讲:2015-05-07(Win32 创建线程) 第74讲:2015-05-08(Win32 线程控制_CONTEXT) 第75讲:2015-05-11(Win32 临界区) 第76讲:2015-05-12(Win32 互斥体) 第77讲:2015-05-13(Win32 事件) 第78讲:2015-05-14(Win32 信号量) 第79讲:2015-05-15(Win32 线程同步与线程互斥) 第80讲:2015-05-18(Win32 进程创建_句柄表) 第81讲:2015-05-20(Win32 以挂起形式创建进程) 第82讲:2015-05-21(Win32 加密壳_项目说明) 项目二:加密壳 开发周期(5天) 需求文档 第83讲:2015-05-28(Win32 枚举窗口_鼠标键盘事件) 第84讲:2015-05-29(Win32 CE练习) 第85讲:2015-06-01(Win32 OD练习) 第86讲:2015-06-03(Win32 ShellCode_远程线程注入) 第87讲:2015-06-04(Win32 加载EXE_模块隐藏) 第88讲:2015-06-09(Win32 IAT_HOOK) 第89讲:2015-06-10(Win32 InlineHook) 第90讲:2015-06-11(Win32 进程通信) 第91讲:2015-06-11(Win32 进程监控_项目说明) 项目三:进程监控 开发周期(5天) 需求文档 第92讲:2015-06-15(硬编码_01) 第93讲:2015-06-16(硬编码_02) 第94讲:2015-06-17(硬编码_03) 第95讲:2015-06-18(硬编码_04) 第96讲:2015-06-19(硬编码_05)
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值