相信大部分人看到标题的第一时间都是懵逼的,什么是非托管进程?什么又是非托管DLL?
别急,这个标题在这篇文章里的意思就是:使用C#向C++进程中注入C++DLL(草拟莱莱还是看不懂啊喂)
无所谓,让我们开始今天的冒险罢!
一. 了解如何注入DLL
首先,了解一下作者为什么要研究这么个玩意:
作者近几个月沉迷 StoneShard(紫色晶石),但是紫色晶石它没有官方mod接口,而且制作mod十分复杂,所以作者一怒之下做了个mod加载器
众所周知 StoneShard 是由 GameMakerStudio: 2 制作的一款游戏,这个引擎的虚拟机用的就是C++,作者制作的mod加载器只不过是脚本加载,所以打算研究一下虚拟机,做一个Runtime加载的玩意
OK,那接下来就来看看如何注入DLL罢
首先,众所周知,要想注入DLL,我们就需要获取到对应的进程,即:
if (Process.GetProcessesByName("StoneShard").Length == 0)//此处写你要注入的进程名 { MessageBox.Show("[Core]Cannot find process, exit."); return; } process = Process.GetProcessesByName("StoneShard")[0]; MessageBox.Show("[Core]Find StoneShard process, process ID: " + process.Id);a
获取到进程ID后,我们需要调用Win32的API来打开进程,以获取句柄:
[DllImport("kernel32.dll")] public static extern IntPtr OpenProcess(int dwDesiredAccess, bool bInheritHandle, int dwProcessId); [DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true)] public static extern IntPtr GetProcAddress(IntPtr hModule, string procName); [DllImport("kernel32.dll")] public static extern IntPtr CreateRemoteThread(IntPtr hProcess, IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId); [DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true)] public static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect); [DllImport("kernel32.dll")] public static extern IntPtr GetModuleHandle(string lpModuleName); //引用Win32API,注意,这部分是写在类里面的 IntPtr GameHandle = WinAPI.OpenProcess(PROCESS_ALL, false, process.Id); //调用Win32API,根据进程ID获取到进程句柄
接下来的事情就比较简单了,只需要根据库名和方法名获取到kernel32.dll中的LoadLibraryA方法,再创建一个线程来调用即可:
IntPtr LoadLibraryAddr = WinAPI.GetProcAddress(WinAPI.GetModuleHandle("kernel32.dll"), "LoadLibraryA"); //获取LoadLibraryA方法 string dllName = Environment.CurrentDirectory + "\\RuntimeShardCore.dll"; IntPtr allocMemAddress = WinAPI.VirtualAllocEx(GameHandle, IntPtr.Zero, (uint)(dllName.Length + 1) * sizeof(char), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); //申请一块虚拟内存来存放要注入的dll路径 int bytesWritten; WinAPI.WriteProcessMemory(GameHandle, allocMemAddress, Encoding.UTF8.GetBytes(dllName), (uint)((dllName.Length + 1) * Marshal.SizeOf(typeof(char))), out bytesWritten); //写入dll路径 IntPtr threadHandle = WinAPI.CreateRemoteThread(GameHandle, IntPtr.Zero, 0, LoadLibraryAddr, allocMemAddress, 0, IntPtr.Zero); //开启线程,调用LoadLibraryA来加载dll
二. 编写C++ DLL
这部分比较简单,只需要在VS内创建一个新项目就可以了,然后只需在DllMain的DLL_ATTACH里创建一个线程来调用另一个方法即可:
#include "pch.h" #include <windows.h> #include <filesystem> #pragma comment(lib, "mscoree.lib") #include "metahost.h" #include <string> #include <stdio.h> #include <stdlib.h> #include <winsock2.h> #include <thread> #pragma comment(lib, "ws2_32.lib") #include "Core.h" int client = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); void ShowMsg(LPCWSTR msg) { MessageBox(NULL, msg, L"DLL Injection", MB_OK); } void Initalize() { Core::LoadGMFunctions();//这部分是加载内置函数的,原理就不放在这了 WORD w_req = MAKEWORD(2, 2); WSADATA wsadata; int err; err = WSAStartup(w_req, &wsadata); if (err != 0) ShowMsg(L"Initalize failed."); else ShowMsg(L"Initalize successfully."); } void Recv() { while (true) { char data[1024]; int ret = recv(client, data, 1024, 0); if (ret > 0) { data[ret] = 0; WCHAR str[1024]; memset(str, 0, sizeof(str)); MultiByteToWideChar(CP_ACP, 0, data, strlen(data) + 1, str, sizeof(str) / sizeof(str[0])); MessageBox(NULL, str, L"RuntimeShardCore", MB_OK); } } } DWORD RunSocket(LPVOID lp) { struct sockaddr_in clientAddress; Initalize(); memset(&clientAddress, 0, sizeof(clientAddress)); clientAddress.sin_family = AF_INET; clientAddress.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");//本地IP clientAddress.sin_port = htons(1444); if (connect(client, (sockaddr*)&clientAddress, sizeof(clientAddress)) == SOCKET_ERROR) { MessageBox(NULL, L"Connect error!", L"RuntimeShardCore", MB_OK); closesocket(client); return 0; } else MessageBox(NULL, L"Connect at 127.0.0.1:1444 successfully.", L"RuntimeShardCore", MB_OK); thread t = thread(Recv);//创建新线程来持续接收消息 t.join(); } //在DllMain中其实调用什么都行,我这里是连接了一下刚才C#程序开的本地TCP Socket. BOOL WINAPI DllMain( HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) { switch (fdwReason) { case DLL_PROCESS_ATTACH: CreateThread(0, 0, RunSocket, 0, 0, 0);//创建线程来调用RunSocket方法 break; default: break; } return true; }
接下来只需要编译这个dll,再把dll的路径塞到刚才那个C#程序里即可
草,手搓BepInEx