跳至正文

抽象邪道①:使用C#向非托管进程中注入非托管DLL

相信大部分人看到标题的第一时间都是懵逼的,什么是非托管进程?什么又是非托管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#程序里即可

发表回复