本篇笔记学习教程:C语言编写dll
本篇笔记参考博客:kernel32.dll这个系统模块,C++笔记-利用远程线程注入获取PC版微信个人昵称
DLL
是Dynamic Link Library的缩写,意为动态链接库
。DLL是一种特殊的可执行文件,它允许*.EXE程序共享执行任务所必需的代码和其他资源。一般情况下DLL不能直接运行,需要宿主程序比如*.EXE程序或其他DLL的动态调用才能够使用。在运行时被系统加载到进程的虚拟空间中,成为调用进程的一部分,进程中所有线程都可以调用其中的函数。
DLL文件相当于es6的模块、易语言的易模块、“Windows系统各功能接口”。
DLL有两种加载方式,一种是隐式链接(静态加载);一种是显式链接(动态加载)。
静态加载是指程序所需要的.lib文件、.dll文件一起打包到.exe程序,运行程序时就会载入到内存空间中。
动态加载是指在程序(.exe)需要时,载入dll文件到内存,再调用里面的函数;实时加载,不需要的时候调用windows接口卸载。应该是相当于前端里动态加载js吧。
很显然,我们自己编写的dll文件是不能被微信静态加载的,那么我们就需要动态加载DLL文件。
动态载入DLL方法是:
LoadLibrary()
函数加载动态链接库到内存,载入程序计算(被调用的那部分)动态代码的逻辑地址;GetProcAddress()
函数动态获得 DLL 函数的入口地址。FreeLibrary()
函数显式地从内存中把它给卸载。每个Windows软件(.exe程序)
在双击启动时,都需要加载Windows的Kernel32.dll
模块,而LoadLibrary()
函数就在这个模块里。
那么,我们可以想办法调用微信进程的LoadLibrary()
函数,来加载自己编写的dll文件。怎么才能做到呢?
CreateRemoteThread()
,它能够创建一个在其它进程地址空间中运行的线程(也称:创建远程线程).。函数 | 参数 |
---|---|
CreateRemoteThread() | 进程句柄(微信进程句柄),lpThreadAttributes (NULL),线程栈初始大小(0:系统默认大小),目标进程目标函数的起始地址(LoadLibrary()函数),传给线程函数的参数(dll文件绝对地址、dll模块句柄),线程的创建标志(0:立即执行),指向所创建线程ID的指针(如果创建失败,该参数为NULL) |
以上分析出来的实现逻辑可以用kernel32.dll 这个系统模块博客里的一句话总结:
先在目标进程的内存空间里开辟一块新地方,往新地方里面写入DLL的路径,再创建远程线程找到LoadLibrary() 函数,并在刚才开辟的新地方中读取DLL路径,进而加载我们自己写的DLL。
ConsoleApplication1.cpp
就用来实现动态注入dll的功能。dllmain.cpp
就用来实现信息弹框,而My.cpp
就用来实现所注入的dll功能。#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
/**
* 查找进程id
* 参数:进程名
* ----不能命名为GetProcessId,GetProcessId是库函数
* 返回值:进程id
* */
DWORD FindProcessId(char * processName) {
// --1.获取系统进程快照
HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hProcessSnap == INVALID_HANDLE_VALUE) {
printf("\nCreateToolhelp32Snapshot ERROR!\n");
return 0;
}
// --2.用PROCESSENTRY32结构体存放进程信息快照
PROCESSENTRY32 processInfo32 = { 0 };
processInfo32.dwSize = sizeof(PROCESSENTRY32);
// --3.遍历进程快照,对比进程名,找到进程id
do {
USES_CONVERSION;
if (strcmp(processName, W2A(processInfo32.szExeFile)) == 0) {
// 4.关闭进程快照
// CloseHandle(hProcessSnap);
return processInfo32.th32ProcessID;
}
} while (Process32Next(hProcessSnap, &processInfo32));
}
/**
* 将Dll文件路径写入到指定进程的内存中,加载自定义dll
* 参数1:进程ID
* 参数2:dll文件绝对路径
* 返回值:内存首地址
* */
INT LoadCustomDll(LPCTSTR dllFilePath, CHAR * processName) {
// ----------------------- 1.通过目标进程名获取目标进程ID ------------------------
DWORD processID = FindProcessId(processName);
// ----------------------- 2.在目标进程内存里写入dll的路径 ------------------------
// 2.1 打开进程获取进程句柄
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processID);
if (NULL == hProcess) {
cout << "OpenProcess ERROR!" << endl;
getchar();
return 0;
}
// 2.2 在该进程内存里开辟新的内存空间
// param2:NULL 由系统自动分配内存地址。
// param3:1 指定内存的大小(以字节为单位)
// param4:MEM_COMMIT 在内存中或磁盘上的页面文件中为页面的指定区域分配物理存储。
// param5:PAGE_READWRITE 启用对页面的提交区域的读写访问。
SIZE_T pathSize = (_tcslen(dllFilePath) + 1) * sizeof(TCHAR);
LPVOID lpBaseAddress = VirtualAllocEx(hProcess, NULL, pathSize, MEM_COMMIT, PAGE_READWRITE);// 内存地址,用CE查看
if (NULL == lpBaseAddress) {
cout << "VirtualAllocEx ERROR!" << endl;
getchar();
return 0;
}
// 2.3 在该进程中将Dll文件路径写入内存区域
// param2:lpBaseAddress 准备写入的内存首地址
// param3:dllFilePath 指向要写的数据的指针(准备写入的东西)
// param4:要写入指定进程的字节数。
// param5:dwWriteSize 返回值,返回实际写入的字节
BOOL isWrited = WriteProcessMemory(hProcess, lpBaseAddress, dllFilePath, pathSize, NULL);
if (0 == isWrited) {
cout << "WriteProcessMemory ERROR!" << endl;
getchar();
return 0;
}
// ----------------------- 3.创建远程线程,让目标进程调用LoadLibrary加载刚才的dll ------------------------
// 3.1 查找模块Kernel32.dll的模块句柄
HMODULE k32DllHandle = GetModuleHandle(_T("kernel32.dll"));
if (0 == k32DllHandle) {
cout << "GetModuleHandle ERROR!" << endl;
getchar();
return 0;
}
// 3.2 查找Kernel32.dll中的LoadLibraryA地址
PTHREAD_START_ROUTINE procAddr = (PTHREAD_START_ROUTINE)GetProcAddress(k32DllHandle, "LoadLibraryW");
if (NULL == procAddr) {
cout << "GetProcAddress ERROR!" << endl;
getchar();
return 0;
}
// 3.3 创建远程线程
HANDLE dllThread = CreateRemoteThreadEx(hProcess, NULL, NULL, procAddr, lpBaseAddress, NULL, NULL, NULL);
if (NULL == dllThread) {
cout << "CreateRemoteThread ERROR!" << endl;
getchar();
return 0;
}
//当句柄所指的线程有信号的时候,才会返回
WaitForSingleObject(dllThread, INFINITE);
CloseHandle(dllThread);
return 1;
}
int main() {
// 1.通过WeChat进程名获取WeChat进程ID
// 2.在WeChat进程内存里写入dll的路径
// 3.创建远程线程,让目标进程调用LoadLibrary加载刚才的dll
char processName[0x100] = { "WeChat.exe" };
// dll绝对地址
wchar_t dllAbsolutePath[] = L"E:\\personal-learn\\c_learn\\Dll5\\Debug\\Dll5.dll";
int execResult = LoadCustomDll(dllAbsolutePath, processName);
if (1 == execResult) {
cout << "LoadCustomDll SUCCESS!" << endl;
}
getchar();
return 0;
}
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
#include "My.h"
#include
#include
BOOL APIENTRY DllMain(HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
//CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)getAllInfo, hModule, 0, NULL);
MessageBox(NULL, L"inject success", L"微信", NULL);
getAllInfo();
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
#include "pch.h"
#include "My.h"
#include
#include
void getAllInfo() {
//WeChatWin.dll的基址
DWORD weChatWinAddr = (DWORD)GetModuleHandle(L"WeChatWin.dll");
//DWORD weChatWinAddr = (DWORD)LoadLibraryW(L"WeChatWin.dll");
// 打印地址
char winAddr[0x100] = { 0 };
//sprintf_s(winAddr, "%s", *((DWORD *)weChatWinAddr));
_ultoa_s(weChatWinAddr, winAddr, 16);
MessageBoxA(NULL, winAddr, "基址", NULL);
// 昵称
char nickName[0x100] = { 0 };
DWORD nickAddr = weChatWinAddr + 0x13972DC;
// (DWORD *)nickAddr----地址转成指针,*((DWORD *)nickAddr)----获取指针里的内容
// sprintf_s(nickName, "%s", *((DWORD *)nickAddr));
sprintf_s(nickName, "%s", nickAddr);
MessageBoxA(NULL, nickName, "昵称-sprintf_s", NULL);
// 昵称:一个字节一个字节读取
char wxID[0x1000] = { 0 };
DWORD weIDDW = weChatWinAddr + 0x13972DC;
for (int i = 0; i < 40; i++) {
wxID[i] = (char)(*(DWORD*)weIDDW);
if (wxID[i] == '0') {
break;
}
weIDDW += 0x1;
}
MessageBoxA(NULL, wxID, "昵称-一个字节一个字节读取", NULL);
// 头像地址
char headPic[0x240] = { 0 };
DWORD picAddr = weChatWinAddr + 0x13975A4;
sprintf_s(headPic, "%s", *((DWORD *)picAddr));
MessageBoxA(NULL, headPic, "头像地址", NULL);
}
ConsoleApplication1.cpp
中的main()函数中,把dll的绝对路径,改成刚才生成的DLL1.dll文件
绝对路径。ConsoleApplication1.exe
,就能看到运行效果了。现在有个问题是,昵称只能正确显示英文和数字的,中文昵称会显示乱码。我对c++不熟悉,找了好久也没找到可以正确转码的方法o(╥﹏╥)o。