[免杀学习]动态链接库初探(上)

Dynamic-Link库 (动态链接库) - Win32 apps | Microsoft Learn

声明:官方文档的原文均用引用格式,其他部分为个人为了方便理解而作出的一些解释或类比

最近打CTF发现这玩意的不确定因素很多,如果不是自身非常强的想要一个不错的成绩还需天时地利人和,所以决定先退一段时间冷静一下,该学学真正扎实的技术,该系列文章旨在为免杀学习打基础

Dynamic-Link库 (动态链接库)

动态链接库 (DLL) 是一个模块,其中包含可由另一个模块 (应用程序或 DLL) 使用的函数和数据。

DLL 可以定义两种类型的函数:导出函数和内部函数。 导出的函数旨在由其他模块调用,以及从定义它们的 DLL 中调用。 内部函数通常只能从定义内部函数的 DLL 中调用。 尽管 DLL 可以导出数据,但其数据通常仅由其函数使用。 但是,没有什么可以阻止另一个模块读取或写入该地址。

DLL 提供了一种模块化应用程序的方法,以便可以更轻松地更新和重复使用其功能。 当多个应用程序同时使用相同的功能时,DLL 还有助于减少内存开销,因为尽管每个应用程序都接收自己的 DLL 数据副本,但应用程序会共享 DLL 代码。

windows 应用程序编程接口 (API) 作为一组 DLL 实现,因此使用 Windows API 的任何进程都使用动态链接。

类比理解:

可以把 DLL 想象成一个工具箱,里面有很多工具,比如锤子、钳子、螺丝刀等。这些工具就是 DLL 的函数和数据,它们可以帮助你完成一些任务,比如修理东西或者组装东西。

DLL 有两种工具:导出的工具和内部的工具。导出的工具是可以给别人借用的,比如你的邻居或者朋友。内部的工具是只能你自己用的,比如你的私人物品或者特殊设备。

DLL 的好处是,它可以让你更方便地更新和复用你的工具。比如,如果你发现了一个更好的锤子,你只需要换掉 DLL 里面的锤子,就可以让所有借用过你锤子的人都用上新锤子。而不需要给每个人都买一个新锤子。

DLL 还可以节省内存空间,因为当多个人同时使用你的工具时,他们只需要共享一份工具箱,而不需要每个人都拷贝一份工具箱。这样就可以减少重复的数据和代码。

Windows API 就是一组很大很全的 DLL,它提供了很多 Windows 系统的功能和服务。比如,它有一个叫做 user32.dll 的 DLL,里面有很多跟用户界面相关的函数和数据,比如创建窗口、显示菜单、处理键盘和鼠标输入等。当你运行一个 Windows 应用程序时,它就会调用 user32.dll 里面的函数来显示界面和响应用户操作。这样就可以让应用程序更容易地使用 Windows 系统的功能。

关于 Dynamic-Link 库

动态链接允许模块仅包含加载时或运行时查找导出的 DLL 函数所需的信息。 动态链接不同于更熟悉的静态链接,其中链接器将库函数的代码复制到调用它的每个模块中。

类比理解

你可以把模块想象成一个房子,里面有很多房间,每个房间都有一个特定的功能,比如厨房、卧室、客厅等。这些房间就是模块的函数,它们可以完成一些任务,比如做饭、睡觉、看电视等。

动态链接就是一种让房子之间共享房间的方法。比如,你有一个很大的游泳池,你不想让每个房子都建一个游泳池,因为那样太浪费空间和钱了。你可以把游泳池放在一个单独的房子里,然后让其他房子都可以通过一个门来进入游泳池。这样,当一个房子想要使用游泳池时,它只需要知道游泳池在哪里,然后打开门就可以了。这个门就是动态链接的信息,它告诉房子如何找到和使用游泳池。

静态链接就是一种让每个房子都拥有自己的游泳池的方法。比如,你有很多小房子,你想让每个房子都可以随时游泳。你可以给每个房子都建一个游泳池,然后把游泳池的设计图纸复制到每个房子里。这样,当一个房子想要使用游泳池时,它不需要知道其他房子的存在,它只需要按照自己的图纸来建造和使用游泳池。这个图纸就是静态链接的信息,它告诉房子如何创建和使用游泳池。

动态链接的类型

在 DLL 中调用函数有两种方法:

  • 在 加载时动态链接中,模块显式调用导出的 DLL 函数,就像它们是本地函数一样。 这要求将模块与包含函数的 DLL 的导入库链接。 导入库为系统提供加载 DLL 所需的信息,并在加载应用程序时查找导出的 DLL 函数。
  • 运行时动态链接中,模块使用 LoadLibraryLoadLibraryEx 函数在运行时加载 DLL。 加载 DLL 后,模块调用 GetProcAddress 函数以获取导出的 DLL 函数的地址。 该模块使用 GetProcAddress 返回的函数指针调用导出的 DLL 函数。 这样就不需要导入库了。

DLL 和内存管理

加载 DLL 的每个进程都会将其映射到其虚拟地址空间。 进程将 DLL 加载到其虚拟地址后,可以调用导出的 DLL 函数。

系统维护每个 DLL 的每个进程引用计数。 当线程加载 DLL 时,引用计数将增加 1。 当进程终止时,或者当引用计数变为零 (运行时动态链接仅) 时,将从进程的虚拟地址空间中卸载 DLL。

与任何其他函数一样,导出的 DLL 函数在调用它的线程的上下文中运行。 因此,以下条件适用:

  • 调用 DLL 的进程线程可以使用 DLL 函数打开的句柄。 同样,调用进程的任何线程打开的句柄都可以在 DLL 函数中使用。
  • DLL 使用调用线程的堆栈和调用进程的虚拟地址空间。
  • DLL 从调用进程的虚拟地址空间分配内存。

