Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Get StartAddress of win32 thread from another process

Background:

I've written a multi-threaded application in Win32, which I start from C# code using Process class from System.Diagnostics namespace.

Now, in the C# code, I want to get the name/symbol of the start address of each thread created in the Win32 application so that I could log thread related information, such as CPU usage, to database. Basically, C# code starts multiple instances of the Win32 Application, monitors them, kills if needed, and then logs info/error/exceptions/reason/etc to database.

For this purpose, I've wrapped two Win32 API viz. SymInitialize and SymFromAddr in programmer-friendly API written by myself, as listed below:

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
}

And then call these API from C# code, using pinvoke. But it does not work and GetLastError gives 126 error code which means:

The specified module could not be found

I'm passing Process.Handle as hModule to both functions; initialize_handler seems to work, but get_function_symbol does not; it gives the above error. I'm not sure if I'm passing the correct handle. I tried passing the following handles:

Process.MainWindowHandle
Process.MainModule.BaseAddress

Both fail at the first step itself (i.e when calling initialize_handler). I'm passing Process.Threads[i].StartAddress as second argument, and that seems to be cause of the failure as ProcessThread.StartAddress seems to be the address of RtlUserThreadStart function, not the address of the start function specific to the application. The MSDN says about it:

Every Windows thread actually begins execution in a system-supplied function, not the application-supplied function. The starting address for the primary thread is, therefore, the same (as it represents the address of the system-supplied function) for every Windows process in the system. However, the StartAddress property allows you to get the starting function address that is specific to your application.

But it doesn't say how to get the startinbg function address specific to the application, using ProcessThread.StartAddress.

Question:

My problem boils to getting the start address of win32 thread from another application (written in C#), as once I get it, I will get the name as well, using the above mentioned APIs. So how to get the start address?


I tested my symbol lookup API from C++ code. It works fine to resolve the address to a symbol, if given the correct address to start with.

Here is my p/invoke declarations:

[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);
like image 351
Nawaz Avatar asked Dec 30 '11 12:12

Nawaz


2 Answers

The key is to call the NtQueryInformationThread function. This is not a completely "official" function (possibly undocumented in the past?), but the documentation suggests no alternative for getting the start address of a thread.

I've wrapped it up into a .NET-friendly call that takes a thread ID and returns the start address as IntPtr. This code has been tested in x86 and x64 mode, and in the latter it was tested on both a 32-bit and a 64-bit target process.

One thing I did not test was running this with low privileges; I would expect that this code requires the caller to have the 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     } } 

Output on my system:

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 

apart from the stuff in parentheses since that requires extra P/Invoke's.


Regarding SymFromAddress "module not found" error, I just wanted to mention that one needs to call SymInitialize with fInvadeProcess = true OR load the module manually, as documented on MSDN.

I know you say this isn't the case in your situation, but I'll leave this in for the benefit of anyone who finds this question via those keywords.

like image 97
Roman Starkov Avatar answered Sep 25 '22 08:09

Roman Starkov


Here's what my understanding of the problem is.

You have a C# app, APP1 that creates a bunch of threads.

Those threads, in turn, each create a process. I am assuming those threads stay alive and are in charge of monitoring the process it spawned.

So for each thread in APP1, you want it to enumerate information on the threads spawned in the child process of that thread.

They way I would have done this back in the good-old-days would be:

  • Code all my Win32 thread monitoring of a given Win32 process into a DLL
  • Inject that DLL into the process I wanted to monitor
  • Use a named pipe or other RPC mechanism to communicate from the injected Win32 process to the host APP1

So in your main threadproc in C#, you would create and monitor a named pipe for your process to communicate once it has been injected.

In C++ land, the pseudo code would be to then create a suspended process, allocate some memory in that process, inject your DLL into the process, then create a remote thread that would execute your injected 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) 

In your DLL, you could put code into DLL_PROCESS_ATTACH that would connect to the named pipe you set up, and initialize all your stuff. Then fire a function to begin monitoring and reporting on the named pipe.

Your C# threadproc would monitor the named pipe for its process, and report it on up to APP1.

UPDATE:

I missed the fact that you control the code for the Win32 proccess. In that case, I would just pass an argument to the proccess that would control the RPC mechanism of your choice for communication (Shared memory, named pipes, queue service, clipboard (ha), etc).

That way, your C# threadproc sets up the RPC communication channel and monitoring, and then provides the "address" information to your Win32 process so it can "dial you back".

I'll leave the other stuff up there in case it is useful to anyone else wanting to monitor a Win32 process where they are not in charge of the code.

like image 38
GalacticJello Avatar answered Sep 24 '22 08:09

GalacticJello