自实现LoadLibrary和GetProcAddress
# 自实现 LoadLibrary 和 GetProcAddress虽然现在有 AI 了,但对于 PE 结构,仍是我们学习 Windows 逆向绕不过去的关卡。
必须把基础打好,才能更加熟练地运用 AI。
完整工程(可编译对照):https://github.com/1903247335/manual-map-loadlibrary
---
## 为什么要自实现 LoadLibrary 和 GetProcAddress
我们可以对 PE 结构更加了解,明白底层 Loader 是怎么样加载程序、展开的。
因为这样可以免除掉部分 R3 层的检测,例如:
- `CreateToolhelp32Snapshot(TH32CS_SNAPMODULE)` + `Module32First` / `Next`
- `EnumProcessModules` / `EnumProcessModulesEx`(psapi)
- `GetModuleHandle` / `GetModuleHandleEx`(按名字找;你的 DLL 本来也没注册名字)
- 自己遍历:
- `PEB → Ldr → InLoadOrderModuleList`
- `InMemoryOrderModuleList`
- `InInitializationOrderModuleList`
---
## 需要的前置知识
- 对常规 Win API 有一定的了解
- 熟悉 PE 结构:只需要会**导入表**、**导出表**、**重定位表**原理
我们的目标是**不考虑 TLS 表**,只考虑简单的程序。单单会这个,别的也可以举一反三了。
---
## 接下来简单说下原理
`LoadLibrary` 是把 DLL 从**文件结构**直接拓展到**内存结构**,然后进行:
1. **导入表修复**(根据 INT 来把 IAT 换成真实函数地址)
2. **重定位修复**(根据写死的地址换成加载之后正确的地址)
很简单:
```text
新地址 = 目标写死的地址 + 实际载入到内存的基地址 - 写死的基地址
```
修复完后找到 DLL 入口直接调用,传入 `DLL_PROCESS_ATTACH`,来实现 DLL 初始化函数执行。
`GetProcAddress` 是根据名字或者序号来读取 DLL 的导出表,拿到对应函数。
`FreeLibrary` 原理就是把 DLL 句柄传入,然后获取到 DLL 主入口,传入 `DLL_PROCESS_DETACH` 来调用卸载入口,再然后进行 `VirtualFree` 进行内存释放。
---
## 接下来就直接开始吧
我不会直接给代码,不然直接粘贴一堆代码太无聊了,我只说思路。
完整代码见仓库:https://github.com/1903247335/manual-map-loadlibrary
### 内存空间开辟
1. 用 `CreateFileA` 打开 DLL 路径
2. `GetFileSize` 来获取 DLL 的文件大小
3. `ReadFile` 根据读出来的 File Size 移动到 `buffer` 当中做准备
### 从文件结构拓展到内存结构
这时候 `buffer` 已经是把 DLL 的文件结构一比一复制了,目的是为了可以直接读取它们的 PE 结构。
读取 DOS 头这些是常规操作了,直接到 `OptionalHeader` 里面拿到了 `SizeOfImage`。拿到它的目的是为了开辟「展开到内存」的结构——它代表着文件展开后的大小。
这时候可以用 `VirtualAlloc` 来真正开辟给 DLL 展开的空间了,同时得到的指针也是 `baseAddress`。
然后我们可以把 PE 头都给移动到 `baseAddress` 中,直接用 `OptionalHeader.SizeOfHeaders`。
(PE 头不需要按节去「拓展」;真正要按内存布局展开的是各个**节区内容**。节表告诉我们每个节该怎么搬。)
### 导入表修复
修复的原理是:从 INT 给出的信息来读取我们要找哪个外部 DLL、哪个函数;
找到它们的地址后,填充到 IAT 里面,以此来达到修复。
这一步是从 INT 中获取我们要找的 DLL(因为是导入系统 DLL,所以直接 `LoadLibrary` 不影响。
但你要是不爽,可以自己再写一个不需要系统函数也能加载系统 DLL 的——不过难度可能会非常的大)。
得到了 DLL 的句柄后,就是判断 INT 是用什么方式载入函数的——有可能是序号,也有可能是函数名。
但最核心的就是得到函数地址,并写回 IAT。
### 重定位表修复
这个实际也不难:
1. 获取当前载入内存的地址,和实际 PE 的 `OptionalHeader.ImageBase` 进行比较,看看差了多少
2. 获取重定位表的每一项,取出 offset
3. 我们要改的地方就是:`当前载入内存的地址 + 页RVA + offset`
4. 然后给这个地方加上(当前载入内存的地址 - ImageBase),就是重定位成功了
实际一点都不难。
### 调用 DllMain
找到 `entryPoint`,然后直接传入规定参数:
```text
DllMain(base, DLL_PROCESS_ATTACH, nullptr)
```
就可以实现调用了。
### 实现 GetProcAddress(根据函数名)
根据导出表来找到对应函数名。`GetProcAddress` 根据序号同理。
### FreeLibrary 实现
找到 `AddressOfEntryPoint`,往里面传入 `DLL_PROCESS_DETACH`,
再 `VirtualFree` 释放内存,就这样。
---
## 写在最后
实际上并没有我们想象的那么难,就是过程代码实现可能曲折点,但思路是摆在那的。
现在有 AI 了,我们可以花更多时间在研究思路上,而不是实际代码上了。
完整工程:https://github.com/1903247335/manual-map-loadlibrary
*仅供学习 Windows PE / Loader 原理,请勿用于未授权场景。* 这应该是反射加载了,恶意软件经常用
还有 Neurosama 以前也实现过,不过只能xp系统 aonima 发表于 2026-9-13 13:24
以前也实现过,不过只能xp系统
现在知道原理就行了 大佬肯定已经很牛逼了 自实现很厉害了。之前尝试过生成纯sc版本的ldrloaddll进行常规加载dll。但是模块里可以看得到,还得在dll里加上模块断链隐藏。仍然躲不过ark的扫描。 感谢感谢,又可以升级改造DNA,重新生产小猪仔啦。 原来是这样吗? 原来是这样的啊? 你说的有道理。。。脱壳搞修复DLL不修复导入表在64位上貌似现在比较严格不好搞。。我也是头一次见 学习了!
页:
[1]