Windows系统编程(五)静态库和动态库
在Windows中存在两种库,分别是静态库lib和动态库dll,它们都属于PE文件。这些库用于提供别人使用我们所写的功能而不会暴露源代码。
现在我们来讲解这两种库的使用方法
静态库
一.在VS中选择创建静态库,命名为Staticlib1
二.我们可以在该静态库中创建相关的头文件,以便封装函数
比如,现在我们创建一个Staticlib.h的头文件,声明了一个函数:void show();
现在在Staticlib1.cpp中包含该头文件并对其进行实现
void show()
{std::cout << “Hello Lib” << std::endl;
}
三.对该静态库进行相关属性的设置:C/C++->代码生成->Spectre缓解->已禁用,运行库->多线程调试
四.运行静态库以后,打开其所在文件夹,找到第二步创建的头文件,并进入debug找到lib文件,将两者进行复制以便接下来使用
五.创建一个新的控制台应用,项目属性修改为同静态库一致修改
六.粘贴我们第四步找到的两个文件到当前控制台应用所在文件夹下
七.在当前控制台应用中添加头文件将Staticlib.h头文件导入,并在该程序中进行包含
八.在当前控制台程序中包含该lib文件:
方法一:#pragma comment(lib,"路径")
方法二:项目属性-链接器-输入:附加依赖项:加入lib文件
九.此时便可以正常使用我们在静态库中定义的函数了
动态库
一.在VS中选择创建动态库,命名为dll1
此时便生成出来了一个动态链接库,在其dllmain.cpp文件中,其包含以下代码
BOOL APIENTRY DllMain( HMODULE hModule,DWORD ul_reason_for_call,LPVOID lpReserved)
{switch (ul_reason_for_call)
{//如果我们只是要从动态链接库中导出函数而不是在以下四个时机执行函数,那么以下四个选项没有用case DLL_PROCESS_ATTACH: //附加到进程的时候触发事件break;case DLL_THREAD_ATTACH: //附加到线程的时候触发事件break;case DLL_THREAD_DETACH: //进程剥离的时候触发事件break;case DLL_PROCESS_DETACH: //线程剥离的时候触发事件break;}return TRUE;
}
二.新建一个头文件mydll.h,用于声明函数;void show(const char* szBuffer);
三.新建一个.cpp文件用于实现该函数:
void show(const char* szBuffer)
{std::cout << szBuffer << std::endl;
}
四.对该动态链接库进行项目属性设置:C/C++->代码生成->Spectre缓解->已禁用,运行库->多线程调试
五.导出函数
注:名称粉碎机制,在我们利用PEtools等工具的时候观察动态库中导出的函数时,函数的名字是乱码
动态链接库导出函数有以下两种方法:
方法一:项目中添加模板定义文件,并在该文件写入以下代码:
LIBRARY
EXPORTS//如下书写我们的函数show(函数名)
方法二:在mydll.h中如此声明函数,具体有两种方式:
_declspec(dllexport)void show(const char* szBuffer);
//这种方式导出函数,会触发名称粉碎机制
extern"C"或EXTERN_C _declspec(dllexport)void show(const char* szBuffer);
//这种方式是以C语言的方式导出函数,不会触发名称粉碎机制
六.运行该动态链接库后,在其文件夹中找到mydll.h头文件以及在debug文件夹中找到dll文件进行复制以备后续使用
七.创建一个新的控制台应用,项目属性修改为同动态库一致修改
八.粘贴我们第六步找到的两个文件到当前控制台应用所在文件夹下
九.在当前控制台应用中添加头文件将mydll.h头文件导入,并在该程序中进行包含Windows.h头文件
十.在当前控制台应用通过以下代码方式便可以正常使用动态链接库中的函数了
接下来让我们看看,具体代码怎么书写
#include<iostream>
#include<Windows.h>
typedef void(*myshow)(const char* szBuffer)
int main()
{HMODOULE hDll = LoadLirary(L”dll1”); //在目标动态链接库中找到函数地址myshow func = (myshow)GetProcAddress(hDll, “show”); func(“sadd”);rertun 0;
}
此时运行程序便可正常使用动态链接库中的函数了
主动加载动态库事件
现在我们尝试在新项目主动加载动态链接库而非使用时,可以使其触发相关的事件
我们回到dllmain.cpp中,添加相关代码使其在加载到进程时可以发生弹窗
BOOL APIENTRY DllMain( HMODULE hModule,DWORD ul_reason_for_call,LPVOID lpReserved)
{switch (ul_reason_for_call){case DLL_PROCESS_ATTACH: //附加到进程的时候触发事件MessageBox(NULL,L"加载模块","das",MB_OK);break;case DLL_THREAD_ATTACH: //附加到线程的时候触发事件break;case DLL_THREAD_DETACH: //进程剥离的时候触发事件break;case DLL_PROCESS_DETACH: //线程剥离的时候触发事件break;}return TRUE;
}
此时我们回到原来的控制台项目中,开始运行程序。当动态链接库在进程加载的时候,会先弹出对话框,之后才会继续执行程序
远程注入DLL
在上述例子中我们已经见到了新项目主要加载动态链接库的情况,现在来尝试被动加载的情况 ,也就是远程注入DLL
接下来我们以上述加载了动态库的控制台项目去写一个远程注入DLL
#include<iostream>
#include<Windows.h>
void Inject(DWORD PID, const WCHAR * szBuffer)//远程注入的进程ID和DLL路径
{//获取目标进程句柄HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, PID);//在目标进程内存中申请一段内存空间用于写入需要远程加载的dllLPVOID lpAddr = VirtualAllocEx(hProcess, NULL, MAX_PATH, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);//向内存中写入要注入的dll路径SIZE_T dwWrittenSize = 0;WriteProcessMemory(hProcess, lpAddr, szBuffer, (wcslen(szBuffer) + 1) * 2, &dwWrittenSize);//注意此处*2是因为我们此处使用的是宽字符集//创建远程线程HANDLE hThread = CreateRemoteThread(hProcess, NULL, NULL, (LPTHREAD_START_ROUTINE)LoadLibraryW, lpAddr, NULL, NULL);//当目标进程启动时会执行该远程线程执行LoadLibraryW函数WaitForSingleObject(hThread, -1);// 等待线程执行结束VirtualFreeEx(hProcess, lpAddr, 0, MEM_RELEASE);//内存释放CloseHandle(hProcess);CloseHandle(hThread);//关闭空闲句柄
}
int main()
{Inject(11996,L"路径")
}
此时,我们便可以将我们选中的DLL注入到目标进程了
接下来我们详解为什么CreateRemoteThread API的第四个参数为什么可以是LoadLibraryW
如下是CreateRemoteThread API原型:
CreateRemoteThread(_In_ HANDLE hProcess,_In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,_In_ SIZE_T dwStackSize,_In_ LPTHREAD_START_ROUTINE lpStartAddress,_In_opt_ LPVOID lpParameter,_In_ DWORD dwCreationFlags,_Out_opt_ LPDWORD lpThreadId);
其中第四个参数是线程回调函数指针,这个参数是LPTHREAD_START_ROUTINE类型的,我们跟进去发现该类型的原型实际上是这样:
typedef DWORD (WINAPI *PTHREAD_START_ROUTINE)(LPVOID lpThreadParameter);
我们再观察LoadLibraryW API:
HMODULE
WINAPI
LoadLibraryW(_In_ LPCWSTR lpLibFileName);
我们发现,它们的返回值都表示一个四字节整数,调用约定也是一样的,具体参数也是指向一段内存的指针。因此这两个函数本质是一样的。所以我们就可以把LoadLibraryW作为CreateRemoteThread的第四个参数。
注:LoadLibrary是加载线程,FreeLibrary是卸载线程
遍历模块
我们知道每个PE文件都是由多个模块组成,现在让我们去遍历获取这些模块
#include <TlHelp32.h>
#include<iostream>
#include<Windows.h>
void ShowModule(DWORD dwPid)
{//创建快照:HANDLE hSnap =CreateToolhelp32Snapshot(TH32CS_SNAPMODULE|TH32CS_SNAPMODULE32,dwPid);MODULEENTRY32 me32 ={size0f(MODULEENTRY32)};while(MOdule32Next(hSnap,&me32)){printf(“%S\r\n”, me32.szModule); printf(“%S\r\n”, me32.szExePath);//注意打印需要格式化printf(“0x%X\r\n\n”,(DWORD)me32.modBaseAddr)//这里添加操作}
}
int main()
{ShowModule(21308);
}
MFC动态链接库
一.在VS中选择创建MFC动态链接库,命名为MFCLibrary1,选择静态链接到MFC的常规DLL
二.MFC动态链接库只有一个入口点,并在其中添加代码
BOOL CMFCLibrary1App::InitInstance()
{CWinApp::InitInstance();MessageBox(L”qweq”, L“DHAO”, MB_OK);return TRUE;
}
三.运行该动态库以后,打开其所在文件夹进入debug找到dll文件,进行复制以便接下来使用
四.接下来就可以正常的将dll文件进行注入了
作业
1.实现远程线程注入功能,并且实现卸载功能
2.添加右键远程线程注入功能
3.添加打开模块功能,模块功能内可以右键卸载指定模块