Find out whether a Process is a System Process - c#

I am trying to find out what programs a user is running while my program is running and output them to a file. Now I'm facing the situation that when retrieving all Processes using Process.GetProcesses() I'm greeted with a list of about 269 processes which amounts to about all the Task Manager is showing, including Windows Processes like 77 svchost processes.
Now I want to filter out some system processes (At least those displayed as "Windows-Processes" in the Task Manager). Is there any way to do this or will I have to maintain a list of process names (or file directories) of all Windows Processes?

Short answer:
The solution within the taskmanager is hard coded based on the following list (taken from the Windows 10 version):
%windir%\explorer.exe
%windir%\system32\ntoskrnl.exe
%windir%\system32\WerFault.exe
%windir%\system32\backgroundTaskHost.exe
%windir%\system32\backgroundTransferHost.exe
%windir%\system32\winlogon.exe
%windir%\system32\wininit.exe
%windir%\system32\csrss.exe
%windir%\system32\lsass.exe
%windir%\system32\smss.exe
%windir%\system32\services.exe
%windir%\system32\taskeng.exe
%windir%\system32\taskhost.exe
%windir%\system32\dwm.exe
%windir%\system32\conhost.exe
%windir%\system32\svchost.exe
%windir%\system32\sihost.exe
Long answer:
It took some time to get to that list - below is the path to enlightenment ;-)
Original answer:
To answer your question Find out whether a Process is a System Process is not as easy as it seems. In order to get this information you have to get the owner of the process which on windows systems is typically realted to Security identifiers.
A security identifier (SID) is a unique value of variable length used to identify a trustee. Each account has a unique SID issued by an authority, such as a Windows domain controller, and stored in a security database. Each time a user logs on, the system retrieves the SID for that user from the database and places it in the access token for that user. The system uses the SID in the access token to identify the user in all subsequent interactions with Windows security. When a SID has been used as the unique identifier for a user or group, it cannot ever be used again to identify another user or group.
You will have seen one of those for sure, it is something like S-1-5-18 or S-1-5-21-2557247...-...-...-1001.
There is a complete list of WellKnown SIDs which also includes a bunch of SIDs you would probably all consider as System Process-related.
If I am right in my assumption, you want to get all processes that are running under the local system account which would be S-1-5-18.
Stop talking, let's code:
First of all we (which is you, I have already tested it ;-) ) need to import GetSecurityInfo from advapi32.dll like this:
[DllImport("advapi32.dll", SetLastError = true)]
private static extern uint GetSecurityInfo(IntPtr handle,
SE_OBJECT_TYPE objectType,
SECURITY_INFORMATION securityInfo,
out IntPtr sidOwner,
out IntPtr sidGroup,
out IntPtr dacl,
out IntPtr sacl,
out IntPtr securityDescriptor);
...which requires two enumerations for SE_OBJECT_TYPE and SECURITY_INFORMATION to be defined like this:
private enum SE_OBJECT_TYPE
{
SE_UNKNOWN_OBJECT_TYPE,
SE_FILE_OBJECT,
SE_SERVICE,
SE_PRINTER,
SE_REGISTRY_KEY,
SE_LMSHARE,
SE_KERNEL_OBJECT,
SE_WINDOW_OBJECT,
SE_DS_OBJECT,
SE_DS_OBJECT_ALL,
SE_PROVIDER_DEFINED_OBJECT,
SE_WMIGUID_OBJECT,
SE_REGISTRY_WOW64_32KEY
}
private enum SECURITY_INFORMATION
{
OWNER_SECURITY_INFORMATION = 1,
GROUP_SECURITY_INFORMATION = 2,
DACL_SECURITY_INFORMATION = 4,
SACL_SECURITY_INFORMATION = 8,
}
Now we are almost there. If you call GetSecurityInfo in the following manner...
uint returnValue = GetSecurityInfo(process.Handle,
SE_OBJECT_TYPE.SE_KERNEL_OBJECT,
SECURITY_INFORMATION.OWNER_SECURITY_INFORMATION,
out IntPtr ownerSid,
out IntPtr groupSid,
out IntPtr dacl,
out IntPtr sacl,
out IntPtr securityDescriptor);
... and get ERROR_SUCESS as a result (which is 0), you can use an instance of the SecurityIdentifier class to check whether the retrieved SID is the local system account or not, like this:
SecurityIdentifier securityIdentifier = new SecurityIdentifier(ownerSid);
if (securityIdentifier.IsWellKnown(WellKnownSidType.LocalSystemSid))
{
// The process is running unter the local system account.
}
That's it.
To achieve the final result you will have to check for multiple SIDs like System, Local service, Network service and so on...
Here is a small example, that does this for all processes on the local machine.
You will need to run this with the right priviledges of course, otherwise you will get access denied errors.
private static void Main(string[] args)
{
const uint ERROR_SUCCESS = 0;
Process[] processes = Process.GetProcesses();
foreach (Process process in processes)
{
try
{
uint returnValue = GetSecurityInfo(process.Handle,
SE_OBJECT_TYPE.SE_KERNEL_OBJECT,
SECURITY_INFORMATION.OWNER_SECURITY_INFORMATION,
out IntPtr ownerSid,
out IntPtr groupSid,
out IntPtr dacl,
out IntPtr sacl,
out IntPtr securityDescriptor);
if (returnValue != ERROR_SUCCESS)
{
// If the function succeeds, the return value is ERROR_SUCCESS.
// If the function fails, the return value is a nonzero error code defined in WinError.h.
continue;
}
SecurityIdentifier securityIdentifier = new SecurityIdentifier(ownerSid);
Console.WriteLine("Owner of process {0} is {1}", process.ProcessName, securityIdentifier);
if (securityIdentifier.IsWellKnown(WellKnownSidType.LocalSystemSid))
{
Console.WriteLine("Running under System Account");
}
}
catch (Exception e)
{
Console.WriteLine("Unable to retrieve owner for process {0}: {1}", process.ProcessName, e.Message);
}
}
Update:
If you compare the result (of the original answer) with the list of processes in the task manager, there is still a discrepancy. As I investigated this issue further, I came accross an article that states, that processes which are marked as critical, will be shown under windows processes.
If the process has a visible window, then Task Manager calls it an "App".
If the process is marked as critical, then Task Manager calls it a "Windows Process".
Otherwise, Task Manager calls it a "Background Process".
This can be evaluated by simply calling IsProcessCritical. Therefore an DllImport is needed...
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool IsProcessCritical(IntPtr hProcess, ref bool Critical);
...afterwards it can be called like this:
bool criticalProcess = false;
if (!IsProcessCritical(process.Handle, ref criticalProcess))
{
// Could not retrieve process information
}
if (criticalProcess)
{
// This is a critical process, it should be listed
// in the "Windows processes" section.
}
Although this sounds promising, it is not - it still leads to incorrect results.
So after installing API Monitor (which is an incredible piece of software by the way) and filtering and searching through more than 5 millions of (already pre-filtered) api calls, I noticed, that Taskmgr.exe calls ExpandEnvironmentString multiple times with arguments, that are seemingly not retrieved prior to the calls.
After further investigation (and logical conclusion) I noticed, that there is a hard coded list embedded within Taskmgr.exe. It can be simply found by using the Process explorer:
Starting the process explorer
Right-click on Taskmgr.exe
Navigating to the strings tab
Scrolling down
Being disappointed
There are the following entries:
%windir%\explorer.exe
%windir%\system32\ntoskrnl.exe
%windir%\system32\WerFault.exe
%windir%\system32\backgroundTaskHost.exe
%windir%\system32\backgroundTransferHost.exe
%windir%\system32\winlogon.exe
%windir%\system32\wininit.exe
%windir%\system32\csrss.exe
%windir%\system32\lsass.exe
%windir%\system32\smss.exe
%windir%\system32\services.exe
%windir%\system32\taskeng.exe
%windir%\system32\taskhost.exe
%windir%\system32\dwm.exe
%windir%\system32\conhost.exe
%windir%\system32\svchost.exe
%windir%\system32\sihost.exe
So my conclusion is:The solution within the taskmanager is hard coded based on the above list (taken from the Windows 10 version).

One way to do this is by filtering out all processes whose path starts with the path of the windows directory.
You can get the path of the windows directory by calling Environment.GetFolderPath
with Environment.SpecialFolder.Windows like so:
var windowsPath = Environment.GetFolderPath(Environment.SpecialFolder.Windows);
And then you can filter out all processes whose image is located somewhere in that folder:
var processes = Process.GetProcesses();
foreach (var process in processes) {
if (!process.MainModule.FileName.StartsWith(windowsPath)) {
// Do something with process
}
}

Just filter the result:
Process.GetProcesses().Where(x => x.MainWindowHandle != IntPtr.Zero)
Checking the path, could be bypassed

Related

GetGuiResources returns 0 (error 87) or nonsense value

I'm currently coding a windows service (installed as LocalSystem) that monitors several things on a pc/server including processes. For the processes, I'm watching the memory usage and also "try" to get the number of GDI Objects per process (as can be seen in task manager).
Sadly, C# Process objects don't have the gdi count built-in so I'm using the GetGuiResources method from 'user32.dll' as shown in this example:
https://www.pinvoke.net/default.aspx/user32.getguiresources.
Basically I have a list of executable names, for each of them I use GetProcessesByName to retrieve all process instances, and then for each unique process I take the handle and send it to the function to get the Gdi objects count back.
When I try this on my local machine as a simple console app (feeding a name through Console.ReadLine), it works no problem as long as the console app is launched as administrator; i get the same numbers as task manager.
However, when the monitoring service calls this function, I get either 0s (returning error code 87) or worse: processes tied to services (no gui) return me some random numbers (12, 7, 4, etc.) when the task manager actually shows 0 (and last error = 0).
So in summary, every process that shows some GID objects in Task Manager returns 0 (error 87), and each process who has 0 returns me a number (no error, or error 183 for the monitoring service itself).
I've tried this on Windows 10, Windows Server 2012, Windows Server 2008, Windows Server 2003, Windows Server 2016. On windows 10 (my machine) I get 0s everywhere, on other OS I get the mentionned results.
Here's a shortened version of the code I use:
// Monitoring processes exeName example: ssms, sqlbrowser
List<Process> result = Process.GetProcessesByName(exeName).ToList();
if (processes != null)
{
for (int i = 0; i < processes.Count; i++)
{
int gdiCount = processes[i].GetGDIObjectsCount(); // extension method
// logging and doing stuff with gdi count here (but i get 0s or random numbers as I told)
}
}
// Process extension method
public static class CProcessExtensions
{
[DllImport("User32", SetLastError = true)]
extern private static int GetGuiResources(IntPtr hProcess, int uiFlags);
private static int GetGDICount(IntPtr processHandle)
{
if (processHandle == IntPtr.Zero)
{
return -1;
}
int count = GetGuiResources(processHandle, 0);
// Logging Marshal.GetLastWin32Error() here
return count;
}
public static int GetGDIObjectsCount(this Process process)
{
IntPtr handle;
process.Refresh();
try
{
handle = process.Handle;
}
catch (Exception ex)
{
handle = IntPtr.Zero;
}
return GetGDICount(handle);
}
}
I've also tried getting the process handles with the OpenProcess dll method but had the same results.
Anyone faced this kind of problem before?
So, thanks to Jeremy Thompson's comment leading me to info about the session 0, and with further research, I was able to solve my problem.
References:
Application Loader to launch process in another session
Wait for process exit (ProcessWaitHandle)
Get Exit code
What I did is modify the sample code from the first reference to provide a process ID (the one I want the GDI objects count of) and launch my little console app (which takes the same process ID also, and returns the GDI count as exit code) in the same session by duplicating the token of the provided process and call CreateProcessAsUser.
By launching the console app in the same session I was able to retrieve the correct info on GDI objects on every OS I previously tested except Win Server 2003, which I can totally live without.

How to start a new Windows logon session (RDP or console) programmatically

I've been banging my head against this for several hours, so I figured it's time to ask. I'll start with a high-level description of the situation. You can find the entire source code at https://github.com/Jay-Rad/InstaTech_Client. This question only pertains to the project in "/InstaTech_Service/".
Overview
The InstaTech client is a remote control app that uses websockets and makes an outbound connection to an ASP.NET server for relay with the viewer. I have different versions, but they all function roughly the same (the Electron version tries WebRTC first before using raw websockets). The viewer portion of the app is web-based, and a demo can be found here: https://instatech.org/Demo/Remote_Control
The WPF (C#) and Electron versions present a GUI with a random ID that they must provide to the person remoting into their computer (similar to TeamViewer). Once a session is started, they capture the screen in different ways. For C#, I'm using a pinvoke to BitBlt to copy the image to an in-memory graphic, which is then sent through the websocket. Subsequent screen captures are compared to the previous one to create a box that encompasses the changed pixels, then that cropped section is sent. Mouse and keyboard inputs are received by the client and executed via pinvoke to keybd_event and mouse_event. These are working great.
The service I created works in similar fashion, but here are the differences. The service itself runs in session 0 under System account. It connects to the server and listens on the websocket. When a connection is made and screen viewing is requested, it launches a separate interactive process in the user's session in WinSta0\Default. Once the new process's websocket is connected, the server begins relaying messages between it and the viewer instead of the service and the viewer.
Although the new process is launched interactively in the user's session, it's running under the System account. This is achieved by a pinvoke to CreateProcessAsUser and duplicating the winlogon.exe access token.
The Problem
This solution works fine if someone is already logged in, even if via RDP. However, if nobody is logged in or the computer gets locked, I can't interact with the logon screen. When doing the screen capture, I'm detecting if the capture fails, which would mean the WinSta0\Default desktop is no longer active. Since I'm using CreateProcessAsUser, I can switch desktops to the WinSta0\Winlogon just fine. I can still see it (even if no one is logged in), but it won't take any inputs. I understand that this is by design for security reasons. Well, strangely, some mouse movements "slip through" if I'm moving it around and cause the cursor to reposition, but the rest get sent to the Default desktop and execute once logged back in.
So the problem is that I can't get an account logged into the computer with this setup. If it matters, I don't care to interact with the Winlogon desktop if someone else is already logged in and locked the computer. I only want to be able to log in if no one else is using it, or it's my account that's logged in and at the lock screen.
Attempted Solutions
I'm assuming that there's no way to circumvent the inability to send simulated inputs to the Winlogon desktop. (Correction: That is, using mouse_event and keybd_event functions. I've seen other applications do it, like TeamViewer and Microsoft SCCM Remote Control. I'm not sure how they do it, though.) If it is somehow possible, I think that'd be the most direct route. But here are some things I've looked into that focus on getting a new logon session started.
Pinvoke to LsaLogonUser. I'm not sure if this would accomplish what I'm after, but I tried anyway. However, even though the call to LsaLogonUser reports success, the handle I'm getting from LsaRegisterLogonProcess (out to lsaHan) is 0. I'm not sure what I'm doing wrong. I'm not too familiar with Win32 calls and trying to pick it up as I go. Maybe the calling process doesn't have the necessary rights. I've tried calling this from the service in session 0 and from the process running in the interactive session. An example of what I'm doing is below.
Microsoft Terminal Services Active Client COM library. I haven't dug too deeply into this, but I wonder if it might be possible to use this to initiate an RDP logon session. Once an RDP logon session is made, spawn a new InstaTech process in that session and connect to it. I doubt this would work if the RDP connection is being attempted from the same computer, though.
Credential Provider. I came across credential providers while researching. I'm not sure if creating one would solve the problem, but it sounds like it'd be a terribly complicated undertaking.
Does anyone have any suggestions? Or am I missing something entirely?
If you'd like to recompile the service and test things, I created a temporary admin account on the server. Any computer with the service installed will show up there, and you can log in using this account. Please keep in mind that anyone reading this post will be able to access any computers running the service, so make sure it's in an isolated environment.
Username: admin
Password: plzh#lpm3purdyplz
The service is self-installing. Pass the -install switch to install, -uninstall to uninstall. The EXE is copied to %programdata%\InstaTech, and the service starts it from there.
Thank you!
Reference Code
public static void CreateNewSession()
{
var kli = new SECUR32.KERB_INTERACTIVE_LOGON()
{
MessageType = SECUR32.KERB_LOGON_SUBMIT_TYPE.KerbInteractiveLogon,
UserName = "myusername#someplace.com",
Password = "superencryptedstring"
};
IntPtr pluid;
IntPtr lsaHan;
ulong secMode;
uint authPackID;
IntPtr kerbLogInfo;
SECUR32.LSA_STRING logonProc = new SECUR32.LSA_STRING()
{
Buffer = Marshal.StringToHGlobalAuto("InstaLogon"),
Length = (ushort)Marshal.SizeOf(Marshal.StringToHGlobalAuto("InstaLogon")),
MaximumLength = (ushort)Marshal.SizeOf(Marshal.StringToHGlobalAuto("InstaLogon"))
};
SECUR32.LSA_STRING originName = new SECUR32.LSA_STRING()
{
Buffer = Marshal.StringToHGlobalAuto("InstaLogon"),
Length = (ushort)Marshal.SizeOf(Marshal.StringToHGlobalAuto("InstaLogon")),
MaximumLength = (ushort)Marshal.SizeOf(Marshal.StringToHGlobalAuto("InstaLogon"))
};
SECUR32.LSA_STRING authPackage = new SECUR32.LSA_STRING()
{
Buffer = Marshal.StringToHGlobalAuto("MICROSOFT_KERBEROS_NAME_A"),
Length = (ushort)Marshal.SizeOf(Marshal.StringToHGlobalAuto("MICROSOFT_KERBEROS_NAME_A")),
MaximumLength = (ushort)Marshal.SizeOf(Marshal.StringToHGlobalAuto("MICROSOFT_KERBEROS_NAME_A"))
};
IntPtr hLogonProc = Marshal.AllocHGlobal(Marshal.SizeOf(logonProc));
Marshal.StructureToPtr(logonProc, hLogonProc, false);
ADVAPI32.AllocateLocallyUniqueId(out pluid);
SECUR32.LsaRegisterLogonProcess(hLogonProc, out lsaHan, out secMode);
SECUR32.LsaLookupAuthenticationPackage(lsaHan, ref authPackage, out authPackID);
kerbLogInfo = Marshal.AllocHGlobal(Marshal.SizeOf(kli));
Marshal.StructureToPtr(kli, kerbLogInfo, false);
var ts = new SECUR32.TOKEN_SOURCE("Insta");
IntPtr profBuf;
uint profBufLen;
long logonID;
IntPtr logonToken;
SECUR32.QUOTA_LIMITS quotas;
SECUR32.WinStatusCodes subStatus;
SECUR32.LsaLogonUser(lsaHan, ref originName, SECUR32.SecurityLogonType.Interactive, authPackID, kerbLogInfo, (uint)Marshal.SizeOf(kerbLogInfo), IntPtr.Zero, ref ts, out profBuf, out profBufLen, out logonID, out logonToken, out quotas, out subStatus);
}
This is the method that the service in session 0 is using to launch another instance in the interactive session. I got most of this from this article: https://www.codeproject.com/kb/vista-security/subvertingvistauac.aspx. I only added the RDP session lookup.
public static bool OpenProcessAsSystem(string applicationName, out PROCESS_INFORMATION procInfo)
{
try
{
uint winlogonPid = 0;
IntPtr hUserTokenDup = IntPtr.Zero, hPToken = IntPtr.Zero, hProcess = IntPtr.Zero;
procInfo = new PROCESS_INFORMATION();
// Obtain session ID for active session.
uint dwSessionId = Kernel32.WTSGetActiveConsoleSessionId();
// Check for RDP session. If active, use that session ID instead.
var rdpSessionID = GetRDPSession();
if (rdpSessionID > 0)
{
dwSessionId = rdpSessionID;
}
// Obtain the process ID of the winlogon process that is running within the currently active session.
Process[] processes = Process.GetProcessesByName("winlogon");
foreach (Process p in processes)
{
if ((uint)p.SessionId == dwSessionId)
{
winlogonPid = (uint)p.Id;
}
}
// Obtain a handle to the winlogon process.
hProcess = Kernel32.OpenProcess(MAXIMUM_ALLOWED, false, winlogonPid);
// Obtain a handle to the access token of the winlogon process.
if (!OpenProcessToken(hProcess, TOKEN_DUPLICATE, ref hPToken))
{
Kernel32.CloseHandle(hProcess);
return false;
}
// Security attibute structure used in DuplicateTokenEx and CreateProcessAsUser.
SECURITY_ATTRIBUTES sa = new SECURITY_ATTRIBUTES();
sa.Length = Marshal.SizeOf(sa);
// Copy the access token of the winlogon process; the newly created token will be a primary token.
if (!DuplicateTokenEx(hPToken, MAXIMUM_ALLOWED, ref sa, (int)SECURITY_IMPERSONATION_LEVEL.SecurityIdentification, (int)TOKEN_TYPE.TokenPrimary, ref hUserTokenDup))
{
Kernel32.CloseHandle(hProcess);
Kernel32.CloseHandle(hPToken);
return false;
}
// By default, CreateProcessAsUser creates a process on a non-interactive window station, meaning
// the window station has a desktop that is invisible and the process is incapable of receiving
// user input. To remedy this we set the lpDesktop parameter to indicate we want to enable user
// interaction with the new process.
STARTUPINFO si = new STARTUPINFO();
si.cb = (int)Marshal.SizeOf(si);
si.lpDesktop = #"winsta0\default"; // interactive window station parameter; basically this indicates that the process created can display a GUI on the desktop
// flags that specify the priority and creation method of the process
uint dwCreationFlags = NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE;
// create a new process in the current user's logon session
bool result = CreateProcessAsUser(hUserTokenDup, // client's access token
null, // file to execute
applicationName, // command line
ref sa, // pointer to process SECURITY_ATTRIBUTES
ref sa, // pointer to thread SECURITY_ATTRIBUTES
false, // handles are not inheritable
dwCreationFlags, // creation flags
IntPtr.Zero, // pointer to new environment block
null, // name of current directory
ref si, // pointer to STARTUPINFO structure
out procInfo // receives information about new process
);
// invalidate the handles
Kernel32.CloseHandle(hProcess);
Kernel32.CloseHandle(hPToken);
Kernel32.CloseHandle(hUserTokenDup);
return result;
}
catch
{
procInfo = new PROCESS_INFORMATION() { };
return false;
}
}
public static uint GetRDPSession()
{
IntPtr ppSessionInfo = IntPtr.Zero;
Int32 count = 0;
Int32 retval = WTSAPI32.WTSEnumerateSessions(WTSAPI32.WTS_CURRENT_SERVER_HANDLE, 0, 1, ref ppSessionInfo, ref count);
Int32 dataSize = Marshal.SizeOf(typeof(WTSAPI32.WTS_SESSION_INFO));
var sessList = new List<WTSAPI32.WTS_SESSION_INFO>();
Int64 current = (int)ppSessionInfo;
if (retval != 0)
{
for (int i = 0; i < count; i++)
{
WTSAPI32.WTS_SESSION_INFO sessInf = (WTSAPI32.WTS_SESSION_INFO)Marshal.PtrToStructure((System.IntPtr)current, typeof(WTSAPI32.WTS_SESSION_INFO));
current += dataSize;
sessList.Add(sessInf);
}
}
uint retVal = 0;
var rdpSession = sessList.Find(ses => ses.pWinStationName.ToLower().Contains("rdp") && ses.State == 0);
if (sessList.Exists(ses => ses.pWinStationName.ToLower().Contains("rdp") && ses.State == 0))
{
retVal = (uint)rdpSession.SessionID;
}
return retVal;
}
I got SendInput to work on the logon desktop (and, as it turns out, the UAC secure desktop). SetThreadDesktop must not give you the same privileges as if you'd initially started the process in the target desktop.
So when I detected a desktop change, instead of calling SetThreadDesktop, I launched yet another process in the new desktop with CreateProcessAsUser. Then I signaled for the viewer to switch and closed the current process.
Edit (years later): I ended up being wrong about this. You just need ensure your current thread doesn't have any open windows or hooks in the current desktop. And since this only sets the desktop for the calling thread (not the process), subsequent threads will need to call this as well.

OpenProcessToken fails when querying process running as NETWORK SERVICE

I have code which needs to query the process token (specifically the SID) of several processes, at least one of which is running as the built-in NETWORK SERVICE account. I'm using .NET to get the process ID, and then p/invoke to advapi32 methods to open the process and the token. All of this code runs as an administrator on the machine.
The issue is that I get ERROR_ACCESS_DENIED (return code 5) from OpenProcessToken every time.
What am I doing wrong? This must be possible (for example, Process Explorer shows the SID and other token information of the target process), so I figure I'm missing something. One thought is that perhaps I need SeDebugPrivilege. (EDIT: Doesn't work -- doing Process.EnterDebugMode to set SeDebugPrivilege on the current thread doesn't change behavior).
My code looks like this:
Process[] procs = Process.GetProcessesByName("processname");
Process process = procs[0];
IntPtr processHandle = NativeMethods.OpenProcess(
ProcessAccessFlags.QueryInformation,
false,
process.Id);
if (
!NativeMethods.OpenProcessToken(
processHandle,
(uint)(TokenAccessLevels.Read),
out tokenHandle))
{
int err = Marshal.GetLastWin32Error();
// fails with err = 5
throw new Win32Exception(err, "Failed to get process token. Error: " + err);
}
Note: I have tried a few other combinations of the access flags on both native calls, including using the PROCESS_ALL_ACCESS.
EDIT: One additional point, querying the same process running as a local limited-access user works fine.

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);
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.
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.
Well, this is definitely not the straightforward approach, but maybe it will help you somehow. You should be able to get the stack trace of another thread in a way used by this project (StackWalk64) and eventually see the name of desired function. It has its own problems, particularly performance of this approach probably won't be too high, but as I understood this is one-shot per thread operation. Question is, will it generally be able to properly walk the stack of your (probably optimized) applications.
First, you can't really do this reliably: if you happen to access Thread.StartAddress before the thread executes the function pointer or after the function returns, you will have no way to know what the starting function actually is.
Secondly, the more likely answer is that there isn't a direct mapping to the starting function when the thread starting function is managed.

Find if process is responding without using System.Diagnostics.Process.Responding

Good day everyone.
This problem was part of another one which it as been solved, i realized that what i thought it was the problem after all, it wasn't. Still thanks to that I've learned a couple things.
My application does loads of work with IE and from time to time, IE is redirected to a website with some bad Javascript code that ends up blocking IE interface. And consequently blocking my application too once everything on my application is running on the same Thread.
To counteract that problem, at startup my application runs a static method in another Thread that every 15 seconds does a simple check if IE is responding or not, and if IE isn't responding, he closes all its process's, liberating the lock on my application main Thread and then my application can resume its work.
To find if IE process's are responding i had a simple code like this:
bool terminate = false;
foreach (System.Diagnostics.Process exe in System.Diagnostics.Process.GetProcesses())
{
if (exe.ProcessName.StartsWith("iexplore"))
{
if (exe.Responding == false)
{
terminate = true;
break;
}
}
}
// Code to close all IE process's...
In order to the Process.Responding property finds if the process is responding, and according to information on MSDN, this property needs another property named MainWindowHandle to be available in order to complete the process of checking. And if MainWindowHandle isn't available Process.Responding always returns true even if the process isn't responding.
And for some reason which i don't know. In Windows XP MainWindowHandle isn't available there so Responding isn't accurate.
Thats why i need to know another way to find if a specific process is responding or not in Windows XP.
Any help is appreciated, thanks.
PS: If your looking for a website to freeze IE here goes: http://aboutmycollege.com/
EDIT: Following 0xA3 suggestion:
I went through all IE process's checking if they had the MainWindowHandle property, those who had that property i send they Responding property to a MessageBox and they report correctly when IE isn't responding on Windows 7 but not on XP.
I executed this code every 15 seconds:
foreach (System.Diagnostics.Process exe in System.Diagnostics.Process.GetProcesses())
{
if (exe.ProcessName.StartsWith("iexplore"))
{
if (exe.MainWindowHandle == IntPtr.Zero)
{
System.Windows.Forms.MessageBox.Show("Process doesn't have MainWindowHandle");
}
else
{
System.Windows.Forms.MessageBox.Show("Process Responding: " + exe.Responding.ToString());
}
}
}
In Windows 7 and Xp he reports the Process's of IE that don't have the MainWindowHandle property, and in Windows 7 he also reports correctly when IE isn't responding. But in XP all IE process's with MainWindowHandle are always responding even when they aren't.
IE is special because each tab has its own process plus there is an additional parent IE process. In fact, only the parent process will have a valid MainWindowHandle.
Did you check whether MainWindowHandle is null for all these processes? If it isn't I think your code should work as expected on XP as well.
Update
Since checking all IE instances didn't help, the next thing I would try is to modify the timeout that is used by Process.Responding. The property internally calls the SendMessageTimeout api function and then checks the return value whether a timeout occurred. If so, the process is assumed to be hanging. The timeout is a hard-coded value of 5 seconds.
You can call SendMessageTimeout yourself using P/Invoke and vary the timeout. Possibly a shorter value would give better results on Windows XP:
[DllImport("user32.dll", CharSet=CharSet.Auto)]
static extern IntPtr SendMessageTimeout(
HandleRef hWnd,
int msg,
IntPtr wParam,
IntPtr lParam,
int flags,
int timeout,
out IntPtr pdwResult);
const int SMTO_ABORTIFHUNG = 2;
bool IsResponding(Process process)
{
HandeRef handleRef = new HandleRef(process, process.MainWindowHandle);
int timeout = 2000;
IntPtr lpdwResult;
IntPtr lResult = SendMessageTimeout(
handleRef,
0,
IntPtr.Zero,
IntPtr.Zero,
SMTO_ABORTIFHUNG,
timeout,
out lpdwResult);
return lResult != IntPtr.Zero;
}

Categories

Resources