【老文】如何将.Net程序集注入非托管进程
字数 1910 2025-08-27 12:33:30
.NET程序集注入非托管进程技术详解
1. 概述
本文详细介绍了如何将.NET程序集注入到非托管进程中执行的技术。主要内容包括:
- 在非托管进程中启动.NET CLR(公共语言运行时)
- 加载自定义.NET程序集
- 在非托管进程中执行托管代码
- 跨架构(32位/64位)注入技术
2. 核心目标
- 不考虑架构在任意进程中启动.NET CLR
- 在任意进程中加载自定义.NET程序
- 在任意进程中执行托管代码
3. 技术实现步骤
3.1 加载CLR(初级)
在非托管进程中启动.NET Framework的基本方法:
#include <metahost.h>
#pragma comment(lib, "mscoree.lib")
#import "mscorlib.tlb" raw_interfaces_only \
high_property_prefixes("_get","_put","_putref") \
rename("ReportEvent", "InteropServices_ReportEvent")
int wmain(int argc, wchar_t* argv[])
{
HRESULT hr;
ICLRMetaHost *pMetaHost = NULL;
ICLRRuntimeInfo *pRuntimeInfo = NULL;
ICLRRuntimeHost *pClrRuntimeHost = NULL;
// 构建运行时
hr = CLRCreateInstance(CLSID_CLRMetaHost, IID_PPV_ARGS(&pMetaHost));
hr = pMetaHost->GetRuntime(L"v4.0.30319", IID_PPV_ARGS(&pRuntimeInfo));
hr = pRuntimeInfo->GetInterface(CLSID_CLRRuntimeHost, IID_PPV_ARGS(&pClrRuntimeHost));
// 启动运行时
hr = pClrRuntimeHost->Start();
// 释放资源
pMetaHost->Release();
pRuntimeInfo->Release();
pClrRuntimeHost->Release();
return 0;
}
关键API说明:
CLRCreateInstance: 获取ICLRMetaHost接口指针ICLRMetaHost::GetRuntime: 获取特定.NET运行时的ICLRRunTimeInfo指针ICLRRunTimeInfo::GetInterface: 将CLR加载到当前进程并获取ICLRRuntimeHost指针ICLRRuntimeHost::Start: 显式启动CLR
3.2 加载CLR(高级)
加载自定义.NET程序集并调用方法:
// 执行托管程序集
DWORD pReturnValue;
hr = pClrRuntimeHost->ExecuteInDefaultAppDomain(
L"T:\\FrameworkInjection\\_build\\debug\\anycpu\\InjectExample.exe",
L"InjectExample.Program",
L"EntryPoint",
L"hello .net runtime",
&pReturnValue);
ExecuteInDefaultAppDomain参数说明:
pwzAssemblyPath: .NET程序(exe/dll)的完整路径pwzTypeName: 要调用的方法的完整类名pwzMethodName: 要调用的方法名pwzArgument: 传递给方法的可选参数pReturnValue: 方法返回值
可调用的方法必须具有签名:static int MethodName(String argument)
示例.NET程序:
using System;
using System.Windows.Forms;
namespace InjectExample
{
public class Program
{
static int EntryPoint(String pwzArgument)
{
System.Media.SystemSounds.Beep.Play();
MessageBox.Show(
"I am a managed app.\n\n" +
"I am running inside: [" +
System.Diagnostics.Process.GetCurrentProcess().ProcessName + "]\n\n" +
(String.IsNullOrEmpty(pwzArgument) ?
"I was not given an argument" :
"I was given this argument: [" + pwzArgument + "]"));
return 0;
}
}
}
3.3 DLL注入(基础)
基本DLL注入技术:
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
注入函数实现:
DWORD_PTR Inject(const HANDLE hProcess, const LPVOID function, const wstring& argument)
{
// 在远程进程中分配内存
LPVOID baseAddress = VirtualAllocEx(hProcess, NULL, GetStringAllocSize(argument),
MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
// 将参数写入远程进程
WriteProcessMemory(hProcess, baseAddress, argument.c_str(),
GetStringAllocSize(argument), NULL);
// 在远程进程中调用函数
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0,
(LPTHREAD_START_ROUTINE)function,
baseAddress, NULL, 0);
// 等待线程退出
WaitForSingleObject(hThread, INFINITE);
// 释放远程进程中的内存
VirtualFreeEx(hProcess, baseAddress, 0, MEM_RELEASE);
// 获取线程退出代码
DWORD exitCode = 0;
GetExitCodeThread(hThread, &exitCode);
// 关闭线程句柄
CloseHandle(hThread);
return exitCode;
}
3.4 DLL注入(高级)
获取远程模块句柄:
DWORD_PTR GetRemoteModuleHandle(const int processId, const wchar_t* moduleName)
{
MODULEENTRY32 me32;
HANDLE hSnapshot = INVALID_HANDLE_VALUE;
me32.dwSize = sizeof(MODULEENTRY32);
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, processId);
if (!Module32First(hSnapshot, &me32))
{
CloseHandle(hSnapshot);
return 0;
}
while (wcscmp(me32.szModule, moduleName) != 0 && Module32Next(hSnapshot, &me32));
CloseHandle(hSnapshot);
if (wcscmp(me32.szModule, moduleName) == 0)
return (DWORD_PTR)me32.modBaseAddr;
return 0;
}
计算函数偏移量:
DWORD_PTR GetFunctionOffset(const wstring& library, const char* functionName)
{
// 在当前进程加载库
HMODULE hLoaded = LoadLibrary(library.c_str());
// 获取函数地址
void* lpInject = GetProcAddress(hLoaded, functionName);
// 计算基地址和函数地址之间的偏移量
DWORD_PTR offset = (DWORD_PTR)lpInject - (DWORD_PTR)hLoaded;
// 卸载库
FreeLibrary(hLoaded);
return offset;
}
3.5 综合利用
完整注入流程:
- 注入Bootstrap.dll到远程进程
- 计算ImplantDotNetAssembly函数在远程进程中的地址
- 调用该函数加载CLR和执行.NET程序集
- 卸载Bootstrap.dll
int wmain(int argc, wchar_t* argv[])
{
// 解析参数 (-m -i -l -a -n)
if (!ParseArgs(argc, argv)) {
PrintUsage();
return -1;
}
// 启用调试权限
EnablePrivilege(SE_DEBUG_NAME, TRUE);
// 获取远程进程句柄
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, g_processId);
// 注入bootstrap.dll到远程进程
FARPROC fnLoadLibrary = GetProcAddress(GetModuleHandle(L"Kernel32"), "LoadLibraryW");
Inject(hProcess, fnLoadLibrary, GetBootstrapPath());
// 计算远程进程中函数的地址
DWORD_PTR hBootstrap = GetRemoteModuleHandle(g_processId, BOOTSTRAP_DLL);
DWORD_PTR offset = GetFunctionOffset(GetBootstrapPath(), "ImplantDotNetAssembly");
DWORD_PTR fnImplant = hBootstrap + offset;
// 构建参数
wstring argument = g_moduleName + DELIM + g_typeName + DELIM +
g_methodName + DELIM + g_Argument;
// 注入托管程序集到远程进程
Inject(hProcess, (LPVOID)fnImplant, argument);
// 从远程进程中卸载bootstrap.dll
FARPROC fnFreeLibrary = GetProcAddress(GetModuleHandle(L"Kernel32"), "FreeLibrary");
CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)fnFreeLibrary,
(LPVOID)hBootstrap, NULL, 0);
// 关闭进程句柄
CloseHandle(hProcess);
return 0;
}
4. 关键注意事项
-
架构兼容性:
- x86架构的Inject.exe和Bootstrap.dll用于注入x86进程
- x64架构用于注入x64进程
- AnyCPU目标平台可以注入到x86或x64进程中
-
CLR初始化限制:
- 避免在DllMain中启动CLR,会导致Windows加载器锁死
- 参考MSDN文档:
-
方法签名要求:
- 通过
ExecuteInDefaultAppDomain调用的方法必须为:
static int MethodName(String argument)
- 通过
-
版本兼容性:
ICLRMetaHost::GetRuntime支持的版本:- V1.0.3705
- V1.1.4322
- V2.0.50727
- V4.0.30319
5. 实用工具扩展
使用.NET反射API扫描程序集获取可注入方法:
// 查找匹配签名的方法:static int MethodName(String argument)
private void ExtractInjectableMethods()
{
Assembly asm = Assembly.LoadFile(ManagedFilename);
InjectableMethods = (from c in asm.GetTypes()
from m in c.GetMethods(BindingFlags.Static |
BindingFlags.Public |
BindingFlags.NonPublic)
where m.ReturnType == typeof(int) &&
m.GetParameters().Length == 1 &&
m.GetParameters().First().ParameterType == typeof(string)
select new MethodItem {
Name = m.Name,
ParameterName = m.GetParameters().First().Name,
TypeName = m.ReflectedType.FullName
}).ToList();
}
6. 编译环境要求
-
开发环境:
- Visual Studio 2012 Express+
- Visual Studio 2012 Express Update 1+
-
运行环境:
- .NET Framework 4.0+
- Visual C++ Redistributable for Visual Studio 2012 Update 1+
- Windows XP SP3+
7. 命令行参数
Inject应用程序支持以下参数:
| 参数 | 描述 | 示例 |
|---|---|---|
| -m | 要执行的托管方法名 | EntryPoint |
| -i | 托管程序的完整路径 | C:\InjectExample.exe |
| -l | 程序集的完整类名 | InjectExample.Program |
| -a | 传递给方法的参数 | "hello inject" |
| -n | 进程ID或名称 | 1500 或 notepad.exe |
示例命令:
Inject.exe -m EntryPoint -i "C:\InjectExample.exe" -l InjectExample.Program -a "hello inject" -n "notepad.exe"
8. 总结
本文详细介绍了将.NET程序集注入非托管进程的完整技术方案,包括:
- 在非托管进程中启动CLR
- 加载和执行自定义.NET程序集
- 跨进程DLL注入技术
- 跨架构兼容性处理
- 实用工具扩展
通过结合非托管代码的灵活性和.NET的强大功能,可以实现强大的进程注入能力,适用于各种特殊场景的需求。