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

如何在C#中像C++一样从内存调用函数?带参数Shellcode调用异常解决

解决C#调用带参数Shellcode的内存读取问题

我之前踩过一模一样的坑!核心问题出在调用约定不匹配和托管/非托管边界的参数传递规则上,尤其是你还需要Shellcode跳回C#函数,这两点没处理好就会出现内存读不了的情况。下面一步步给你拆解解决方案:

1. 先搞定调用约定:这是带参数崩溃的核心原因

C++里默认用cdecl调用约定(栈由调用者清理),但C#的Marshal.GetDelegateFromFunctionPointer默认用的是StdCall(Windows下,栈由被调用者清理)。两种约定的栈处理逻辑完全不同,无参数时栈平衡问题没暴露,带参数直接就乱了。

解决方法:用[UnmanagedFunctionPointer]特性给委托明确指定和Shellcode一致的调用约定。比如Shellcode是C++写的默认cdecl,那委托要这么声明:

// 匹配Shellcode的调用约定,参数类型要和Shellcode严格对应
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void MyShellcodeDelegate(int param1, IntPtr param2); 

如果你的Shellcode用的是stdcall(比如Windows API风格),就把CallingConvention.Cdecl换成CallingConvention.StdCall。

2. 内存分配要正确:必须加执行权限

分配内存时要确保有PAGE_EXECUTE_READWRITE权限(写完后可以改成PAGE_EXECUTE_READ更安全),否则DEP会阻止执行。用VirtualAlloc来分配:

[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr VirtualAlloc(IntPtr lpAddress, uint dwSize, AllocationType flAllocationType, MemoryProtection flProtect);

// 分配可执行内存
IntPtr shellcodeMem = VirtualAlloc(
    IntPtr.Zero, 
    (uint)shellcode.Length, 
    AllocationType.Commit | AllocationType.Reserve, 
    MemoryProtection.PAGE_EXECUTE_READWRITE
);
// 把Shellcode写入内存
Marshal.Copy(shellcode, 0, shellcodeMem, shellcode.Length);

3. Shellcode跳回C#函数:别直接用托管函数地址

托管函数的地址可能被JIT动态编译改变,或者被GC回收,直接跳会导致内存错误。正确做法是把C#函数包装成非托管回调:

// 声明回调的委托,同样要匹配调用约定
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void MyManagedCallback(int result);

// 必须保持委托的引用!防止GC回收
private static MyManagedCallback _callbackInstance = MyCallbackFunction;

// 你的C#回调函数
private static void MyCallbackFunction(int result)
{
    Console.WriteLine($"Shellcode返回的结果:{result}");
}

// 获取回调的稳定非托管地址
IntPtr callbackPtr = Marshal.GetFunctionPointerForDelegate(_callbackInstance);
// 把这个地址传给Shellcode(作为参数或者写入Shellcode指定位置)

⚠️ 重点:一定要保留_callbackInstance的静态引用,不然GC会把委托回收,导致回调地址失效。

4. 正确调用Shellcode

把内存地址转换成委托后直接调用即可:

MyShellcodeDelegate shellcodeFunc = (MyShellcodeDelegate)Marshal.GetDelegateFromFunctionPointer(
    shellcodeMem, 
    typeof(MyShellcodeDelegate)
);
// 传入参数,这里的参数类型要和委托、Shellcode完全匹配
shellcodeFunc(123, callbackPtr);

额外避坑提示

  • 架构必须匹配:如果Shellcode是x86的,C#程序必须编译成x86;x64同理,不能用AnyCPU,否则位数不匹配会直接导致参数传递混乱。
  • 参数类型严格对应:C++里的char*对应C#的IntPtr(或者string,但IntPtr更可控),指针类型别乱转,不然封送会出错。
  • 内存清理:用完后记得用VirtualFree释放内存,避免内存泄漏。

完整示例代码

下面是一个可运行的小例子,Shellcode会接收参数并调用C#回调:

using System;
using System.Runtime.InteropServices;

class ShellcodeTest
{
    // 导入kernel32的内存操作函数
    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern IntPtr VirtualAlloc(IntPtr lpAddress, uint dwSize, AllocationType flAllocationType, MemoryProtection flProtect);

    [DllImport("kernel32.dll")]
    private static extern bool VirtualFree(IntPtr lpAddress, uint dwSize, FreeType dwFreeType);

    // 枚举定义
    [Flags]
    private enum AllocationType { Commit = 0x1000, Reserve = 0x2000 }
    [Flags]
    private enum MemoryProtection { PAGE_EXECUTE_READWRITE = 0x40 }
    [Flags]
    private enum FreeType { MEM_RELEASE = 0x8000 }

    // Shellcode委托(cdecl调用约定)
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    public delegate void ShellcodeFunc(int input, IntPtr callback);

    // C#回调委托
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    public delegate void ManagedCallback(int output);

    // 保留回调引用,防止GC回收
    private static ManagedCallback _callback = OnShellcodeCallback;

    static void Main(string[] args)
    {
        // 示例x86 Shellcode:把第一个参数传给回调,然后返回
        // 指令:push ecx; call [esp+4]; add esp,8; ret
        byte[] shellcode = new byte[] { 0x51, 0xFF, 0x54, 0x24, 0x04, 0x83, 0xC4, 0x08, 0xC3 };

        // 获取回调的非托管地址
        IntPtr callbackPtr = Marshal.GetFunctionPointerForDelegate(_callback);

        // 分配可执行内存
        IntPtr shellcodeMem = VirtualAlloc(IntPtr.Zero, (uint)shellcode.Length, AllocationType.Commit | AllocationType.Reserve, MemoryProtection.PAGE_EXECUTE_READWRITE);
        Marshal.Copy(shellcode, 0, shellcodeMem, shellcode.Length);

        // 转换并调用Shellcode
        ShellcodeFunc func = (ShellcodeFunc)Marshal.GetDelegateFromFunctionPointer(shellcodeMem, typeof(ShellcodeFunc));
        func(666, callbackPtr);

        // 释放内存
        VirtualFree(shellcodeMem, 0, FreeType.MEM_RELEASE);
    }

    private static void OnShellcodeCallback(int output)
    {
        Console.WriteLine($"从Shellcode收到数据:{output}");
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.08 15:08:11