热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

在C#中使用CheatEngine中的指针

如何解决《在C#中使用CheatEngine中的指针》经验,为你挑选了1个好方法。

我想我会为将来的人们发布一个解决方案.

如果你不想深入了解保存在那里的C++代码并在C#中重写,只需在github上使用这个程序就可以解决这个问题:

https://github.com/makemek/cheatengine-threadstack-finder

直接下载链接在这里:

https://github.com/makemek/cheatengine-threadstack-finder/files/685703/threadstack.zip

您可以将此可执行文件传递给进程ID,并解析出您需要的线程地址.

基本上,我所做的是我的进程运行exe,重定向输出,并解析它.

然后这个过程结束了,我们做了我们需要的事情 - 我觉得我在欺骗,但它确实有效.

输出threadstack.exe通常如下所示:

PID 6540 (0x198c)
Grabbing handle
Success
PID: 6540 Thread ID: 0x1990
PID: 6540 Thread ID: 0x1b1c
PID: 6540 Thread ID: 0x1bbc
TID: 0x1990 = THREADSTACK 0 BASE ADDRESS: 0xbcff8c
TID: 0x1b1c = THREADSTACK 1 BASE ADDRESS: 0x4d8ff8c
TID: 0x1bbc = THREADSTACK 2 BASE ADDRESS: 0x518ff8c

这是我最终用来获取我需要的地址的代码:

[DllImport("kernel32.dll", SetLastError = true)]
static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, [Out] byte[] lpBuffer, int dwSize, out int lpNumberOfBytesRead);

////////////////////////////////////////////////////////////////////
// These are used to find the StardewValley.Farmer structure     //
//////////////////////////////////////////////////////////////////
private IntPtr Thread0Address;
private IntPtr FarmerStartAddress;
private static int[] FARMER_OFFSETS = { 0x4, 0x478, 0x218, 0x24C };
private static int FARMER_FIRST = 0x264;
//////////////////////////////////////////////////////////////////

private async void hookAll()
{
    SVProcess = Process.GetProcessesByName("Stardew Valley")[0];
    SVHandle = OpenProcess(ProcessAccessFlags.All, true, SVProcess.Id);
    SVBaseAddress = SVProcess.MainModule.BaseAddress;
    Thread0Address = (IntPtr) await getThread0Address();
    getFarmerStartAddress();
}
private Task getThread0Address()
{
    var proc = new Process
    {
        StartInfo = new ProcessStartInfo
        {
            FileName = "threadstack.exe",
            Arguments = SVProcess.Id + "",
            UseShellExecute = false,
            RedirectStandardOutput = true,
            CreateNoWindow = true
        }
    };
    proc.Start();
    while (!proc.StandardOutput.EndOfStream)
    {
        string line = proc.StandardOutput.ReadLine();
        if (line.Contains("THREADSTACK 0 BASE ADDRESS: "))
        {
            line = line.Substring(line.LastIndexOf(":") + 2);
            return Task.FromResult(int.Parse(line.Substring(2), System.Globalization.NumberStyles.HexNumber));
        }
    }
    return Task.FromResult(0);
}
private void getFarmerStartAddress()
{
    IntPtr curAdd = (IntPtr) ReadInt32(Thread0Address - FARMER_FIRST);
    foreach (int x in FARMER_OFFSETS)
        curAdd = (IntPtr) ReadInt32(curAdd + x);
    FarmerStartAddress = (IntPtr) curAdd;
}
private int ReadInt32(IntPtr addr)
{
    byte[] results = new byte[4];
    int read = 0;
    ReadProcessMemory(SVHandle, addr, results, results.Length, out read);
    return BitConverter.ToInt32(results, 0);
}

最后

如果您对更新C++代码感兴趣,我相信相关部分就在这里.

它实际上不看太复杂了-我想我们就可以抓取的基址kernal32.dll,并通过检查,看它是否是寻找线程堆栈地址>=基地址或<=base address + size一边读4个字节-我不得不玩它.

