[逆向工程]C++实现DLL卸载(二十六)

[逆向工程]C++实现DLL卸载(二十六)

引言

DLL注入(DLL Injection)是Windows系统下实现进程间通信、功能扩展、监控调试的核心技术之一。本文将从原理分析、代码实现、实战调试到防御方案,全方位讲解如何用C++实现DLL注入,并提供可直接编译的完整项目代码。

一、资源准备

1.资源准备

gmp.exe 被注入的程序

injector.exe 注入器

mandaohook.dll 需注入的dll

unloader.exe 卸载dll程序

2.任务目标

将编写好的mandaohook.dll通过injector.exe注入器注入到gmp.exe可运行程序中。注入之后将mandaohook.dll卸载。

二、DLL卸载的核心原理

DLL(动态链接库)卸载的核心原理可以从操作系统的角度和程序运行机制的角度来理解。

1. 操作系统角度
  • 引用计数机制

    • 在 Windows 操作系统中,DLL 的加载和卸载是基于引用计数的。当一个程序(可以是可执行程序,也可以是其他 DLL)第一次调用 LoadLibrary 函数加载一个 DLL 时,操作系统的加载器会将该 DLL 加载到内存中。同时,系统会为该 DLL 创建一个引用计数器,并将其值设置为 1。
    • 如果其他程序或模块再次调用 LoadLibrary 加载同一个 DLL,引用计数器的值会递增。这个引用计数器的作用是记录当前有多少个模块正在使用这个 DLL。
    • 当一个模块调用 FreeLibrary 函数来释放 DLL 时,引用计数器的值会递减。只有当引用计数器的值为 0 时,操作系统才会真正地从内存中卸载这个 DLL。例如,一个应用程序在运行过程中先后调用了两次 LoadLibrary 加载了同一个 DLL,那么引用计数为 2。当它调用了一次 FreeLibrary 之后,引用计数变为 1,DLL 仍然保留在内存中。只有当它再次调用 FreeLibrary,引用计数变为 0,DLL 才会被卸载。
  • 内存映射机制

    • DLL 文件本身是一种可执行文件格式(PE 格式)。当 DLL 被加载时,操作系统会根据其文件结构将 DLL 的代码段、数据段等映射到进程的虚拟地址空间中。DLL 的代码段是共享的,多个进程加载同一个 DLL 时,它们共享同一个代码段的物理内存,这样可以节省内存资源。
    • 在卸载 DLL 时,操作系统会撤销这种内存映射。对于代码段,如果它是共享的,当最后一个进程卸载 DLL 时,操作系统才会真正释放对应的物理内存。而对于数据段,由于每个进程加载 DLL 时可能会有自己的数据副本(如果 DLL 中定义了可写数据),操作系统会清理每个进程的虚拟地址空间中对应的区域。
2. 程序运行机制角度
  • DLL 的入口函数

    • DLL 文件中有一个特殊的入口函数 DllMain。当 DLL 被加载时,操作系统会调用 DllMain 函数,并传入 DLL_PROCESS_ATTACH 参数。这个函数可以用于初始化 DLL 的资源,比如分配内存、初始化全局变量等。
    • 当 DLL 被卸载时,操作系统会调用 DllMain 函数,并传入 DLL_PROCESS_DETACH 参数。DLL 的编写者可以在 DllMain 函数中处理 DLL_PROCESS_DETACH 情形,进行资源清理工作,例如释放分配的内存、关闭文件句柄、清理线程等。这一步是 DLL 卸载过程中很重要的环节,因为如果 DLL 没有正确清理资源,可能会导致内存泄漏、资源占用等问题。
  • 线程和资源的清理

    • 如果 DLL 创建了线程,那么在卸载之前需要确保这些线程已经正确结束。否则,可能会出现线程还在运行,但 DLL 已经被卸载的情况,这会导致线程访问无效的代码和数据,从而引发程序崩溃。
    • DLL 卸载还需要清理它所占用的资源,比如文件句柄、网络连接、注册表项等。这些资源的清理工作通常在 DllMainDLL_PROCESS_DETACH 处理分支中完成。如果 DLL 没有正确清理这些资源,它们可能会被操作系统回收,导致其他程序无法正常使用这些资源,或者出现资源泄漏的情况。

