.net也能写内存挂

最近在研究.net的内存挂。

写了很久的c++,发现c#写出来的东西实在太香。

折腾c#外挂已经有很长时间了。都是用socket和c++配合。

这个模式其实蛮成功的,用rpc调用的方式加上c#的天生await 非常好写逻辑

类似这样

最近想换个口味。注入托管dll到非托管进程

这样做只是为了解决我目前遇见的一个问题。

在一个多线程的程序上逆向,我挂了很多钩子,导致我读写数据和储存我自己的数据

非常容易出现多线程冲突问题,换到.net里以后

lock 和Monitor 在.net里是同线程不互锁。这样能让我不容易出现互锁现象。

有一段时间正在烦恼那些卖驱动的,只有读写功能为什么还能实现很多功能。

在我的认知里,要调用游戏部分函数才能更方便自己做出更有用的功能

当然这里确实有些外挂是只读取角色顶点就能绘制的。

后来细想一下其实不需要调用功能通过写入代码的方式获取执行就可以了。

就是只要有读写就可以了。

游戏外挂无非就是 读写和调用。 调用是可以通过写来实现。

比如hook某个dx的函数。或者修改虚函数表的地址,然后jmp 到自己的函数里

达到获取执行权限。

好比挂钩了GetTickCount 这个API ,然后目标游戏不断的调用这个API

我们在这里插入自己的调用逻辑就可以了。

想明白了这个,于是我就干起了注入托管dll 到游戏进程里的勾当

当然这个托管dll实在是太大了(因为会用Costura.Fody把第三方库都打包在一起)

想法是这样,注入到游戏进程里以后申请内存空间,然后通过asm编译成bytes

然后写入到内存后,再去调用他就可以了

这里要安利一个asm的库

GitHub - icedland/iced: Blazing fast and correct x86/x64 disassembler, assembler, decoder, encoder for Rust, .NET, Java, Python, Lua

这个库很香

在c#里写asm长这样子,大概就是写好asm以后编译成bytes的过程

然后要介绍在c#内怎么完成thiscall

游戏大部分是thiscall 所以我在内存中申请一段asm

然后通过c#去调用这个段代码就可以了

asm 的作用就是把传入的参数 push到堆栈然后call

之后返回eax 这样就完整的跑通调用了。

iced把asm生成好,然后通过c#的委托去调用这段asm代码即可

最后实现的效果类是这样

至于读写就更简单了。C#自带Marshal可以直接读写。

而且c#也支持不安全指针 直接 *(int*)(0x123456) = 100;

然后我们无限的包装自己的读写函数,比如byte float int string 之类的读写就可以

至于hook 也可以通过委托回调到自己的c#代码,hook在c#完成编译以后写入到目标地址

至此,完整的c#外挂需要的功能都实现了。

例子分3个工程

TestApp:测试工程模拟调用目标程序比如游戏

InjectionDLL:注入DLL,负责加载.net的dll,如果是远线程注入,就注入这个DLL即可,例子工程是主动loadlibrary

CShareLoadModule : c#的主要工作dll

附上源码一份

netInjection.rar

//以下是补充 2022 10 09

/

因为上面代码是用asm作为Thiscall 调用的,后来发现c#是自带调用约定的可以更优美的实现thiscall 调用

