Подтвердить что ты не робот

Получить StartAddress из win32-потока из другого процесса

История:

Я написал многопоточное приложение в Win32, которое я начинаю с кода С#, используя класс Process из пространства имен System.Diagnostics.

Теперь, в коде С#, я хочу получить имя/символ начального адреса каждого потока, созданного в приложении Win32, чтобы я мог записывать информацию, связанную с потоком, такую ​​как использование ЦП, в базу данных. В основном, код С# запускает несколько экземпляров приложения Win32, контролирует их, при необходимости убивает, а затем регистрирует информацию/ошибку/исключения/причину/etc в базе данных.

С этой целью я обернул два API Win32: SymInitialize и SymFromAddr в программном API, написанном мной, как указано ниже:

extern "C"
{
    //wraps SymInitialize
    DllExport bool initialize_handler(HANDLE hModue);

    //wraps SymFromAddr
    DllExport bool get_function_symbol(HANDLE hModule, //in
                                       void *address,  //in
                                       char *name);    //out
}

И затем вызовите эти API из кода С#, используя pinvoke. Но это не работает, и GetLastError дает 126 код ошибки, что означает:

Указанный модуль не найден

Я передаю Process.Handle как hModule для обеих функций; initialize_handler, похоже, работает, но get_function_symbol не работает; он дает указанную выше ошибку. Я не уверен, передаю ли я правильную ручку. Я попытался передать следующие дескрипторы:

Process.MainWindowHandle
Process.MainModule.BaseAddress

Оба сбой выполняются на самом первом этапе (т.е. при вызове initialize_handler). Я передаю Process.Threads[i].StartAddress в качестве второго аргумента, и это, по-видимому, является причиной сбоя, поскольку ProcessThread.StartAddress представляется адресом функции RtlUserThreadStart, не адресом конкретной функции запуска к приложению. MSDN говорит об этом:

Каждый поток Windows фактически начинает выполнение в системной заданной функции, а не в приложении. Исходный адрес для основного потока, следовательно, тот же (как он представляет адрес функции, поставляемой системой) для каждого процесса Windows в системе. Однако свойство StartAddress позволяет получить адрес начальной функции, специфичный для вашего приложения.

Но он не говорит, как получить адрес функции startinbg, специфичный для приложения, используя ProcessThread.StartAddress.

Вопрос:

Моя проблема сводится к тому, чтобы получить начальный адрес потока win32 из другого приложения (написанного на С#), как только я его получу, я также получу имя, используя вышеупомянутые API. Итак, как получить начальный адрес?


Я проверил свой API поиска символов из кода на С++. Он отлично работает, чтобы разрешить адрес символу, если задан правильный адрес для начала.

Вот мои объявления p/invoke:

[DllImport("UnmanagedSymbols.dll", SetLastError = true, CallingConvention= CallingConvention.Cdecl)]
static extern bool initialize_handler(IntPtr hModule);

[DllImport("UnmanagedSymbols.dll", SetLastError = true, CallingConvention = CallingConvention.Cdecl)]
static extern bool get_function_symbol(IntPtr hModule, IntPtr address, StringBuilder name);
4b9b3361

Ответ 1

Ключом является вызов функции 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 "module not found" я просто хотел упомянуть, что нужно вызвать SymInitialize с помощью fInvadeProcess = true ИЛИ загрузить модуль вручную, как описано в MSDN.

Я знаю, что вы говорите, что это не так в вашей ситуации, но я оставлю это в интересах любого, кто находит этот вопрос через эти ключевые слова.

Ответ 2

Здесь мое понимание проблемы.

У вас есть приложение С#, APP1, которое создает кучу потоков.

Эти потоки, в свою очередь, создают процесс. Я предполагаю, что эти потоки остаются в живых и отвечают за мониторинг процесса, который он породил.

Итак, для каждого потока в APP1 вы хотите, чтобы он перечислял информацию о потоках, порожденных в дочернем процессе этого потока.

Они могли бы сделать это в добрые старые дни:

  • Содержит весь мой мониторинг потоков Win32 для данного процесса Win32 в DLL
  • Вставить эту DLL в процесс, который я хотел отслеживать
  • Использовать именованный канал или другой механизм RPC для связи из инъецированного процесса Win32 на хост APP1

Итак, в вашем основном threadproc в С# вы должны создать и контролировать именованный канал для вашего процесса для связи после его ввода.

В С++ land псевдо-код должен был бы создать приостановленный процесс, выделить в нем некоторую память, вставить вашу DLL в процесс, а затем создать удаленный поток, который будет выполнять вашу вложенную dll:

char * dllName = "your cool dll with thread monitoring stuff.dll"

// Create a suspended process
CreateProces("your Win32 process.exe", ...CREATE_SUSPENDED..., pi)

// Allocate memory in the process to hold your DLL name to load
lpAlloc = VirtualAlloc(ph.hProcess, ... MEM_COMMIT, PAGE_READWRITE)

// Write the name of your dll to load in the process memory
WriteProcessMemeory(pi.hProcess, lpAlloc, dllName, ...)

// Get the address of LoadLibrary
fnLoadLibrary = GetProcAddress(GetModuleHandle("kernel32.dll"), "LoadLibraryA")

// Create a remote thread in the process, giving it the threadproc for LoadLibrary
// and the argument of your DLL name
hTrhead = CreateRemoteThread(pi.hProcess, ..., fnLoadLibrary, lpAlloc, ...)

// Wait for your dll to load
WaitForSingleObject(hThread)

// Go ahead and start the Win32 process
ResumeThread(ph.hThread)

В вашей DLL вы можете поместить код в DLL_PROCESS_ATTACH, который будет подключаться к именованному каналу, который вы настроили, и инициализировать все свои вещи. Затем запустите функцию, чтобы начать мониторинг и отчетность по именованному каналу.

Ваш С# threadproc будет контролировать именованный канал для его процесса и сообщать об этом до APP1.

UPDATE:

Я пропустил тот факт, что вы управляете кодом для Win32-процесса. В этом случае я просто передал бы аргумент процессу, который бы контролировал механизм RPC по вашему выбору для связи (общая память, именованные каналы, служба очереди, буфер обмена (ha) и т.д.).

Таким образом, ваш С# threadproc настраивает канал связи RPC и мониторинг, а затем предоставляет информацию о "адресе" вашему процессу Win32, чтобы он мог "набрать вас".

Я оставлю другие вещи там, если это полезно для всех, кто хочет контролировать процесс Win32, где они не отвечают за код.

Ответ 3

Ну, это определенно не простой подход, но, возможно, это поможет вам как-то. Вы должны иметь возможность получить трассировку стека другого потока, используя этот проект (StackWalk64) и в конечном итоге увидеть имя желаемой функции. У этого есть свои проблемы, в особенности производительность этого подхода, вероятно, не будет слишком высокой, но, как я понял, это одноразовая работа в потоке. Вопрос в том, сможет ли он в общем случае правильно пройти стек ваших (возможно оптимизированных) приложений.

Ответ 4

Во-первых, вы не можете действительно сделать это надежно: если вам придётся получить доступ к Thread.StartAddress, прежде чем поток выполнит указатель функции или после возвращения функции, вы не сможете узнать, что такое начальная функция.

Во-вторых, более вероятным ответом является то, что при запуске функции запуска потока нет прямого сопоставления с функцией запуска.