DWORD GetThreadStartAddress(HANDLE processHandle, HANDLE hThread) {
    /* rewritten from https://github.com/cheat-engine/cheat-engine/blob/master/Cheat%20Engine/CEFuncProc.pas#L3080 */
    DWORD used = 0, ret = 0;
    DWORD stacktop = 0, result = 0;

    MODULEINFO mi;

    GetModuleInformation(processHandle, GetModuleHandle("kernel32.dll"), &mi, sizeof(mi));
    stacktop = (DWORD)GetThreadStackTopAddress_x86(processHandle, hThread);

    /* The stub below has the same result as calling GetThreadStackTopAddress_x86() 
    change line 54 in ntinfo.cpp to return tbi.TebBaseAddress
    Then use this stub
    */
    //LPCVOID tebBaseAddress = GetThreadStackTopAddress_x86(processHandle, hThread);
    //if (tebBaseAddress)
    //  ReadProcessMemory(processHandle, (LPCVOID)((DWORD)tebBaseAddress + 4), &stacktop, 4, NULL);

    CloseHandle(hThread);

    if (stacktop) {
        //find the stack entry pointing to the function that calls "ExitXXXXXThread"
        //Fun thing to note: It's the first entry that points to a address in kernel32

        DWORD* buf32 = new DWORD[4096];

        if (ReadProcessMemory(processHandle, (LPCVOID)(stacktop - 4096), buf32, 4096, NULL)) {
            for (int i = 4096 / 4 - 1; i >= 0; --i) {
                if (buf32[i] >= (DWORD)mi.lpBaseOfDll && buf32[i] <= (DWORD)mi.lpBaseOfDll + mi.SizeOfImage) {
                    result = stacktop - 4096 + i * 4;
                    break;
                }

            }
        }

        delete buf32;
    }

    return result;
}

您可以在C#中获取线程基地址,如下所示:

/sf/ask/17360801/

关键是要调用该NtQueryInformationThread函数.这不是一个完全"官方"的功能(过去可能没有记录?),但文档建议无法获取线程的起始地址.

我把它包装成一个.NET友好的调用,它接受一个线程ID并返回起始地址IntPtr.此代码已在x86和x64模式下进行了测试,在后者中,它在32位和64位目标进程上进行了测试.

我没有测试的一件事是以低权限运行它; 我希望这段代码要求调用者拥有SeDebugPrivilege.

using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;

class Program
{
    static void Main(string[] args)
    {
        PrintProcessThreads(Process.GetCurrentProcess().Id);
        PrintProcessThreads(4156); // some other random process on my system
        Console.WriteLine("Press Enter to exit.");
        Console.ReadLine();
    }

    static void PrintProcessThreads(int processId)
    {
        Console.WriteLine(string.Format("Process Id: {0:X4}", processId));
        var threads = Process.GetProcessById(processId).Threads.OfType();
        foreach (var pt in threads)
            Console.WriteLine("  Thread Id: {0:X4}, Start Address: {1:X16}",
                              pt.Id, (ulong) GetThreadStartAddress(pt.Id));
    }

    static IntPtr GetThreadStartAddress(int threadId)
    {
        var hThread = OpenThread(ThreadAccess.QueryInformation, false, threadId);
        if (hThread == IntPtr.Zero)
            throw new Win32Exception();
        var buf = Marshal.AllocHGlobal(IntPtr.Size);
        try
        {
            var result = NtQueryInformationThread(hThread,
                             ThreadInfoClass.ThreadQuerySetWin32StartAddress,
                             buf, IntPtr.Size, IntPtr.Zero);
            if (result != 0)
                throw new Win32Exception(string.Format("NtQueryInformationThread failed; NTSTATUS = {0:X8}", result));
            return Marshal.ReadIntPtr(buf);
        }
        finally
        {
            CloseHandle(hThread);
            Marshal.FreeHGlobal(buf);
        }
    }

    [DllImport("ntdll.dll", SetLastError = true)]
    static extern int NtQueryInformationThread(
        IntPtr threadHandle,
        ThreadInfoClass threadInformationClass,
        IntPtr threadInformation,
        int threadInformationLength,
        IntPtr returnLengthPtr);

    [DllImport("kernel32.dll", SetLastError = true)]
    static extern IntPtr OpenThread(ThreadAccess dwDesiredAccess, bool bInheritHandle, int dwThreadId);