复制代码
        [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
        public delegate IntPtr ThisCall_0(IntPtr ptr);
        [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
        public delegate IntPtr ThisCall_1(IntPtr ptr, IntPtr p1);
        [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
        public delegate IntPtr ThisCall_2(IntPtr ptr, IntPtr p1, IntPtr p2);
        [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
        public delegate IntPtr ThisCall_3(IntPtr ptr, IntPtr p1, IntPtr p2, IntPtr p3);
        [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
        public delegate IntPtr ThisCall_4(IntPtr ptr, IntPtr p1, IntPtr p2, IntPtr p3, IntPtr p4);
        [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
        public delegate IntPtr ThisCall_5(IntPtr ptr, IntPtr p1, IntPtr p2, IntPtr p3, IntPtr p4, IntPtr p5);
        [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
        public delegate IntPtr ThisCall_6(IntPtr ptr, IntPtr p1, IntPtr p2, IntPtr p3, IntPtr p4, IntPtr p5, IntPtr p6);
        [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
        public delegate IntPtr ThisCall_7(IntPtr ptr, IntPtr p1, IntPtr p2, IntPtr p3, IntPtr p4, IntPtr p5, IntPtr p6, IntPtr p7);
        [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
        public delegate IntPtr ThisCall_8(IntPtr ptr, IntPtr p1, IntPtr p2, IntPtr p3, IntPtr p4, IntPtr p5, IntPtr p6, IntPtr p7, IntPtr p8);
        [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
        public delegate IntPtr ThisCall_9(IntPtr ptr, IntPtr p1, IntPtr p2, IntPtr p3, IntPtr p4, IntPtr p5, IntPtr p6, IntPtr p7, IntPtr p8, IntPtr p9);
        [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
        public delegate IntPtr ThisCall_10(IntPtr ptr, IntPtr p1, IntPtr p2, IntPtr p3, IntPtr p4, IntPtr p5, IntPtr p6, IntPtr p7, IntPtr p8, IntPtr p9, IntPtr p10);
        [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
        public delegate IntPtr ThisCall_11(IntPtr ptr, IntPtr p1, IntPtr p2, IntPtr p3, IntPtr p4, IntPtr p5, IntPtr p6, IntPtr p7, IntPtr p8, IntPtr p9, IntPtr p10, IntPtr p11);
        [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
        public delegate IntPtr ThisCall_12(IntPtr ptr, IntPtr p1, IntPtr p2, IntPtr p3, IntPtr p4, IntPtr p5, IntPtr p6, IntPtr p7, IntPtr p8, IntPtr p9, IntPtr p10, IntPtr p11, IntPtr p12);
        [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
        public delegate IntPtr ThisCall_13(IntPtr ptr, IntPtr p1, IntPtr p2, IntPtr p3, IntPtr p4, IntPtr p5, IntPtr p6, IntPtr p7, IntPtr p8, IntPtr p9, IntPtr p10, IntPtr p11, IntPtr p12, IntPtr p13);
        [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
        public delegate IntPtr ThisCall_14(IntPtr ptr, IntPtr p1, IntPtr p2, IntPtr p3, IntPtr p4, IntPtr p5, IntPtr p6, IntPtr p7, IntPtr p8, IntPtr p9, IntPtr p10, IntPtr p11, IntPtr p12, IntPtr p13, IntPtr p14);

        unsafe public static IntPtr ThisCall(IntPtr address, IntPtr dwECX, params object[] args)
        {
            IntPtr p1 = IntPtr.Zero;


            IntPtr[] paramPtr = new IntPtr[args.Length*2];
            int addParamCount = 0;
            for (int i = 0; i < args.Length; i++)
            {
                var paramtype = args[i].GetType();
                if (paramtype == typeof(float))
                {
                    var v = (float)args[i];
                    paramPtr[addParamCount] = *(IntPtr*)&v;
                }
                else if (paramtype == typeof(double))
                {
                    var v = (double)args[i];
                    paramPtr[addParamCount] = *(IntPtr*)&v;
                    addParamCount++;
                    paramPtr[addParamCount] = *(IntPtr*)((&v) +4);
                }
                else if (paramtype == typeof(long))
                {
                    var v = (long)args[i];
                    paramPtr[addParamCount] = *(IntPtr*)&v;
                    addParamCount++;
                    paramPtr[addParamCount] = *(IntPtr*)((&v) + 4);
                }
                else if (paramtype == typeof(IntPtr))
                {
                    var v = (IntPtr)args[i];
                    paramPtr[addParamCount] = v;
                }
                else if (paramtype == typeof(PtrGameUIWindow))
                {
                    paramPtr[addParamCount] = (args[i] as PtrGameUIWindow).ptr;
                }
                else
                {
                    paramPtr[addParamCount] = (IntPtr)Convert.ToInt32(args[i]);
                }
                addParamCount++;
            }
            Log.Console($"ThisCall:0x{address.ToString("X8")} ECX:0x{dwECX.ToString("X8")} {paramPtr.ArrayToString(0, addParamCount)}");
            switch (args.Length)
            {
                case 0:
                    p1 = Marshal.GetDelegateForFunctionPointer<ThisCall_0>(address).Invoke(dwECX);
                    break;
                case 1:
                    p1 = Marshal.GetDelegateForFunctionPointer<ThisCall_1>(address).Invoke(dwECX, paramPtr[0]);
                    break;
                case 2:
                    p1 = Marshal.GetDelegateForFunctionPointer<ThisCall_2>(address).Invoke(dwECX, paramPtr[0], paramPtr[1]);
                    break;
                case 3:
                    p1 = Marshal.GetDelegateForFunctionPointer<ThisCall_3>(address).Invoke(dwECX, paramPtr[0], paramPtr[1], paramPtr[2]);
                    break;
                case 4:
                    p1 = Marshal.GetDelegateForFunctionPointer<ThisCall_4>(address).Invoke(dwECX, paramPtr[0], paramPtr[1], paramPtr[2], paramPtr[3]);
                    break;
                case 5:
                    p1 = Marshal.GetDelegateForFunctionPointer<ThisCall_5>(address).Invoke(dwECX, paramPtr[0], paramPtr[1], paramPtr[2], paramPtr[3], paramPtr[4]);
                    break;
                case 6:
                    p1 = Marshal.GetDelegateForFunctionPointer<ThisCall_6>(address).Invoke(dwECX, paramPtr[0], paramPtr[1], paramPtr[2], paramPtr[3], paramPtr[4], paramPtr[5]);
                    break;
                case 7:
                    p1 = Marshal.GetDelegateForFunctionPointer<ThisCall_7>(address).Invoke(dwECX, paramPtr[0], paramPtr[1], paramPtr[2], paramPtr[3], paramPtr[4], paramPtr[5], paramPtr[6]);
                    break;
                case 8:
                    p1 = Marshal.GetDelegateForFunctionPointer<ThisCall_8>(address).Invoke(dwECX, paramPtr[0], paramPtr[1], paramPtr[2], paramPtr[3], paramPtr[4], paramPtr[5], paramPtr[6], paramPtr[7]);
                    break;
                case 9:
                    p1 = Marshal.GetDelegateForFunctionPointer<ThisCall_9>(address).Invoke(dwECX, paramPtr[0], paramPtr[1], paramPtr[2], paramPtr[3], paramPtr[4], paramPtr[5], paramPtr[6], paramPtr[7], paramPtr[8]);
                    break;
                case 10:
                    p1 = Marshal.GetDelegateForFunctionPointer<ThisCall_10>(address).Invoke(dwECX, paramPtr[0], paramPtr[1], paramPtr[2], paramPtr[3], paramPtr[4], paramPtr[5], paramPtr[6], paramPtr[7], paramPtr[8], paramPtr[9]);
                    break;
                case 11:
                    p1 = Marshal.GetDelegateForFunctionPointer<ThisCall_11>(address).Invoke(dwECX, paramPtr[0], paramPtr[1], paramPtr[2], paramPtr[3], paramPtr[4], paramPtr[5], paramPtr[6], paramPtr[7], paramPtr[8], paramPtr[9], paramPtr[10]);
                    break;
                case 12:
                    p1 = Marshal.GetDelegateForFunctionPointer<ThisCall_12>(address).Invoke(dwECX, paramPtr[0], paramPtr[1], paramPtr[2], paramPtr[3], paramPtr[4], paramPtr[5], paramPtr[6], paramPtr[7], paramPtr[8], paramPtr[9], paramPtr[10], paramPtr[11]);
                    break;
                case 13:
                    p1 = Marshal.GetDelegateForFunctionPointer<ThisCall_13>(address).Invoke(dwECX, paramPtr[0], paramPtr[1], paramPtr[2], paramPtr[3], paramPtr[4], paramPtr[5], paramPtr[6], paramPtr[7], paramPtr[8], paramPtr[9], paramPtr[10], paramPtr[11], paramPtr[12]);
                    break;
                case 14:
                    p1 = Marshal.GetDelegateForFunctionPointer<ThisCall_14>(address).Invoke(dwECX, paramPtr[0], paramPtr[1], paramPtr[2], paramPtr[3], paramPtr[4], paramPtr[5], paramPtr[6], paramPtr[7], paramPtr[8], paramPtr[9], paramPtr[10], paramPtr[11], paramPtr[12], paramPtr[13]);
                    break;
                default:
                    Log.Error(new Exception("ThisCall 参数个数未预测"));
                    p1 = IntPtr.Zero;
                    break;
            }

            return p1;
        }

因为c#会定期GC的问题,导致c#自身函数可能会被GC修改函数位置

所以需要固定住代码位置新增一下函数固定Delegate

复制代码
        [UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Ansi)]
        public delegate IntPtr Delegate_NewStringID2String(IntPtr dwESP);
        
        
        public uint LockDelegate(Delegate func)
        {
            GCHandleList.Add(GCHandle.Alloc(func));
            GCHandleList.Add(GCHandle.Alloc(Marshal.GetFunctionPointerForDelegate(func), GCHandleType.Pinned));
            GCHandleList.Add(GCHandle.Alloc(func.Method.MethodHandle.GetFunctionPointer()));
            return (uint)Marshal.GetFunctionPointerForDelegate(func);
        }
        
        
        //使用方法
        
        public static IntPtr MyStringID2String(IntPtr dwESP)
        {
            var ret = IntPtr.IntPtr.Zero;
            return ret;
        }
        
        public override void Start()
        {
                var asm = new Assembler(32);
                asm.pushad();
                asm.mov(eax, esp);
                asm.add(eax, 0x20);
                asm.push(eax);
                asm.mov(eax, LockDelegate(new Delegate_NewStringID2String(MyStringID2String)));
                asm.call(eax);
                asm.mov(__[esp + 0x1c], eax);
                asm.popad();
                asm.ret();
    
                var newJmpCode = PatchSelfHelper.AllocMem(0x30);
                PatchSelfHelper.WriteAssembler(newJmpCode, asm);
        }
相关推荐
wenha6 小时前
踩坑记录:UTF-8、UTF-8-BOM 与 GB2312 读取的乱码真相
utf-8·.net·编码·utf-8-bom
江沉晚呤时12 小时前
C# 整型溢出处理机制:checked 与 unchecked 上下文解析
c#·.net
余衫马14 小时前
在 Windows 服务中托管 ASP.NET Core Web API (.net6)
运维·windows·后端·asp.net·.net
步步为营DotNet15 小时前
LM-Kit.NET:.NET 生态一站式本地 AI 开发平台
人工智能·.net
步步为营DotNet15 小时前
.NET 实战 LlamaSharp:本地运行开源大模型
.net
CSharp精选营17 小时前
推荐一个开箱即用的.NET权限管理平台:Magic.NET
.net·开源项目·权限管理·企业级框架·后台脚手架
切糕师学AI1 天前
.NET CLR GC 调优完全指南:从理论到生产实战
.net·gc·clr
唐青枫2 天前
C#.NET TaskCompletionSource 深入解析:手动控制 Task、桥接回调事件与实战避坑
c#·.net
OctShop大型商城源码2 天前
C#.NET多商户商城系统源码_OctShop:技术与机遇的融合
c#·.net·多商户商城系统源码·商城系统源码
编码者卢布2 天前
【App Service】常规排查 App Service 启动 Application Insights 无数据的步骤 (.NET版本)
python·flask·.net