在C#中使用Cheat Engine中的指针

关于该计划

我有一个程序写入我正在试验的游戏的记忆。 当我使用常规静态地址时,代码对我很有用,但由于某些原因,一旦找到工作指针,我似乎无法做到这一点。 例如,我在指针扫描几次后在Cheat Engine中找到了这个:

在此处输入图像描述

每次加载游戏并编辑值时,此地址都有效。 问题是我不明白如何在我的程序中使用它。 这是我尝试插入值的声明变量:

bool UnlimitedAmmo = false; string AmmoPointer = "031B7324"; // <--- The address int[] AmmoOffset = { 0x2c, 0x1e8, 0x3c8, 0x6d4, 0x508 }; // <--- It's pointers int AmmoToFill = 1337; // <--- The Amount of ammo to give 

我传递这些变量如下:

 MyMemory.ReadProcess = MyProcess[0]; MyMemory.Open(); int PointerAddress = HexToDec(AmmoPointer); int[] PointerOffest = AmmoOffset; int BytesWritten; byte[] ValueToWrite = BitConverter.GetBytes(AmmoToFill); string WrittenAddress = MyMemory.PointerWrite((IntPtr)PointerAddress, ValueToWrite, PointerOffest, out BytesWritten); MyMemory.CloseHandle(); 

我曾经使用静态地址(对于不同的游戏),一旦插入地址和偏移量,我的代码就可以正常工作了。 这次我很难过。 任何帮助和解释都将深表感谢。 提前致谢。

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

如果你不想深入了解保存在那里的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的基地址并在线程堆栈中查找该地址,检查是否>=到基地址或<=到读取每4个字节时的base address + size虽然我不得不玩它。

 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#中获取线程基地址,如下所示:

https://stackoverflow.com/a/8737521/1274820

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

我把它包装成一个.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 “找不到模块”错误,我只想提一下,需要使用fInvadeProcess = true调用SymInitialize或手动加载模块, 如MSDN上所述 。