简单理解:

DLL 是一种可以被多个程序共享的文件,它里面有一些可以执行特定功能的代码。当一个程序需要用到 DLL 里面的功能时,它就会把 DLL 加载到自己的内存空间里,这样就可以调用 DLL 里面的代码了。

系统会记录每个 DLL 被哪些程序加载了,每加载一次,就加一。当一个程序不需要用到 DLL 了,或者退出了,就会把 DLL 从自己的内存空间里卸载掉,这时候就减一。当一个 DLL 没有被任何程序加载时,它就会从系统内存里消失。

当一个程序调用 DLL 里面的代码时,就相当于让 DLL 里面的代码在自己的程序里运行。这样,DLL 里面的代码就可以使用程序里的资源,比如句柄、堆栈、内存等。反过来,程序里的代码也可以使用 DLL 里面的资源。这样就实现了程序和 DLL 的互相协作。

自己编写一个动态链接库

环境:VISUAL STUDIO 2022

为了更深的体会动态链接库的原理,我们自己来写一个自定义的DLL并调用他

首先用VC建立一个项目Project1,然后头文件->添加->新建项

命名为MyDll.h

// MyDll.h
#ifndef __MYDLL_H__
#define __MYDLL_H__

// 使用 __declspec(dllexport) 修饰符导出函数
__declspec(dllexport) int add(int a, int b);

#endif

这个头文件中的#ifndef __MYDLL_H__和#define __MYDLL_H__的作用是防止头文件被重复包含。如果一个头文件被多次包含,可能会导致一些标识符(如类型、枚举和静态变量)被重复声明,从而引起编译错误。使用这两个预处理指令可以避免这种情况。

具体的原理是这样的:当编译器遇到#include "MyDll.h"时,它会打开MyDll.h文件,并检查是否已经定义了__MYDLL_H__这个宏。如果没有定义,它就会执行#define MYDLL_H,并继续处理头文件中的内容。如果已经定义了,说明这个头文件已经被包含过了,那么它就会跳过头文件中的内容,直到遇到#endif为止。这样就可以保证头文件中的内容只被包含一次。

__declspec(dllexport) int add(int a, int b); 的意思是声明一个名为add的函数,它接受两个int类型的参数a和b,返回一个int类型的值,declspec(dllexport)关键字将该函数导出到DLL中,以便其他程序可以调用,总结就是使得我们自定义的add函数成为一个导出函数能够提供给别人用

第二步,源文件->添加->新建项

命名为MyDll.cpp

// MyDll.cpp
#include <windows.h>
#include "MyDll.h"


int add(int a, int b)
{
    return a + b;
}

第三步,更改配置类型

对项目名Project1右键->属性->配置属性->常规->配置类型->动态库(.dll)

最后生成解决方案,会在Debug或者Release目录下(取决于你用的什么模式)生成lib和dll,不过名字会是Project1.lib,Project.dll

如果想要自定义生成名字需要设置一下,很简单,就是把上图中的目标文件名改为MyDll就行了

加载时动态链接

当我们需要在代码中以加载时动态链接的方式引入dll函数,可以这样做

首先把生成的lib和dll放入新创建的Project2目录下,然后新建cpp,这次需要把__declspec修饰符的参数改为dllimport,表示从外部导入

// main.c
#include <stdio.h>
#pragma comment(lib,"MyDll.lib" )
__declspec(dllimport) int add(int a, int b);
int main()
{
    int x = 10;
    int y = 20;
    int z = add(x, y); // 调用 DLL 中的函数
    printf("The sum of %d and %d is %d\n", x, y, z);
    return 0;
}

编译运行,成功

最后提一下,如果你想导出一个C++风格的函数,例如使用命名空间、类、重载等特性的函数,那么你需要在__declspec(dllexport)前加上extern “C”,以指示编译器使用C语言的命名规则,否则会产生复杂的修饰符。例如:

extern “C” __declspec(dllexport) int add(int a, int b);

这样就可以保证导出的函数名为add,而不是其他形式。(例如imp_add等)

切记,extern “C” 一定要配对用,否则会出现函数名不匹配导致报错

比如在dll的cpp中导出函数

extern “C” __declspec(dllexport) int add(int a, int b);

那么,就必须在导入函数时也使用extern “C”

extern “C” __declspec(dllimport) int add(int a, int b);

举一反三

如果我们是个攻击者,我们可以在编写DLL库时编写DLLmain入口点函数,使其在被加载时额外触发一些操作

// MyDll.cpp
#include <windows.h>
#include "MyDll.h"
#include <iostream>
BOOL APIENTRY DllMain(HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        std::cout << "hacked!" << "\n";
        break;
    case DLL_THREAD_ATTACH:

    case DLL_THREAD_DETACH:

    case DLL_PROCESS_DETACH:
        break; 
    } 
    return TRUE;
}
int add(int a, int b)
{
    return a + b;
}

重新生成dll和lib,放入Project2目录下,运行后可以看出,在DLL被加载时就触发了我们自定义的操作,打印hacked!

重新生成dll和lib,放入Project2目录下,运行后可以看出,在DLL被加载时就触发了我们自定义的操作,打印hacked!

总结

第一次接触动态链接库这个概念,想要摸清楚还是比较难理解的(对我来说),所以特地花了一些篇幅来写这个东西,也算是加强了自己对动态链接的理解

还有运行时进行动态链接,因为这个操作涉及的函数比较杂也稍微难一点点,所以下篇再说

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值