s0m1ng

二进制学习中

DLL专题

前言

DLL(Dynamic Link Library,动态链接库)是 Windows 下的一种可执行模块,
可以被多个程序同时加载使用。可以导出函数

常见用途:

  • 封装公共函数(比如数学库、图形库)

  • 插件系统(比如浏览器插件)

  • 逆向工程与注入(CTF、安全研究中常用)

DLL基础:

DllMain:

  • dll没有 mainWinMain

  • 它有一个可选的 DllMain 入口点函数。这个函数不是给普通用户调用的,而是操作系统加载器在特定事件发生时(DLL 被加载、卸载、进程创建线程、线程结束)自动调用的。

  • 它的主要目的是进行初始化和清理工作(例如,创建/销毁全局对象、初始化线程本地存储 TLS)。如果不需要这些,完全可以不实现 DllMain

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <windows.h>

BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH: // DLL被映射到进程的地址空间
// 初始化代码,例如创建互斥体、加载资源
break;
case DLL_THREAD_ATTACH: // 进程创建了一个新线程
// 线程相关的初始化
break;
case DLL_THREAD_DETACH: // 线程正常退出
// 线程相关的清理
break;
case DLL_PROCESS_DETACH: // DLL被从进程的地址空间卸载
// 清理代码,例如释放资源
break;
}
return TRUE; // 返回TRUE表示成功
}

导出函数,有两种方式:

写.def文件

1
2
3
4
5
6
; mylib.def
LIBRARY "MyLibrary"
EXPORTS
AddFunction @1
MultiplyFunction @2
MyExportedVariable DATA ; 导出变量需要DATA关键字

然后在编译时链接这个文件。这种方式可以精确控制导出函数的名字和序号。

使用关键字(更常见)

可在如下所示的函数声明中使用 __declspec(dllexport) 关键字。

1
2
3
4
5
__declspec(dllexport) double WINAPI my_C_export(double x)
{
/* Modify x and return it. */
return x * 2.0;
}

必须在声明的最左侧添加 __declspec(dllexport) 关键字。 这种方法的优点是该函数不需要在 DEF 文件中列出,并且导出状态与定义一致。

如果要避免使用 C++ 名称修饰来提供 C++ 函数,必须按如下方式声明函数。

1
2
3
4
5
6
extern "C"
__declspec(dllexport) double WINAPI my_undecorated_Cpp_export(double x)
{
// Modify x and return it.
return x * 2.0;
}

链接器将使该函数显示为 my_undecorated_Cpp_export,即源代码中显示的名称,没有任何修饰。

编写一个dll并编译

mydll.h

1
2
3
4
5
6
#pragma once


__declspec(dllexport) int add(int a, int b);

__declspec(dllexport) int mul(int a, int b);t b);

这里我们下面的源文件是.c,所以不加extern “C”

mydll.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include "mydll.h"
#include <stdio.h>
#include <windows.h>
int add(int a, int b)
{
return a + b;
}
int mul(int a, int b)
{
return a * b;
}
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
return TRUE;
}

编译指令:

1
gcc -shared -o mydll.dll mydll.c -Wl,--out-implib,libmydll.a

解释一下参数:

  • -shared:告诉 gcc 生成动态链接库

  • -o mydll.dll:输出 DLL 文件

  • -Wl,--out-implib,libmydll.a:同时生成一个静态导入库(方便别人链接)

这里的静态导入库的作用是可以把dll和exe合成一个文件。方便发布,一般我们直接gcc -shared -o mydll.dll mydll.c就可以

main.c(测试函数)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include<stdio.h>
#include<windows.h>
typedef int (*add_t)(int,int);
typedef int (*mul_t)(int,int);
int main()
{
HMODULE h = LoadLibraryA("C:\\Users\\Lenovo\\OneDrive\\Desktop\\c++andpy\\cpp\\mydll\\mydll.dll"); // 或者写绝对路径
if (!h) {
printf("LoadLibrary failed: %lu\n", GetLastError());
return 1;
}
add_t add = (add_t)GetProcAddress(h, "add");
mul_t mul = (mul_t)GetProcAddress(h, "mul");
int a=10,b=20;
int sum;
sum=add(a,b);
printf("sum=%d\n",sum);
int m=mul(a,b);
printf("mul=%d\n",m);
return 0;
}

运行结果:

1
2
sum=30
mul=200

DLL调试:

dll调试如果用vscode的话太逆天,掌握不好注入器和被注入exe之间的关系,用vs就很轻松

资源管理器

右键我们的文件夹,点最下面的属性

编译dll

然后配置类型需要改成动态库.dll

目标exe

在调试的行那,右边的命令放要注入的exe路径,然后在我们的dll对应的.c文件那直接像正常的.c文件那样下断点就可以了

断点

然后直接运行这个dll对应的.c源程序,在exe文件的进程空间导入dll文件(dll注入或loadlibrary)后,我们就可以正常调试了

DLL注入

您的支持将鼓励我继续创作!

欢迎关注我的其它发布渠道