静态库和动态库及DLL进程注入
一. 静态库
- 原理:编译期间,将链接生成的目标文件和库文件全部编译进可执行文件(.exe)中
- 缺点:可执行文件大,也不够灵活
- 生成:设置工程"win32项目--静态库",这里对输出目录作了修改生成到”lib“目录里。
1 #ifndef _STATICLIB_H__ 2 #define _STATICLIB_H__ 3 4 //extern "C"作用:指示编译器这部分代码按C语言(而不是C++)的方式进行编译。 5 //由于C++支持函数重载,因此编译器编译函数的过程中会将函数的参数类型也加到编译后的代码中,而不仅仅是函数名;而C语言并不支持函数重载,因此编译C语言代码的函数时不会带上函数的参数类型,一般只包括函数名 6 7 extern "C" int Sum(int iAddend, int iAugend); 8 9 #endif /*_STATICLIB_H__*/
1 #include "stdafx.h" 2 #include "staticLib.h" 3 4 int Sum(int iAddend, int iAugend) 5 { 6 return (iAddend + iAugend); 7 }
- 接口导出:以上述项目"staticLib"项目为例,且以"Debug"生成的“staticLib.lib”进行举例。
-
方式一:代码导入
1 #include "stdafx.h" 2 3 //静态库调用 4 #include "../LibSum/LibSum.h" 5 #pragma comment(lib, "../lib/LibSum.lib") 6 7 int _tmain(int argc, _TCHAR* argv[]) 8 { 9 printf("LibSum: %d \n", LibSum(10, 12)); 10 11 return 0; 12 }
2. 方式2:VS配置。我们把单独生成的”LibSum.lib“放在"lib"目录,而"LibSum.h"放在"include"目录,这样更符合我们在项目中使用库的方式。
1 #include "stdafx.h" 2 3 #include "../include/LibSum.h" 4 //1.项目右键-->VC++目录-->包含目录-->../include 5 //2.项目右键-->链接器-->常规-->附加库目录-->../lib 6 //3.项目右键-->链接器-->输入-->附加依赖项-->staticLib.lib 7 8 int _tmain(int argc, _TCHAR* argv[]) 9 { 10 printf("LibSum: %d \n", LibSum(10, 12)); 11 12 return 0; 13 }
注:这里在工程里添加的目录都是绝对路径,所以使用调试代码里需注意设置。
3. 导入失败原因解析:(1) 包含目录路径是否出错,仔细检查拼写错误等;
(2) 字符集 是否匹配,"Unicode"与"多字符集"的设置;
(3) 活动平台 "x64" 与 "win32" 是否匹配,当然,一般情况,我们平台是默认的"win32"。
二. 动态库
1. win32动态库
(1)原理:在.exe执行期间,动态加载,动态释放,且任何编程语言都能调用;
(2)缺点:多个.exe使用同一个共享dll库发生版本冲突;
(3)生成:设置工程"win32项目--动态库",最后编译会生成"DllSum.lib"和"DllSum.dll"两个文件。
①方式一:代码导入---__declspec(dllexport): 表示该符号是从本DLL导出的符号; __declspec(dllimport) 表示该符号是从别的DLL中导入的。
1 extern "C" _declspec(dllexport) int Dll_Sum(int iAddend, int iAugend);
1 #include "stdafx.h" 2 #include "DllSum.h" 3 4 int DllSum(int iAddend, int iAugend) 5 { 6 return iAddend + iAugend; 7 }
②方式二:在"DllSum"项目中添加"DllSum.def"文件,这样就不需要在"DllSum.h"中进行申明。
1 LIBRARY "DllSum" 2 EXPORTS 3 DllDec @1
1 #include "stdafx.h" 2 #include "DllSum.h" 3 4 5 6 int DllDec(int iSubtraction, int iMinuend) 7 { 8 return (iSubtraction - iMinuend); 9 }
(4)接口导出:这里以上述项目"DllSum"为例。
① 方式一:隐式调用----这里需要三个文件,DllSum.h,DllSum.lib,DllSum.dll。方式其实和静态库一样,两种静态库的导出方式都可。
1 #include "../include/DllSum.h" 2 #pragma comment(lib, "../Debug/DllSum.lib") 3 4 int _tmain(int argc, _TCHAR* argv[]) 5 { 6 printf("LibSum: %d \n", DllSum(10, 18)); 7 8 return 0; 9 }
② 方式二:显式调用--直接将生成的"DllSum.lib"和"DllSum.dll"放入需要调用的可执行文件目录。
1 #include "stdafx.h" 2 #include <windows.h> 3 4 using DLL_SUM = int(*)(int, int); 5 6 int _tmain(int argc, _TCHAR* argv[]) 7 { 8 HINSTANCE hInstance = ::LoadLibrary(_T("DllSum.dll")); 9 if (nullptr != hInstance) 10 { 11 DLL_SUM pfnDllDec = (DLL_SUM)::GetProcAddress(hInstance, "DllDec"); 12 if (nullptr != pfnDllDec) 13 { 14 printf("DllDec: %d\n", pfnDllDec(18, 10)); 15 } 16 FreeLibrary(hInstance); 17 } 18 19 return 0; 20 }
③ 方式三:为了更符合项目环境搭建,在这里对"DllSum"项目生成的三个文件分别放在对应的目录,在这里配置"DllSum"
如此编译生成的"DllSum.dll"就放在了bin目录,"DllSum.lib"就放在了lib目录,我们再创建一个include目录,将DllSum.h放进去,那动态库的三要素就齐。
下面开始配置"DllLibCallTest"项目,如下图:
"DllSum.lib"就加载好了,再来搞定"DllSum.dll",这里我们需要用到"添加系统环境变量",”我的电脑“右键"属性",其他配置如下:
代码如下:
1 #include "stdafx.h" 2 #include "../include/DllSum.h" 3 4 int _tmain(int argc, _TCHAR* argv[]) 5 { 6 printf("LibSum: %d \n", DllSum(10, 18)); 7 8 return 0; 9 }
这样搭建的理由:如果dll里的代码需要频繁变动,这样设置后,不需要每次生成"xx.lib"和"xx.dll"复制粘贴到测试程序目录,各自编译更新,不需要再做其他操作。当然我们的程序都打包在同一级目录,感觉不到太大的差异,实际项目肯定不是如此。
(5)调试:这里使用"LibDllCallTest"测试项目与"DllSum"项目进行测试。我们需要在”DllSum"项目属性里设置“LibDllCallTest.exe”位置,这里又涉及到绝对路径,需要注意下,详见图
设置完成后,我们"F5"调试代码,就可以进入"DllSum"的工程代码里进行调试了,如下图。
2. MFC动态库(MFCDLL)
(1) 原理:和win32动态库一样,但可以使用MFC库
(2) 缺点:和win32的dll一样
(3) 生成:在这里作一个UI界面的Dll进行测试,先在工程里选择生成"MFC DLL"工程,然后在"资源视图"里添加一个"Dialog"资源。
先申明一个函数接口,这里有现成的".def"文件,我们就在这里进行申明:函数名需注意不要与系统API冲突
1 ; MFCDllUI.def : 声明 DLL 的模块参数。 2 3 LIBRARY 4 5 EXPORTS 6 ; 此处可以是显式导出 7 ShowUI @1
接着在"MFCDllUI.cpp"里写函数接口,在这里我们调用界面,同样,编译生成"MFCDllUI.lib"和“MFCDll.dll”两个文件,代码如下:
1 // MFCDllUI.cpp : 定义 DLL 的初始化例程。 2 // 3 4 #include "stdafx.h" 5 #include "MFCDllUI.h" 6 #include "DlgTest.h" 7 8 #ifdef _DEBUG 9 #define new DEBUG_NEW 10 #endif 11 12 // 13 //TODO: 如果此 DLL 相对于 MFC DLL 是动态链接的, 14 // 则从此 DLL 导出的任何调入 15 // MFC 的函数必须将 AFX_MANAGE_STATE 宏添加到 16 // 该函数的最前面。 17 // 18 // 例如: 19 // 20 // extern "C" BOOL PASCAL EXPORT ExportedFunction() 21 // { 22 // AFX_MANAGE_STATE(AfxGetStaticModuleState()); 23 // // 此处为普通函数体 24 // } 25 // 26 // 此宏先于任何 MFC 调用 27 // 出现在每个函数中十分重要。 这意味着 28 // 它必须作为函数中的第一个语句 29 // 出现,甚至先于所有对象变量声明, 30 // 这是因为它们的构造函数可能生成 MFC 31 // DLL 调用。 32 // 33 // 有关其他详细信息, 34 // 请参阅 MFC 技术说明 33 和 58。 35 // 36 37 // CMFCDllUIApp 38 39 BEGIN_MESSAGE_MAP(CMFCDllUIApp, CWinApp) 40 END_MESSAGE_MAP() 41 42 43 // CMFCDllUIApp 构造 44 45 CMFCDllUIApp::CMFCDllUIApp() 46 { 47 // TODO: 在此处添加构造代码, 48 // 将所有重要的初始化放置在 InitInstance 中 49 } 50 51 52 // 唯一的一个 CMFCDllUIApp 对象 53 54 CMFCDllUIApp theApp; 55 56 57 // CMFCDllUIApp 初始化 58 59 BOOL CMFCDllUIApp::InitInstance() 60 { 61 CWinApp::InitInstance(); 62 63 return TRUE; 64 } 65 66 void ShowUI() 67 { 68 //状态切换: AFX_MANAGE_STATE(AfxGetStaticModuleState( ))原因:当用户调用多个.dll时或MFC程序调用MFCDll时,在Resource.h里存在 69 //相同ID号的资源时,系统要如何识别?此名就是用来标识我正在使用MFCDll里的资源。 70 AFX_MANAGE_STATE(AfxGetStaticModuleState()); 71 72 DlgTest objDlgTest; 73 objDlgTest.DoModal(); 74 }
(4) 导出:使用"DllLibCallTest"项目测试,这里使用隐式调用,注:导出接口需要"_declspec(dllimport)"
1 #include "stdafx.h" 2 3 #pragma comment(lib, "../Debug/MFCDllUI.lib") 4 _declspec(dllimport) void DllTest();//这里需要注意 5 6 int _tmain(int argc, _TCHAR* argv[]) 7 { 8 DllTest(); 9 10 return 0; 11 }
3. Dll进程注入
(1)特点:进程与进程之间相互独立,每个进程都有自己的单独的4GB地址空间;
(2) 不同点:常见的有共享内存,管道,邮件槽等 (相互通知才能通信) ; Dll注入(不通知,主动),常见的像木马等;
(3) 提权:有些系统进程由于权限不够而OpenProcess等失败;
1 BOOL PromotePrivilege(PCTSTR szPrivilege, BOOL fEnable = TRUE) 2 { 3 //启用debug特权允许应用程序查看 szPrivilege = SE_DEBUG_NAME 4 BOOL fOk = FALSE; 5 HANDLE hToken; 6 7 // 尝试打开此进程的访问令牌TOKEN_ADJUST_PRIVILEGES 需要启用或禁用访问令牌中的特权 8 if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) 9 { 10 // 试图修改给定的权限 11 TOKEN_PRIVILEGES tp; 12 // LookupPrivilegeValue函数检索在指定系统上使用的本地惟一标识符(LUID),以本地表示指定的特权名称。 13 LookupPrivilegeValue(NULL, szPrivilege, &tp.Privileges[0].Luid); 14 tp.PrivilegeCount = 1; 15 tp.Privileges[0].Attributes = fEnable ? SE_PRIVILEGE_ENABLED : 0; 16 AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL); 17 fOk = (GetLastError() == ERROR_SUCCESS); 18 19 // Don't forget to close the token handle 20 CloseHandle(hToken); 21 } 22 return(fOk); 23 }
(4) 获取进程:遍历当前系统正在运行的所有进程名和PID
1 //获取PID 2 #include <map> 3 #include <TlHelp32.h> 4 5 #ifdef _UNICODE 6 using _tmap = std::map<std::wstring, DWORD>; 7 #else 8 using _tmap = std::map<std::string, DWORD>; 9 #endif 10 11 void traverseProcesses(_tmap &mapProId) 12 { 13 PROCESSENTRY32 stPe32 = { 0 }; 14 stPe32.dwSize = sizeof (stPe32); 15 16 HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);//获取进程快照 17 if (INVALID_HANDLE_VALUE == hProcessSnap) return; 18 19 if (FALSE == Process32First(hProcessSnap, &stPe32)) return; 20 do//轮询进程 21 { 22 _tprintf(_T("%s: %d \n"), stPe32.szExeFile, stPe32.th32ProcessID); 23 mapProId.insert(_tmap::value_type(stPe32.szExeFile, stPe32.th32ProcessID)); 24 } while (Process32Next(hProcessSnap, &stPe32)); 25 }
(5)Dll注入:打开指定进程,再创建一个线程加载Dll
1 BOOL DllToProcess(LPCTSTR lpDllName, DWORD dwPid) 2 { 3 //打开进程 4 HANDLE hDestProcess = OpenProcess(PROCESS_ALL_ACCESS | PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION | PROCESS_VM_WRITE, FALSE, dwPid); 5 if (INVALID_HANDLE_VALUE != hDestProcess) 6 { 7 DWORD dwNewSize = (_tcslen(lpDllName) + 1) * sizeof (TCHAR); 8 LPVOID lpAddr = VirtualAllocEx(hDestProcess, nullptr, dwNewSize, MEM_COMMIT, PAGE_READWRITE);//在打开的进程中申请内存空间 9 if ((nullptr != lpAddr) && (WriteProcessMemory(hDestProcess, lpAddr, lpDllName, dwNewSize, nullptr)))//在内存中写入dll名 10 { 11 #ifdef _UNICODE 12 LPTHREAD_START_ROUTINE pfnStartAddr = (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(_T("Kernel32.dll")), "LoadLibraryW");//获取LoadLibrary地址 13 #else 14 LPTHREAD_START_ROUTINE pfnStartAddr = (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(_T("Kernel32.dll")), "LoadLibraryA"); 15 #endif 16 //创建线程加载Dll 17 DWORD dwThreadID = 0; 18 HANDLE hDllThread = CreateRemoteThread(hDestProcess, NULL, 0, pfnStartAddr, lpAddr, NULL, &dwThreadID); 19 if (nullptr != hDllThread) 20 { 21 ::WaitForSingleObject(hDllThread, INFINITE);// 等待远程线程结束 22 ::VirtualFreeEx(hDestProcess, lpAddr, dwNewSize, MEM_DECOMMIT);//清理内存 23 ::CloseHandle(hDllThread); 24 ::CloseHandle(hDestProcess); 25 return TRUE; 26 } 27 } 28 } 29 return FALSE; 30 }
(6) 完整测试代码如下:先在电脑上打开"计算器"程序,进程里叫做“Calculator.exe”,再使用“MFCDLLUI.dll”来进行测试
1 #include "stdafx.h" 2 //windows进程:进程与进程之间相互独立,每个进程都有自己的4GB地址空间; 3 //进程通信:常见的有共享内存,管道,邮件槽等 相互通知 4 //dll注入 不通知 5 #include <windows.h> 6 #include <psapi.h> 7 8 //提权:原因是OpenProcess权限可能不够 9 BOOL PromotePrivilege(PCTSTR szPrivilege, BOOL fEnable = TRUE) 10 { 11 //启用debug特权允许应用程序查看 szPrivilege = SE_DEBUG_NAME 12 BOOL fOk = FALSE; 13 HANDLE hToken; 14 15 // 尝试打开此进程的访问令牌TOKEN_ADJUST_PRIVILEGES 需要启用或禁用访问令牌中的特权 16 if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) 17 { 18 // 试图修改给定的权限 19 TOKEN_PRIVILEGES tp; 20 // LookupPrivilegeValue函数检索在指定系统上使用的本地惟一标识符(LUID),以本地表示指定的特权名称。 21 LookupPrivilegeValue(NULL, szPrivilege, &tp.Privileges[0].Luid); 22 tp.PrivilegeCount = 1; 23 tp.Privileges[0].Attributes = fEnable ? SE_PRIVILEGE_ENABLED : 0; 24 AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL); 25 fOk = (GetLastError() == ERROR_SUCCESS); 26 27 // Don't forget to close the token handle 28 CloseHandle(hToken); 29 } 30 return(fOk); 31 } 32 33 //获取PID 34 #include <map> 35 #include <TlHelp32.h> 36 37 #ifdef _UNICODE 38 using _tmap = std::map<std::wstring, DWORD>; 39 #else 40 using _tmap = std::map<std::string, DWORD>; 41 #endif 42 43 void traverseProcesses(_tmap &mapProId) 44 { 45 PROCESSENTRY32 stPe32 = { 0 }; 46 stPe32.dwSize = sizeof (stPe32); 47 48 HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);//获取进程快照 49 if (INVALID_HANDLE_VALUE == hProcessSnap) return; 50 51 if (FALSE == Process32First(hProcessSnap, &stPe32)) return; 52 do//轮询进程 53 { 54 _tprintf(_T("%s: %d \n"), stPe32.szExeFile, stPe32.th32ProcessID); 55 mapProId.insert(_tmap::value_type(stPe32.szExeFile, stPe32.th32ProcessID)); 56 } while (Process32Next(hProcessSnap, &stPe32)); 57 } 58 59 //dll进程注入 60 BOOL DllToProcess(LPCTSTR lpDllName, DWORD dwPid) 61 { 62 //打开进程 63 HANDLE hDestProcess = OpenProcess(PROCESS_ALL_ACCESS | PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION | PROCESS_VM_WRITE, FALSE, dwPid); 64 if (INVALID_HANDLE_VALUE != hDestProcess) 65 { 66 DWORD dwNewSize = (_tcslen(lpDllName) + 1) * sizeof (TCHAR); 67 LPVOID lpAddr = VirtualAllocEx(hDestProcess, nullptr, dwNewSize, MEM_COMMIT, PAGE_READWRITE);//在打开的进程中申请内存空间 68 if ((nullptr != lpAddr) && (WriteProcessMemory(hDestProcess, lpAddr, lpDllName, dwNewSize, nullptr)))//在内存中写入dll名 69 { 70 #ifdef _UNICODE 71 LPTHREAD_START_ROUTINE pfnStartAddr = (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(_T("Kernel32.dll")), "LoadLibraryW");//获取LoadLibrary地址 72 #else 73 LPTHREAD_START_ROUTINE pfnStartAddr = (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(_T("Kernel32.dll")), "LoadLibraryA"); 74 #endif 75 //创建线程加载Dll 76 DWORD dwThreadID = 0; 77 HANDLE hDllThread = CreateRemoteThread(hDestProcess, NULL, 0, pfnStartAddr, lpAddr, NULL, &dwThreadID); 78 if (nullptr != hDllThread) 79 { 80 ::WaitForSingleObject(hDllThread, INFINITE);// 等待远程线程结束 81 ::VirtualFreeEx(hDestProcess, lpAddr, dwNewSize, MEM_DECOMMIT);//清理内存 82 ::CloseHandle(hDllThread); 83 ::CloseHandle(hDestProcess); 84 return TRUE; 85 } 86 else 87 { 88 printf("Error CreateRemoteThread,%d\n", GetLastError()); 89 return FALSE; 90 } 91 } 92 } 93 return FALSE; 94 } 95 96 int _tmain(int argc, _TCHAR* argv[]) 97 { 98 _tmap mapProId; 99 BOOL bReturn = PromotePrivilege(SE_DEBUG_NAME);//具体特权可参见MSDN 100 if (TRUE == bReturn) 101 { 102 traverseProcesses(mapProId);//枚举当前系统运行的所有进程 103 for (auto atProID : mapProId) 104 { 105 if (atProID.first.compare(_T("Calculator.exe")) == 0)//以“计算器”举例 106 { 107 DllToProcess(_T("D:\code\DllLibCallTest\Debug\MFCDllUI.dll"), atProID.second); 108 } 109 } 110 } 111 112 return 0; 113 }
我们将代码断点在线程退出之前,再使用“wsyscheck.exe”查看,可以看到"MFCDllUI.exe"正在运行。
(7) 特别注意:我们需要统一需要注入进程的平台,比如:测试的"计算器"是在“x64”系统,而我们一般工程都是默认为"win32",这时“CreateRemoteThread”会创建失败,一般错误代码为:5。
三. 调用实例
- 本例程是用计算机举例,属于系统软件,也可使用用户软件进行测试,效果更明显!如果使用非窗口的Dll,怎么测试是否注入成功?使用wsyscheck.exe查看,已打打包在源代码里;
- 下载地址:https://files.cnblogs.com/files/blogs/666666/DllLibCall.rar?t=1709561139&download=true