三、完整C++实现代码

1. 卸载器代码(unloader.cpp)
#include <Windows.h>
#include <TlHelp32.h>
#include <iostream>
#include <memory>
#include <string>
#include <shellapi.h> // 自动释放资源模板
template<typename T>
struct HandleDeleter {void operator()(T* handle) const {if (handle) CloseHandle(handle);}
};
using UniqueHandle = std::unique_ptr<void, HandleDeleter<void>>;// 查找模块基址(HMODULE)
HMODULE FindModuleBase(DWORD pid, const wchar_t* moduleName) {MODULEENTRY32W me = { sizeof(me) };UniqueHandle snapshot(CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, pid));if (!snapshot.get()) return nullptr;if (Module32FirstW(snapshot.get(), &me)) {do {// 提取模块基名const wchar_t* baseName = wcsrchr(me.szExePath, L'\\');baseName = baseName ? baseName + 1 : me.szExePath;if (_wcsicmp(baseName, moduleName) == 0) {return me.hModule;}} while (Module32NextW(snapshot.get(), &me));}return nullptr;
}// 主卸载函数
bool UnloadDll(DWORD pid, const wchar_t* dllName) {// 1. 打开目标进程UniqueHandle hProcess(OpenProcess(PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION |PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE,FALSE, pid));if (!hProcess) return false;// 2. 查找目标模块基址HMODULE hModule = FindModuleBase(pid, dllName);if (!hModule) {std::wcerr << L"错误:未找到模块 " << dllName << std::endl;return false;}// 3. 获取FreeLibrary地址auto pFreeLibrary = reinterpret_cast<LPTHREAD_START_ROUTINE>(GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "FreeLibrary"));if (!pFreeLibrary) return false;// 4. 创建远程线程卸载DLLUniqueHandle hThread(CreateRemoteThread(hProcess.get(), nullptr, 0,pFreeLibrary,reinterpret_cast<LPVOID>(hModule), // 直接传递HMODULE0, nullptr));if (!hThread) return false;// 5. 等待卸载完成return WaitForSingleObject(hThread.get(), 5000) == WAIT_OBJECT_0;
}int main(int argc, char* argv[]) {// 转换命令行参数为宽字符LPWSTR* szArglist = CommandLineToArgvW(GetCommandLineW(), &argc);if (!szArglist || argc != 3) {std::wcerr << L"参数错误! 正确用法: unloader.exe <PID> <DLL名称>" << std::endl;LocalFree(szArglist);return EXIT_FAILURE;}DWORD pid = _wtoi(szArglist[1]);const wchar_t* dllName = szArglist[2];if (UnloadDll(pid, dllName)) {OutputDebugStringW(L"DLL成功卸载了-!");std::wcout << L"DLL卸载成功!" << std::endl;LocalFree(szArglist);return EXIT_SUCCESS;} else {DWORD err = GetLastError();std::wcerr << L"卸载失败! 错误代码: 0x" << std::hex << err << std::endl;LocalFree(szArglist);return EXIT_FAILURE;}
}

编译:

g++ -o unloader.exe unloader.cpp -lpsapi -lmingw32 -static

在这里插入图片描述

四、关键API函数解析

代码实现了一个远程卸载 DLL 的功能,通过注入远程线程调用目标进程的 FreeLibrary 函数来卸载指定的 DLL。以下是代码中关键的 API 函数解析:

1. CreateToolhelp32Snapshot
UniqueHandle snapshot(CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, pid));
  • 功能:创建一个进程或线程的快照。
  • 参数
    • TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32:指定快照的类型,这里表示获取指定进程的模块信息。
    • pid:目标进程的进程 ID。
  • 返回值:返回一个句柄,用于后续的模块遍历操作。
  • 作用:用于获取目标进程加载的所有模块信息,以便查找特定的 DLL。
