-
Notifications
You must be signed in to change notification settings - Fork 197
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Include NtQueryInformationProcess function and its associated structures like PROCESS_BASIC_INFORMATION/PEB/RTL_USER_PROCESS_PARAMETERS #123
Comments
On you second question, I believe the implementation of |
Will you please reply with a list of the names of the associated structures you'd like? |
Functions Structures Enums
Well, the |
Thanks for all the links. That saved me some time implementing. It appears that there only a few of the info class values supported so I only included those and not the entire list on pinvoke.net. Please see the unit test for use. This will go out in the next release, but feel free to pull the interim NuGet package from AppVeyor (see home page for link and details). |
Not too sure whether I should make a new issue or not, but here I go ... I've made this method below from the nuget package source, with the package version 3.2.8 linked in the home page public string GetCommandLine(Kernel32.SafeHPROCESS handle)
{
var info = NtDll.NtQueryInformationProcess<NtDll.PROCESS_BASIC_INFORMATION>(handle,
NtDll.PROCESSINFOCLASS.ProcessBasicInformation);
var pebSz = Marshal.SizeOf<NtDll.PEB>();
var pebPtr = Marshal.AllocHGlobal(pebSz);
Kernel32.ReadProcessMemory(handle, info.AsRef().PebBaseAddress, pebPtr, pebSz, out var pebSzRead).CheckValid();
var peb = Marshal.PtrToStructure<NtDll.PEB>(pebPtr);
Marshal.FreeHGlobal(pebPtr);
var rtlUserParamsSz = Marshal.SizeOf<NtDll.RTL_USER_PROCESS_PARAMETERS>();
var rtlUserParamsPtr = Marshal.AllocHGlobal(rtlUserParamsSz);
Kernel32.ReadProcessMemory(handle, peb.ProcessParameters, rtlUserParamsPtr, rtlUserParamsSz,
out var rtlUserParamsRead).CheckValid();
/* errors out below with ExecutionEngineException*/
var rtlUserParams = Marshal.PtrToStructure<NtDll.RTL_USER_PROCESS_PARAMETERS>(rtlUserParamsPtr);
Marshal.FreeHGlobal(rtlUserParamsPtr);
return rtlUserParams.CommandLine.Buffer; // lets test if this works
} The last Looking at the source of the structure of /// <summary>
/// <para>[This structure may be altered in future versions of Windows.]</para>
/// <para>Contains process parameter information.</para>
/// </summary>
[PInvokeData("winternl.h", MSDNShortId = "e736aefa-9945-4526-84d8-adb6e82b9991")]
public struct RTL_USER_PROCESS_PARAMETERS
{
/// <summary>Reserved for internal use by the operating system.</summary>
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
private readonly byte[] Reserved1;
/// <summary>Reserved for internal use by the operating system.</summary>
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)]
private readonly IntPtr[] Reserved2;
/// <summary>The path of the image file for the process.</summary>
public NtDll.UNICODE_STRING ImagePathName;
/// <summary>The command-line string passed to the process.</summary>
public NtDll.UNICODE_STRING CommandLine;
} it has the For your information, I'm using this code in |
I put a link in my last post to the unit test. It gets the command line successfully. |
But for reading the command line from another process, I have to use public string GetCommandLine(Kernel32.SafeHPROCESS handle)
{
var info = NtDll.NtQueryInformationProcess<NtDll.PROCESS_BASIC_INFORMATION>(handle,
NtDll.PROCESSINFOCLASS.ProcessBasicInformation);
var pebSz = Marshal.SizeOf<NtDll.PEB>();
var pebPtr = Marshal.AllocHGlobal(pebSz);
Kernel32.ReadProcessMemory(handle, info.AsRef().PebBaseAddress, pebPtr, pebSz, out var pebSzRead).CheckValid();
var peb = Marshal.PtrToStructure<NtDll.PEB>(pebPtr);
Marshal.FreeHGlobal(pebPtr);
var rtlUserParamsSz = Marshal.SizeOf<NtDll.RTL_USER_PROCESS_PARAMETERS>();
var rtlUserParamsPtr = Marshal.AllocHGlobal(rtlUserParamsSz);
Kernel32.ReadProcessMemory(handle, peb.ProcessParameters, rtlUserParamsPtr, rtlUserParamsSz,
out var rtlUserParamsRead).CheckValid();
/* errors out below with ExecutionEngineException*/
var rtlUserParams = Marshal.PtrToStructure<NtDll.RTL_USER_PROCESS_PARAMETERS>(rtlUserParamsPtr);
Marshal.FreeHGlobal(rtlUserParamsPtr);
return rtlUserParams.CommandLine.Buffer; // lets test if this works
} but like I mentioned in an above reply, gets me |
I ran the following test without errors, but with bad strings: [Test]
public void GetCommandLineTest()
{
var randProc = System.Diagnostics.Process.GetProcesses().Where(p => p.ProcessName.StartsWith("devenv")).First();
using var hProc = Kernel32.OpenProcess((uint)(Kernel32.ProcessAccess.PROCESS_QUERY_INFORMATION | Kernel32.ProcessAccess.PROCESS_VM_READ), false, (uint)randProc.Id);
Assert.That(hProc, ResultIs.ValidHandle);
NtQueryResult<PROCESS_BASIC_INFORMATION> info = null;
Assert.That(() => info = NtQueryInformationProcess<PROCESS_BASIC_INFORMATION>(hProc, PROCESSINFOCLASS.ProcessBasicInformation), Throws.Nothing);
Assert.That(info, Is.Not.Null);
Assert.That(info.AsRef().PebBaseAddress, Is.Not.EqualTo(IntPtr.Zero));
using var pebPtr = new SafeHGlobalStruct<PEB>();
Assert.That(Kernel32.ReadProcessMemory(hProc, info.AsRef().PebBaseAddress, pebPtr, pebPtr.Size, out var pebSzRead), ResultIs.Successful);
Assert.That(pebSzRead, Is.LessThanOrEqualTo(pebPtr.Size));
using var rtlUserParamsPtr = new SafeHGlobalStruct<RTL_USER_PROCESS_PARAMETERS>();
Assert.That(Kernel32.ReadProcessMemory(hProc, pebPtr.Value.ProcessParameters, rtlUserParamsPtr, rtlUserParamsPtr.Size, out var rtlUserParamsRead), ResultIs.Successful);
Assert.That(rtlUserParamsRead, Is.LessThanOrEqualTo(rtlUserParamsPtr.Size));
var rtlUser = rtlUserParamsPtr.Value;
Assert.That(rtlUser.ImagePathName.Length, Is.GreaterThan(0));
StringAssert.StartsWith("C:\\", rtlUser.ImagePathName.ToString());
TestContext.WriteLine($"Img: {rtlUser.ImagePathName}; CmdLine: {rtlUser.CommandLine}");
} I then searched for a reason and found this on StackOverflow. It appears this is not a reliable approach as the memory can change. |
Hi, can I know what platform that test is run on, and the output of the test? What do you mean by 'bad strings?' |
Win10 1903 with Admin privileges. Test compiled as |
I believe that The Marshaller has no idea that the UNICODE_STRING struct was actually read from other processes' memory. I think we need to set |
… boundries (thanks @Enigmatrix) and tested. Now #123 is implemented.
Great suggestion. It is now working. See unit test for code. |
Hi @dahall, @Enigmatrix ! I'm trying to use the same code as the one in your unit test but I'm always getting a string.Empty result when trying to get the command line:
Do yo have any ideads what could be wrong ? I'm using Win10 20H2, the code is running in AnyCPU / Debug Thanks ! |
@ThomasLebrun When you debug, which of the four failure conditions is returning |
@ThomasLebrun I ran the following unit test, which I adapted from your code, with success. I ran this with elevated privileges. Process proc = null;
try
{
proc = Process.Start("notepad.exe", TestCaseSources.LogFile);
System.Threading.Thread.Sleep(500);
HWND hWnd = proc.MainWindowHandle;
unsafe
{
using var processInfos = NtQueryInformationProcess<PROCESS_BASIC_INFORMATION>(proc.Handle, PROCESSINFOCLASS.ProcessBasicInformation);
Assert.That(processInfos, ResultIs.ValidHandle);
Assert.That(((PROCESS_BASIC_INFORMATION*)processInfos)->PebBaseAddress, Is.Not.EqualTo(IntPtr.Zero));
using var pebPtr = new SafeHGlobalStruct<PEB>();
Assert.That(Kernel32.ReadProcessMemory(proc.Handle, ((PROCESS_BASIC_INFORMATION*)processInfos)->PebBaseAddress, pebPtr, pebPtr.Size, out _), ResultIs.Successful);
using var processParameters = new SafeHGlobalStruct<RTL_USER_PROCESS_PARAMETERS>();
Assert.That(Kernel32.ReadProcessMemory(proc.Handle, pebPtr.Value.ProcessParameters, processParameters, processParameters.Size, out _), ResultIs.Successful);
var parameters = processParameters.Value;
Assert.That(GetString(parameters.ImagePathName, proc.Handle), Is.Not.Empty);
Assert.That(GetString(parameters.CommandLine, proc.Handle), Is.Not.Empty);
}
}
finally
{
proc?.CloseMainWindow();
} |
@dahall I'm giving a try to your solution => do you have the definition of the GetString method you're using ? |
None of the conditions where returning string.Empty: it was the GetString method |
public static string GetString(in UNICODE_STRING us, HPROCESS hProc)
{
using var mem = new SafeCoTaskMemString(us.MaximumLength);
return ReadProcessMemory(hProc, us.Buffer, mem, mem.Size, out _) ? mem : string.Empty;
} |
Here is the code I'm using: `unsafe
|
Using the code available here (https://stackoverflow.com/a/16142791/1438337), I get the correct CommandLine. I've noticed there is a difference between this code and your: there is an offset when reading PebBaseAddress and when getting UNICODE_STRING_WOW64 / RTL_USER_PROCESS_PARAMETERS (offseet with different values according to x32/x64): maybe that could be the issue ? I would like, if possible, not having to do the P/Invoke import on my side and rely on your lib instead :) |
Thank you for the link. After reviewing and trying to fix, I have a stronger desire to avoid NtDll. :) Effectively, the problem comes when running as a 64-bit process and trying to query information from a 32-bit process or running as a 32-bit process and trying to query information from a 64-bit process. If they match, then the code I've published works fine. So, what I have done is added xx_WOW64 structures to address the latter and throw an exception for the former. I've added a function to Vanara.PInvoke.NtDll called string imgName = null;
if (NtQueryInformationProcessRequiresWow64Structs(hProcToQuery))
{
var pfn = NtQueryInformationProcess<UNICODE_STRING_WOW64>(hProc, PROCESSINFOCLASS.ProcessImageFileName);
imgName = pfn.Value.ToString(hProcToQuery);
}
else
{
var pfn = NtQueryInformationProcess<UNICODE_STRING>(hProc, PROCESSINFOCLASS.ProcessImageFileName);
imgName = pfn.Value.ToString(hProcToQuery);
} This is all the work I'm willing to do on this. These old libs that were written in 32-bit days and forced into the 64-bit world are just a mess. If you decide to do further work, I'm glad to include via a PR. |
Is your feature request related to a problem? Please describe.
I would request that we include NtQueryInformationProcess into Vanara.PInvoke.NTDll, and also put the associated structures for the function in suitable locations.
On a side note, given a
UNICODE_STRING
from the Vanara library, can I read its buffer contents from another process in an idiomatic way?The text was updated successfully, but these errors were encountered: