CreateProcessAsUser: Service gets "5: Access Denied" trying to access network share - c#

I use CreateProcessAsUser from a Windows Service in order to launch an application for the current active user. So far it works great with applications on a local drive.
But if the executable exists on a network share, the service generates 5: ERROR_ACCESS_DENIED when I use the full server name (\myserver\path\app.exe). I can also generate 2: ERROR_FILE_NOT_FOUND if I use the mapped drive instead (P:\path\app.exe).
I can launch the application fine from explorer. It really sounds like I cannot get a proper token duplicate as the service fails to properly impersonate me on the server.
I tried several different implementations of CreateProcessAsUser from various posts to no avail. This is brand new (psychedelic) stuff for me, and frankly, I can't wait to get back into .NET :) I guess the offending line is around here:
DuplicateTokenEx(
hUserToken,
(Int32)MAXIMUM_ALLOWED,
ref sa,
(Int32)SECURITY_IMPERSONATION_LEVEL.SecurityIdentification,
(Int32)TOKEN_TYPE.TokenPrimary,
ref hUserTokenDup);
CreateEnvironmentBlock(ref pEnv, hUserTokenDup, true);
Int32 dwCreationFlags = NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT;
PROCESS_INFORMATION pi;
STARTUPINFO si = new STARTUPINFO();
si.cb = Marshal.SizeOf(si);
si.lpDesktop = "winsta0\\default";
CreateProcessAsUser(hUserTokenDup, // client's access token
null, // file to execute
commandLine, // command line
ref sa, // pointer to process SECURITY_ATTRIBUTES
ref sa, // pointer to thread SECURITY_ATTRIBUTES
false, // handles are not inheritable
dwCreationFlags, // creation flags
pEnv, // pointer to new environment block
workingDirectory, // name of current directory
ref si, // pointer to STARTUPINFO structure
out pi); // receives information about new process
Here's the full sample code, I guess it can be useful:
using System;
using System.Text;
using System.Security;
using System.Management;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace Win32
{
public class Win32API
{
[StructLayout(LayoutKind.Sequential)]
struct SECURITY_ATTRIBUTES
{
public Int32 Length;
public IntPtr lpSecurityDescriptor;
public Boolean bInheritHandle;
}
enum TOKEN_TYPE
{
TokenPrimary = 1,
TokenImpersonation = 2
}
[StructLayout(LayoutKind.Sequential)]
struct STARTUPINFO
{
public Int32 cb;
public String lpReserved;
public String lpDesktop;
public String lpTitle;
public UInt32 dwX;
public UInt32 dwY;
public UInt32 dwXSize;
public UInt32 dwYSize;
public UInt32 dwXCountChars;
public UInt32 dwYCountChars;
public UInt32 dwFillAttribute;
public UInt32 dwFlags;
public short wShowWindow;
public short cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
}
[StructLayout(LayoutKind.Sequential)]
struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public UInt32 dwProcessId;
public UInt32 dwThreadId;
}
enum SECURITY_IMPERSONATION_LEVEL
{
SecurityAnonymous = 0,
SecurityIdentification = 1,
SecurityImpersonation = 2,
SecurityDelegation = 3,
}
const UInt32 MAXIMUM_ALLOWED = 0x2000000;
const Int32 CREATE_UNICODE_ENVIRONMENT = 0x00000400;
const Int32 NORMAL_PRIORITY_CLASS = 0x20;
const Int32 CREATE_NEW_CONSOLE = 0x00000010;
[DllImport("kernel32.dll", SetLastError = true)]
static extern Boolean CloseHandle(IntPtr hSnapshot);
[DllImport("kernel32.dll")]
public static extern UInt32 WTSGetActiveConsoleSessionId();
[DllImport("Wtsapi32.dll")]
static extern UInt32 WTSQueryUserToken(UInt32 SessionId, ref IntPtr phToken);
[DllImport("advapi32.dll", EntryPoint = "CreateProcessAsUser", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
extern static Boolean CreateProcessAsUser(
IntPtr hToken,
String lpApplicationName,
String lpCommandLine,
ref SECURITY_ATTRIBUTES lpProcessAttributes,
ref SECURITY_ATTRIBUTES lpThreadAttributes,
Boolean bInheritHandle,
Int32 dwCreationFlags,
IntPtr lpEnvironment,
String lpCurrentDirectory,
ref STARTUPINFO lpStartupInfo,
out PROCESS_INFORMATION lpProcessInformation);
[DllImport("advapi32.dll", EntryPoint = "DuplicateTokenEx")]
extern static Boolean DuplicateTokenEx(
IntPtr ExistingTokenHandle,
UInt32 dwDesiredAccess,
ref SECURITY_ATTRIBUTES lpThreadAttributes,
Int32 TokenType,
Int32 ImpersonationLevel,
ref IntPtr DuplicateTokenHandle);
[DllImport("userenv.dll", SetLastError = true)]
static extern Boolean CreateEnvironmentBlock(
ref IntPtr lpEnvironment,
IntPtr hToken,
Boolean bInherit);
[DllImport("userenv.dll", SetLastError = true)]
static extern Boolean DestroyEnvironmentBlock(IntPtr lpEnvironment);
/// <summary>
/// Creates the process in the interactive desktop with credentials of the logged in user.
/// </summary>
public static Boolean CreateProcessAsUser(String commandLine, String workingDirectory, out StringBuilder output)
{
Boolean processStarted = false;
output = new StringBuilder();
try
{
UInt32 dwSessionId = WTSGetActiveConsoleSessionId();
output.AppendLine(String.Format("Active console session Id: {0}", dwSessionId));
IntPtr hUserToken = IntPtr.Zero;
WTSQueryUserToken(dwSessionId, ref hUserToken);
SECURITY_ATTRIBUTES sa = new SECURITY_ATTRIBUTES();
sa.Length = Marshal.SizeOf(sa);
IntPtr hUserTokenDup = IntPtr.Zero;
DuplicateTokenEx(
hUserToken,
(Int32)MAXIMUM_ALLOWED,
ref sa,
(Int32)SECURITY_IMPERSONATION_LEVEL.SecurityIdentification,
(Int32)TOKEN_TYPE.TokenPrimary,
ref hUserTokenDup);
if (hUserTokenDup != IntPtr.Zero)
{
output.AppendLine(String.Format("DuplicateTokenEx() OK (hToken: {0})", hUserTokenDup));
Int32 dwCreationFlags = NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE;
IntPtr pEnv = IntPtr.Zero;
if (CreateEnvironmentBlock(ref pEnv, hUserTokenDup, true))
{
dwCreationFlags |= CREATE_UNICODE_ENVIRONMENT;
output.AppendLine(String.Format("CreateEnvironmentBlock() success."));
}
else
{
output.AppendLine(String.Format("CreateEnvironmentBlock() FAILED (Last Error: {0})", Marshal.GetLastWin32Error()));
pEnv = IntPtr.Zero;
}
// Launch the process in the client's logon session.
PROCESS_INFORMATION pi;
STARTUPINFO si = new STARTUPINFO();
si.cb = Marshal.SizeOf(si);
si.lpDesktop = "winsta0\\default";
output.AppendLine(String.Format("CreateProcess (Path:{0}, CurrDir:{1})", commandLine, workingDirectory));
if (CreateProcessAsUser(hUserTokenDup, // client's access token
null, // file to execute
commandLine, // command line
ref sa, // pointer to process SECURITY_ATTRIBUTES
ref sa, // pointer to thread SECURITY_ATTRIBUTES
false, // handles are not inheritable
dwCreationFlags, // creation flags
pEnv, // pointer to new environment block
workingDirectory, // name of current directory
ref si, // pointer to STARTUPINFO structure
out pi // receives information about new process
))
{
processStarted = true;
output.AppendLine(String.Format("CreateProcessAsUser() OK (PID: {0})", pi.dwProcessId));
}
else
{
output.AppendLine(String.Format("CreateProcessAsUser() failed (Last Error: {0})", Marshal.GetLastWin32Error()));
}
if (DestroyEnvironmentBlock(pEnv))
{
output.AppendLine("DestroyEnvironmentBlock: Success");
}
else
{
output.AppendLine(String.Format("DestroyEnvironmentBlock() failed (Last Error: {0})", Marshal.GetLastWin32Error()));
}
}
else
{
output.AppendLine(String.Format("DuplicateTokenEx() failed (Last Error: {0})", Marshal.GetLastWin32Error()));
}
CloseHandle(hUserTokenDup);
CloseHandle(hUserToken);
}
catch (Exception ex)
{
output.AppendLine("Exception occurred: " + ex.Message);
}
return processStarted;
}
}
}
It works great with local executables like this:
StringBuilder result = new StringBuilder();
Win32API.CreateProcessAsUser(#"C:\Windows\notepad.exe", #"C:\Windows\", out result);
My question: What needs to be tuned in order to properly access a network share using a duplicate token?

When you use this against a share that allows guest access (i.e. no username/password), the command works correctly, but when you use it against a share that requires authentication to use it doesn't work.
UI invocations get the redirector involved, which automatically establishes the connection to the remote server which is needed for the execution.
A workaround, mind you, but not a real solution is to use a cmd based relay to get to the executable, so for the command line you make it something like:
CreateProcessAsUser(#"cmd /c ""start \\server\share\binary.exe""", #"C:\Windows", out result);
Then change the startupinfo to SW_HIDE the cmd window using:
si.cb = Marshal.SizeOf(si);
si.lpDesktop = "winsta0\\default";
si.dwFlags = 0x1; // STARTF_USESHOWWINDOW
si.wShowWindow = 0; // SW_HIDE
The cmd invocation is the bit of shim to get completely into the user's environment before starting the command - this will take advantage of all credentials for accessing the server.
Mind you, you will probably have to have a little bit of logic to prevent the SW_HIDE for directly invoked applications (e.g. check for cmd at the start of the commandLine string?)

Related

Changing user name of a Windows service in C# [duplicate]

This question already has an answer here:
When calling Windows API functions from C#, which source for signatures to trust: .NET Framework source code or PInvoke?
(1 answer)
Closed 6 months ago.
I'm trying to change user name and password of a Windows service in C# via WinAPI calls. I have created the service myself using WinAPI's CreateService(), based off of the code in this Stack Overflow answer.
If I use the same credentials in a call to ChangeServiceConfigA(), I'm told that user name and password are incorrect. I can change everything else with ChangeServiceConfigA(), even the password, but once I try to set user name and password, I get said error.
My code looks like this:
private const uint SERVICE_NO_CHANGE = 0xffffffff;
[Flags]
private enum ServiceAccessRights
{
QueryConfig = 0x1,
ChangeConfig = 0x2,
QueryStatus = 0x4,
EnumerateDependants = 0x8,
Start = 0x10,
Stop = 0x20,
PauseContinue = 0x40,
Interrogate = 0x80,
UserDefinedControl = 0x100,
Delete = 0x00010000,
StandardRightsRequired = 0xF0000,
AllAccess = StandardRightsRequired | QueryConfig | ChangeConfig |
QueryStatus | EnumerateDependants | Start | Stop | PauseContinue |
Interrogate | UserDefinedControl
}
private enum ServiceBootFlag : UInt32
{
Start = 0x00000000,
SystemStart = 0x00000001,
AutoStart = 0x00000002,
DemandStart = 0x00000003,
Disabled = 0x00000004,
ServiceNoChange = SERVICE_NO_CHANGE
}
private enum ServiceError : UInt32
{
Ignore = 0x00000000,
Normal = 0x00000001,
Severe = 0x00000002,
Critical = 0x00000003,
ServiceNoChange = SERVICE_NO_CHANGE
}
private enum ServiceType : UInt32
{
ServiceFileSystemDriver = 0x00000002,
ServiceKernelDrvier = 0x00000001,
ServiceWin32OwnProcess = 0x00000010,
ServiveWin32ShareProcess = 0x00000020,
ServiceNoChange = SERVICE_NO_CHANGE
}
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
private static extern IntPtr CreateService(IntPtr hSCManager, string lpServiceName, string lpDisplayName, ServiceAccessRights dwDesiredAccess, int dwServiceType, ServiceBootFlag dwStartType, ServiceError dwErrorControl, string lpBinaryPathName, string? lpLoadOrderGroup, IntPtr lpdwTagId, string? lpDependencies, string? lpServiceStartName, string? lpPassword);
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
private static extern bool ChangeServiceConfigA(IntPtr hService, ServiceType dwServiceType, ServiceBootFlag dwStartType, ServiceError dwErrorControl, string? lpBinaryPathName, string? lpLoadOrderGroup, IntPtr lpdwTagId, string? lpDependencies, string? lpServiceStartName, string? lpPassword, string? lpDisplayName);
public static bool ChangeService(string serviceName, string? userName, string? password)
{
IntPtr scm = OpenSCManager(ScmAccessRights.Connect);
try
{
IntPtr service = OpenService(scm, serviceName, ServiceAccessRights.ChangeConfig);
if (service == IntPtr.Zero)
return false;
if (!ChangeServiceConfigA(service,
ServiceType.ServiceNoChange,
ServiceBootFlag.ServiceNoChange,
ServiceError.ServiceNoChange,
null, null, IntPtr.Zero, null,
userName, password, null))
throw new ApplicationException(":( sadface");
CloseServiceHandle(service);
return true;
}
finally
{
CloseServiceHandle(scm);
}
}
Argh, I found the answer by accident when doing the other WinAPI thing I meant to do:
QueryServiceConfigA() uses ANSI strings (while QueryServiceConfigW() uses Unicode), but most importanly, QueryServiceConfig() is an alias that automatically chooses the right one.
Declared like this, it works like a charm:
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
private static extern bool ChangeServiceConfig(IntPtr hService, ServiceType dwServiceType, ServiceBootFlag dwStartType, ServiceError dwErrorControl, string? lpBinaryPathName, string? lpLoadOrderGroup, IntPtr lpdwTagId, string? lpDependencies, string? lpServiceStartName, string? lpPassword, string? lpDisplayName);

MemoryMappedFileSecurity missing in .NET 6

I have a service that makes use of MemoryMappedFiles for interprocess communication. It has worked great for many years and was developed in .NET Framework 4.6.1. Now comes the time to port the code to .NET 6. I've gotten the bulk of it to work correctly except for one issue: the security ACL for the memory mapped file. That argument seems to have disappeared in .NET 6.
Here is a snippet from the 4.6.1 Framework version
fs = new FileStream(FileName, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite);
FileSecurity fSec = File.GetAccessControl(FileName);
fSec.AddAccessRule(new FileSystemAccessRule("everyone", FileSystemRights.FullControl, AccessControlType.Allow));
File.SetAccessControl(FileName, fSec);
if (fs.Length == 0)
fs.SetLength(_SectionSize);
long fLen = fs.Length;
MemoryMappedFileSecurity security = new MemoryMappedFileSecurity();
security.AddAccessRule(new AccessRule<MemoryMappedFileRights>("everyone", MemoryMappedFileRights.FullControl, AccessControlType.Allow));
//Name = #"Global\DCCCache"; // "Global\" when running as a service so session 0 stuff available to everyone
_MMFHandle = MemoryMappedFile.CreateFromFile(fs, Name, _SectionSize, MemoryMappedFileAccess.ReadWrite, security, HandleInheritability.Inheritable, false);
_VAHandle = _MMFHandle.CreateViewAccessor();
This all works and allows non-admin user processes access to the memory mapped file.
.NET 6 drops the security argument from the .CreateFromFile method. As a result, only processes running with Administrator privileges have access to the memory mapped file. An "Access Denied" IO exception is thrown from the OpenExisting method of MemoryMappedFile for non-admin processes.
Is there a way to modify the security when I create a memory mapped file so non-admin processes have access?
I did a workaround. Put together the call I needed to create the memory mapped file with the correct security and call it once in the service that owns it. I changed the mainline code to use OpenExisting after the call to the routine below so the remainder of the code can use the .NET 6 library as intended. Not ideal, but it fixed the issue.
using System.ComponentModel;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
namespace MMFService {
internal class MMFNet6Shim : IDisposable {
private bool win32Result = false;
private int cbSid = SECURITY_MAX_SID_SIZE;
private SECURITY_ATTRIBUTES securityAttributes = new SECURITY_ATTRIBUTES();
private SafeMemoryMappedFileHandle hFile;
private const int SDDL_REVISION_1 = 1;
private const int SECURITY_MAX_SID_SIZE = 68;
private const int PAGE_READWRITE = 0x04;
private const int FILE_MAP_WRITE = 0X02;
public MMFNet6Shim(FileStream fs, string DBName) {
win32Result = ConvertStringSecurityDescriptorToSecurityDescriptor("D:(A;OICI;GA;;;WD)", SDDL_REVISION_1, out securityAttributes.lpSecurityDescriptor, IntPtr.Zero);
if (!win32Result)
throw new Exception("ConvertStringSecurityDescriptorToSecurityDescriptor", new Win32Exception(Marshal.GetLastWin32Error()));
securityAttributes.nLength = Marshal.SizeOf(securityAttributes);
securityAttributes.bInheritHandle = false;
long fLen = fs.Length;
hFile = CreateFileMapping(fs.SafeFileHandle, ref securityAttributes, PAGE_READWRITE, 0, Convert.ToInt32(fLen), DBName);
if (hFile.IsInvalid)
throw new Exception("CreateFileMapping", new Win32Exception(Marshal.GetLastWin32Error()));
}
public void Dispose() {
if(!hFile.IsInvalid)
hFile.Close();
}
[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES {
public int nLength;
public IntPtr lpSecurityDescriptor;
public bool bInheritHandle;
}
[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern bool ConvertStringSecurityDescriptorToSecurityDescriptor
(
[In] string StringSecurityDescriptor,
[In] int StringSDRevision,
[Out] out IntPtr SecurityDescriptor,
[Out] IntPtr SecurityDescriptorSize
);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr LocalFree([In] IntPtr hMem);
[DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern SafeMemoryMappedFileHandle CreateFileMapping(
[In] SafeFileHandle hFile,
[In][Optional] ref SECURITY_ATTRIBUTES lpAttributes,
[In] int flProtect,
[In] int dwMaximumSizeHigh,
[In] int dwMaximumSizeLow,
[In][Optional] string lpName
);
}
}
The easiest way is to set access for Everyone:
using System.Runtime.InteropServices;
[DllImport("advapi32.dll", EntryPoint = "SetSecurityInfo", CallingConvention = CallingConvention.Winapi,
SetLastError = true, ExactSpelling = true, CharSet = CharSet.Unicode)]
private static extern uint SetSecurityInfoByHandle(SafeHandle handle, uint objectType, uint securityInformation,
byte[]? owner, byte[]? group, byte[]? dacl, byte[]? sacl);
then
mmfile = MemoryMappedFile.CreateNew("Global\\jdfghdfghsd", requiredsize, MemoryMappedFileAccess.ReadWrite);
if (SetSecurityInfoByHandle(mmfile.SafeMemoryMappedFileHandle, 1, 4, null, null, null, null) != 0)
throw new Exception("MemoryMappedFile set security failed");
Set customized access rights if required, look at SetSecurityInfo help
So, sample apps:
App1:
using System.IO.MemoryMappedFiles;
using System.Runtime.InteropServices;
[DllImport("advapi32.dll", EntryPoint = "SetSecurityInfo", CallingConvention = CallingConvention.Winapi,
SetLastError = true, ExactSpelling = true, CharSet = CharSet.Unicode)]
static extern uint SetSecurityInfoByHandle(SafeHandle handle, uint objectType, uint securityInformation,
byte[]? owner, byte[]? group, byte[]? dacl, byte[]? sacl);
var mmfile = MemoryMappedFile.CreateNew("Global\\dsfgsdfsdf", 4, MemoryMappedFileAccess.ReadWrite);
if (SetSecurityInfoByHandle(mmfile.SafeMemoryMappedFileHandle, 1, 4, null, null, null, null) != 0)
throw new Exception("Access rights set up failed");
mmfile.CreateViewAccessor(0, 4).Write(0, 234);
Console.ReadLine();
App2:
using System.IO.MemoryMappedFiles;
Console.WriteLine(MemoryMappedFile.OpenExisting("Global\\dsfgsdfsdf", MemoryMappedFileRights.ReadWrite).CreateViewAccessor(0, 4).ReadInt32(0));
Console.ReadLine();
Run app1, then app2. It should emit 234. Then press Enter for app1.
Be sure the user runs app1 is able to create the global objects (by default only administrators and services are allowed)

Setting PIN prompt in Smart Card Crypto Provider's dialog box

I want to change the text that shows when invoking CryptoApi operation that requires smart card PIN. Current prompt is pretty generic (and in system's language), "Please enter your authentication PIN":
This dialog shows when calling CryptSignMessage in COM object, but the call is made from C# WPF desktop app (.NET 4.5). How can I customize the dialog? I've found PP_PIN_PROMPT_STRING parameter for CryptSetProvParam function, but the function requires HCRYPTPROV and I don't have that handle. All I have is reader's name and signing certificate. Just can't wrap my head around it.
Is it possible to customize PIN dialog from either C++ or C# (preferably C#)?
I believe the following should work. As I don't have anything setup to test collecting the information I can't verify.
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CryptAcquireContext(out IntPtr phProv, string pszContainer, string pszProvider, uint dwProvType, uint dwFlags);
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool CryptSetProvParam(IntPtr hProv, uint dwParam, [In] byte[] pbData, uint dwFlags);
[DllImport("advapi32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CryptReleaseContext(IntPtr hProv, uint dwFlags);
const string MS_DEF_PROV = "Microsoft Base Cryptographic Provider v1.0";
const uint NTE_BAD_KEYSET = 0x80090016;
const uint PROV_RSA_FULL = 1;
const uint CRYPT_VERIFYCONTEXT = 0xF0000000;
const uint CRYPT_NEWKEYSET = 0x00000008;
const uint PP_PIN_PROMPT_STRING = 0x2C;
public void SetPinText(string text)
{
byte[] data = Encoding.UTF8.GetBytes(text);
IntPtr hCryptProv = IntPtr.Zero;
try
{
if (!CryptAcquireContext(out hCryptProv, null, MS_DEF_PROV, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
{
if (Convert.ToUInt32(Marshal.GetLastWin32Error()) == NTE_BAD_KEYSET)
{
if (!CryptAcquireContext(out hCryptProv, null, null, PROV_RSA_FULL, CRYPT_NEWKEYSET))
throw new Exception("Unable to acquire crypt context.");
}
else
{
throw new Exception("Unable to acquire crypt context.");
}
}
if (!CryptSetProvParam(hCryptProv, PP_PIN_PROMPT_STRING, data, 0))
throw new Exception("Unable to set prompt string.");
}
finally
{
if (hCryptProv != IntPtr.Zero)
{
CryptReleaseContext(hCryptProv, 0);
}
}
}

Enabling SE_DEBUG_NAME in c#

I am currently developing a C# application that is used to monitor few other processes. I want to take the dumps of these processes occasionally. For this, currently I am PInvoking MiniDumpWriteDump() function. I have both a command line utility and a graphical utility, which PInvokes the MiniDumpWriteDump() function. The problem I am currently facing is that I get perfect memory dumps when using the command line utility, but from the graphical utility that calls the same function, it creates an empty file every time( even when run as administrator ).
The GetLastWin32Error() gives 0x80000057 - Parameter is invalid, but I am passing exactly the same values to my PInvoked function in both cases. So I am suspecting the invalid value error is from some call the MiniDumpWriteDump() is making internally and my best guess is that this is a process privilege issue. I would like to know a few things here :
Is there any difference in privileges between a command line utility and a graphical utility by default on launch?
If its not a privilege issue, what can be the possible causes of the problem I am faced with?
If it is a problem with privileges, is there any way of enabling SE_DEBUG_NAME privilege from my utility using C# ( without PInvoking AdjustTokenPrivileges() and associated functions ) ?
Here is My PInvoke declaration :
public enum Typ : uint
{
// From dbghelp.h:
MiniDumpNormal = 0x00000000,
MiniDumpWithDataSegs = 0x00000001,
MiniDumpWithFullMemory = 0x00000002,
MiniDumpWithHandleData = 0x00000004,
MiniDumpFilterMemory = 0x00000008,
MiniDumpScanMemory = 0x00000010,
MiniDumpWithUnloadedModules = 0x00000020,
MiniDumpWithIndirectlyReferencedMemory = 0x00000040,
MiniDumpFilterModulePaths = 0x00000080,
MiniDumpWithProcessThreadData = 0x00000100,
MiniDumpWithPrivateReadWriteMemory = 0x00000200,
MiniDumpWithoutOptionalData = 0x00000400,
MiniDumpWithFullMemoryInfo = 0x00000800,
MiniDumpWithThreadInfo = 0x00001000,
MiniDumpWithCodeSegs = 0x00002000,
MiniDumpWithoutAuxiliaryState = 0x00004000,
MiniDumpWithFullAuxiliaryState = 0x00008000,
MiniDumpWithPrivateWriteCopyMemory = 0x00010000,
MiniDumpIgnoreInaccessibleMemory = 0x00020000,
MiniDumpWithTokenInformation = 0x00040000,
MiniDumpValidTypeFlags = 0x0003ffff,
};
[DllImport("dbghelp.dll",
EntryPoint = "MiniDumpWriteDump",
CallingConvention = CallingConvention.StdCall,
CharSet = CharSet.Unicode,
ExactSpelling = true, SetLastError = true)]
static extern bool MiniDumpWriteDump(
IntPtr hProcess,
uint processId,
IntPtr hFile,
uint dumpType,
IntPtr expParam,
IntPtr userStreamParam,
IntPtr callbackParam);
public static bool Write(string fileName, IntPtr prochandle, uint procid)
{
return Write(fileName, (Typ.MiniDumpWithFullMemory|Typ.MiniDumpWithDataSegs|Typ.MiniDumpWithHandleData|Typ.MiniDumpWithThreadInfo|Typ.MiniDumpWithTokenInformation), prochandle, procid);
}
public static bool Write(string fileName, Typ dumpTyp, IntPtr prochandle, uint procid)
{
using (var fs = new System.IO.FileStream(fileName, System.IO.FileMode.Create, System.IO.FileAccess.Write, System.IO.FileShare.None))
{
bool bRet = MiniDumpWriteDump(
prochandle,
procid,
fs.SafeFileHandle.DangerousGetHandle(),
(uint)dumpTyp,
IntPtr.Zero,
IntPtr.Zero,
IntPtr.Zero);
if (!bRet)
{
throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
}
return bRet;
}
}
}
Here is how I call it (using the same function dump() from both console and GUI application):
void dump(procId, filePath)
{
Process p = Process.GetProcessById(procId);
MiniDumper.Write(filePath, p.Handle, (uint)p.Id);
}

.NET Start Process with higher rights

I am trying to execute a program with admin rights through a C# application, which gets invoked with user rights only.
Code
ProcessStartInfo psi;
try
{
psi = new ProcessStartInfo(#"WINZIP32.EXE");
psi.UseShellExecute = false;
SecureString pw = new SecureString();
pw.AppendChar('p');
pw.AppendChar('a');
pw.AppendChar('s');
pw.AppendChar('s');
pw.AppendChar('w');
pw.AppendChar('o');
pw.AppendChar('r');
pw.AppendChar('d');
psi.Password = pw;
psi.UserName = "administrator";
Process.Start(psi);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
It does start winzip, but only with user rights. Is there something I am doing wrong or is it even possible to start a process with higher rights?
thank you!
Edit:
Here is the reason behind the question, maybe it helps to understand what i actually need.
I used winzip for example to get a general idea what's incorrect with my code. The actual problem is, our company uses 2 versions of a program. But before you start any of the versions you need to import a dll file with regsvr32 (with admin rights). Now I would like to write a program that let the user select the version, import the dll and starts the correct application.
You need to set ProcessStartInfo.UseShellExecute to true and ProcessStartInfo.Verb to runas:
Process process = null;
ProcessStartInfo processStartInfo = new ProcessStartInfo();
processStartInfo.FileName = "WINZIP32.EXE";
processStartInfo.Verb = "runas";
processStartInfo.WindowStyle = ProcessWindowStyle.Normal;
processStartInfo.UseShellExecute = true;
process = Process.Start(processStartInfo);
This will cause the application to run as the administrator. UAC will however prompt the user to confirm. If this is not desirable then you'll need to add a manifest to permanently elevate the host process privilages.
You can run a process as another user(even an administrator) using the CreateProcessAsUser function (Win32 API).
CreateProcessAsUser accepts a user token as the first parameter, that is an impersonation token.
You have to use DLLImport to load the function from the Windows DLL.
Take a look at this example implementation that i have used in one of my projects :
[StructLayout(LayoutKind.Sequential)]
internal struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public uint dwProcessId;
public uint dwThreadId;
}
[StructLayout(LayoutKind.Sequential)]
internal struct SECURITY_ATTRIBUTES
{
public uint nLength;
public IntPtr lpSecurityDescriptor;
public bool bInheritHandle;
}
[StructLayout(LayoutKind.Sequential)]
public struct STARTUPINFO
{
public uint cb;
public string lpReserved;
public string lpDesktop;
public string lpTitle;
public uint dwX;
public uint dwY;
public uint dwXSize;
public uint dwYSize;
public uint dwXCountChars;
public uint dwYCountChars;
public uint dwFillAttribute;
public uint dwFlags;
public short wShowWindow;
public short cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
}
internal enum SECURITY_IMPERSONATION_LEVEL
{
SecurityAnonymous,
SecurityIdentification,
SecurityImpersonation,
SecurityDelegation
}
internal enum TOKEN_TYPE
{
TokenPrimary = 1,
TokenImpersonation
}
public class ProcessAsUser
{
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool CreateProcessAsUser(
IntPtr hToken,
string lpApplicationName,
string lpCommandLine,
ref SECURITY_ATTRIBUTES lpProcessAttributes,
ref SECURITY_ATTRIBUTES lpThreadAttributes,
bool bInheritHandles,
uint dwCreationFlags,
IntPtr lpEnvironment,
string lpCurrentDirectory,
ref STARTUPINFO lpStartupInfo,
out PROCESS_INFORMATION lpProcessInformation);
[DllImport("advapi32.dll", EntryPoint = "DuplicateTokenEx", SetLastError = true)]
private static extern bool DuplicateTokenEx(
IntPtr hExistingToken,
uint dwDesiredAccess,
ref SECURITY_ATTRIBUTES lpThreadAttributes,
Int32 ImpersonationLevel,
Int32 dwTokenType,
ref IntPtr phNewToken);
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool OpenProcessToken(
IntPtr ProcessHandle,
UInt32 DesiredAccess,
ref IntPtr TokenHandle);
[DllImport("userenv.dll", SetLastError = true)]
private static extern bool CreateEnvironmentBlock(
ref IntPtr lpEnvironment,
IntPtr hToken,
bool bInherit);
[DllImport("userenv.dll", SetLastError = true)]
private static extern bool DestroyEnvironmentBlock(
IntPtr lpEnvironment);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool CloseHandle(
IntPtr hObject);
private const short SW_SHOW = 5;
private const uint TOKEN_QUERY = 0x0008;
private const uint TOKEN_DUPLICATE = 0x0002;
private const uint TOKEN_ASSIGN_PRIMARY = 0x0001;
private const int GENERIC_ALL_ACCESS = 0x10000000;
private const int STARTF_USESHOWWINDOW = 0x00000001;
private const int STARTF_FORCEONFEEDBACK = 0x00000040;
private const uint CREATE_UNICODE_ENVIRONMENT = 0x00000400;
private const int STARTF_RUNFULLSCREEN = 0x00000020;
private static bool LaunchProcessAsUser(string cmdLine, IntPtr token, IntPtr envBlock)
{
bool result = false;
PROCESS_INFORMATION pi = new PROCESS_INFORMATION();
SECURITY_ATTRIBUTES saProcess = new SECURITY_ATTRIBUTES();
SECURITY_ATTRIBUTES saThread = new SECURITY_ATTRIBUTES();
saProcess.nLength = (uint)Marshal.SizeOf(saProcess);
saThread.nLength = (uint)Marshal.SizeOf(saThread);
STARTUPINFO si = new STARTUPINFO();
si.cb = (uint)Marshal.SizeOf(si);
//if this member is NULL, the new process inherits the desktop
//and window station of its parent process. If this member is
//an empty string, the process does not inherit the desktop and
//window station of its parent process; instead, the system
//determines if a new desktop and window station need to be created.
//If the impersonated user already has a desktop, the system uses the
//existing desktop.
si.lpDesktop = #"WinSta0\Default"; //Modify as needed
si.dwFlags = STARTF_USESHOWWINDOW | STARTF_FORCEONFEEDBACK;
si.wShowWindow = SW_SHOW;
//Set other si properties as required.
result = CreateProcessAsUser(
token,
null,
cmdLine,
ref saProcess,
ref saThread,
false,
CREATE_UNICODE_ENVIRONMENT,
envBlock,
null,
ref si,
out pi);
if (result == false)
{
int error = Marshal.GetLastWin32Error();
string message = String.Format("CreateProcessAsUser Error: {0}", error);
Debug.WriteLine(message);
}
return result;
}
/// <summary>
/// LaunchProcess As User Overloaded for Window Mode
/// </summary>
/// <param name="cmdLine"></param>
/// <param name="token"></param>
/// <param name="envBlock"></param>
/// <param name="WindowMode"></param>
/// <returns></returns>
private static bool LaunchProcessAsUser(string cmdLine, IntPtr token, IntPtr envBlock,uint WindowMode)
{
bool result = false;
PROCESS_INFORMATION pi = new PROCESS_INFORMATION();
SECURITY_ATTRIBUTES saProcess = new SECURITY_ATTRIBUTES();
SECURITY_ATTRIBUTES saThread = new SECURITY_ATTRIBUTES();
saProcess.nLength = (uint)Marshal.SizeOf(saProcess);
saThread.nLength = (uint)Marshal.SizeOf(saThread);
STARTUPINFO si = new STARTUPINFO();
si.cb = (uint)Marshal.SizeOf(si);
//if this member is NULL, the new process inherits the desktop
//and window station of its parent process. If this member is
//an empty string, the process does not inherit the desktop and
//window station of its parent process; instead, the system
//determines if a new desktop and window station need to be created.
//If the impersonated user already has a desktop, the system uses the
//existing desktop.
si.lpDesktop = #"WinSta0\Default"; //Default Vista/7 Desktop Session
si.dwFlags = STARTF_USESHOWWINDOW | STARTF_FORCEONFEEDBACK;
//Check the Startup Mode of the Process
if (WindowMode == 1)
si.wShowWindow = SW_SHOW;
else if (WindowMode == 2)
{ //Do Nothing
}
else if (WindowMode == 3)
si.wShowWindow = 0; //Hide Window
else if (WindowMode == 4)
si.wShowWindow = 3; //Maximize Window
else if (WindowMode == 5)
si.wShowWindow = 6; //Minimize Window
else
si.wShowWindow = SW_SHOW;
//Set other si properties as required.
result = CreateProcessAsUser(
token,
null,
cmdLine,
ref saProcess,
ref saThread,
false,
CREATE_UNICODE_ENVIRONMENT,
envBlock,
null,
ref si,
out pi);
if (result == false)
{
int error = Marshal.GetLastWin32Error();
string message = String.Format("CreateProcessAsUser Error: {0}", error);
Debug.WriteLine(message);
}
return result;
}
private static IntPtr GetPrimaryToken(int processId)
{
IntPtr token = IntPtr.Zero;
IntPtr primaryToken = IntPtr.Zero;
bool retVal = false;
Process p = null;
try
{
p = Process.GetProcessById(processId);
}
catch (ArgumentException)
{
string details = String.Format("ProcessID {0} Not Available", processId);
Debug.WriteLine(details);
throw;
}
//Gets impersonation token
retVal = OpenProcessToken(p.Handle, TOKEN_DUPLICATE, ref token);
if (retVal == true)
{
SECURITY_ATTRIBUTES sa = new SECURITY_ATTRIBUTES();
sa.nLength = (uint)Marshal.SizeOf(sa);
//Convert the impersonation token into Primary token
retVal = DuplicateTokenEx(
token,
TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_QUERY,
ref sa,
(int)SECURITY_IMPERSONATION_LEVEL.SecurityIdentification,
(int)TOKEN_TYPE.TokenPrimary,
ref primaryToken);
//Close the Token that was previously opened.
CloseHandle(token);
if (retVal == false)
{
string message = String.Format("DuplicateTokenEx Error: {0}", Marshal.GetLastWin32Error());
Debug.WriteLine(message);
}
}
else
{
string message = String.Format("OpenProcessToken Error: {0}", Marshal.GetLastWin32Error());
Debug.WriteLine(message);
}
//We'll Close this token after it is used.
return primaryToken;
}
private static IntPtr GetEnvironmentBlock(IntPtr token)
{
IntPtr envBlock = IntPtr.Zero;
bool retVal = CreateEnvironmentBlock(ref envBlock, token, false);
if (retVal == false)
{
//Environment Block, things like common paths to My Documents etc.
//Will not be created if "false"
//It should not adversley affect CreateProcessAsUser.
string message = String.Format("CreateEnvironmentBlock Error: {0}", Marshal.GetLastWin32Error());
Debug.WriteLine(message);
}
return envBlock;
}
public static bool Launch(string appCmdLine /*,int processId*/)
{
bool ret = false;
//Either specify the processID explicitly
//Or try to get it from a process owned by the user.
//In this case assuming there is only one explorer.exe
Process[] ps = Process.GetProcessesByName("explorer");
int processId = -1;//=processId
if (ps.Length > 0)
{
processId = ps[0].Id;
}
if (processId > 1)
{
IntPtr token = GetPrimaryToken(processId);
if (token != IntPtr.Zero)
{
IntPtr envBlock = GetEnvironmentBlock(token);
ret = LaunchProcessAsUser(appCmdLine, token, envBlock);
if (envBlock != IntPtr.Zero)
DestroyEnvironmentBlock(envBlock);
CloseHandle(token);
}
}
return ret;
}
Ref:
How to start a Process as administrator mode in C#
Elevating process privilege programmatically?
var psi = new ProcessStartInfo
{
FileName = "notepad",
UserName = "admin",
Domain = "",
Password = pass,
UseShellExecute = true,
RedirectStandardOutput = true,
RedirectStandardError = true,
Verb = "runas";
};
Process.Start(psi);
//
var pass = new SecureString();
pass.AppendChar('s');
pass.AppendChar('e');
pass.AppendChar('c');
pass.AppendChar('r');
pass.AppendChar('e');
pass.AppendChar('t');
Process.Start("notepad", "admin", pass, "");
//Vista or higher check
if (System.Environment.OSVersion.Version.Major >= 6)
{
p.StartInfo.Verb = "runas";
}
Ref: How to run/start a new process with admin rights? ASP.net Forum
Another way is to impersonate the admin user. You can do this by
calling the Logon function and impersonate the user whose token you
will get then. For impersonating a user in code, look at:
WindowsImpersonationContext
Class
. Use the check
http://www.csharpfriends.com/Forums/ShowPost.aspx?PostID=31611 for
GetCurrentUser to see whether impersonation succeeded.
Code Snippet:
System.Diagnostics.Process process = null;
System.Diagnostics.ProcessStartInfo processStartInfo;
processStartInfo = new System.Diagnostics.ProcessStartInfo();
processStartInfo.FileName = "regedit.exe";
if (System.Environment.OSVersion.Version.Major >= 6) // Windows Vista or higher
{
processStartInfo.Verb = "runas";
}
else
{
// No need to prompt to run as admin
}
processStartInfo.Arguments = "";
processStartInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Normal;
processStartInfo.UseShellExecute = true;
try
{
process = System.Diagnostics.Process.Start(processStartInfo);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
finally
{
if (process != null)
{
process.Dispose();
}
}
//Try this with administrator login, i have not tested yet..
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassword,
int dwLogonType, int dwLogonProvider, out SafeTokenHandle phToken);
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
public extern static bool CloseHandle(IntPtr handle);
// Test harness.
// If you incorporate this code into a DLL, be sure to demand FullTrust.
[PermissionSetAttribute(SecurityAction.Demand, Name = "FullTrust")]
private void button1_Click(object sender, EventArgs e)
{
SafeTokenHandle safeTokenHandle;
const int LOGON32_PROVIDER_DEFAULT = 0;
//This parameter causes LogonUser to create a primary token.
const int LOGON32_LOGON_INTERACTIVE = 2;
bool returnValue = LogonUser("administrator", "", "password",
LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT,
out safeTokenHandle);
Console.WriteLine("LogonUser called.");
if (false == returnValue)
{
int ret = Marshal.GetLastWin32Error();
Console.WriteLine("LogonUser failed with error code : {0}", ret);
throw new System.ComponentModel.Win32Exception(ret);
}
using (safeTokenHandle)
{
Console.WriteLine("Did LogonUser Succeed? " + (returnValue ? "Yes" : "No"));
Console.WriteLine("Value of Windows NT token: " + safeTokenHandle);
// Check the identity.
Console.WriteLine("Before impersonation: "
+ WindowsIdentity.GetCurrent().Name);
// Use the token handle returned by LogonUser.
WindowsIdentity newId = new WindowsIdentity(safeTokenHandle.DangerousGetHandle());
using (WindowsImpersonationContext impersonatedUser = newId.Impersonate())
{
System.Diagnostics.Process process = null;
System.Diagnostics.ProcessStartInfo processStartInfo;
processStartInfo = new System.Diagnostics.ProcessStartInfo();
processStartInfo.FileName = "regedit.exe";
//if (System.Environment.OSVersion.Version.Major >= 6) // Windows Vista or higher
//{
// processStartInfo.Verb = "runas";
//}
//else
//{
// // No need to prompt to run as admin
//}
processStartInfo.Arguments = "";
processStartInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Normal;
processStartInfo.UseShellExecute = true;
try
{
process = System.Diagnostics.Process.Start(processStartInfo);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
finally
{
if (process != null)
{
process.Dispose();
}
}
// Check the identity.
Console.WriteLine("After impersonation: "
+ WindowsIdentity.GetCurrent().Name);
}
// Releasing the context object stops the impersonation
// Check the identity.
Console.WriteLine("After closing the context: " + WindowsIdentity.GetCurrent().Name);
}
}

Categories

Resources