    [DllImport("kernel32.dll", SetLastError = true)]
    static extern bool CloseHandle(IntPtr hObject);

    [Flags]
    public enum ThreadAccess : int
    {
        Terminate = 0x0001,
        SuspendResume = 0x0002,
        GetCOntext= 0x0008,
        SetCOntext= 0x0010,
        SetInformation = 0x0020,
        QueryInformation = 0x0040,
        SetThreadToken = 0x0080,
        ImpersOnate= 0x0100,
        DirectImpersOnation= 0x0200
    }

    public enum ThreadInfoClass : int
    {
        ThreadQuerySetWin32StartAddress = 9
    }
}

我的系统输出:

Process Id: 2168    (this is a 64-bit process)
  Thread Id: 1C80, Start Address: 0000000001090000
  Thread Id: 210C, Start Address: 000007FEEE8806D4
  Thread Id: 24BC, Start Address: 000007FEEE80A74C
  Thread Id: 12F4, Start Address: 0000000076D2AEC0
Process Id: 103C    (this is a 32-bit process)
  Thread Id: 2510, Start Address: 0000000000FEA253
  Thread Id: 0A0C, Start Address: 0000000076F341F3
  Thread Id: 2438, Start Address: 0000000076F36679
  Thread Id: 2514, Start Address: 0000000000F96CFD
  Thread Id: 2694, Start Address: 00000000025CCCE6

除了括号中的东西,因为那需要额外的P/Invoke.


关于SymFromAddress"模块未找到"的错误,我只是想告诉您一个需要调用SymInitializefInvadeProcess = true或手动加载模块,在MSDN上的记录.



1> user1274820..:

我想我会为将来的人们发布一个解决方案.

如果你不想深入了解保存在那里的C++代码并在C#中重写,只需在github上使用这个程序就可以解决这个问题:

https://github.com/makemek/cheatengine-threadstack-finder

直接下载链接在这里:

https://github.com/makemek/cheatengine-threadstack-finder/files/685703/threadstack.zip

您可以将此可执行文件传递给进程ID,并解析出您需要的线程地址.

基本上,我所做的是我的进程运行exe,重定向输出,并解析它.

然后这个过程结束了,我们做了我们需要的事情 - 我觉得我在欺骗,但它确实有效.

输出threadstack.exe通常如下所示:

PID 6540 (0x198c)
Grabbing handle
Success
PID: 6540 Thread ID: 0x1990
PID: 6540 Thread ID: 0x1b1c
PID: 6540 Thread ID: 0x1bbc
TID: 0x1990 = THREADSTACK 0 BASE ADDRESS: 0xbcff8c
TID: 0x1b1c = THREADSTACK 1 BASE ADDRESS: 0x4d8ff8c
TID: 0x1bbc = THREADSTACK 2 BASE ADDRESS: 0x518ff8c

这是我最终用来获取我需要的地址的代码:

[DllImport("kernel32.dll", SetLastError = true)]
static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, [Out] byte[] lpBuffer, int dwSize, out int lpNumberOfBytesRead);

////////////////////////////////////////////////////////////////////
// These are used to find the StardewValley.Farmer structure     //
//////////////////////////////////////////////////////////////////
private IntPtr Thread0Address;
private IntPtr FarmerStartAddress;
private static int[] FARMER_OFFSETS = { 0x4, 0x478, 0x218, 0x24C };
private static int FARMER_FIRST = 0x264;
//////////////////////////////////////////////////////////////////

