I am trying to read the value of ImageBaseAddress in the PEB of a process I have created using Win32 API calls in C# with P/Invoke. However the call to ReadProcessMemory returns false, indicating it has failed. Inspecting with Visual Studio debugger I see the bytes array passed to the function is populated with zeroes. However a Win32Exception isn't raised, printing Win32Exception(Marshal.GetLastWin32Error()).Message gives The operation completed successfully.
Edit:
After addressing issues highlighted in the comments that I wasn't actually retrieving the latest Win32 error, I now see that the error I am getting is Invalid handle
Below is my code, and two screenshots to help illustrate my point.
using System.ComponentModel;
namespace ReadProcess
{
class Program
{
[StructLayout(LayoutKind.Sequential)]
internal struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public int dwProcessId;
public int dwThreadId;
}
[StructLayout(LayoutKind.Sequential)]
internal struct STARTUPINFO
{
uint cb;
IntPtr lpReserved;
IntPtr lpDesktop;
IntPtr lpTitle;
uint dwX;
uint dwY;
uint dwXSize;
uint dwYSize;
uint dwXCountChars;
uint dwYCountChars;
uint dwFillAttributes;
uint dwFlags;
ushort wShowWindow;
ushort cbReserved;
IntPtr lpReserved2;
IntPtr hStdInput;
IntPtr hStdOutput;
IntPtr hStdErr;
}
[StructLayout(LayoutKind.Sequential)]
internal struct PROCESS_BASIC_INFORMATION
{
public IntPtr ExitStatus;
public IntPtr PebAddress;
public IntPtr AffinityMask;
public IntPtr BasePriority;
public IntPtr UniquePID;
public IntPtr InheritedFromUniqueProcessId;
}
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, [Out] byte[] lpBuffer, int dwSize, out IntPtr lpNumberOfBytesRead);
[DllImport("ntdll.dll", SetLastError = true)]
static extern int NtQueryInformationProcess(IntPtr processHandle, int processInformationClass, IntPtr processInformation, uint processInformationLength, IntPtr returnLength);
[DllImport("kernel32.dll")]
static extern bool CloseHandle(IntPtr hProcess);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool CreateProcess(IntPtr lpApplicationName, string lpCommandLine, IntPtr lpProcAttribs, IntPtr lpThreadAttribs, bool bInheritHandles, uint dwCreateFlags, IntPtr lpEnvironment, IntPtr lpCurrentDir, [In] ref STARTUPINFO lpStartinfo, out PROCESS_INFORMATION lpProcInformation);
public static IntPtr GetPEBAddress(IntPtr hProcess)
{
//Allocate memory for a new PROCESS_BASIC_INFORMATION structure
IntPtr pbi = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(PROCESS_BASIC_INFORMATION)));
//Allocate memory for a long
IntPtr outLong = Marshal.AllocHGlobal(sizeof(long));
IntPtr outPtr = IntPtr.Zero;
int queryStatus = 0;
//Store API call success in a boolean
queryStatus = NtQueryInformationProcess(hProcess, 0, pbi, (uint)Marshal.SizeOf(typeof(PROCESS_BASIC_INFORMATION)), outLong);
//Close handle and free allocated memory
CloseHandle(hProcess);
Marshal.FreeHGlobal(outLong);
//STATUS_SUCCESS = 0, so if API call was successful querySuccess should contain 0 ergo we reverse the check.
if (queryStatus == 0)
outPtr = Marshal.PtrToStructure<PROCESS_BASIC_INFORMATION>(pbi).PebAddress;
//Free allocated space
Marshal.FreeHGlobal(pbi);
//Return pointer to PEB base address
return outPtr;
}
static void Main(string[] args)
{
STARTUPINFO startInfo = new STARTUPINFO();
PROCESS_INFORMATION procInfo = new PROCESS_INFORMATION();
CreateProcess((IntPtr)0, "notepad", (IntPtr)0, (IntPtr)0, false, 0x00000004, (IntPtr)0, (IntPtr)0, ref startInfo, out procInfo);
byte[] ImageBaseAddress = new byte[IntPtr.Size];
IntPtr lpNumberOfBytesRead;
IntPtr pPEB = GetPEBAddress(procInfo.hProcess);
ReadProcessMemory(procInfo.hProcess, pPEB + 16, ImageBaseAddress, 8, out lpNumberOfBytesRead);
string errorMessage = new Win32Exception(Marshal.GetLastWin32Error()).Message;
Console.WriteLine("File handle: 0x{0:X16}", procInfo.hProcess);
Console.WriteLine("PEB base address: 0x{0:X16}", pPEB);
Console.WriteLine("Last Win32 Error: {0}", errorMessage);
}
}
}
Here is the output of the program compared with the output of !peb in a WinDBG session that is attached to the notepad process.
Here is a screenshot of Visual Studio debugger, the breakpoint is on the Console.WriteLine call, after ReadProcessMemory has been called. (edit: Win32 error is now outdated but this screenshot still highlights that I am correctly identifying the PEB base address).
You have many glaring errors in your PInvoke code.
Your primary issue: You cannot see the error code, as you are not checking GetLastWin32Error immediately after the call. It cannot wait as it gets overwritten quickly by other calls.
Furthermore, NtQueryInformationProcess doesn't use SetLastError at all, it returns a NTSTATUS. You can find the full enum list here.
Ideally you should force Unicode using CreateProcessW and CharSet.Unicode. Also use SafeFileHandle for the process and thread handles, and use using rather than CloseHandle to dispose them.
But in this case I recommend you just create the process using Process.Create. Then you don't need to manage that handle, you just need to put the Process into a using.
The call to ReadProcessMemory should use IntPtr.Size to match the buffer array.
Do not attempt manual marshalling unless you know what you are doing. You have memory leaks all over, and none of it is necessary as you can just pass in string and ref parameters where necessary.
Some IntPtr and uint types are wrong also.
[StructLayout(LayoutKind.Sequential)]
internal struct PROCESS_BASIC_INFORMATION
{
public int ExitStatus;
public IntPtr PebAddress;
public IntPtr AffinityMask;
public int BasePriority;
public IntPtr UniquePID;
public IntPtr InheritedFromUniqueProcessId;
}
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool ReadProcessMemory (
IntPtr hProcess,
IntPtr lpBaseAddress,
[Out] byte[] lpBuffer,
int dwSize,
out IntPtr lpNumberOfBytesRead);
[DllImport("ntdll.dll", SetLastError = false)]
static extern NtStatus NtQueryInformationProcess(
IntPtr processHandle,
int processInformationClass,
out PROCESS_BASIC_INFORMATION processInformation,
int processInformationLength,
out int returnLength);
public static IntPtr GetPEBAddress(SafeFileHandle hProcess)
{
var result = NtQueryInformationProcess(hProcess, 0, out var pbi, Marshal.SizeOf<PROCESS_BASIC_INFORMATION>(), out var outLong);
if(result != NtStatus.Success)
throw new Exception(result.ToString());
return pbi.PebAddress;
}
static void Main(string[] args)
{
try
{
using (var proc = Process.Start("notepad.exe"))
{
byte[] ImageBaseAddress = new byte[IntPtr.Size];
IntPtr pPEB = GetPEBAddress(proc.Handle);
if(!ReadProcessMemory(proc.Handle, pPEB + 16, ImageBaseAddress, IntPtr.Size, out var lpNumberOfBytesRead))
throw new Win32Exception(Marshal.GetLastWin32Error());
Console.WriteLine("PEB base address: 0x{0:X16}", pPEB);
// use ImageBaseAddress here ??
}
}
catch(Exception ex)
{
Console.WriteLine("Last Win32 Error: {0}", ex.Message);
}
}
I must say, this whole thing seems rather suspect, as you appear to be trying to read the PEB block. But the PEB block can and does change between different versions of Windows. In particular, offset 16 does not appear to line up with anything either in x64 or x86.
Related
I am trying to write C# code that, running in an elevated process, creates a non-elevated process. The (only) answer to the SO question How to call CreateProcess() with STARTUPINFOEX from C# and re-parent the child contains ready-to-use code. I copied this code, but instead of creating a process, it throws an AccessViolationException. The exception text Attempted to read or write protected memory. This is often an indication that other memory is corrupt. does not help to identify the culprit, however from low-level debugging I can see that the processor is trying to read from memory at a very small address like 0x00000004 which, of course, goes wrong.
The code, briefly explained, retrieves a handle of the desktop process, then calls InitializeProcThreadAttributeList and UpdateProcThreadAttribute to initialize the respective fields of a STARTUPINFOEX struct which is then passed into the CreateProcess Windows API function. CreateProcess should then create the new process as a child of the desktop process.
This function expects in its 9th parameter a pointer to either a STARTUPINFO or a STARTUPINFOEX (which contains a STATUPINFO at its begin). If it's a STARTUPINFOEX, the 6th parameter should contain the EXTENDED_STARTUPINFO_PRESENT flag.
When I don't pass the EXTENDED_STARTUPINFO_PRESENT flag so that CreateProcess think it's being passed just a STARTUPINFO, all works fine except that the created process is elevated (as is the calling process). However, as soon as I add this flag, the access violation occurs. For hours I have tried modifying parameter attributes etc., but the problem persists. I have the essentially same code running in C++, so I know it can work. What am I doing wrong?
The code below doesn't contain elaborated error checking, but it should compile and demonstrate the problem out of the box.
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace RunNonElevatedCSharp
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
StartNonElevated(#"C:\WINDOWS\system32\cmd.exe", "");
}
public static int StartNonElevated(string strAppPath, string strCommandLine)
{
bool bSuccess = false;
IntPtr hShellProcess = IntPtr.Zero;
var pInfo = new PROCESS_INFORMATION();
var sInfoEx = new STARTUPINFOEX();
sInfoEx.StartupInfo.cb = Marshal.SizeOf(sInfoEx);
try
{
IntPtr hShellWnd = GetShellWindow();
if (hShellWnd == IntPtr.Zero)
{
return 0;
}
UInt32 pid;
if (GetWindowThreadProcessId(hShellWnd, out pid) == 0)
{
return 0;
}
hShellProcess = OpenProcess(PROCESS_CREATE_PROCESS, FALSE, pid);
if (hShellProcess == IntPtr.Zero)
{
return 0;
}
IntPtr nBufferSize = IntPtr.Zero;
InitializeProcThreadAttributeList(IntPtr.Zero, 1, 0, ref nBufferSize);
if (nBufferSize == IntPtr.Zero)
{
return 0;
}
sInfoEx.lpAttributeList = Marshal.AllocHGlobal(nBufferSize);
if (sInfoEx.lpAttributeList == IntPtr.Zero)
{
return 0;
}
if (!InitializeProcThreadAttributeList(sInfoEx.lpAttributeList, 1, 0, ref nBufferSize))
{
return 0;
}
if (!UpdateProcThreadAttribute(sInfoEx.lpAttributeList, 0, (IntPtr)PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, hShellProcess, (IntPtr)IntPtr.Size, IntPtr.Zero, IntPtr.Zero))
{
return 0;
}
// s1 and s2 may not be required
string s1 = "" + strAppPath + "";
string s2 = "";
// The next line causes an access violation unless you remove the 'EXTENDED_STARTUPINFO_PRESENT' flag
if (!CreateProcess(s1, s2, IntPtr.Zero, IntPtr.Zero, false, CREATE_NEW_CONSOLE | EXTENDED_STARTUPINFO_PRESENT | 0,
IntPtr.Zero, null, ref sInfoEx, out pInfo))
{
return 0;
}
bSuccess = true;
CloseHandle(pInfo.hThread);
CloseHandle(pInfo.hProcess);
return pInfo.dwProcessId;
}
finally
{
if (!bSuccess)
{
var lastError = Marshal.GetLastWin32Error();
Debug.WriteLine("Error: " + lastError.ToString());
}
if (sInfoEx.lpAttributeList != IntPtr.Zero)
{
DeleteProcThreadAttributeList(sInfoEx.lpAttributeList);
Marshal.FreeHGlobal(sInfoEx.lpAttributeList);
}
if (hShellProcess != IntPtr.Zero)
CloseHandle(hShellProcess);
}
}
[DllImport("User32.dll", SetLastError = true)]
public static extern IntPtr GetShellWindow();
[DllImport("User32.dll", SetLastError = true)]
public static extern UInt32 GetWindowThreadProcessId(IntPtr hWnd, out UInt32 lpdwProcessId);
[DllImport("Kernel32.dll", SetLastError = true)]
public static extern IntPtr OpenProcess(UInt32 dwDesiredAccess, int bInheritHandle, UInt32 dwProcessId);
[DllImport("kernel32.dll", SetLastError = true)][return: MarshalAs(UnmanagedType.Bool)]
public static extern bool InitializeProcThreadAttributeList(IntPtr lpAttributeList, int dwAttributeCount, int dwFlags, ref IntPtr lpSize);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool UpdateProcThreadAttribute(IntPtr lpAttributeList, uint dwFlags, IntPtr Attribute, IntPtr lpValue, IntPtr cbSize,
IntPtr lpPreviousValue, IntPtr lpReturnSize);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, EntryPoint = "CreateProcessW", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool CreateProcess(string lpApplicationName, string lpCommandLine,
[In, Optional] IntPtr lpProcessAttributes, [In, Optional] IntPtr lpThreadAttributes,
bool bInheritHandles, uint dwCreationFlags,
[In, Optional] IntPtr lpEnvironment, [In, Optional] string lpCurrentDirectory,
[In] ref STARTUPINFOEX lpStartupInfo, [Out] out PROCESS_INFORMATION lpProcessInformation);
[DllImport("Kernel32.dll", SetLastError = true)]
public static extern int CloseHandle(IntPtr hObject);
[DllImport("kernel32.dll")]
private static extern void DeleteProcThreadAttributeList(IntPtr lpAttributeList);
public const UInt32 PROCESS_CREATE_PROCESS = 0x0080;
public const int FALSE = 0;
public const int PROC_THREAD_ATTRIBUTE_PARENT_PROCESS = 0x00020000;
public const uint CREATE_NEW_CONSOLE = 0x00000010;
public const uint EXTENDED_STARTUPINFO_PRESENT = 0x00080000;
}
[StructLayout(LayoutKind.Sequential)]
public struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public int dwProcessId;
public int dwThreadId;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct STARTUPINFOEX
{
public STARTUPINFO StartupInfo;
public IntPtr lpAttributeList;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct STARTUPINFO
{
public Int32 cb;
public string lpReserved;
public string lpDesktop;
public string lpTitle;
public Int32 dwX;
public Int32 dwY;
public Int32 dwXSize;
public Int32 dwYSize;
public Int32 dwXCountChars;
public Int32 dwYCountChars;
public Int32 dwFillAttribute;
public Int32 dwFlags;
public Int16 wShowWindow;
public Int16 cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
}
}
When the access violation occurs, the program cannot continue; the "finally" block isn't executed.
Looking forward to any replies...
Hans
Your call to UpdateProcThreadAttribute() is wrong. The 4th parameter needs the address of hShellProcess, not the value. This is even stated as much in this answer to the other question you linked to (the code in the other question has the same bug):
Second, the lpValue parameter of the UpdateProcThreadAttribute function must be a pointer to the attribute value (in your case, parentHandle), not the value itself.
The documentation for PROC_THREAD_ATTRIBUTE_PARENT_PROCESS says:
The lpValue parameter is a pointer to a handle to a process to use instead of the calling process as the parent for the process being created. The process to use must have the PROCESS_CREATE_PROCESS access right.
You said that you copied the other answer's code, but your code does not look like the other answer's code, and certainly does not contain the fix that the other answer had provided.
In your code, change this:
if (!UpdateProcThreadAttribute(..., hShellProcess, ...))
To this:
IntPtr lpValue = IntPtr.Zero;
...
lpValue = Marshal.AllocHGlobal(IntPtr.Size);
Marshal.WriteIntPtr(lpValue, hShellProcess);
if (!UpdateProcThreadAttribute(..., lpValue, ...))
...
Marshal.FreeHGlobal(lpValue);
I have created a new desktop using CreateDesktop and would like to be able to enumerate all processes within that desktop.
I have tried this:
foreach (Process process in Process.GetProcesses())
{
foreach (ProcessThread processThread in process.Threads)
{
IntPtr hDesk = GetThreadDesktop((uint)processThread.Id);
if (hDesk == desk)
{
// Do something
}
}
}
However, I get Access is denied exception. I can also use
EnumDesktopWindows
However, this doesn't work for command line applications. (I am trying to prevent keyloggers)
Is there any way to get all processes within a desktop?
Thanks
I spent the last hour playing with this and I have it working fine from a console window. The code is messy, but you should be able to make your way through it:
class Program
{
private static class Win32Native
{
[Flags]
public enum CreateDesktopFlags : uint
{
DF_NONE = 0,
DF_ALLOWOTHERACCOUNTHOOK = 1
}
[Flags]
public enum CreateWindowAccessMask : uint
{
DESKTOP_READOBJECTS = 0x0001,
DESKTOP_CREATEWINDOW = 0x0002,
DESKTOP_CREATEMENU = 0x0004,
DESKTOP_HOOKCONTROL = 0x0008,
DESKTOP_JOURNALRECORD = 0x0010,
DESKTOP_JOURNALPLAYBACK = 0x0020,
DESKTOP_ENUMERATE = 0x0040,
DESKTOP_WRITEOBJECTS = 0x0080,
DESKTOP_SWITCHDESKTOP = 0x0100,
DESKTOP_ALL_ACCESS = 0x01FF
}
[Flags]
public enum CreateProcessFlags : uint
{
CREATE_NEW_CONSOLE = 0x00000010,
CREATE_NEW_PROCESS_GROUP = 0x00000200
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct STARTUPINFO
{
public int cb;
public string lpReserved;
public string lpDesktop;
public string lpTitle;
public int dwX;
public int dwY;
public int dwXSize;
public int dwYSize;
public int dwXCountChars;
public int dwYCountChars;
public int dwFillAttribute;
public int dwFlags;
public short wShowWindow;
public short cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
}
[StructLayout(LayoutKind.Sequential)]
public struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public int dwProcessId;
public int dwThreadId;
}
[DllImport("user32.dll")]
public static extern IntPtr GetProcessWindowStation();
[return: MarshalAs(UnmanagedType.Bool)]
public delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
[return: MarshalAs(UnmanagedType.Bool)]
public delegate bool EnumDesktopProc([MarshalAs(UnmanagedType.LPWStr)] string lpszDesktop, IntPtr lParam);
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool EnumDesktopWindows(IntPtr hDesktop, EnumWindowsProc lpfn, IntPtr lParam);
[DllImport("user32.dll", EntryPoint = "GetWindowTextW", CharSet = CharSet.Unicode)]
public static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
[DllImport("user32.dll", EntryPoint = "GetClassNameW", CharSet = CharSet.Unicode)]
public static extern int GetClassName(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
[DllImport("user32.dll", SetLastError = true, EntryPoint = "CreateDesktopW", CharSet = CharSet.Unicode)]
public static extern IntPtr CreateDesktop(
string lpszDesktop, IntPtr lpszDevice,
IntPtr pDevMode, CreateDesktopFlags dwFlags,
CreateWindowAccessMask dwDesiredAccess,
IntPtr lpsa);
[DllImport("user32.dll", SetLastError = true, EntryPoint = "EnumDesktopsW", CharSet = CharSet.Unicode)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool EnumDesktops(IntPtr hwinsta, EnumDesktopProc lpEnumFunc, IntPtr lParam);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CloseDesktop(IntPtr hDesktop);
[DllImport("kernel32.dll", SetLastError = true, EntryPoint = "CreateProcessW", CharSet = CharSet.Unicode)]
[return:MarshalAs(UnmanagedType.Bool)]
public static extern bool CreateProcess(
string lpApplicationName,
string lpCommandLine,
IntPtr lpProcessAttributes,
IntPtr lpThreadAttributes,
bool bInheritHandles,
CreateProcessFlags dwCreationFlags,
IntPtr lpEnvironment,
string lpCurrentDirectory,
ref STARTUPINFO lpStartupInfo,
out PROCESS_INFORMATION lpProcessInformation);
[DllImport("kernel32.dll")]
[return:MarshalAs(UnmanagedType.Bool)]
public static extern bool CloseHandle(IntPtr hObject);
[DllImport("kernel32.dll")]
[return:MarshalAs(UnmanagedType.Bool)]
public static extern bool TerminateProcess(IntPtr hProcess, uint uExitCode);
}
static int Main(string[] args)
{
StringBuilder sbWndText = new StringBuilder(512),
sbWndClass = new StringBuilder(512);
Console.WriteLine("Trying current desktop:");
if(!Win32Native.EnumDesktopWindows(IntPtr.Zero, (hWnd, lParam) =>
{
Win32Native.GetWindowText(hWnd, sbWndText, sbWndText.Capacity);
Win32Native.GetClassName(hWnd, sbWndClass, sbWndClass.Capacity);
Console.WriteLine($"Found Window: {hWnd} with title \"{sbWndText}\" and class name \"{sbWndClass}\"");
return true;
}, IntPtr.Zero))
{
var error = Marshal.GetLastWin32Error();
Console.WriteLine($"EnumDesktopWindows for current desktop failed with error {error}");
}
Console.WriteLine("Current desktops: ");
Win32Native.EnumDesktops(Win32Native.GetProcessWindowStation(), (desktopName, lParam) =>
{
Console.WriteLine($"Found desktop: {desktopName}");
return true;
}, IntPtr.Zero);
Console.WriteLine("Trying new desktop:");
const string DesktopName = "ANDY DESKTOP NEATO 2";
var hDesktop = Win32Native.CreateDesktop(
DesktopName, IntPtr.Zero, IntPtr.Zero,
Win32Native.CreateDesktopFlags.DF_ALLOWOTHERACCOUNTHOOK,
Win32Native.CreateWindowAccessMask.DESKTOP_ALL_ACCESS,
IntPtr.Zero);
if(hDesktop != IntPtr.Zero)
{
Win32Native.EnumDesktops(Win32Native.GetProcessWindowStation(), (desktopName, lParam) =>
{
Console.WriteLine($"Found desktop: {desktopName}");
return true;
}, IntPtr.Zero);
var si = new Win32Native.STARTUPINFO();
si.cb = Marshal.SizeOf(si);
si.lpDesktop = DesktopName;
var pi = new Win32Native.PROCESS_INFORMATION();
if(!Win32Native.CreateProcess(
null, "cmd.exe", IntPtr.Zero, IntPtr.Zero, false,
Win32Native.CreateProcessFlags.CREATE_NEW_CONSOLE |
Win32Native.CreateProcessFlags.CREATE_NEW_PROCESS_GROUP,
IntPtr.Zero, null, ref si, out pi))
{
var error = Marshal.GetLastWin32Error();
Console.WriteLine($"Unable to create process on new desktop: {error}");
}
Console.WriteLine("WAITING 2 SECONDS FOR PROCESS TO START...");
Thread.Sleep(2000); // breath so the process starts
if (!Win32Native.EnumDesktopWindows(hDesktop, (hWnd, lParam) =>
{
Win32Native.GetWindowText(hWnd, sbWndText, sbWndText.Capacity);
Win32Native.GetClassName(hWnd, sbWndClass, sbWndClass.Capacity);
Console.WriteLine($"Found Window: {hWnd} with title \"{sbWndText}\" and class name \"{sbWndClass}\"");
return true;
}, IntPtr.Zero))
{
var error = Marshal.GetLastWin32Error();
Console.WriteLine($"EnumDesktopWindows for new desktop failed with error {error}");
}
// IMPORTANT: close the processes you start, otherwise you desktop won't self-destruct.
Win32Native.TerminateProcess(pi.hProcess, 42);
Win32Native.CloseHandle(pi.hProcess);
Win32Native.CloseHandle(pi.hThread);
Win32Native.CloseDesktop(hDesktop);
}
else
{
Console.WriteLine($"Unable to create desktop: {Marshal.GetLastWin32Error()}");
}
return 0;
}
}
So the issue I think you were having with EnumDesktopWindows is that when you called CreateDesktop, you didn't ask for sufficient privileges. I set all privileges on (value of 0x1FF) and then EnumDesktopWindows worked for me.
Keep in mind if you call EnumDesktopWindows and there are no windows to enumerate, it will return false with a value of 0 when you call GetLastError.
So what I did to prove that it actually is working is I created a process (cmd.exe) in the new desktop, then called EnumDesktopWindows.
Also keep in mind if you don't destroy all the processes in your new desktop, the desktop will not "self-destruct" and it will be alive until all the processes are destroyed or you logoff/reboot.
I also ran this as a normal user. I didn't need to elevate to administrator to make this work.
However, this doesn't work for command line applications. (I am trying
to prevent keyloggers)
Assume that you want to prevent from hooking keyboard input of the desktop.
Since hook need a message loop to receive keyboard messages, which requires to create a window. So restrict other users to create window application associate to your desktop can prevent them from logging keyboard inputs.
If you want to check the desktop name of a given process to see if it is same with your desktop's name (applied for window application) you can follow these steps:
Call OpenProcess() to get a HANDLE from the target process ID.
Call NtQueryInformationProcess() to retrieve the address of the process's PEB structure.
Call ReadProcessMemory() to read the PEB. It's ProcessParams.DesktopName field contains the name of the workstation/desktop currently associated with the process (there are many more fields available in the PEB.ProcessParams then what MSDN shows).
Refer to "How to get window station for a given process?"
I have a need of changing checkbox state in TreeView (exactly 'SysTreeView32') item owned by external application - for automation purposes. I already have TreeView handle and TreeViewItem handle. I have also found some examples how I can set checkbox state, but for some reason, it is not working (SendMessage returns 0 or crashes entire application). But to the code. What i tried already is this:
TVITEM struct:
[StructLayout(LayoutKind.Sequential, Pack = 8, CharSet = CharSet.Auto)]
internal struct TVITEM
{
public int mask;
public IntPtr hItem;
public int state;
public int stateMask;
[MarshalAs(UnmanagedType.LPTStr)]
public string pszText;
public int cchTextMax;
public int iImage;
public int iSelectedImage;
public int cChildren;
public IntPtr lParam;
}
pinvoke for SendMessage:
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern int SendMessage(IntPtr hWnd, int msg, IntPtr wParam, ref TVITEM lParam);
And my method:
internal static void SetTreeNodeState(int treeViewHandler, int treeViewItemHandler, bool state)
{
TVITEM tvItem = new TVITEM();
tvItem.mask = TVIF_STATE | TVIF_HANDLE;
tvItem.hItem = (IntPtr)treeViewItemHandler;
tvItem.stateMask = TVIS_STATEIMAGEMASK;
tvItem.state = (state ? 2 : 1) << 12;
var result = SendMessage((IntPtr)treeViewHandler, TVM_SETITEMW, IntPtr.Zero, ref tvItem);
}
This is the closest approach (i think, at last i did not crash target application once). Of course i have tried to sniff messages for target tree view using Spy++. What concerns me is that Spy++ shows that LParam for SendMessage is actually "TVITEMEXW" but i can beryl find anything about that struct.
Generally i also tried same think with TVM_GETITEMW, but however i did not crash application, SendMessage always returns zero.
What i'm doing wrong?
When you send this particular message, you are expected to supply the address of a struct. Because the window is owned by a different process, the address you supply is not valid. Windows processes have isolated virtual memory address space. The address you supply is valid in your process, but only in your process.
In order to get around this, and send this message, you would need to allocate the memory in the target process, using VirtualAllocEx. You would also need to use WriteProcessMemory in order to populate the struct. You'd need to take care of any possible issues with struct layout if your process and the target process had different bitness. You'd need to do the same trick with members like pszText which themselves are pointers.
There are many questions here already that cover the subject of cross process message marshalling. I am sure that you will be able to locate them. Likewise there are a great many tutorials to be found on the web that you will find now that you are aware of the issue.
Perhaps a bigger problem is that the other process may not respond the way you expect to being poked from the outside in this way. Don't be at all surprised if you find it very challenging to be able to write your own cross process automation. Rather than doing so, why not use UI Automation?
Okey, thanks to David Heffernan, i figured this out. I Have created overload for SendMessage, that accepts lParam as object by ref:
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, IntPtr lpBuffer, uint nSize, out UIntPtr lpNumberOfBytesWritten);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] buffer, Int32 nSize, out IntPtr lpNumberOfBytesRead);
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
private static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize, AllocationType flAllocationType, MemoryProtection flProtect);
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
private static extern bool VirtualFreeEx(IntPtr hProcess, IntPtr lpAddress, int dwSize, AllocationType dwFreeType);
private static IntPtr SendMessage<T>(Process process, IntPtr hWnd, int msg, int wParam, ref T lParam)
{
uint size = (uint)Marshal.SizeOf(lParam);
byte[] buffer = new byte[size];
IntPtr processHandle = process.Handle;
IntPtr pPointer = VirtualAllocEx(processHandle, IntPtr.Zero, size, AllocationType.Commit | AllocationType.Reserve, MemoryProtection.ReadWrite);
IntPtr inputPtr = Marshal.AllocHGlobal((int)size);
IntPtr outputPtr = Marshal.AllocHGlobal((int)size);
Marshal.StructureToPtr(lParam, inputPtr, false);
WriteProcessMemory(processHandle, pPointer, inputPtr, size, out UIntPtr nNbBytesWritten);
IntPtr resultPtr = SendMessage(hWnd, msg, wParam, pPointer);
ReadProcessMemory(processHandle, pPointer, buffer, buffer.Length, out IntPtr nNbBytesRead);
Marshal.Copy(buffer, 0, outputPtr, (int)size);
T result = Marshal.PtrToStructure<T>(outputPtr);
lParam = result;
Marshal.FreeHGlobal(inputPtr);
Marshal.FreeHGlobal(outputPtr);
VirtualFreeEx(processHandle, pPointer, 0, AllocationType.Release);
return resultPtr;
}
Usage example
Set checkbox state for given tree view item:
internal static void SetTreeNodeState(IntPtr treeViewHandle, IntPtr treeViewItemHandle, bool state)
{
TVITEM tvItem = new TVITEM
{
mask = TVIF_STATE | TVIF_HANDLE,
hItem = treeViewItemHandle,
stateMask = TVIS_STATEIMAGEMASK,
state = (uint)(state ? 2 : 1) << 12
};
Process process = Process.GetProcessesByName("ProcessName")[0];
IntPtr ptr = SendMessage(process, treeViewHandle, TVM_SETITEMW, 0, ref tvItem);
}
Get checkbox state for given tree view item:
internal static bool GetTreeNodeState( IntPtr treeViewHandle, IntPtr treeViewItemHandle)
{
TVITEM tvItem = new TVITEM
{
mask = TVIF_STATE | TVIF_HANDLE,
hItem = treeViewItemHandle,
stateMask = TVIS_STATEIMAGEMASK,
state = 0
};
Process process = Process.GetProcessesByName("ProcessName")[0];
IntPtr ptr = SendMessage(process, treeViewHandle, TVM_GETITEMW, 0, ref tvItem);
if (ptr != IntPtr.Zero)
{
uint iState = tvItem.state >> 12;
return iState == 2 ? true : false;
}
return false;
}
TVITEM:
[StructLayout(LayoutKind.Sequential)]
internal struct TVITEM
{
public uint mask;
public IntPtr hItem;
public uint state;
public uint stateMask;
public IntPtr pszText;
public int cchTextMax;
public int iImage;
public int iSelectedImage;
public int cChildren;
public IntPtr lParam;
}
After running my silverlight 5.0 application with several PInvokes in it for 5 minutes or so, I get the following error:
Attempted to read or write protected memory
Likely, I'm getting a memory leak somewhere.
The problem is how do I debug this? I don't get a stacktrace at all so I can't pinpoint the offending code exactly.
By commenting out code and rerunning application several times, I think I managed to track down the problem but I can't figure out the problem.
Potentially Offending Code:
private void InitUSBEvents()
{
const string clsName = "SLUsbClass";
const string wndName = "SLUsbWindow";
Win32.WNDCLASSEX wndClassEx = new Win32.WNDCLASSEX();
wndClassEx.cbSize = Marshal.SizeOf(wndClassEx);
wndClassEx.lpszClassName = clsName;
wndClassEx.lpfnWndProc = WndProc;
rClassAtomValue = Win32.RegisterClassEx2(ref wndClassEx);
windowHandle = Win32.CreateWindowEx2(0, rClassAtomValue, wndName, 0, 0, 0, 0, 0, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
Win32Usb.RegisterKeyboardUsbEvents(windowHandle);
}
[AllowReversePInvokeCalls]
private IntPtr WndProc(IntPtr hWnd, WM msg, IntPtr wParam, IntPtr lParam)
{
switch (msg)
{
case WM.INPUT:
//Console.WriteLine("Key Event");
break;
default:
return Win32.DefWindowProc(hWnd, msg, wParam, lParam);
}
return IntPtr.Zero;
}
PInvoke Related Declarations:
public class Win32
{
[DllImport("user32.dll", SetLastError = true, EntryPoint = "CreateWindowEx")]
public static extern IntPtr CreateWindowEx(
WindowStylesEx dwExStyle,
string lpClassName,
string lpWindowName,
WindowStyles dwStyle,
int x,
int y,
int nWidth,
int nHeight,
IntPtr hWndParent,
IntPtr hMenu,
IntPtr hInstance,
IntPtr lpParam);
// Create a window, but accept a atom value.
[DllImport("user32.dll", SetLastError = true, EntryPoint = "CreateWindowEx")]
public static extern IntPtr CreateWindowEx2(
WindowStylesEx dwExStyle,
UInt16 lpClassName,
string lpWindowName,
WindowStyles dwStyle,
int x,
int y,
int nWidth,
int nHeight,
IntPtr hWndParent,
IntPtr hMenu,
IntPtr hInstance,
IntPtr lpParam);
[DllImport("kernel32.dll", EntryPoint = "LocalAlloc")]
internal static extern IntPtr LocalAlloc_NoSafeHandle(
LocalMemoryFlags uFlags, IntPtr sizetdwBytes);
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern IntPtr LocalFree(IntPtr hMem);
[DllImport("user32.dll", SetLastError = true, EntryPoint = "RegisterClassEx")]
public static extern UInt16 RegisterClassEx2([In] ref WNDCLASSEX lpwcx);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.U2)]
public static extern short RegisterClassEx([In] ref WNDCLASSEX lpwcx);
[DllImport("user32.dll")]
public static extern IntPtr DefWindowProc(IntPtr hWnd, WM uMsg, IntPtr wParam, IntPtr lParam);
}
public class Win32Usb
{
public static bool RegisterKeyboardUsbEvents(IntPtr hWnd)
{
//Create an array of all the raw input devices we want to
//listen to. In this case, only keyboard devices.
//RIDEV_INPUTSINK determines that the window will continue
//to receive messages even when it doesn't have the focus.
RAWINPUTDEVICE[] rid = new RAWINPUTDEVICE[1];
rid[0].usUsagePage = 0x01;
rid[0].usUsage = 0x06;
rid[0].dwFlags = RIDEV_INPUTSINK;
rid[0].hwndTarget = hWnd;
return RegisterRawInputDevices(rid, (uint)rid.Length, (uint)Marshal.SizeOf(rid[0]));
}
[StructLayout(LayoutKind.Explicit)]
internal class RAWINPUT
{
[FieldOffset(0)]
public RAWINPUTHEADER header;
[FieldOffset(16)]
public RAWMOUSE mouse;
[FieldOffset(16)]
public RAWKEYBOARD keyboard;
[FieldOffset(16)]
public RAWHID hid;
}
[StructLayout(LayoutKind.Sequential)]
internal class RAWINPUTDEVICELIST
{
public IntPtr hDevice;
[MarshalAs(UnmanagedType.U4)]
public int dwType;
}
[StructLayout(LayoutKind.Sequential)]
internal struct RAWINPUTDEVICE
{
[MarshalAs(UnmanagedType.U2)]
public ushort usUsagePage;
[MarshalAs(UnmanagedType.U2)]
public ushort usUsage;
[MarshalAs(UnmanagedType.U4)]
public int dwFlags;
public IntPtr hwndTarget;
}
}
I can't really find anything wrong with the PInvoke declarations either. What are some good strategies to debug PInvoke related errors on Silverlight? Or is there a way to force the stacktrace to output? (I've tried turning on all exceptions to be thrown in Debug->Exception)
The WndProc delegate that you are passing in the wndClassEx struct is being garbage collected. When you pass a delegate to unmanaged code that delegate will only be kept alive for the time of the call to the unmanaged function. Once the call ends there is no longer reference to it in your Silverlight application so the garbage collection considers it dead and will collect it.
This example of creating a window class in Silverlight caches the WndProc delegate in a static method so that it does not get garbage collected.
Also see this (older, but still valid) MSDN article on P/Invoke.
#shf301 is correct that you need to take steps to stop your delegate from being collected. I won't attempt to repeat his point here.
However, on top of that issue, your structs are incorrectly defined. They will work under 32 bit, but not under 64 bit. The rule of thumb with FieldOffset is that you only ever use it with an offset of 0. This rule is what allows you to write code that works on both 32 and 64 bit. In your code you used an offset of 16 which is fine for 32 bit, but not 64 bit. Define your structures following this pattern:
[StructLayout(LayoutKind.Explicit)]
struct RAWINPUTUNION
{
[FieldOffset(0)]
public RAWMOUSE mouse;
[FieldOffset(0)]
public RAWKEYBOARD keyboard;
[FieldOffset(0)]
public RAWHID hid;
}
[StructLayout(LayoutKind.Sequential)]
struct RAWINPUT
{
public RAWINPUTHEADER header;
public RAWINPUTUNION data;
}
I am trying to PInvoke CreateDesktop in a way that passes the flag to inherit the desktop by child processes. The declaration is as follows:
[DllImport("user32", EntryPoint = "CreateDesktopW", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern IntPtr CreateDesktop(string lpszDesktop, IntPtr lpszDevice, IntPtr pDevmode, int dwFlags,
int dwDesiredAccess, [MarshalAs(UnmanagedType.LPStruct)] SECURITY_ATTRIBUTES lpsa);
[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES
{
public int nLength;
public IntPtr lpSecurityDescriptor;
public int bInheritHandle;
}
And I use it as follows:
Win32.SECURITY_ATTRIBUTES sa = new Win32.SECURITY_ATTRIBUTES();
sa.nLength = Marshal.SizeOf(sa);
sa.bInheritHandle = 1;
testDesktopHandle = Win32.CreateDesktop(name, IntPtr.Zero, IntPtr.Zero, 0, Win32.GENERIC_ALL, sa);
And it unfortunately doesn't work, I get the following error:
System.Runtime.InteropServices.MarshalDirectiveException: Cannot marshal 'parameter #6': Invalid managed/unmanaged type combination (this value type must be paired with Struct).
Any ideas what I am doing wrong?
Try changing parameter #6 to
static extern IntPtr CreateDesktop(..., [In] ref SECURITY_ATTRIBUTES lpsa);
(This compiles and doesn't throw an exception at runtime, but I've tested it only with bogus arguments.)
Compare with C++ declaration of CreateDesktop:
HDESK WINAPI CreateDesktop(..., __in_opt LPSECURITY_ATTRIBUTES lpsa);
↑ ↑ ↑
[In] ref SECURITY_ATTRIBUTES lpsa
LP stands for "long pointer", i.e. LPSECURITY_ATTRIBUTES is a pointer to a SECURITY_ATTRIBUTES struct. So in C# you need to pass your struct instance (value type) by reference.
Consider using the following prototype instead:
[DllImport("user32", EntryPoint = "CreateDesktopW", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern IntPtr CreateDesktop(string lpszDesktop, IntPtr lpszDevice, IntPtr pDevmode, int dwFlags,
int dwDesiredAccess, IntPtr lpsa);
Then to call it just create a pinned handle:
GCHandle handle = GCHandle.Alloc(myStruct);
try {
IntPtr pinnedAddress = handle.AddrOfPinnedObject();
}
finally {
handle.Free();
}
This works VERY well for calling PInvoke'd methods with structs.