跳至正文

抽象邪道①:使用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
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
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获取到进程句柄
[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获取到进程句柄
[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
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
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;
}
#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; }
#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#程序里即可

《抽象邪道①:使用C#向非托管进程中注入非托管DLL》有1个想法

发表回复