private async void hookAll()
{
    SVProcess = Process.GetProcessesByName("Stardew Valley")[0];
    SVHandle = OpenProcess(ProcessAccessFlags.All, true, SVProcess.Id);
    SVBaseAddress = SVProcess.MainModule.BaseAddress;
    Thread0Address = (IntPtr) await getThread0Address();
    getFarmerStartAddress();
}
private Task getThread0Address()
{
    var proc = new Process
    {
        StartInfo = new ProcessStartInfo
        {
            FileName = "threadstack.exe",
            Arguments = SVProcess.Id + "",
            UseShellExecute = false,
            RedirectStandardOutput = true,
            CreateNoWindow = true
        }
    };
    proc.Start();
    while (!proc.StandardOutput.EndOfStream)
    {
        string line = proc.StandardOutput.ReadLine();
        if (line.Contains("THREADSTACK 0 BASE ADDRESS: "))
        {
            line = line.Substring(line.LastIndexOf(":") + 2);
            return Task.FromResult(int.Parse(line.Substring(2), System.Globalization.NumberStyles.HexNumber));
        }
    }
    return Task.FromResult(0);
}
private void getFarmerStartAddress()
{
    IntPtr curAdd = (IntPtr) ReadInt32(Thread0Address - FARMER_FIRST);
    foreach (int x in FARMER_OFFSETS)
        curAdd = (IntPtr) ReadInt32(curAdd + x);
    FarmerStartAddress = (IntPtr) curAdd;
}
private int ReadInt32(IntPtr addr)
{
    byte[] results = new byte[4];
    int read = 0;
    ReadProcessMemory(SVHandle, addr, results, results.Length, out read);
    return BitConverter.ToInt32(results, 0);
}

最后

如果您对更新C++代码感兴趣,我相信相关部分就在这里.

它实际上不看太复杂了-我想我们就可以抓取的基址kernal32.dll,并通过检查,看它是否是寻找线程堆栈地址>=基地址或<=base address + size一边读4个字节-我不得不玩它.

DWORD GetThreadStartAddress(HANDLE processHandle, HANDLE hThread) {
    /* rewritten from https://github.com/cheat-engine/cheat-engine/blob/master/Cheat%20Engine/CEFuncProc.pas#L3080 */
    DWORD used = 0, ret = 0;
    DWORD stacktop = 0, result = 0;

    MODULEINFO mi;

    GetModuleInformation(processHandle, GetModuleHandle("kernel32.dll"), &mi, sizeof(mi));
    stacktop = (DWORD)GetThreadStackTopAddress_x86(processHandle, hThread);

    /* The stub below has the same result as calling GetThreadStackTopAddress_x86() 
    change line 54 in ntinfo.cpp to return tbi.TebBaseAddress
    Then use this stub
    */
    //LPCVOID tebBaseAddress = GetThreadStackTopAddress_x86(processHandle, hThread);
    //if (tebBaseAddress)
    //  ReadProcessMemory(processHandle, (LPCVOID)((DWORD)tebBaseAddress + 4), &stacktop, 4, NULL);

    CloseHandle(hThread);

    if (stacktop) {
        //find the stack entry pointing to the function that calls "ExitXXXXXThread"
        //Fun thing to note: It's the first entry that points to a address in kernel32

        DWORD* buf32 = new DWORD[4096];

        if (ReadProcessMemory(processHandle, (LPCVOID)(stacktop - 4096), buf32, 4096, NULL)) {
            for (int i = 4096 / 4 - 1; i >= 0; --i) {
                if (buf32[i] >= (DWORD)mi.lpBaseOfDll && buf32[i] <= (DWORD)mi.lpBaseOfDll + mi.SizeOfImage) {
                    result = stacktop - 4096 + i * 4;
                    break;
                }

            }
        }

        delete buf32;
    }

    return result;
}

您可以在C#中获取线程基地址,如下所示:

/sf/ask/17360801/

关键是要调用该NtQueryInformationThread函数.这不是一个完全"官方"的功能(过去可能没有记录?),但文档建议无法获取线程的起始地址.

我把它包装成一个.NET友好的调用,它接受一个线程ID并返回起始地址IntPtr.此代码已在x86和x64模式下进行了测试,在后者中,它在32位和64位目标进程上进行了测试.

我没有测试的一件事是以低权限运行它; 我希望这段代码要求调用者拥有SeDebugPrivilege.

using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;

class Program
{
    static void Main(string[] args)
    {
        PrintProcessThreads(Process.GetCurrentProcess().Id);
        PrintProcessThreads(4156); // some other random process on my system
        Console.WriteLine("Press Enter to exit.");
        Console.ReadLine();
    }

