【老文】如何将.Net程序集注入非托管进程
字数 1910 2025-08-27 12:33:30

.NET程序集注入非托管进程技术详解

1. 概述

本文详细介绍了如何将.NET程序集注入到非托管进程中执行的技术。主要内容包括:

  • 在非托管进程中启动.NET CLR(公共语言运行时)
  • 加载自定义.NET程序集
  • 在非托管进程中执行托管代码
  • 跨架构(32位/64位)注入技术

2. 核心目标

  1. 不考虑架构在任意进程中启动.NET CLR
  2. 在任意进程中加载自定义.NET程序
  3. 在任意进程中执行托管代码

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参数说明:

  1. pwzAssemblyPath: .NET程序(exe/dll)的完整路径
  2. pwzTypeName: 要调用的方法的完整类名
  3. pwzMethodName: 要调用的方法名
  4. pwzArgument: 传递给方法的可选参数
  5. 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 综合利用

完整注入流程:

  1. 注入Bootstrap.dll到远程进程
  2. 计算ImplantDotNetAssembly函数在远程进程中的地址
  3. 调用该函数加载CLR和执行.NET程序集
  4. 卸载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. 关键注意事项

  1. 架构兼容性

    • x86架构的Inject.exe和Bootstrap.dll用于注入x86进程
    • x64架构用于注入x64进程
    • AnyCPU目标平台可以注入到x86或x64进程中
  2. CLR初始化限制

  3. 方法签名要求

    • 通过ExecuteInDefaultAppDomain调用的方法必须为:
      static int MethodName(String argument)
  4. 版本兼容性

    • 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程序集注入非托管进程的完整技术方案,包括:

  1. 在非托管进程中启动CLR
  2. 加载和执行自定义.NET程序集
  3. 跨进程DLL注入技术
  4. 跨架构兼容性处理
  5. 实用工具扩展

通过结合非托管代码的灵活性和.NET的强大功能,可以实现强大的进程注入能力,适用于各种特殊场景的需求。

.NET程序集注入非托管进程技术详解 1. 概述 本文详细介绍了如何将.NET程序集注入到非托管进程中执行的技术。主要内容包括: 在非托管进程中启动.NET CLR(公共语言运行时) 加载自定义.NET程序集 在非托管进程中执行托管代码 跨架构(32位/64位)注入技术 2. 核心目标 不考虑架构在任意进程中启动.NET CLR 在任意进程中加载自定义.NET程序 在任意进程中执行托管代码 3. 技术实现步骤 3.1 加载CLR(初级) 在非托管进程中启动.NET Framework的基本方法: 关键API说明: CLRCreateInstance : 获取 ICLRMetaHost 接口指针 ICLRMetaHost::GetRuntime : 获取特定.NET运行时的 ICLRRunTimeInfo 指针 ICLRRunTimeInfo::GetInterface : 将CLR加载到当前进程并获取 ICLRRuntimeHost 指针 ICLRRuntimeHost::Start : 显式启动CLR 3.2 加载CLR(高级) 加载自定义.NET程序集并调用方法: ExecuteInDefaultAppDomain 参数说明: pwzAssemblyPath : .NET程序(exe/dll)的完整路径 pwzTypeName : 要调用的方法的完整类名 pwzMethodName : 要调用的方法名 pwzArgument : 传递给方法的可选参数 pReturnValue : 方法返回值 可调用的方法必须具有签名: static int MethodName(String argument) 示例.NET程序: 3.3 DLL注入(基础) 基本DLL注入技术: 注入函数实现: 3.4 DLL注入(高级) 获取远程模块句柄: 计算函数偏移量: 3.5 综合利用 完整注入流程: 注入Bootstrap.dll到远程进程 计算ImplantDotNetAssembly函数在远程进程中的地址 调用该函数加载CLR和执行.NET程序集 卸载Bootstrap.dll 4. 关键注意事项 架构兼容性 : x86架构的Inject.exe和Bootstrap.dll用于注入x86进程 x64架构用于注入x64进程 AnyCPU目标平台可以注入到x86或x64进程中 CLR初始化限制 : 避免在DllMain中启动CLR,会导致Windows加载器锁死 参考MSDN文档: loaderLock MDA Initialization of Mixed Assemblies 方法签名要求 : 通过 ExecuteInDefaultAppDomain 调用的方法必须为: static int MethodName(String argument) 版本兼容性 : ICLRMetaHost::GetRuntime 支持的版本: V1.0.3705 V1.1.4322 V2.0.50727 V4.0.30319 5. 实用工具扩展 使用.NET反射API扫描程序集获取可注入方法: 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 | 示例命令: 8. 总结 本文详细介绍了将.NET程序集注入非托管进程的完整技术方案,包括: 在非托管进程中启动CLR 加载和执行自定义.NET程序集 跨进程DLL注入技术 跨架构兼容性处理 实用工具扩展 通过结合非托管代码的灵活性和.NET的强大功能,可以实现强大的进程注入能力,适用于各种特殊场景的需求。