PC微信hook学习笔记(二)—— C++语言编写dll

PC微信hook学习笔记(二)—— C语言编写dll

  • 1. 实现原理
    • 1.1 动态链接库DLL
      • 1.1.1 dll加载方式
    • 1.2 动态载入DLL
    • 1.3 windows底层dll
  • 2. 实现逻辑
  • 3. 实现过程
    • 3.1 准备工作
    • 3.2 代码
      • 3.2.1 ConsoleApplication1.cpp
      • 3.2.2 dllmain.cpp
      • 3.2.3 My.cpp
    • 3.3 编译

本篇笔记学习教程:C语言编写dll
本篇笔记参考博客:kernel32.dll这个系统模块,C++笔记-利用远程线程注入获取PC版微信个人昵称

1. 实现原理

1.1 动态链接库DLL

DLL是Dynamic Link Library的缩写,意为动态链接库。DLL是一种特殊的可执行文件,它允许*.EXE程序共享执行任务所必需的代码和其他资源。一般情况下DLL不能直接运行,需要宿主程序比如*.EXE程序或其他DLL的动态调用才能够使用。在运行时被系统加载到进程的虚拟空间中,成为调用进程的一部分,进程中所有线程都可以调用其中的函数。

DLL文件相当于es6的模块、易语言的易模块、“Windows系统各功能接口”。

1.1.1 dll加载方式

DLL有两种加载方式,一种是隐式链接(静态加载);一种是显式链接(动态加载)。
静态加载是指程序所需要的.lib文件、.dll文件一起打包到.exe程序,运行程序时就会载入到内存空间中。
动态加载是指在程序(.exe)需要时,载入dll文件到内存,再调用里面的函数;实时加载,不需要的时候调用windows接口卸载。应该是相当于前端里动态加载js吧。

很显然,我们自己编写的dll文件是不能被微信静态加载的,那么我们就需要动态加载DLL文件。

1.2 动态载入DLL

动态载入DLL方法是:

  1. LoadLibrary() 函数加载动态链接库到内存,载入程序计算(被调用的那部分)动态代码的逻辑地址;
  2. GetProcAddress()函数动态获得 DLL 函数的入口地址。
  3. 当一个 DLL 文件用 LoadLibrary 显式加载后,在任何时刻均可以通过调用 FreeLibrary() 函数显式地从内存中把它给卸载。

1.3 windows底层dll

  • Kernel32.dll:用于管理内存、进程和线程
  • User32.dll:用于执行用户界面任务,比如把用户的鼠标点击操作传递给窗口
  • GDI32.dll:全称是Graphical Device Interface(图形设备接口),包含用于画图和显示文本的函数,比如要显示一个程序窗口,就调用了其中的函数来画这个窗口。

2. 实现逻辑

每个Windows软件(.exe程序)在双击启动时,都需要加载Windows的Kernel32.dll模块,而LoadLibrary() 函数就在这个模块里。
那么,我们可以想办法调用微信进程的LoadLibrary() 函数,来加载自己编写的dll文件。怎么才能做到呢?

  1. 每个软件所调用的Kernel32.dll是windows系统自带的,那么Kernel32.dll的基址就是固定不变的。所以,软件A获取到的Kernel32.dll–LoadLibrary()函数地址 = 软件B里的Kernel32.dll–LoadLibrary()函数地址
  2. 虽然能找到Kernel32.dll–LoadLibrary()函数地址,但微信进程是不会自己调用这个函数的。有个Windows API函数是CreateRemoteThread(),它能够创建一个在其它进程地址空间中运行的线程(也称:创建远程线程).。
函数 参数
CreateRemoteThread() 进程句柄(微信进程句柄),lpThreadAttributes (NULL),线程栈初始大小(0:系统默认大小),目标进程目标函数的起始地址(LoadLibrary()函数),传给线程函数的参数(dll文件绝对地址、dll模块句柄),线程的创建标志(0:立即执行),指向所创建线程ID的指针(如果创建失败,该参数为NULL)
  1. CreateRemoteThread()有两个参数需要注意:
    (1)进程句柄:一种方式是获取进程快照取得进程PID(CreateToolhelp32Snapshot);一种是先获取窗口句柄(FindWindow),再获取进程PID(GetWindowThreadProcessId),最后都用OpenProcess获取进程句柄。
    (2)传递给LoadLibrary()函数的DLL路径:需要先在微信进程内存中开辟一块空间(VirtualAllocEx),再把DLL路径写入进去(WriteProcessMemory)。

以上分析出来的实现逻辑可以用kernel32.dll 这个系统模块博客里的一句话总结:

先在目标进程的内存空间里开辟一块新地方,往新地方里面写入DLL的路径,再创建远程线程找到LoadLibrary() 函数,并在刚才开辟的新地方中读取DLL路径,进而加载我们自己写的DLL。

3. 实现过程

3.1 准备工作

  1. 去官网下载并安装Visual Studio 2017,参考Visual Studio 2017安装步骤和使用方法。本文只需要勾选使用C++的桌面开发的开发功能。(PS:我刚开始是用vscode,但是折腾好久也没效果,就算最后写好的.cpp文件在vscode里执行,也会有引入库路径找不到的问题,c++ 新手慎重啊)
  2. 新建项目和解决方案:菜单→文件→项目,选择【控制台应用】。这里只是选择一个可以生成.exe文件的项目类型,熟悉Visual Studio 2017的小伙伴随意。
    PC微信hook学习笔记(二)—— C++语言编写dll_第1张图片
    这里的ConsoleApplication1.cpp就用来实现动态注入dll的功能。
    PC微信hook学习笔记(二)—— C++语言编写dll_第2张图片
  3. 在刚才新建的解决方案目录下,新建【动态链接库】项目。
    PC微信hook学习笔记(二)—— C++语言编写dll_第3张图片
    这里的dllmain.cpp就用来实现信息弹框,而My.cpp就用来实现所注入的dll功能。
    PC微信hook学习笔记(二)—— C++语言编写dll_第4张图片

3.2 代码

3.2.1 ConsoleApplication1.cpp

#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;
}

3.2.2 dllmain.cpp

// 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;
}

3.2.3 My.cpp

#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);
}

3.3 编译

  1. 选中【ConsoleApplication1】,右击→重新生成,会生成ConsoleApplication1.exe执行文件,文件地址会出现在运行结果弹框里。
  2. 选中【DLL1】(我这里是DLL5),右击→重新生成,会生成DLL1.dll文件,文件地址会出现在运行结果弹框里。
  3. ConsoleApplication1.cpp中的main()函数中,把dll的绝对路径,改成刚才生成的DLL1.dll文件绝对路径。
  4. 回到桌面,打开微信,双击运行ConsoleApplication1.exe,就能看到运行效果了。
  5. 出现错误需要调试的时候,一定要把运行的微信和ConsoleApplication1.exe都关闭后,再重新生成文件。

现在有个问题是,昵称只能正确显示英文和数字的,中文昵称会显示乱码。我对c++不熟悉,找了好久也没找到可以正确转码的方法o(╥﹏╥)o。

PC微信hook学习笔记(二)—— C++语言编写dll_第5张图片
PC微信hook学习笔记(二)—— C++语言编写dll_第6张图片

你可能感兴趣的:(WeChatRobot)