2. Module32FirstWModule32NextW
if (Module32FirstW(snapshot.get(), &me)) {do {// 遍历模块} while (Module32NextW(snapshot.get(), &me));
}
  • 功能Module32FirstW 用于获取快照中的第一个模块信息,Module32NextW 用于获取下一个模块信息。
  • 参数
    • snapshot.get():快照句柄。
    • &me:指向 MODULEENTRY32W 结构的指针,用于存储模块信息。
  • 返回值:成功返回非零值,失败返回零。
  • 作用:遍历目标进程加载的所有模块,通过比较模块名称来查找指定的 DLL。
3. OpenProcess
UniqueHandle hProcess(OpenProcess(PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION |PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE,FALSE, pid));
  • 功能:打开一个已存在的进程,并返回一个句柄。
  • 参数
    • PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE:请求的访问权限,这里包括创建线程、查询信息、操作虚拟内存等权限。
    • FALSE:是否继承句柄。
    • pid:目标进程的进程 ID。
  • 返回值:成功返回进程句柄,失败返回 NULL
  • 作用:获取对目标进程的访问权限,以便后续操作。
4. GetProcAddress
auto pFreeLibrary = reinterpret_cast<LPTHREAD_START_ROUTINE>(GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "FreeLibrary"));
  • 功能:获取指定模块中导出函数的地址。
  • 参数
    • GetModuleHandleW(L"kernel32.dll"):获取 kernel32.dll 的模块句柄。
    • "FreeLibrary":需要获取地址的函数名称。
  • 返回值:成功返回函数地址,失败返回 NULL
  • 作用:获取目标进程中的 FreeLibrary 函数地址,用于后续卸载 DLL。
5. CreateRemoteThread
UniqueHandle hThread(CreateRemoteThread(hProcess.get(), nullptr, 0,pFreeLibrary,reinterpret_cast<LPVOID>(hModule),0, nullptr
));
  • 功能:在目标进程中创建一个线程。
  • 参数
    • hProcess.get():目标进程句柄。
    • nullptr:安全属性,这里为 NULL 表示默认。
    • 0:堆栈大小,这里为 0 表示默认。
    • pFreeLibrary:线程函数入口点,这里为 FreeLibrary
    • reinterpret_cast<LPVOID>(hModule):线程函数的参数,这里传递的是目标 DLL 的模块句柄。
    • 0:创建标志,这里为 0 表示默认。
    • nullptr:线程 ID 指针,这里为 NULL 表示不获取线程 ID。
  • 返回值:成功返回线程句柄,失败返回 NULL
  • 作用:在目标进程中创建一个线程,调用 FreeLibrary 函数来卸载指定的 DLL。
6. WaitForSingleObject
return WaitForSingleObject(hThread.get(), 5000) == WAIT_OBJECT_0;
  • 功能:等待指定对象变为信号状态。
  • 参数
    • hThread.get():对象句柄,这里为远程线程句柄。
    • 5000:等待时间(毫秒),这里为 5 秒。
  • 返回值:如果对象变为信号状态,返回 WAIT_OBJECT_0;超时返回 WAIT_TIMEOUT
  • 作用:等待远程线程完成卸载操作,超时返回失败。
7. CommandLineToArgvW
LPWSTR* szArglist = CommandLineToArgvW(GetCommandLineW(), &argc);
  • 功能:将命令行字符串分解为参数列表。
  • 参数
    • GetCommandLineW():获取命令行字符串。
    • &argc:参数个数。
  • 返回值:成功返回指向参数列表的指针,失败返回 NULL
  • 作用:解析命令行参数,获取目标进程的 PID 和 DLL 名称。
8. LocalFree
LocalFree(szArglist);
  • 功能:释放由 CommandLineToArgvW 分配的内存。
  • 参数
    • szArglist:需要释放的内存指针。
  • 作用:清理命令行参数列表占用的内存。

五、测试结果

gmp.exe injector.exe mandaohook.dll unloader.exe 放入同一个文件夹下:

在这里插入图片描述

1.先打开gmp.exe 工具

2.再打开DebugView

3.执行injector.exe 注入器注入

4.查看注入信息

在这里插入图片描述

