You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET Framework C# DLL注入后无法执行代码的求助

解决.NET Framework托管DLL注入后无法执行的问题

核心问题分析

你的代码存在几个关键错误,导致托管DLL无法正常执行:

  • 托管DLL无法直接被LoadLibrary加载执行:普通C# DLL是托管程序集,依赖CLR运行时,若目标进程未加载.NET CLR,直接用LoadLibrary加载只会将DLL映射到进程内存,不会触发托管代码执行。
  • 错误获取目标进程的DLL句柄:GetModuleHandle(dllPath)是在注入器进程中调用,而非目标进程,无法获取目标进程中已加载的DLL句柄。
  • DllMain签名不符合原生规范:原生DLL的DllMain有固定签名(BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)),你定义的托管DllMain无法被原生系统调用识别,即便用DllExport也无法正确触发。
  • 断点无法命中:托管代码需JIT编译后才能执行,且调试器需附加到目标进程并加载托管符号,直接设置断点可能因符号未加载或代码未执行导致无法命中。

解决方案

方案一:用C++/CLI包装原生入口(推荐)

C++/CLI DLL可同时兼容原生和托管代码,能通过原生DllMain触发托管逻辑,无需复杂的CLR注入:

  1. 创建C++/CLI Class Library(目标平台x86,.NET Framework 4.8)
  2. 编写原生DllMain,在进程加载时调用托管代码:
#include "pch.h"
#include <msclr/marshal.h>
using namespace msclr::interop;
using namespace System;
using namespace System::Windows::Forms;

BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        // 触发托管代码
        MessageBox::Show("works");
        break;
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}
  1. 编译后,用你的注入器加载这个C++/CLI DLL即可自动触发弹窗。

方案二:修复注入器逻辑 + 正确导出托管函数

若坚持用纯C# DLL,需通过DllExport导出符合原生调用规范的函数,并修正注入器的句柄获取逻辑:

1. 修正托管DLL代码(用DllExport)

using System;
using System.Windows;
using RGiesecke.DllExport;

namespace ARKInjectableDLL
{
    public class Main
    {
        [DllExport("RunManagedCode", CallingConvention = CallingConvention.StdCall)]
        public static void RunManagedCode()
        {
            // 加入调试触发,方便断点命中
            System.Diagnostics.Debugger.Launch();
            MessageBox.Show("works");
        }
    }
}

2. 修正注入器的InjectDLL方法

[DllImport("kernel32.dll", SetLastError = true)]
static extern bool GetExitCodeThread(IntPtr hThread, out uint lpExitCode);

public static void InjectDLL(int processId, string dllPath)
{
    IntPtr processHandle = OpenProcess(PROCESS_ALL_ACCESS, false, processId);
    if (processHandle == IntPtr.Zero) return;

    // 分配内存存储DLL路径
    int pathByteCount = Encoding.ASCII.GetByteCount(dllPath) + 1;
    IntPtr dllPathAddress = VirtualAllocEx(processHandle, IntPtr.Zero, (uint)pathByteCount, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
    if (dllPathAddress == IntPtr.Zero) return;

    UIntPtr bytesWritten;
    bool writeSuccess = WriteProcessMemory(processHandle, dllPathAddress, Encoding.ASCII.GetBytes(dllPath), (uint)pathByteCount, out bytesWritten);
    if (!writeSuccess) return;

    // 获取LoadLibraryA地址
    IntPtr loadLibraryAddress = GetProcAddress(GetModuleHandle("kernel32.dll"), "LoadLibraryA");
    if (loadLibraryAddress == IntPtr.Zero) return;

    // 创建远程线程加载DLL
    IntPtr threadHandle;
    IntPtr threadId;
    threadHandle = CreateRemoteThread(processHandle, IntPtr.Zero, 0, loadLibraryAddress, dllPathAddress, 0, out threadId);
    if (threadHandle == IntPtr.Zero) return;

    // 等待线程结束,获取LoadLibrary的返回值(目标进程中的DLL句柄)
    WaitForSingleObject(threadHandle, INFINITE);
    uint dllHandleValue;
    GetExitCodeThread(threadHandle, out dllHandleValue);
    IntPtr dllHandle = new IntPtr(dllHandleValue);
    if (dllHandle == IntPtr.Zero) return;

    // 获取导出函数的地址
    IntPtr methodAddress = GetProcAddress(dllHandle, "RunManagedCode");
    if (methodAddress == IntPtr.Zero) return;

    // 创建远程线程执行托管函数
    CreateRemoteThread(processHandle, IntPtr.Zero, 0, methodAddress, IntPtr.Zero, 0, out threadId);
}

断点调试注意事项

  • 在托管代码中加入System.Diagnostics.Debugger.Launch(),执行时会弹出调试器选择窗口,选择你的VS实例即可附加调试。
  • 确保VS调试器附加到目标进程,而非注入器进程。
  • 若仍无法命中断点,检查项目是否生成了符号文件(.pdb),并在调试器中手动加载符号(调试 -> 窗口 -> 模块,找到你的DLL,右键加载符号)。

内容的提问来源于stack exchange,提问作者Arkinetic

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 22:34:59