I'm trying to call into the crypt32.dll method CryptProtectData from managed code, but I do not seem to have the marshaling types quite right in my delegate's declaration:
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate bool CryptProtectDataDelegate(
IntPtr pDataIn, // DATA_BLOB*
//StringBuilder szDataDescr, // LPCWSTR
[MarshalAs(UnmanagedType.LPWStr)] string szDataDescr, // LPCWSTR
IntPtr pOptionalEntropy, // DATA_BLOB*
int pvReserved, // PVOID
IntPtr pPromptStruct, // CRYPTPROTECT_PROMPTSTRUCT*
int dwFlags, // DWORD
IntPtr pDataOut // DATA_BLOB*
);
which when invoked,
bool theResult = cryptProtectData(
pDataIn,
null,
IntPtr.Zero, // null,
0, // null,
IntPtr.Zero, // null,
flag,
pDataOut);
causes the exception
+CryptProtectDataDelegate::Invoke' has unbalanced the stack. This is likely because the managed PInvoke signature does not match the
unmanaged target signature. Check that the calling convention and
parameters of the PInvoke signature match the target unmanaged
signature.'
The native definition for CryptProtectData is
DPAPI_IMP BOOL CryptProtectData(
DATA_BLOB *pDataIn,
LPCWSTR szDataDescr,
DATA_BLOB *pOptionalEntropy,
PVOID pvReserved,
CRYPTPROTECT_PROMPTSTRUCT *pPromptStruct,
DWORD dwFlags,
DATA_BLOB *pDataOut
);
where DPAPI_IMP is defined as
#define DPAPI_IMP DECLSPEC_IMPORT
I'm note sure what the legal type representations are that I should be using for the parameters in the delegate definition?
Assuming PVOID is a void *, I found some documentation which suggests that it could be represented as an int, and b/c it can be null, I set it to 0 (https://learn.microsoft.com/en-us/dotnet/framework/interop/default-marshaling-behavior).
Below is what should be a complete and run-able example (that will crash)
using System;
using System.Runtime.InteropServices;
namespace CallNativeDLLs
{
static class NativeMethods
{
[DllImport("kernel32.dll")]
public static extern IntPtr LoadLibrary(string dllToLoad);
[DllImport("kernel32.dll")]
public static extern IntPtr GetProcAddress(IntPtr hModule, string procedureName);
[DllImport("kernel32.dll")]
public static extern bool FreeLibrary(IntPtr hModule);
}
class Program
{
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate bool CryptProtectDataDelegate(
IntPtr pDataIn, // DATA_BLOB*
//StringBuilder szDataDescr, // LPCWSTR
[MarshalAs(UnmanagedType.LPWStr)] string szDataDescr, // LPCWSTR
IntPtr pOptionalEntropy, // DATA_BLOB*
int pvReserved, // PVOID
IntPtr pPromptStruct, // CRYPTPROTECT_PROMPTSTRUCT*
int dwFlags, // DWORD
IntPtr pDataOut // DATA_BLOB*
);
static void Main(string[] args)
{
IntPtr pDll = NativeMethods.LoadLibrary(#"c:\windows\system32\crypt32.DLL");
IntPtr pAddressOfFunctionToCall = NativeMethods.GetProcAddress(pDll, "CryptProtectData");
var cryptProtectData = (CryptProtectDataDelegate)Marshal.GetDelegateForFunctionPointer(
pAddressOfFunctionToCall,
typeof(CryptProtectDataDelegate));
IntPtr pDataIn = Marshal.StringToHGlobalAnsi("hi");
int flag = (int)0x4; //CRYPTPROTECT_LOCAL_MACHINE
var pDataOut = new IntPtr();
// EXCEPTION thrown here
bool theResult = cryptProtectData(
pDataIn,
null,
IntPtr.Zero, // null,
0, // null,
IntPtr.Zero, // null,
flag,
pDataOut);
bool result = NativeMethods.FreeLibrary(pDll);
Console.WriteLine(theResult);
}
}
}
Update: Using Amy's link, the following runs (though I haven't tried decrypting the string yet, and CRYPTPROTECT_LOCAL_MACHINE is weak)
using System;
using System.Runtime.InteropServices;
namespace CallNativeDLLs
{
static class NativeMethods
{
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct DATA_BLOB
{
public int cbData;
public IntPtr pbData;
}
[Flags]
public enum CryptProtectPromptFlags
{
CRYPTPROTECT_PROMPT_ON_UNPROTECT = 0x1,
CRYPTPROTECT_PROMPT_ON_PROTECT = 0x2
}
[Flags]
public enum CryptProtectFlags
{
CRYPTPROTECT_UI_FORBIDDEN = 0x1,
CRYPTPROTECT_LOCAL_MACHINE = 0x4,
CRYPTPROTECT_CRED_SYNC = 0x8,
CRYPTPROTECT_AUDIT = 0x10,
CRYPTPROTECT_NO_RECOVERY = 0x20,
CRYPTPROTECT_VERIFY_PROTECTION = 0x40
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct CRYPTPROTECT_PROMPTSTRUCT
{
public int cbSize;
public CryptProtectPromptFlags dwPromptFlags;
public IntPtr hwndApp;
public String szPrompt;
}
[DllImport("Crypt32.dll",
SetLastError = true,
CharSet = System.Runtime.InteropServices.CharSet.Auto)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CryptProtectData(
ref DATA_BLOB pDataIn,
String szDataDescr,
ref DATA_BLOB pOptionalEntropy,
IntPtr pvReserved,
ref CRYPTPROTECT_PROMPTSTRUCT pPromptStruct,
CryptProtectFlags dwFlags,
ref DATA_BLOB pDataOut
);
}
class Program
{
static void Main(string[] args)
{
IntPtr pbData = Marshal.StringToHGlobalAnsi("hi");
var pDataIn = new NativeMethods.DATA_BLOB{cbData=0,pbData=pbData};
var pOptionalEntropy = new NativeMethods.DATA_BLOB();
var pPromptStruct = new NativeMethods.CRYPTPROTECT_PROMPTSTRUCT();
var pDataOut = new NativeMethods.DATA_BLOB();
bool theResult = NativeMethods.CryptProtectData(
ref pDataIn,
null,
ref pOptionalEntropy, // null,
IntPtr.Zero, // null,
ref pPromptStruct, // null,
NativeMethods.CryptProtectFlags.CRYPTPROTECT_LOCAL_MACHINE,
ref pDataOut);
Console.WriteLine(theResult);
}
}
}
Related
I'm trying to PInvoke UpdateProcThreadAttribute() with PROC_THREAD_ATTRIBUTE_PREFERRED_NODE attribute, so that I could launch a process on a specific NUMA node. I'm working on Windows Server 2019.
I found this question that helped me to PInvoke UpdateProcThreadAttribute():
How to call CreateProcess() with STARTUPINFOEX from C# and re-parent the child
When I'm using PROC_THREAD_ATTRIBUTE_PARENT_PROCESS everything works fine, but when trying to set PROC_THREAD_ATTRIBUTE_PREFERRED_NODE as an attribute I keep getting error 24: ERROR_BAD_LENGTH (The program issued a command but the command length is incorrect).
public class ProcessCreator
{
[DllImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool CreateProcess(
string lpApplicationName, string lpCommandLine, ref SECURITY_ATTRIBUTES lpProcessAttributes,
ref SECURITY_ATTRIBUTES lpThreadAttributes, bool bInheritHandles, uint dwCreationFlags,
IntPtr lpEnvironment, string lpCurrentDirectory, [In] ref STARTUPINFOEX lpStartupInfo,
out PROCESS_INFORMATION lpProcessInformation);
[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", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private 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 DeleteProcThreadAttributeList(IntPtr lpAttributeList);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool CloseHandle(IntPtr hObject);
[DllImport("kernel32.dll")]
static extern uint GetLastError();
public static bool CreateProcess(string appName, string args, string dir, out PROCESS_INFORMATION pInfo, ILogger logger)
{
const int PROC_THREAD_ATTRIBUTE_PARENT_PROCESS = 0x00020000;
const int PROC_THREAD_ATTRIBUTE_PREFERRED_NODE = 0x00020004;
pInfo = new PROCESS_INFORMATION();
var sInfoEx = new STARTUPINFOEX();
sInfoEx.StartupInfo.cb = Marshal.SizeOf(sInfoEx);
IntPtr lpValue = IntPtr.Zero;
try
{
var lpSize = IntPtr.Zero;
var success = InitializeProcThreadAttributeList(IntPtr.Zero, 1, 0, ref lpSize);
if (success || lpSize == IntPtr.Zero)
{
return false;
}
sInfoEx.lpAttributeList = Marshal.AllocHGlobal(lpSize);
success = InitializeProcThreadAttributeList(sInfoEx.lpAttributeList, 1, 0, ref lpSize);
if (!success)
{
return false;
}
// This value should persist until the attribute list is destroyed using the DeleteProcThreadAttributeList function
lpValue = Marshal.AllocHGlobal(IntPtr.Size);
// Setting preferred NUMA to be 0, for example.
Marshal.WriteIntPtr(lpValue, (IntPtr)0);
success = UpdateProcThreadAttribute(
sInfoEx.lpAttributeList,
0,
(IntPtr)PROC_THREAD_ATTRIBUTE_PREFERRED_NODE,
lpValue,
(IntPtr)IntPtr.Size,
IntPtr.Zero,
IntPtr.Zero);
if (!success)
{
logger.Info($"Error: {GetLastError()}");
return false;
}
var pSec = new SECURITY_ATTRIBUTES();
var tSec = new SECURITY_ATTRIBUTES();
pSec.nLength = Marshal.SizeOf(pSec);
tSec.nLength = Marshal.SizeOf(tSec);
return CreateProcess(appName, args, ref pSec, ref tSec, false, 0, IntPtr.Zero, dir, ref sInfoEx, out pInfo);
}
finally
{
// Free the attribute list
if (sInfoEx.lpAttributeList != IntPtr.Zero)
{
DeleteProcThreadAttributeList(sInfoEx.lpAttributeList);
Marshal.FreeHGlobal(sInfoEx.lpAttributeList);
}
Marshal.FreeHGlobal(lpValue);
// Close process and thread handles
if (pInfo.hProcess != IntPtr.Zero)
{
CloseHandle(pInfo.hProcess);
}
if (pInfo.hThread != IntPtr.Zero)
{
CloseHandle(pInfo.hThread);
}
}
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
struct STARTUPINFOEX
{
public STARTUPINFO StartupInfo;
public IntPtr lpAttributeList;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
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;
}
[StructLayout(LayoutKind.Sequential)]
public struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public int dwProcessId;
public int dwThreadId;
}
[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES
{
public int nLength;
public IntPtr lpSecurityDescriptor;
public int bInheritHandle;
}
}
Any help would be much appreciated.
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'm creating a windows service app in visual studio and I want to get the currently active window title.
below is the code I have tried but the function GetForegroundWindow() returns 0 every time.
Is it okay to use win32api in windows services?
public partial class My_Service: ServiceBase
{
Timer timer = new Timer();
[DllImport("User32.dll")]
static extern IntPtr GetForegroundWindow();
[DllImport("User32.dll")]
static extern int GetWindowText(IntPtr hWnd, StringBuilder text, int count);
public My_Service()
{
InitializeComponent();
}
protected override void OnStart(string[] args)
{
WriteToFile("Service is started at " + DateTime.Now);
timer.Elapsed += new ElapsedEventHandler(OnElapsedTime);
timer.Interval = 10000; //number in milisecinds
timer.Enabled = true;
}
protected override void OnStop()
{
WriteToFile("Service is stopped at " + DateTime.Now);
}
private void OnElapsedTime(object source, ElapsedEventArgs e)
{
string title = GetActivewindow();
WriteToFile("Service is recall at " + title + DateTime.Now);
}
public void WriteToFile(string Message)
{
....
}
public string GetActivewindow()
{
const int nChars = 256;
StringBuilder buff = new StringBuilder(nChars);
string title = "- ";
IntPtr handle;
handle = GetForegroundWindow();
if( GetWindowText(handle,buff,nChars) > 0)
{
title = buff.ToString();
}
return title;
}
}
As comments, and also according to the document: About Window Stations and Desktops - Window Station and Desktop Creation.
Your service may be running under Service-0x0-3e7$\default, but the foreground window you want to retrieve is in the default desktop of the interactive window station (Winsta0\default)
The interactive window station is the only window station that can
display a user interface or receive user input. It is assigned to the
logon session of the interactive user, and contains the keyboard,
mouse, and display device. It is always named "WinSta0". All other
window stations are noninteractive, which means they cannot display a
user interface or receive user input.
That means you cannot use GetForegroundWindow directly from the service. You could create a child process in Winsta0\default and redirect its output. Then call GetForegroundWindow in the child process and output.
Sample(removed error checking):
Child:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
using System.IO;
using System.Threading;
namespace ConsoleApp1
{
class Program
{
[DllImport("User32.dll")]
static extern IntPtr GetForegroundWindow();
[DllImport("User32.dll")]
static extern int GetWindowText(IntPtr hWnd, StringBuilder text, int count);
static void Main(string[] args)
{
const int nChars = 256;
StringBuilder buff = new StringBuilder(nChars);
string title = "- ";
IntPtr handle;
handle = GetForegroundWindow();
if (GetWindowText(handle, buff, nChars) > 0)
{
title = buff.ToString();
}
Console.WriteLine(title);
return;
}
}
}
Service:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.ServiceProcess;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
using System.IO;
using System.Diagnostics.Tracing;
namespace WindowsService3
{
public partial class MyNewService : ServiceBase
{
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal 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;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public Int32 dwProcessId;
public Int32 dwThreadId;
}
[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES
{
public int nLength;
public IntPtr lpSecurityDescriptor;
public int bInheritHandle;
}
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern bool CreateProcessAsUser(
IntPtr hToken,
string lpApplicationName,
string lpCommandLine,
IntPtr lpProcessAttributes,
IntPtr lpThreadAttributes,
bool bInheritHandles,
uint dwCreationFlags,
IntPtr lpEnvironment,
IntPtr lpCurrentDirectory,
ref STARTUPINFO lpStartupInfo,
out PROCESS_INFORMATION lpProcessInformation);
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern bool CreatePipe(
ref IntPtr hReadPipe,
ref IntPtr hWritePipe,
ref SECURITY_ATTRIBUTES lpPipeAttributes,
uint nSize);
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern bool ReadFile(
IntPtr hFile,
byte[] lpBuffer,
uint nNumberOfBytesToRead,
out uint lpNumberOfBytesRead,
IntPtr lpOverlapped);
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern bool CloseHandle(IntPtr hObject);
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern uint WTSGetActiveConsoleSessionId();
[DllImport("Wtsapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern bool WTSQueryUserToken(UInt32 SessionId, out IntPtr hToken);
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern uint WaitForSingleObject(IntPtr hProcess, uint dwMilliseconds);
public MyNewService()
{
InitializeComponent();
}
protected override void OnStart(string[] args)
{
System.Diagnostics.Debugger.Launch();
using (StreamWriter sw = File.CreateText("Path\\Log.txt"))
{
IntPtr read = new IntPtr();
IntPtr write = new IntPtr();
IntPtr read2 = new IntPtr();
IntPtr write2 = new IntPtr();
SECURITY_ATTRIBUTES saAttr = new SECURITY_ATTRIBUTES();
saAttr.nLength = Marshal.SizeOf(typeof(SECURITY_ATTRIBUTES));
saAttr.bInheritHandle = 1;
saAttr.lpSecurityDescriptor = IntPtr.Zero;
CreatePipe(ref read, ref write, ref saAttr, 0);
CreatePipe(ref read2, ref write2, ref saAttr, 0);
uint CREATE_NO_WINDOW = 0x08000000;
int STARTF_USESTDHANDLES = 0x00000100;
STARTUPINFO si = new STARTUPINFO();
si.cb = Marshal.SizeOf(typeof(STARTUPINFO));
si.hStdOutput = write;
si.hStdError = write;
si.hStdInput = read2;
si.lpDesktop = "Winsta0\\default";
si.dwFlags = STARTF_USESTDHANDLES;
PROCESS_INFORMATION pi;
IntPtr hToken;
bool err = WTSQueryUserToken(WTSGetActiveConsoleSessionId(), out hToken);
string path = "Path\\ConsoleApp1.exe";
if (CreateProcessAsUser(hToken, path, null, IntPtr.Zero, IntPtr.Zero, true, CREATE_NO_WINDOW, IntPtr.Zero, IntPtr.Zero, ref si, out pi))
{
uint ret = WaitForSingleObject(pi.hProcess, 2000); //wait for the child process exit.
if (ret == 0)
{
byte[] title = new byte[200];
uint reads = 0;
CloseHandle(write);
err = ReadFile(read, title, 200, out reads, IntPtr.Zero);
string result = System.Text.Encoding.UTF8.GetString(title).Replace("\0","").Replace("\r", "").Replace("\n", "");
sw.WriteLine(result);
}
}
CloseHandle(read2);
CloseHandle(write2);
CloseHandle(read);
}
}
protected override void OnStop()
{
}
}
}
I'm trying to import and use CryptCATCDFEnumMembersByCDFTagEx in order to create .cat files using C#. The function is returning null and the parameters do not seem to be initialized. I wrote the corresponding code in C++ and that works fine. I'm guessing my structures are off.
Any help would be appreciated. The code should create a Good.cat file in the working directory of the program, which should contain the hashes of the files specified below [CatalogFiles].
Catalog.cdf
[CatalogHeader]
Name=Good.cat
ResultDir=.\
PublicVersion=0x0000001
EncodingType=0x00010001
CATATTR1=0x10010001:OSAttr:2:6.0
[CatalogFiles]
Notepad.exe=C:\Windows\Notepad.exe
C# sample program.
class Program
{
//https://msdn.microsoft.com/en-us/library/windows/desktop/bb427419%28v=vs.85%29.aspx
[StructLayout(LayoutKind.Sequential)]
public struct CRYPTCATCDF
{
uint cbStruct;
IntPtr hFile;
uint dwCurFilePos;
uint dwLastMemberOffset;
bool fEOF;
[MarshalAs(UnmanagedType.LPWStr)]
string pwszResultDir;
IntPtr hCATStore;
}
//https://msdn.microsoft.com/en-us/library/windows/desktop/aa381414%28v=vs.85%29.aspx
[StructLayout(LayoutKind.Sequential)]
public struct CRYPTOAPI_BLOB
{
public uint cbData; // UInt32 cbData;
//[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)]
public IntPtr pbData; //public byte[] pbData
} // CRYPT_INTEGER_BLOB, CRYPT_ATTR_BLOB, CRYPT_OBJID_BLOB, CRYPT_HASH_BLOB
//https://msdn.microsoft.com/en-us/library/windows/desktop/bb736433%28v=vs.85%29.aspx
[StructLayout(LayoutKind.Sequential)]
public struct SIP_INDIRECT_DATA
{
public CRYPT_ATTRIBUTE_TYPE_VALUE Data;
public CRYPT_ALGORITHM_IDENTIFIER DigestAlgorithm;
public CRYPTOAPI_BLOB Digest;
}
//https://msdn.microsoft.com/en-us/library/windows/desktop/aa381151%28v=vs.85%29.aspx
[StructLayout(LayoutKind.Sequential)]
public struct CRYPT_ATTRIBUTE_TYPE_VALUE {
[MarshalAs(UnmanagedType.LPStr)]
public string pszObjId;
public CRYPTOAPI_BLOB Value;
}
//https://msdn.microsoft.com/en-us/library/windows/desktop/aa381133%28v=vs.85%29.aspx
[StructLayout(LayoutKind.Sequential)]
public struct CRYPT_ALGORITHM_IDENTIFIER
{
[MarshalAs(UnmanagedType.LPStr)]
public string pszObjId;
public CRYPTOAPI_BLOB Parameters;
}
//https://msdn.microsoft.com/en-us/library/windows/desktop/aa373931%28v=vs.85%29.aspx
[StructLayout(LayoutKind.Sequential)]
public struct GUID
{
int a;
short b;
short c;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
byte[] d;
}
//https://msdn.microsoft.com/en-us/library/windows/desktop/aa379905%28v=vs.85%29.aspx
[StructLayout(LayoutKind.Sequential)]
public class CRYPTCATMEMBER
{
public uint cbStruct;
[MarshalAs(UnmanagedType.LPWStr)]
public string pwszReferenceTag;
[MarshalAs(UnmanagedType.LPWStr)]
public string pwszFileName;
public GUID gSubjectType;
public uint fdwMemberFlags;
public IntPtr pIndirectData; //struct SIP_INDIRECT_DATA_ *pIndirectData;
public uint dwCertVersion;
public uint dwReserved;
public IntPtr hReserved;
public CRYPTOAPI_BLOB sEncodedIndirectData;
public CRYPTOAPI_BLOB sEncodedMemberInfo;
}
//https://msdn.microsoft.com/en-us/library/windows/desktop/bb410248%28v=vs.85%29.aspx
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void PFN_CDF_PARSE_ERROR_CALLBACK(
[In] uint dwErrorArea,
[In] uint dwLocalError,
[In, MarshalAs(UnmanagedType.LPWStr)] string pwszLine
);
//https://msdn.microsoft.com/en-us/library/windows/desktop/bb427424%28v=vs.85%29.aspx
[DllImport("wintrust.dll", ExactSpelling = true, SetLastError = true, CharSet = CharSet.Unicode)]
static extern IntPtr CryptCATCDFOpen(
[In, MarshalAs(UnmanagedType.LPWStr)] string pwszFilePath,
[In, Optional] IntPtr pfnParseError
);
//https://msdn.microsoft.com/en-us/library/windows/desktop/bb427423%28v=vs.85%29.aspx
[DllImport("wintrust.dll", ExactSpelling = true, SetLastError = true, CharSet = CharSet.Unicode)]
static extern string CryptCATCDFEnumMembersByCDFTagEx(
[In] IntPtr pCDF,
[In, Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pwszPrevCDFTag,
[In] IntPtr pfnParseError,
[In] CRYPTCATMEMBER ppMember,
[In] bool fContinueOnError,
[In] IntPtr pvReserved
);
private static void ParseErrorCallback(uint u1, uint u2, string s)
{
Console.WriteLine(u1 + " " + u2 + " " + s);
}
static void Main(string[] args)
{
PFN_CDF_PARSE_ERROR_CALLBACK pfn = new PFN_CDF_PARSE_ERROR_CALLBACK(ParseErrorCallback);
StringBuilder sb = new StringBuilder(256);
string s = string.Empty;
IntPtr cdfPtr = CryptCATCDFOpen("catalog.cdf", Marshal.GetFunctionPointerForDelegate(pfn));
CRYPTCATCDF cdf = (CRYPTCATCDF)Marshal.PtrToStructure(cdfPtr, typeof(CRYPTCATCDF));
CRYPTCATMEMBER ccm = new CRYPTCATMEMBER();
ccm.pIndirectData = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(SIP_INDIRECT_DATA)));
do
{
s = CryptCATCDFEnumMembersByCDFTagEx(cdfPtr, sb, Marshal.GetFunctionPointerForDelegate(pfn), ccm, true, IntPtr.Zero);
Console.WriteLine(s ?? "N/A");
} while (s != null);
}
}
Working C++ example
void callback(DWORD u1, DWORD u2, LPWSTR s)
{
printf("%d %d %s", u1, u2, s);
}
typedef LPWSTR(WINAPI *CryptCATCDFEnumMembersByCDFTagEx)(
CRYPTCATCDF *pCDF,
LPWSTR pwszPrevCDFTag,
PFN_CDF_PARSE_ERROR_CALLBACK pfnParseError,
CRYPTCATMEMBER **ppMember,
BOOL fContinueOnError,
LPVOID pvReserved);
int _tmain(int argc, _TCHAR* argv[])
{
CRYPTCATCDF *cdf;
CRYPTCATMEMBER *pMember = NULL;
LPWSTR pwszMemberTag = NULL;
HINSTANCE dllHandle = LoadLibrary(L"wintrust.dll");
cdf = CryptCATCDFOpen(L"catalog.cdf", (PFN_CDF_PARSE_ERROR_CALLBACK)callback);
CryptCATCDFEnumMembersByCDFTagEx fptr = (CryptCATCDFEnumMembersByCDFTagEx)GetProcAddress(dllHandle, "CryptCATCDFEnumMembersByCDFTagEx");
while (pwszMemberTag = (*fptr)(cdf, pwszMemberTag, NULL, &pMember, TRUE, NULL))
{
wprintf(L"%s", pwszMemberTag);
}
CryptCATCDFClose(cdf);
}
With minor changes to your program, I'm able to create the catalog file. Did not modify any structs or its members. Still using "CRYPTCATMEMBER" as class
Changes:
Assign null to string variable, in this case "s".
Calling CryptCATCDFClose function after enumerating the files in catalog definition file(.cdf).
Working Console program.
static void Main(string[] args)
{
CRYPTCATMEMBER ccm = null;
try
{
PFN_CDF_PARSE_ERROR_CALLBACK pfn = ParseErrorCallback;
string s = null; //This null assignment is deliberately done.
IntPtr cdfPtr = CryptCATCDFOpen("catalog.cdf", Marshal.GetFunctionPointerForDelegate(pfn));
CRYPTCATCDF cdf = (CRYPTCATCDF) Marshal.PtrToStructure(cdfPtr, typeof(CRYPTCATCDF)); //This call is required else the catlog file creation fails
ccm = new CRYPTCATMEMBER
{
pIndirectData = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(SIP_INDIRECT_DATA)))
};
do
{
s = CryptCATCDFEnumMembersByCDFTagEx(cdfPtr, s, Marshal.GetFunctionPointerForDelegate(pfn), ccm, true, IntPtr.Zero);
Console.WriteLine(s ?? "N/A");
} while (s != null);
CryptCATCDFClose(cdfPtr); //This is required to update the .cat with the files details specified in .cdf file.
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
finally
{
// Free the unmanaged memory.
if (ccm != null)
{
Marshal.FreeHGlobal(ccm.pIndirectData);
}
}
}
I need to create a new process but so that it is a "child" of another process not the current process eg re-parent the new process.
The following have got me almost there .NET : How to call CreateProcessAsUser() with STARTUPINFOEX from C# and .NET : How to PInvoke UpdateProcThreadAttribute and http://winprogger.com/launching-a-non-child-process/
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
public class ProcessCreator
{
[DllImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool CreateProcess(
string lpApplicationName, string lpCommandLine, ref SECURITY_ATTRIBUTES lpProcessAttributes,
ref SECURITY_ATTRIBUTES lpThreadAttributes, bool bInheritHandles, uint dwCreationFlags,
IntPtr lpEnvironment, string lpCurrentDirectory, [In] ref STARTUPINFOEX lpStartupInfo,
out PROCESS_INFORMATION lpProcessInformation);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool UpdateProcThreadAttribute(
out IntPtr lpAttributeList, uint dwFlags, IntPtr Attribute, IntPtr lpValue,
IntPtr cbSize, IntPtr lpPreviousValue, IntPtr lpReturnSize);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool InitializeProcThreadAttributeList(
out IntPtr lpAttributeList, int dwAttributeCount, int dwFlags, ref IntPtr lpSize);
public static bool CreateProcess(int parentProcessId)
{
const uint EXTENDED_STARTUPINFO_PRESENT = 0x00080000;
const int PROC_THREAD_ATTRIBUTE_PARENT_PROCESS = 0x00020000;
var pInfo = new PROCESS_INFORMATION();
var sInfoEx = new STARTUPINFOEX();
sInfoEx.StartupInfo = new STARTUPINFO();
if (parentProcessId > 0)
{
var lpSize = IntPtr.Zero;
IntPtr dummyPtr;
var success = InitializeProcThreadAttributeList(out dummyPtr, 1, 0, ref lpSize);
if (success || lpSize == IntPtr.Zero)
{
return false;
}
sInfoEx.lpAttributeList = Marshal.AllocHGlobal(lpSize);
if (sInfoEx.lpAttributeList == IntPtr.Zero)
{
return false;
}
success = InitializeProcThreadAttributeList(out sInfoEx.lpAttributeList, 1, 0, ref lpSize);
if (!success)
{
return false;
}
var parentHandle = Process.GetProcessById(parentProcessId).Handle;
success = UpdateProcThreadAttribute(
out sInfoEx.lpAttributeList,
0,
(IntPtr)PROC_THREAD_ATTRIBUTE_PARENT_PROCESS,
parentHandle,
(IntPtr)IntPtr.Size,
IntPtr.Zero,
IntPtr.Zero);
if (!success)
{
return false;
}
sInfoEx.StartupInfo.cb = Marshal.SizeOf(sInfoEx);
}
var pSec = new SECURITY_ATTRIBUTES();
var tSec = new SECURITY_ATTRIBUTES();
pSec.nLength = Marshal.SizeOf(pSec);
tSec.nLength = Marshal.SizeOf(tSec);
var lpApplicationName = Path.Combine(Environment.SystemDirectory, "notepad.exe");
return CreateProcess(lpApplicationName, null, ref pSec, ref tSec, false, EXTENDED_STARTUPINFO_PRESENT, IntPtr.Zero, null, ref sInfoEx, out pInfo);
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
struct STARTUPINFOEX
{
public STARTUPINFO StartupInfo;
public IntPtr lpAttributeList;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
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;
}
[StructLayout(LayoutKind.Sequential)]
internal struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public int dwProcessId;
public int dwThreadId;
}
[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES
{
public int nLength;
public IntPtr lpSecurityDescriptor;
public int bInheritHandle;
}
}
ProcessCreator.CreateProcess(0) starts Notepad as a child of current process which is the default behaviour. So far so good.
If the value passed in is non 0 the code attempts to start Notepad as a child of the process whose process ID matches the input value (I am assuming that process exists for now).
Unfortunately that part does not work and throws the following exception:
FatalExecutionEngineError was detected
Message: The runtime has encountered a fatal error. The address of the error was at 0x69a2c7ad, on thread 0x1de0. The error code is 0xc0000005. This error may be a bug in the CLR or in the unsafe or non-verifiable portions of user code. Common sources of this bug include user marshaling errors for COM-interop or PInvoke, which may corrupt the stack.
Any pointers much appreciated.
There are two issues with your code. First, the lpAttributeList parameter of the InitializeProcThreadAttributeList and UpdateProcThreadAttribute functions must be typed as IntPtr with no out modifier. Second, the lpValue parameter of the UpdateProcThreadAttribute function must be a pointer to the attribute value (in your case, parentHandle), not the value itself. Below is the fixed code.
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
public class ProcessCreator
{
[DllImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool CreateProcess(
string lpApplicationName, string lpCommandLine, ref SECURITY_ATTRIBUTES lpProcessAttributes,
ref SECURITY_ATTRIBUTES lpThreadAttributes, bool bInheritHandles, uint dwCreationFlags,
IntPtr lpEnvironment, string lpCurrentDirectory, [In] ref STARTUPINFOEX lpStartupInfo,
out PROCESS_INFORMATION lpProcessInformation);
[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", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private 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 DeleteProcThreadAttributeList(IntPtr lpAttributeList);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool CloseHandle(IntPtr hObject);
public static bool CreateProcess(int parentProcessId)
{
const uint EXTENDED_STARTUPINFO_PRESENT = 0x00080000;
const int PROC_THREAD_ATTRIBUTE_PARENT_PROCESS = 0x00020000;
var pInfo = new PROCESS_INFORMATION();
var sInfoEx = new STARTUPINFOEX();
sInfoEx.StartupInfo.cb = Marshal.SizeOf(sInfoEx);
IntPtr lpValue = IntPtr.Zero;
try
{
if (parentProcessId > 0)
{
var lpSize = IntPtr.Zero;
var success = InitializeProcThreadAttributeList(IntPtr.Zero, 1, 0, ref lpSize);
if (success || lpSize == IntPtr.Zero)
{
return false;
}
sInfoEx.lpAttributeList = Marshal.AllocHGlobal(lpSize);
success = InitializeProcThreadAttributeList(sInfoEx.lpAttributeList, 1, 0, ref lpSize);
if (!success)
{
return false;
}
var parentHandle = Process.GetProcessById(parentProcessId).Handle;
// This value should persist until the attribute list is destroyed using the DeleteProcThreadAttributeList function
lpValue = Marshal.AllocHGlobal(IntPtr.Size);
Marshal.WriteIntPtr(lpValue, parentHandle);
success = UpdateProcThreadAttribute(
sInfoEx.lpAttributeList,
0,
(IntPtr)PROC_THREAD_ATTRIBUTE_PARENT_PROCESS,
lpValue,
(IntPtr)IntPtr.Size,
IntPtr.Zero,
IntPtr.Zero);
if (!success)
{
return false;
}
}
var pSec = new SECURITY_ATTRIBUTES();
var tSec = new SECURITY_ATTRIBUTES();
pSec.nLength = Marshal.SizeOf(pSec);
tSec.nLength = Marshal.SizeOf(tSec);
var lpApplicationName = Path.Combine(Environment.SystemDirectory, "notepad.exe");
return CreateProcess(lpApplicationName, null, ref pSec, ref tSec, false, EXTENDED_STARTUPINFO_PRESENT, IntPtr.Zero, null, ref sInfoEx, out pInfo);
}
finally
{
// Free the attribute list
if (sInfoEx.lpAttributeList != IntPtr.Zero)
{
DeleteProcThreadAttributeList(sInfoEx.lpAttributeList);
Marshal.FreeHGlobal(sInfoEx.lpAttributeList);
}
Marshal.FreeHGlobal(lpValue);
// Close process and thread handles
if (pInfo.hProcess != IntPtr.Zero)
{
CloseHandle(pInfo.hProcess);
}
if (pInfo.hThread != IntPtr.Zero)
{
CloseHandle(pInfo.hThread);
}
}
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
struct STARTUPINFOEX
{
public STARTUPINFO StartupInfo;
public IntPtr lpAttributeList;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
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;
}
[StructLayout(LayoutKind.Sequential)]
internal struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public int dwProcessId;
public int dwThreadId;
}
[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES
{
public int nLength;
public IntPtr lpSecurityDescriptor;
public int bInheritHandle;
}
}