[逆向工程]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.pswp.cn/web/80367.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/80367.shtml
英文地址,請注明出處:http://en.pswp.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的預期,長…