c# - 使用反射在 C# 中查找字段的指针

标签 c# memory reflection jit

此问题仅用于教育目的。
我知道 native 程序是如何工作的。编译器获取每个原语并给它一个地址,然后在程序中使用该地址。对于结构,它只是将地址堆叠在一起(使用一些填充)——但基本上,结构并不真正“存在”。

native 程序没有告诉我它有哪些字段和变量。它只访问不同的地址——如果我查看程序集,我可以根据需要命名每个地址,但程序不会给我该信息。因此,假设我正在寻找一个特定的变量,如果不检查程序的执行或程序集,我就无法做到这一点。

.NET 环境确实告诉我它有哪些变量以及它有哪些字段。使用 Assembly 类和 Reflection 命名空间,我可以加载一个文件并查看它有哪些字段和类。
然后,使用搜索内存的程序(无论是否为 native 内存),我可以找到该字段的物理位置(通过使用它的值、过滤等)——就像 Cheat Engine 所做的那样。它会给我字段在内存中的实际地址,这是由 JIT 制作的程序集访问的。
我知道 MSIL 不包含有关特定字段所需位置的信息。我也几乎可以肯定 JIT 永远不会通过删除任何类来优化代码。

我知道 .NET 调试器是程序中的一个实际类,它允许 Visual Studio 与应用程序的内部信息进行交互。缺少调试器时,Visual Studio 无法读取或写入字段,也无法以任何方式检查应用程序。


有什么方法可以在不使用Cheat Engine 或类似工具的情况下找到静态(或特定实例)类中字段的物理位置正在运行的 .NET 进程中? 每次执行后地址是否相同(例如在 native 程序中)?它可能仅在不同的平台或机器上有所不同吗? JIT 如何决定放置字段的位置?

如果我不清楚,我希望在不访问程序代码的情况下执行此操作,即在外部由另一个进程(如调试器,但适用于在发行版下编译的程序)。

最佳答案

下一段代码在 Paint.net 中注入(inject) Injector 方法并获取 MainForm 字段。

NInject.exe

public static int Injector(string parameter)
{
  try
  {
    var mainForm = Application.OpenForms.OfType<Form>().FirstOrDefault(form => form.GetType().FullName.EndsWith("MainForm"));

    var builder = new StringBuilder();
    builder.AppendFormat("process: {0}\r\n\r\n", Application.ExecutablePath);
    builder.AppendFormat("type: {0}\r\n", mainForm.GetType().FullName);
    foreach (var field in mainForm.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
    {
      builder.AppendFormat("field {0}: {1}\r\n", field.Name, field.GetValue(mainForm));
    }


    new Form()
    {
      Controls = 
      {
        new TextBox
        {
          Text = builder.ToString(),
          Multiline = true,
          Dock = DockStyle.Fill
        }
      }
    }
    .ShowDialog();
  }
  catch (Exception exc)
  {
    MessageBox.Show(exc.ToString());
  }
  return 0;      
}

static void Main(string[] args)
{
  var process = System.Diagnostics.Process.GetProcessesByName("PaintDotNet").FirstOrDefault();

  var processHandle = OpenProcess(ProcessAccessFlags.All, false, process.Id);

  var proxyPath = System.IO.Path.Combine(System.Windows.Forms.Application.StartupPath, "NInjector.dll");

  var pathBytes = System.Text.Encoding.ASCII.GetBytes(proxyPath);

  var remoteBuffer = VirtualAllocEx(processHandle, IntPtr.Zero, (uint)pathBytes.Length, AllocationType.Commit, MemoryProtection.ReadWrite);
  WriteProcessMemory(process.Handle, remoteBuffer, pathBytes, (uint)pathBytes.Length, IntPtr.Zero);


  var remoteThread = CreateRemoteThread(processHandle, IntPtr.Zero, 0, GetProcAddress(GetModuleHandle("kernel32"), "LoadLibraryA") , remoteBuffer, 0, IntPtr.Zero);

  WaitForSingleObject(remoteThread, unchecked((uint)-1)); 

  CloseHandle(remoteThread);

}

NInjector.dll( native )

#include "MSCorEE.h"
#pragma comment  (lib, "MSCorEE")

void StartTheDotNetRuntime()
{
    MessageBox(0, L"Started", L"proxy", 0);

    ICLRRuntimeHost *pClrHost = NULL;
    HRESULT hr = CorBindToRuntimeEx(
        NULL, L"wks", 0, CLSID_CLRRuntimeHost,
        IID_ICLRRuntimeHost, (PVOID*)&pClrHost);


    hr = pClrHost->Start();


    DWORD dwRet = 0;
    hr = pClrHost->ExecuteInDefaultAppDomain(
        L"bla-bla\\NInject.exe",
        L"NInject.NInject_Program", L"Injector", L"MyParameter", &dwRet);



    hr = pClrHost->Stop();

    pClrHost->Release();
}


BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
    StartTheDotNetRuntime();
    break;
    case DLL_THREAD_ATTACH:
    break;
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:


        break;
    }
    return TRUE;
}

输出:

process: C:\Program Files\Paint.NET\PaintDotNet.exe

type: PaintDotNet.Dialogs.MainForm
field appWorkspace: PaintDotNet.Controls.AppWorkspace
field defaultButton: System.Windows.Forms.Button, Text: 
field floaters: PaintDotNet.Dialogs.FloatingToolForm[]
field floaterOpacityTimer:  [System.Windows.Forms.Timer], Interval: 25
field deferredInitializationTimer: 
field components: System.ComponentModel.Container
field killAfterInit: False
field singleInstanceManager: PaintDotNet.SystemLayer.SingleInstanceManager
field queuedInstanceMessages: System.Collections.Generic.List`1[System.String]
field processingOpen: False
field scrollPosition: {X=0,Y=0}

关于c# - 使用反射在 C# 中查找字段的指针,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11672131/

相关文章:

java - 不使用 getDeclaredMethod 接收 Java 方法

c# - 为网站抓取工具实现的 TPL 数据流

c# - 在应用程序启动时实例化一个 ViewModel

c# - 我怎样才能确保这个私有(private)只读数组实际上是私有(private)和只读的?

c++ - 内存中函数的校验和

calloc -- 清零内存的用处

go - 如何使用 Gorm 删除 Go 中的重复代码

c# - 从 .NET 传递到 VBA 的 COM 对象的对象生命周期

c++ - 从指针 vector 的 vector 中释放内存

java - 将方法实现作为参数发送