    static void PrintProcessThreads(int processId)
    {
        Console.WriteLine(string.Format("Process Id: {0:X4}", processId));
        var threads = Process.GetProcessById(processId).Threads.OfType();
        foreach (var pt in threads)
            Console.WriteLine("  Thread Id: {0:X4}, Start Address: {1:X16}",
                              pt.Id, (ulong) GetThreadStartAddress(pt.Id));
    }

    static IntPtr GetThreadStartAddress(int threadId)
    {
        var hThread = OpenThread(ThreadAccess.QueryInformation, false, threadId);
        if (hThread == IntPtr.Zero)
            throw new Win32Exception();
        var buf = Marshal.AllocHGlobal(IntPtr.Size);
        try
        {
            var result = NtQueryInformationThread(hThread,
                             ThreadInfoClass.ThreadQuerySetWin32StartAddress,
                             buf, IntPtr.Size, IntPtr.Zero);
            if (result != 0)
                throw new Win32Exception(string.Format("NtQueryInformationThread failed; NTSTATUS = {0:X8}", result));
            return Marshal.ReadIntPtr(buf);
        }
        finally
        {
            CloseHandle(hThread);
            Marshal.FreeHGlobal(buf);
        }
    }

    [DllImport("ntdll.dll", SetLastError = true)]
    static extern int NtQueryInformationThread(
        IntPtr threadHandle,
        ThreadInfoClass threadInformationClass,
        IntPtr threadInformation,
        int threadInformationLength,
        IntPtr returnLengthPtr);

    [DllImport("kernel32.dll", SetLastError = true)]
    static extern IntPtr OpenThread(ThreadAccess dwDesiredAccess, bool bInheritHandle, int dwThreadId);

    [DllImport("kernel32.dll", SetLastError = true)]
    static extern bool CloseHandle(IntPtr hObject);

    [Flags]
    public enum ThreadAccess : int
    {
        Terminate = 0x0001,
        SuspendResume = 0x0002,
        GetCOntext= 0x0008,
        SetCOntext= 0x0010,
        SetInformation = 0x0020,
        QueryInformation = 0x0040,
        SetThreadToken = 0x0080,
        ImpersOnate= 0x0100,
        DirectImpersOnation= 0x0200
    }

    public enum ThreadInfoClass : int
    {
        ThreadQuerySetWin32StartAddress = 9
    }
}

我的系统输出:

Process Id: 2168    (this is a 64-bit process)
  Thread Id: 1C80, Start Address: 0000000001090000
  Thread Id: 210C, Start Address: 000007FEEE8806D4
  Thread Id: 24BC, Start Address: 000007FEEE80A74C
  Thread Id: 12F4, Start Address: 0000000076D2AEC0
Process Id: 103C    (this is a 32-bit process)
  Thread Id: 2510, Start Address: 0000000000FEA253
  Thread Id: 0A0C, Start Address: 0000000076F341F3
  Thread Id: 2438, Start Address: 0000000076F36679
  Thread Id: 2514, Start Address: 0000000000F96CFD
  Thread Id: 2694, Start Address: 00000000025CCCE6

除了括号中的东西,因为那需要额外的P/Invoke.


关于SymFromAddress"模块未找到"的错误,我只是想告诉您一个需要调用SymInitializefInvadeProcess = true或手动加载模块,在MSDN上的记录.