5.卸载mandaohook.dll

unloader.exe PID mandaohook.dll

在这里插入图片描述

结语

如果本教程对您有帮助,请点赞❤️收藏⭐关注支持!欢迎在评论区留言交流技术细节!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/80367.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

lesson01-PyTorch初见(理论+代码实战)

一、初识PyTorch 二、同类框架 PyTorchVSTensorFlow 三、参数 对比 四、PyTorch生态 四、常用的网络层 五、代码分析 import torch from torch import autogradx torch.tensor(1.) a torch.tensor(1., requires_gradTrue) b torch.tensor(2., requires_gradTrue) c tor…

STM32中的DMA

DMA介绍 什么是DMA? DMA&#xff08;Direct Memory Access&#xff0c;直接存储器访问&#xff09;提供在外设与内存、存储器和存储器之间的高速数据传输使用。它允许不同速度的硬件装置来沟通&#xff0c;而不需要依赖于CPU&#xff0c;在这个时间中&#xff0c;CPU对于内存…

聊聊JetCache的缓存构建

序 本文主要研究一下JetCache的缓存构建 invokeWithCached com/alicp/jetcache/anno/method/CacheHandler.java private static Object invokeWithCached(CacheInvokeContext context)throws Throwable {CacheInvokeConfig cic context.getCacheInvokeConfig();CachedAnnoC…

c#队列及其操作

可以用数组、链表实现队列&#xff0c;大致与栈相似&#xff0c;简要介绍下队列实现吧。值得注意的是循环队列判空判满操作&#xff0c;在用链表实现时需要额外思考下出入队列条件。 设计头文件 #ifndef ARRAY_QUEUE_H #define ARRAY_QUEUE_H#include <stdbool.h> #incl…

开源项目实战学习之YOLO11:12.3 ultralytics-models-sam-encoders.py源码分析

👉 点击关注不迷路 👉 点击关注不迷路 👉 另外,前些天发现了一个巨牛的AI人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。感兴趣的可以点击相关跳转链接。 点击跳转到网站。 ultralytics-models-sam 1.sam-modules-encoders.pyblocks.py: 定义模型中的各…

STM32 | FreeRTOS 消息队列

01 一、概述 队列又称消息队列&#xff0c;是一种常用于任务间通信的数据结构&#xff0c;队列可以在任务与任务间、中断和任务间传递信息&#xff0c;实现了任务接收来自其他任务或中断的不固定长度的消息&#xff0c;任务能够从队列里面读取消息&#xff0c;当队列中的消…

Java 安全漏洞扫描工具:如何快速发现和修复潜在问题?

Java 安全漏洞扫描工具&#xff1a;如何快速发现和修复潜在问题&#xff1f; 在当今的软件开发领域&#xff0c;Java 作为一种广泛使用的编程语言&#xff0c;其应用的规模和复杂度不断攀升。然而&#xff0c;随着应用的拓展&#xff0c;Java 应用面临的潜在安全漏洞风险也日益…

Python绘制克利夫兰点图:从入门到实战

Python绘制克利夫兰点图&#xff1a;从入门到实战 引言 克利夫兰点图&#xff08;Cleveland Dot Plot&#xff09;是一种强大的数据可视化工具&#xff0c;由统计学家William Cleveland在1984年提出。这种图表特别适合展示多个类别的数值比较&#xff0c;比传统的条形图更直观…

LVGL- Calendar 日历控件

1 日历控件 1.1 日历背景 lv_calendar 是 LVGL&#xff08;Light and Versatile Graphics Library&#xff09;提供的标准 GUI 控件之一&#xff0c;用于显示日历视图。它支持用户查看某年某月的完整日历&#xff0c;还可以实现点击日期、标记日期、导航月份等操作。这个控件…

多指标组合策略

该策略(MultiConditionStrategy)是一种基于多种技术指标和市场条件的交易策略。它通过综合考虑多个条件来生成交易信号,从而决定买入或卖出的时机。 以下是对该策略的详细分析: 交易逻辑思路 1. 条件1:星期几和价格变化判断 - 该条件根据当前日期是星期几以及价格的变化…

BC 范式与 4NF

接下来我们详细解释 BC 范式&#xff08;Boyce-Codd范式&#xff0c;简称 BCNF&#xff09;&#xff0c;并通过具体例子说明其定义和应用。 一、BC范式的定义 BC范式&#xff08;Boyce-Codd范式&#xff0c;BCNF&#xff09;是数据库规范化理论中的一种范式&#xff0c;它比第…

基于 CSS Grid 的网页,拆解页面整体布局结构

通过以下示例拆解网页整体布局结构&#xff1a; 一、基础结构&#xff08;HTML骨架&#xff09; <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"…

采购流程规范化如何实现?日事清流程自动化助力需求、采购、财务高效协作

采购审批流程全靠人推进&#xff0c;内耗严重&#xff0c;效率低下&#xff1f; 花重金上了OA&#xff0c;结果功能有局限、不灵活&#xff1f; 问题出在哪里&#xff1f;是我们的要求太多、太苛刻吗&#xff1f;NO&#xff01; 流程名称&#xff1a; 采购审批管理 流程功能…

全栈项目搭建指南:Nuxt.js + Node.js + MongoDB

全栈项目搭建指南&#xff1a;Nuxt.js Node.js MongoDB 一、项目概述 我们将构建一个完整的全栈应用&#xff0c;包含&#xff1a; 前端&#xff1a;Nuxt.js (SSR渲染)后端&#xff1a;Node.js (Express/Koa框架)数据库&#xff1a;MongoDB后台管理系统&#xff1a;集成在同…

NVMe简介6之PCIe事务层

PCIe的事务层连接了PCIe设备核心与PCIe链路&#xff0c;这里主要基于PCIe事务层进行分析。事务层采用TLP传输事务&#xff0c;完整的TLP由TLPPrefix、TLP头、Payload和TLP Digest组成。TLP头是TLP中最关键的部分&#xff0c;一般由三个或四个双字的长度&#xff0c;其格式定义如…

Python异常模块和包

异常 当检测到一个错误时&#xff0c;Python解释器就无法继续执行了&#xff0c;反而出现了一些错误的提示&#xff0c;这就是所谓的“异常”, 也就是我们常说的BUG 例如&#xff1a;以r方式打开一个不存在的文件。 f open(‘python1.txt’,‘r’,encoding‘utf-8’) 当我们…

汇编:循环程序设计

一、 实验要求 熟练掌握循环程序设计的基本方法熟练掌握单片机外部存储空间的访问方法 二、 实验设计 1.整体思路 先初始化一些寄存器和数据存储位置&#xff0c;然后调用两个子程序Procedure1和Procedure2&#xff0c;分别从SRC复制数据到DEST&#xff0c;一个从开头到末尾&…

典籍知识问答模块AI问答bug修改

一、修改流式数据处理问题 1.问题描述&#xff1a;由于传来的数据形式如下&#xff1a; event:START data:350 data:< data:t data:h data:i data:n data:k data:> data: data: data: data: data:嗯 data:&#xff0c; 导致需要修改获取正常的当前信息id并更…

【金仓数据库征文】- 金融HTAP实战:KingbaseES实时风控与毫秒级分析一体化架构

文章目录 引言&#xff1a;金融数字化转型的HTAP引擎革命一、HTAP架构设计与资源隔离策略1.1 混合负载物理隔离架构1.1.1 行列存储分区策略1.1.2 四级资源隔离机制 二、实时流处理与增量同步优化2.1 分钟级新鲜度保障2.1.1 WAL日志增量同步2.1.2 流计算优化 2.2 物化视图实时刷…

季报中的FPGA行业:U型反转,春江水暖

上周Lattice,AMD两大厂商相继发布2025 Q1季报,尽管恢复速度各异,但同时传递出FPGA行业整体回暖的复苏信号。 5月5日,Lattice交出了“勉强及格”的答卷,报告季度营收1亿2000万,与华尔街的预期基本相符。 对于这家聚焦在中小规模器件的领先厂商而言,按照其CEO的预期,长…