Я решил, что я опубликую решение для этого для людей в будущем.
Один из способов справиться с этим, если вы не хотите, чтобы погрузиться в код C++ сохранённые там и переписать в C# просто использовать эту программу на GitHub:
https://github.com/makemek/cheatengine-threadstack-finder
Прямая ссылка скачать здесь:
https://github.com/makemek/cheatengine-threadstack-finder/files/685703/threadstack.zip
вы можете передать этот исполняемый идентификатор процесса и разобрать адрес нить вам нужно.
В основном, то, что я сделал, это мой процесс, который запускает 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<int> 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# как это:
https://stackoverflow.com/a/8737521/1274820
Ключ для вызова функции NtQueryInformationThread
. Это не полностью «официальная» функция (возможно, недокументированная в прошлом?), Но в документации нет альтернативы для получения начального адреса потока.
Я завернул его в .NET-дружественный вызов, который принимает идентификатор потока и возвращает начальный адрес как 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<ProcessThread>();
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
«модуль не найден» ошибка, я просто хотел сказать, что нужно позвонить SymInitialize
с fInvadeProcess = true
или загрузить модуль вручную, as documented on MSDN.
Я начал щедрость по этому вопросу, поскольку в настоящее время я пытаюсь понять то же самое. А именно, как получить адрес 'THREADSTACK0' в C#. Я нашел некоторую информацию о том, как он создан в читэнгине, и я могу прочитать адрес в cheatengine, но ни один из threadstartaddresses, в который я втягиваю C#, соответствует тому, что возвращает CE – user1274820
См. Здесь: http://forum.cheatengine.org/ viewtopic.php? р = 5487976 – user1274820