推荐阅读
  • C#设计模式之八装饰模式(Decorator Pattern)【结构型】
    一、引言今天我们要讲【结构型】设计模式的第三个模式,该模式是【装饰模式】,英文名称:DecoratorPattern。我第一次看到这个名称想到的是另外一个词语“装修”,我就说说我对“装修”的理 ... [详细]
  • 本文讨论了在Windows 8上安装gvim中插件时出现的错误加载问题。作者将EasyMotion插件放在了正确的位置,但加载时却出现了错误。作者提供了下载链接和之前放置插件的位置,并列出了出现的错误信息。 ... [详细]
  • 本文介绍了九度OnlineJudge中的1002题目“Grading”的解决方法。该题目要求设计一个公平的评分过程,将每个考题分配给3个独立的专家,如果他们的评分不一致,则需要请一位裁判做出最终决定。文章详细描述了评分规则,并给出了解决该问题的程序。 ... [详细]
  • Python如何调用类里面的方法
    本文介绍了在Python中调用同一个类中的方法需要加上self参数,并且规范写法要求每个函数的第一个参数都为self。同时还介绍了如何调用另一个类中的方法。详细内容请阅读剩余部分。 ... [详细]
  • 本文介绍了使用PHP实现断点续传乱序合并文件的方法和源码。由于网络原因,文件需要分割成多个部分发送,因此无法按顺序接收。文章中提供了merge2.php的源码,通过使用shuffle函数打乱文件读取顺序,实现了乱序合并文件的功能。同时,还介绍了filesize、glob、unlink、fopen等相关函数的使用。阅读本文可以了解如何使用PHP实现断点续传乱序合并文件的具体步骤。 ... [详细]
  • 深入理解Kafka服务端请求队列中请求的处理
    本文深入分析了Kafka服务端请求队列中请求的处理过程,详细介绍了请求的封装和放入请求队列的过程,以及处理请求的线程池的创建和容量设置。通过场景分析、图示说明和源码分析,帮助读者更好地理解Kafka服务端的工作原理。 ... [详细]
  • 第四章高阶函数(参数传递、高阶函数、lambda表达式)(python进阶)的讲解和应用
    本文主要讲解了第四章高阶函数(参数传递、高阶函数、lambda表达式)的相关知识,包括函数参数传递机制和赋值机制、引用传递的概念和应用、默认参数的定义和使用等内容。同时介绍了高阶函数和lambda表达式的概念,并给出了一些实例代码进行演示。对于想要进一步提升python编程能力的读者来说,本文将是一个不错的学习资料。 ... [详细]
  • Java程序设计第4周学习总结及注释应用的开发笔记
    本文由编程笔记#小编为大家整理,主要介绍了201521123087《Java程序设计》第4周学习总结相关的知识,包括注释的应用和使用类的注释与方法的注释进行注释的方法,并在Eclipse中查看。摘要内容大约为150字,提供了一定的参考价值。 ... [详细]
  • 在C#中,使用关键字abstract来定义抽象类和抽象方法。抽象类是一种不能被实例化的类,它只提供部分实现,但可以被其他类继承并创建实例。抽象类可以用于类、方法、属性、索引器和事件。在一个类声明中使用abstract表示该类倾向于作为其他类的基类成员被标识为抽象,或者被包含在一个抽象类中,必须由其派生类实现。本文介绍了C#中抽象类和抽象方法的基础知识,并提供了一个示例代码。 ... [详细]
  • 1Lock与ReadWriteLock1.1LockpublicinterfaceLock{voidlock();voidlockInterruptibl ... [详细]
  • 在开发中,有时候一个业务上要求的原子操作不仅仅包括数据库,还可能涉及外部接口或者消息队列。此时,传统的数据库事务无法满足需求。本文介绍了Java中如何利用java.lang.Runtime.addShutdownHook方法来保证业务线程的完整性。通过添加钩子,在程序退出时触发钩子,可以执行一些操作,如循环检查某个线程的状态,直到业务线程正常退出,再结束钩子程序。例子程序展示了如何利用钩子来保证业务线程的完整性。 ... [详细]
  • Mono为何能跨平台
    概念JIT编译(JITcompilation),运行时需要代码时,将Microsoft中间语言(MSIL)转换为机器码的编译。CLR(CommonLa ... [详细]
  • 颜色迁移(reinhard VS welsh)
    不要谈什么天分,运气,你需要的是一个截稿日,以及一个不交稿就能打爆你狗头的人,然后你就会被自己的才华吓到。------ ... [详细]
  • Opencv提供了几种分类器,例程里通过字符识别来进行说明的1、支持向量机(SVM):给定训练样本,支持向量机建立一个超平面作为决策平面,使得正例和反例之间的隔离边缘被最大化。函数原型:训练原型cv ... [详细]
  • ihaveusedthedelphidatabindingwizardwithmyxmlfile,andeverythingcompilesandrunsfine. ... [详细]
author-avatar
轻梦云裳_904
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有