I'm using a MemoryMappedFile in .NET 5 and encounter a problem that seemed to have been solved long ago.
I use it for inter-process communication so it must be located in ring 0. For that to achieve I use the name prefix Global:
var mmf = MemoryMappedFile.CreateOrOpen(#"Global\test", 1024, MemoryMappedFileAccess.ReadWrite);
This works fine for privileged processes.
For non-privileged ones I want to use
mmf = MemoryMappedFile.OpenExisting(#"Global\test", MemoryMappedFileRights.ReadWrite);
But that fails with an "UnauthorizedAccessException".
The solution that worked for .NET Framework up to 4.8 was to explicitly set MemoryMappedFileSecurity as described e.g. here: Gaining access to a MemoryMappedFile from low-integrity process
Unfortunately the MemoryMappedFileSecurity does no longer seem to exist and MemoryMappedFile.OpenExisting (or CreateNew) do no have an overload for that or similar either.
Interim solution
.Net5 mostlikely won't get that missing feature. See https://github.com/dotnet/runtime/issues/941
As a workaround I use the P/Invoke approach. In case someone has the same problem here is a working solution using null DACL to grant all access
private void CreateFile()
{
try
{
using(var secAttribs = CreateSecAttribs())
{
memBuffer = NativeMethods.CreateFileMapping(
UIntPtr.MaxValue,
secAttribs,
(uint)FileMapProtection.PAGE_READWRITE,
0,
(uint)MemOffset.TotalSize,
BufferName);
if (memBuffer == IntPtr.Zero)
{
uint lasterror = NativeMethods.GetLastError();
throw new Win32Exception((int)lasterror, string.Format(CultureInfo.InvariantCulture, "Error creating shared memory. Errorcode is {0}", lasterror));
}
IntPtr accessor = NativeMethods.MapViewOfFile(memBuffer, (uint)ViewAccess.FILE_MAP_ALL_ACCESS, 0, 0, (int)MemOffset.TotalSize);
if (accessor == IntPtr.Zero)
{
uint lasterror = NativeMethods.GetLastError();
throw new Win32Exception((int)lasterror, string.Format(CultureInfo.InvariantCulture, "Error creating shared memory view. Errorcode is {0}", lasterror));
}
//Do sth with accessor using System.Runtime.InteropServices.Marshal
}
}
catch (Exception)
{
memBuffer = IntPtr.Zero;
throw;
}
}
private static NativeMethods.SECURITY_ATTRIBUTES CreateSecAttribs()
{
//Create the descriptor with a null DACL --> Everything is granted.
RawSecurityDescriptor sec = new RawSecurityDescriptor(ControlFlags.DiscretionaryAclPresent, null, null, null, null);
return new NativeMethods.SECURITY_ATTRIBUTES(sec);
}
For reference:
internal static class NativeMethods
{
#region Structures
[StructLayout(LayoutKind.Sequential)]
internal class SECURITY_ATTRIBUTES : IDisposable
{
public int nLength;
public IntPtr lpSecurityDescriptor;
public int bInheritHandle;
public SECURITY_ATTRIBUTES()
{
nLength = Marshal.SizeOf(typeof(SECURITY_ATTRIBUTES));
lpSecurityDescriptor = IntPtr.Zero;
bInheritHandle = 1;
}
public SECURITY_ATTRIBUTES(RawSecurityDescriptor sec) :
this()
{
byte[] binDACL = new byte[sec.BinaryLength];
sec.GetBinaryForm(binDACL, 0);
lpSecurityDescriptor = Marshal.AllocHGlobal(sec.BinaryLength);
Marshal.Copy(binDACL, 0, lpSecurityDescriptor, sec.BinaryLength);
}
~SECURITY_ATTRIBUTES()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
}
if (lpSecurityDescriptor != IntPtr.Zero)
{
Marshal.FreeHGlobal(lpSecurityDescriptor);
lpSecurityDescriptor = IntPtr.Zero;
}
}
}
#endregion
#region General imports
[DllImport("kernel32", EntryPoint = "CloseHandle", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern int CloseHandle(IntPtr hHandle);
[DllImport("kernel32", EntryPoint = "GetLastError", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern uint GetLastError();
#endregion
#region Memory Mapped Files imports
[DllImport("kernel32.dll", EntryPoint = "CreateFileMapping", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern IntPtr CreateFileMapping(UIntPtr hFile, SECURITY_ATTRIBUTES lpAttributes, uint flProtect, uint dwMaximumSizeHigh, uint dwMaximumSizeLow, string lpName);
[DllImport("kernel32.dll", EntryPoint = "MapViewOfFile", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern IntPtr MapViewOfFile(IntPtr hFileMappingObject, uint dwDesiredAccess, uint dwFileOffsetHigh, uint dwFileOffsetLow, uint /* UIntPtr */ dwNumberOfBytesToMap);
[DllImport("kernel32.dll", EntryPoint = "UnmapViewOfFile", SetLastError = true, CharSet = CharSet.Unicode)]
[return: MarshalAs(UnmanagedType.VariantBool)]
internal static extern bool UnmapViewOfFile(IntPtr lpBaseAddress);
#endregion
}
Update
Still no official integration until .Net 7
Adam Sitnik was so friendly to manually recreate MemoryMappedFileSecurity and Wrappers for .Net7.
See https://gist.github.com/adamsitnik/6370b05b9e80bc14b62ac6efe5d1e2e2#file-mmfs-cs-L41-L184
You might need to add CreateOrOpen() based on .Net4 or remove the ERROR_ALREADY_EXIST error case in CreateCore()
Managed Solution
This works when first called by a windows service and then by a user program (and of course vv).
var everyoneRule = new AccessRule<MemoryMappedFileRights>(
new SecurityIdentifier(WellKnownSidType.WorldSid, null),
MemoryMappedFileRights.ReadWrite,
AccessControlType.Allow);
MemoryMappedFileSecurity mmfSec = new MemoryMappedFileSecurity();
mmfSec.AddAccessRule(everyoneRule);
mmf = MemoryMappedFileFactory.Create(#"Global\Test", 1024, MemoryMappedFileAccess.ReadWrite, MemoryMappedFileOptions.None, mmfSec, HandleInheritability.None);
Related
i would like to have the possibility to change the monitors brightness from a .NET desktop application. (running on win7 with nvidia gpu)
i found this winapi function:
https://msdn.microsoft.com/en-us/library/windows/desktop/dd692972(v=vs.85).aspx
and there are some SO questions with examples, but calling this does nothing for me.
but i found that my nvidia control panel allows to adjust the brightness with a slider.
so i was wondering if there is an API to use this functionality? and if maybe someone has some sample code on how to access it?
I am running win7 with AMD card and following example has worked for me.
SetBrightness expects argument in 0-100 range.
I have only one monitor to test so I set brightness just for first one.
using System;
using System.Runtime.InteropServices;
namespace SampleBrightness
{
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct PHYSICAL_MONITOR
{
public IntPtr hPhysicalMonitor;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
public string szPhysicalMonitorDescription;
}
public class BrightnessController : IDisposable
{
[DllImport("user32.dll", EntryPoint = "MonitorFromWindow")]
public static extern IntPtr MonitorFromWindow([In] IntPtr hwnd, uint dwFlags);
[DllImport("dxva2.dll", EntryPoint = "DestroyPhysicalMonitors")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool DestroyPhysicalMonitors(uint dwPhysicalMonitorArraySize, ref PHYSICAL_MONITOR[] pPhysicalMonitorArray);
[DllImport("dxva2.dll", EntryPoint = "GetNumberOfPhysicalMonitorsFromHMONITOR")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetNumberOfPhysicalMonitorsFromHMONITOR(IntPtr hMonitor, ref uint pdwNumberOfPhysicalMonitors);
[DllImport("dxva2.dll", EntryPoint = "GetPhysicalMonitorsFromHMONITOR")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetPhysicalMonitorsFromHMONITOR(IntPtr hMonitor, uint dwPhysicalMonitorArraySize, [Out] PHYSICAL_MONITOR[] pPhysicalMonitorArray);
[DllImport("dxva2.dll", EntryPoint = "GetMonitorBrightness")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetMonitorBrightness(IntPtr handle, ref uint minimumBrightness, ref uint currentBrightness, ref uint maxBrightness);
[DllImport("dxva2.dll", EntryPoint = "SetMonitorBrightness")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool SetMonitorBrightness(IntPtr handle, uint newBrightness);
private uint _physicalMonitorsCount = 0;
private PHYSICAL_MONITOR[] _physicalMonitorArray;
private IntPtr _firstMonitorHandle;
private uint _minValue = 0;
private uint _maxValue = 0;
private uint _currentValue = 0;
public BrightnessController(IntPtr windowHandle)
{
uint dwFlags = 0u;
IntPtr ptr = MonitorFromWindow(windowHandle, dwFlags);
if (!GetNumberOfPhysicalMonitorsFromHMONITOR(ptr, ref _physicalMonitorsCount))
{
throw new Exception("Cannot get monitor count!");
}
_physicalMonitorArray = new PHYSICAL_MONITOR[_physicalMonitorsCount];
if (!GetPhysicalMonitorsFromHMONITOR(ptr, _physicalMonitorsCount, _physicalMonitorArray))
{
throw new Exception("Cannot get phisical monitor handle!");
}
_firstMonitorHandle = _physicalMonitorArray[0].hPhysicalMonitor;
if (!GetMonitorBrightness(_firstMonitorHandle, ref _minValue, ref _currentValue, ref _maxValue))
{
throw new Exception("Cannot get monitor brightness!");
}
}
public void SetBrightness(int newValue)
{
newValue = Math.Min(newValue, Math.Max(0, newValue));
_currentValue = (_maxValue - _minValue) * (uint)newValue / 100u + _minValue;
SetMonitorBrightness(_firstMonitorHandle, _currentValue);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
if (_physicalMonitorsCount > 0)
{
DestroyPhysicalMonitors(_physicalMonitorsCount, ref _physicalMonitorArray);
}
}
}
}
}
Hope this helps.
I am having problems performing serial communications via the Win32 API from C#. No matter which values I use when calling SetCommTimeouts(), the call to ReadFile will not return unless one or more characters are received.
Using the .Net System.IO.Port.SerialPort class is not an option. It has serious bugs regarding USB-connected COM-ports which is the reason why I am trying to use the Win32 API directly instead.
Could the problem be with marshalling the CommTimeouts structure so that the API receives incorrect values?
Complete source code provided below:
namespace SerialTest
{
using System;
using System.Globalization;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
[Flags]
internal enum AccessRights : uint
{
GenericRead = (0x80000000),
GenericWrite = (0x40000000),
GenericExecute = (0x20000000),
GenericAll = (0x10000000)
}
[Flags]
internal enum ShareModes : uint
{
FileShareRead = 0x00000001,
FileShareWrite = 0x00000002,
FileShareDelete = 0x00000004
}
internal enum CreationDispositions
{
CreateNew = 1,
CreateAlways = 2,
OpenExisting = 3,
OpenAlways = 4,
TruncateExisting = 5
}
internal class CommTimeouts
{
public UInt32 ReadIntervalTimeout;
public UInt32 ReadTotalTimeoutMultiplier;
public UInt32 ReadTotalTimeoutConstant;
public UInt32 WriteTotalTimeoutMultiplier;
public UInt32 WriteTotalTimeoutConstant;
}
internal class Kernel32
{
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern SafeFileHandle CreateFile(
string lpFileName,
uint dwDesiredAccess,
uint dwShareMode,
IntPtr lpSecurityAttributes,
uint dwCreationDisposition,
uint dwFlagsAndAttributes,
IntPtr hTemplateFile
);
[DllImport("kernel32.dll", EntryPoint = "SetCommTimeouts", SetLastError = true)]
public static extern bool SetCommTimeouts(SafeHandle hFile, CommTimeouts timeouts);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool ReadFile(SafeHandle hFile, [Out] byte[] lpBuffer,
uint nNumberOfBytesToRead, out uint lpNumberOfBytesRead, IntPtr lpOverlapped);
}
public class SerialTest
{
public void Test(string portName)
{
if (portName.Length > 5) portName = #"\\.\" + portName;
var hPort = Kernel32.CreateFile(portName,
(uint) (AccessRights.GenericRead | AccessRights.GenericWrite),
0, // Not shared
IntPtr.Zero, // Security attributes,
(uint) CreationDispositions.OpenExisting,
0,
IntPtr.Zero // Template file
);
if (hPort.IsInvalid)
{
throw new Exception("Could not open port " + portName + ". Error: " + Marshal.GetLastWin32Error().ToString(CultureInfo.InvariantCulture));
}
try
{
// Set timeout so call returns immediately
var timeouts = new CommTimeouts
{
ReadIntervalTimeout = 0xFFFFFFFF,
ReadTotalTimeoutMultiplier = 0,
ReadTotalTimeoutConstant = 0,
WriteTotalTimeoutMultiplier = 0,
WriteTotalTimeoutConstant = 0
};
if (!Kernel32.SetCommTimeouts(hPort, timeouts))
{
var error = Marshal.GetLastWin32Error();
throw new Exception("Could not set timeouts. Error: " + error.ToString(CultureInfo.InvariantCulture));
}
var buf = new byte[1];
uint readBytes;
if (!Kernel32.ReadFile(hPort,
buf,
1,
out readBytes,
IntPtr.Zero))
{
var error = Marshal.GetLastWin32Error();
throw new Exception("Could not read. Error: " + error.ToString(CultureInfo.InvariantCulture));
}
}
finally
{
hPort.Close();
}
}
}
}
The SetCommTimeouts definition which I found online was incorrect. Thanks to Richard, I now use the correct definition.
static extern bool SetCommTimeouts(IntPtr hFile, [In] ref COMMTIMEOUTS
lpCommTimeouts);
My app runs as requestedExecutionLevel set to highestAvailable.
How do I run a process unelevated?
I tried the following but it didn't work:
Process.Start(new ProcessStartInfo {FileName = "foo.exe", Verb = "open"})
I have tried the following trust levels to start my process using Win32 API but none of them work correctly:
0
1260: This program is blocked by group policy. For more information, contact your system administrator.
0x1000
The application was unable to start correctly (0xc0000142). Click OK to close the application.
0x10000
Process starts then hangs
0x20000
All options are not available
0x40000
Runs as admin
If I run tskill foo from my elevated app, it restarts foo with correct privileges.
What I need is a solution in which I don't have to specify the trust level. The process should start with the correct trust level automatically just like the tskill tool restarts foo.exe in the correct trust level. The user selects and runs foo.exe and so it can be anything.
If I can get the trust level of a process somehow, I can do this easily since foo.exe runs when my app can capture its trust level.
The Win32 Security Management functions provide the capability to create a restricted token with normal user rights; with the token, you can call CreateProcessAsUser to run the process with that token. Below is a proof of concept that runs cmd.exe as a normal user, regardless of whether the process is run in an elevated context.
// Initialize variables.
IntPtr hSaferLevel, hToken;
STARTUPINFO si = default(STARTUPINFO);
SECURITY_ATTRIBUTES processAttributes = default(SECURITY_ATTRIBUTES);
SECURITY_ATTRIBUTES threadAttributes = default(SECURITY_ATTRIBUTES);
PROCESS_INFORMATION pi;
si.cb = Marshal.SizeOf(si);
// The process to start (for demonstration, cmd.exe)
string ProcessName = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.System),
"cmd.exe");
// Create the restricted token info
if (!SaferCreateLevel(
SaferScopes.User,
SaferLevels.NormalUser, // Program will execute as a normal user
1, // required
out hSaferLevel,
IntPtr.Zero))
throw new Win32Exception(Marshal.GetLastWin32Error());
// From the level create a token
if (!SaferComputeTokenFromLevel(
hSaferLevel,
IntPtr.Zero,
out hToken,
SaferComputeTokenFlags.None,
IntPtr.Zero))
throw new Win32Exception(Marshal.GetLastWin32Error());
// Run the process with the restricted token
if (!CreateProcessAsUser(
hToken,
ProcessName,
null, ref processAttributes, ref threadAttributes,
true, 0, IntPtr.Zero, null,
ref si, out pi))
throw new Win32Exception(Marshal.GetLastWin32Error());
// Cleanup
if (!CloseHandle(pi.hProcess))
throw new Win32Exception(Marshal.GetLastWin32Error());
if (!CloseHandle(pi.hThread))
throw new Win32Exception(Marshal.GetLastWin32Error());
if (!SaferCloseLevel(hSaferLevel))
throw new Win32Exception(Marshal.GetLastWin32Error());
This approach makes use the following Win32 functions:
SaferIdentifyLevel to indicate the identity level (limited, normal, or elevated). Setting the levelId to SAFER_LEVELID_NORMALUSER (0x20000) provides the normal user level.
SaferComputeTokenFromLevel creates a token for the provided level. Passing NULL to the InAccessToken parameter uses the identity of the current thread.
CreateProcessAsUser creates the process with the provided token. Since the session is already interactive, most of the parameters can be kept at default values. (The third parameter, lpCommandLine can be provided as a string to specify the command line.)
CloseHandle (Kernel32) and SaferCloseLevel to free allocated memory.
Finally, the P/Invoke code is below (copied mostly from pinvoke.net):
[Flags]
public enum SaferLevels : uint
{
Disallowed = 0,
Untrusted = 0x1000,
Constrained = 0x10000,
NormalUser = 0x20000,
FullyTrusted = 0x40000
}
[Flags]
public enum SaferComputeTokenFlags : uint
{
None = 0x0,
NullIfEqual = 0x1,
CompareOnly = 0x2,
MakeIntert = 0x4,
WantFlags = 0x8
}
[Flags]
public enum SaferScopes : uint
{
Machine = 1,
User = 2
}
[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES
{
public int nLength;
public IntPtr lpSecurityDescriptor;
public int bInheritHandle;
}
[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;
}
[DllImport("advapi32", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool SaferComputeTokenFromLevel(IntPtr LevelHandle, IntPtr InAccessToken, out IntPtr OutAccessToken, int dwFlags, IntPtr lpReserved);
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
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", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool SaferCreateLevel(
SaferScopes dwScopeId,
SaferLevels dwLevelId,
int OpenFlags,
out IntPtr pLevelHandle,
IntPtr lpReserved);
[DllImport("advapi32", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool SaferCloseLevel(
IntPtr pLevelHandle);
[DllImport("advapi32", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool SaferComputeTokenFromLevel(
IntPtr levelHandle,
IntPtr inAccessToken,
out IntPtr outAccessToken,
SaferComputeTokenFlags dwFlags,
IntPtr lpReserved
);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool CloseHandle(IntPtr hObject);
I had best results by cloning Explorer's token as follows:
var shellWnd = WinAPI.GetShellWindow();
if (shellWnd == IntPtr.Zero)
throw new Exception("Could not find shell window");
uint shellProcessId;
WinAPI.GetWindowThreadProcessId(shellWnd, out shellProcessId);
var hShellProcess = WinAPI.OpenProcess(0x00000400 /* QueryInformation */, false, shellProcessId);
var hShellToken = IntPtr.Zero;
if (!WinAPI.OpenProcessToken(hShellProcess, 2 /* TOKEN_DUPLICATE */, out hShellToken))
throw new Win32Exception();
uint tokenAccess = 8 /*TOKEN_QUERY*/ | 1 /*TOKEN_ASSIGN_PRIMARY*/ | 2 /*TOKEN_DUPLICATE*/ | 0x80 /*TOKEN_ADJUST_DEFAULT*/ | 0x100 /*TOKEN_ADJUST_SESSIONID*/;
var hToken = IntPtr.Zero;
WinAPI.DuplicateTokenEx(hShellToken, tokenAccess, IntPtr.Zero, 2 /* SecurityImpersonation */, 1 /* TokenPrimary */, out hToken);
var pi = new WinAPI.PROCESS_INFORMATION();
var si = new WinAPI.STARTUPINFO();
si.cb = Marshal.SizeOf(si);
if (!WinAPI.CreateProcessWithTokenW(hToken, 0, null, cmdArgs, 0, IntPtr.Zero, null, ref si, out pi))
throw new Win32Exception();
Alternative approach
Originally I went with drf's excellent answer, but expanded it somewhat. If the above (clone Explorer's token) is not to your liking, keep reading but see a gotcha at the very end.
When using drf's method as described, the process is started without administrative access, but it still has a high integrity level. A typical un-elevated process has a medium integrity level.
Try this: use Process Hacker to see the properties of the process started this way; you will see that PH considers the process to be elevated even though it doesn't have administrative access. Add an Integrity column and you'll see it's "High".
The fix is reasonably simple: after using SaferComputeTokenFromLevel, we need to change the token integrity level to Medium. The code to do this might look something like this (converted from MSDN sample):
// Get the Medium Integrity SID
if (!WinAPI.ConvertStringSidToSid("S-1-16-8192", out pMediumIntegritySid))
throw new Win32Exception();
// Construct a structure describing the token integrity level
var TIL = new TOKEN_MANDATORY_LABEL();
TIL.Label.Attributes = 0x00000020 /* SE_GROUP_INTEGRITY */;
TIL.Label.Sid = pMediumIntegritySid;
pTIL = Marshal.AllocHGlobal(Marshal.SizeOf<TOKEN_MANDATORY_LABEL>());
Marshal.StructureToPtr(TIL, pTIL, false);
// Modify the token
if (!WinAPI.SetTokenInformation(hToken, 25 /* TokenIntegrityLevel */, pTIL,
(uint) Marshal.SizeOf<TOKEN_MANDATORY_LABEL>()
+ WinAPI.GetLengthSid(pMediumIntegritySid)))
throw new Win32Exception();
Alas, this still doesn't really solve the problem completely. The process won't have administrative access; it won't have a high integrity, but it will still have a token that's marked as "elevated".
Whether this is a problem for you or not I don't know, but it may have been why I ended up cloning Explorer's token in the end, as described at the start of this answer.
Here is my full source code (modified drf's answer), in all its P/Invoke glory:
var hSaferLevel = IntPtr.Zero;
var hToken = IntPtr.Zero;
var pMediumIntegritySid = IntPtr.Zero;
var pTIL = IntPtr.Zero;
var pi = new WinAPI.PROCESS_INFORMATION();
try
{
var si = new WinAPI.STARTUPINFO();
si.cb = Marshal.SizeOf(si);
var processAttributes = new WinAPI.SECURITY_ATTRIBUTES();
var threadAttributes = new WinAPI.SECURITY_ATTRIBUTES();
var args = CommandRunner.ArgsToCommandLine(Args);
if (!WinAPI.SaferCreateLevel(WinAPI.SaferScopes.User, WinAPI.SaferLevels.NormalUser, 1, out hSaferLevel, IntPtr.Zero))
throw new Win32Exception();
if (!WinAPI.SaferComputeTokenFromLevel(hSaferLevel, IntPtr.Zero, out hToken, WinAPI.SaferComputeTokenFlags.None, IntPtr.Zero))
throw new Win32Exception();
if (!WinAPI.ConvertStringSidToSid("S-1-16-8192", out pMediumIntegritySid))
throw new Win32Exception();
var TIL = new TOKEN_MANDATORY_LABEL();
TIL.Label.Attributes = 0x00000020 /* SE_GROUP_INTEGRITY */;
TIL.Label.Sid = pMediumIntegritySid;
pTIL = Marshal.AllocHGlobal(Marshal.SizeOf<TOKEN_MANDATORY_LABEL>());
Marshal.StructureToPtr(TIL, pTIL, false);
if (!WinAPI.SetTokenInformation(hToken, 25 /* TokenIntegrityLevel */, pTIL, (uint) Marshal.SizeOf<TOKEN_MANDATORY_LABEL>() + WinAPI.GetLengthSid(pMediumIntegritySid)))
throw new Win32Exception();
if (!WinAPI.CreateProcessAsUser(hToken, null, commandLine, ref processAttributes, ref threadAttributes, true, 0, IntPtr.Zero, null, ref si, out pi))
throw new Win32Exception();
}
finally
{
if (hToken != IntPtr.Zero && !WinAPI.CloseHandle(hToken))
throw new Win32Exception();
if (pMediumIntegritySid != IntPtr.Zero && WinAPI.LocalFree(pMediumIntegritySid) != IntPtr.Zero)
throw new Win32Exception();
if (pTIL != IntPtr.Zero)
Marshal.FreeHGlobal(pTIL);
if (pi.hProcess != IntPtr.Zero && !WinAPI.CloseHandle(pi.hProcess))
throw new Win32Exception();
if (pi.hThread != IntPtr.Zero && !WinAPI.CloseHandle(pi.hThread))
throw new Win32Exception();
}
And here are the P/Invoke definitions you'll need in addition to those listed in drf's answer:
[DllImport("advapi32.dll", SetLastError = true)]
public static extern Boolean SetTokenInformation(IntPtr TokenHandle, int TokenInformationClass,
IntPtr TokenInformation, UInt32 TokenInformationLength);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CloseHandle(IntPtr hObject);
[DllImport("advapi32.dll")]
public static extern uint GetLengthSid(IntPtr pSid);
[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool ConvertStringSidToSid(
string StringSid,
out IntPtr ptrSid);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr LocalFree(IntPtr hMem);
Raymond Chen addressed this in his blog:
How can I launch an unelevated process from my elevated process and vice versa?
Searching in GitHub for a C# version of this code, I found the following implementation in Microsoft's Node.js tools for Visual Studio repository: SystemUtilities.cs (see the ExecuteProcessUnElevated function).
Just in case the file disappears, here's the file's contents:
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Runtime.InteropServices;
namespace Microsoft.NodejsTools.SharedProject
{
/// <summary>
/// Utility for accessing window IShell* interfaces in order to use them to launch a process unelevated
/// </summary>
internal class SystemUtility
{
/// <summary>
/// We are elevated and should launch the process unelevated. We can't create the
/// process directly without it becoming elevated. So to workaround this, we have
/// explorer do the process creation (explorer is typically running unelevated).
/// </summary>
internal static void ExecuteProcessUnElevated(string process, string args, string currentDirectory = "")
{
var shellWindows = (IShellWindows)new CShellWindows();
// Get the desktop window
object loc = CSIDL_Desktop;
object unused = new object();
int hwnd;
var serviceProvider = (IServiceProvider)shellWindows.FindWindowSW(ref loc, ref unused, SWC_DESKTOP, out hwnd, SWFO_NEEDDISPATCH);
// Get the shell browser
var serviceGuid = SID_STopLevelBrowser;
var interfaceGuid = typeof(IShellBrowser).GUID;
var shellBrowser = (IShellBrowser)serviceProvider.QueryService(ref serviceGuid, ref interfaceGuid);
// Get the shell dispatch
var dispatch = typeof(IDispatch).GUID;
var folderView = (IShellFolderViewDual)shellBrowser.QueryActiveShellView().GetItemObject(SVGIO_BACKGROUND, ref dispatch);
var shellDispatch = (IShellDispatch2)folderView.Application;
// Use the dispatch (which is unelevated) to launch the process for us
shellDispatch.ShellExecute(process, args, currentDirectory, string.Empty, SW_SHOWNORMAL);
}
/// <summary>
/// Interop definitions
/// </summary>
private const int CSIDL_Desktop = 0;
private const int SWC_DESKTOP = 8;
private const int SWFO_NEEDDISPATCH = 1;
private const int SW_SHOWNORMAL = 1;
private const int SVGIO_BACKGROUND = 0;
private readonly static Guid SID_STopLevelBrowser = new Guid("4C96BE40-915C-11CF-99D3-00AA004AE837");
[ComImport]
[Guid("9BA05972-F6A8-11CF-A442-00A0C90A8F39")]
[ClassInterfaceAttribute(ClassInterfaceType.None)]
private class CShellWindows
{
}
[ComImport]
[Guid("85CB6900-4D95-11CF-960C-0080C7F4EE85")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
private interface IShellWindows
{
[return: MarshalAs(UnmanagedType.IDispatch)]
object FindWindowSW([MarshalAs(UnmanagedType.Struct)] ref object pvarloc, [MarshalAs(UnmanagedType.Struct)] ref object pvarlocRoot, int swClass, out int pHWND, int swfwOptions);
}
[ComImport]
[Guid("6d5140c1-7436-11ce-8034-00aa006009fa")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IServiceProvider
{
[return: MarshalAs(UnmanagedType.Interface)]
object QueryService(ref Guid guidService, ref Guid riid);
}
[ComImport]
[Guid("000214E2-0000-0000-C000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IShellBrowser
{
void VTableGap01(); // GetWindow
void VTableGap02(); // ContextSensitiveHelp
void VTableGap03(); // InsertMenusSB
void VTableGap04(); // SetMenuSB
void VTableGap05(); // RemoveMenusSB
void VTableGap06(); // SetStatusTextSB
void VTableGap07(); // EnableModelessSB
void VTableGap08(); // TranslateAcceleratorSB
void VTableGap09(); // BrowseObject
void VTableGap10(); // GetViewStateStream
void VTableGap11(); // GetControlWindow
void VTableGap12(); // SendControlMsg
IShellView QueryActiveShellView();
}
[ComImport]
[Guid("000214E3-0000-0000-C000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IShellView
{
void VTableGap01(); // GetWindow
void VTableGap02(); // ContextSensitiveHelp
void VTableGap03(); // TranslateAcceleratorA
void VTableGap04(); // EnableModeless
void VTableGap05(); // UIActivate
void VTableGap06(); // Refresh
void VTableGap07(); // CreateViewWindow
void VTableGap08(); // DestroyViewWindow
void VTableGap09(); // GetCurrentInfo
void VTableGap10(); // AddPropertySheetPages
void VTableGap11(); // SaveViewState
void VTableGap12(); // SelectItem
[return: MarshalAs(UnmanagedType.Interface)]
object GetItemObject(UInt32 aspectOfView, ref Guid riid);
}
[ComImport]
[Guid("00020400-0000-0000-C000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
private interface IDispatch
{
}
[ComImport]
[Guid("E7A1AF80-4D96-11CF-960C-0080C7F4EE85")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
private interface IShellFolderViewDual
{
object Application { [return: MarshalAs(UnmanagedType.IDispatch)] get; }
}
[ComImport]
[Guid("A4C6892C-3BA9-11D2-9DEA-00C04FB16162")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface IShellDispatch2
{
void ShellExecute([MarshalAs(UnmanagedType.BStr)] string File, [MarshalAs(UnmanagedType.Struct)] object vArgs, [MarshalAs(UnmanagedType.Struct)] object vDir, [MarshalAs(UnmanagedType.Struct)] object vOperation, [MarshalAs(UnmanagedType.Struct)] object vShow);
}
}
}
The easiest solution would be starting the process using explorer.exe. This will start any process unelevated. You can just start explorer.exe using
System.Diagnostics.Process.Start();
The file name will be "C:\Windows\explorer.exe" and the arguments will be the executable you want to start unelevated, surrounded by quotes.
Example:
If I wanted to start F:\folder\example.exe unelevated I would do this:
using System.Diagnostics;
namespace example
{
class exampleClass
{
ProcessStartInfo exampleStartInfo = new ProcessStartInfo();
exampleStartInfo.FileName = "C:\\Windows\\explorer.exe";
exampleStartInfo.Arguments = "\"F:\\folder\\example.exe\"";
Process.Start(exampleStartInfo);
}
}
This might not work on older versions of windows, but it at least works on my laptop, so it certainly does on windows 10.
I am using sharedmemory in my c# app with c++ interop. Currently I am marshalling a struct to a pointer and broadcasting the message. The program I am broadcasting to, opens up correctly with the debug message, but doesn't show/bring-in the data that I had in use within my struct.
Thanks!
The app I am trying to talk to was written in c++ and I am coding in c#. I am using all the DLLImports correctly (I think) and it compiles and runs error free.
using System.Runtime.InteropServices;
[DllImport("user32", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode)]
public static extern uint RegisterWindowMessageW([In]string lpString);
[DllImport("user32", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi)]
public static extern uint RegisterWindowMessageA([In]string lpString);
[DllImport("kernel32", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto)]
public static extern IntPtr OpenFileMapping(FileMapAccessRights dwDesiredAccess, int bInheritHandle, [In]String lpName);
[DllImport("kernel32", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto)]
public static extern IntPtr MapViewOfFile(IntPtr hFileMappingObject, FileMapAccessRights dwDesiredAccess, uint dwFileOffsetHigh, uint dwFileOffsetLow, UIntPtr dwNumberOfBytesToMap);
[DllImport("kernel32", CallingConvention = CallingConvention.StdCall)]
public static extern int UnmapViewOfFile(IntPtr lpBaseAddress);
[DllImport("kernel32", CallingConvention = CallingConvention.StdCall)]
public static extern int CloseHandle(IntPtr hObject);
[DllImport("user32.dll")]
public static extern IntPtr PostMessage(IntPtr hWnd, uint msg, int wParam, int lParam);
uint WM_ZOOM_XYZ = RegisterWindowMessageA("WM_ZOOM_XYZ");
int i = Broadcast_Zoom_Message(10000, 10000, 0, WM_ZOOM_XYZ);
public int Broadcast_Zoom_Message(double dbX, double dbY, double dbZ, uint uMessage)
{
string smSharedMemory = "COORDINATES";
IntPtr hMem = OpenFileMapping(FileMapAccessRights.Write, FALSE, smSharedMemory);
if (IntPtr.Zero == hMem)
{
return 0;
}
IntPtr pvHead = MapViewOfFile(hMem, FileMapAccessRights.Write, 0, 0, UIntPtr.Zero);
if (IntPtr.Zero == pvHead)
{
CloseHandle(hMem);
MessageBox.Show(
"Unable to view " + smSharedMemory,
"Error", MessageBoxButtons.OK, MessageBoxIcon.Stop);
return 0;
}
CoordinatesStruct structCoords = new CoordinatesStruct();
Marshal.PtrToStructure(pvHead, structCoords);
int bVersionOk = FALSE;
if (1 == structCoords.uMajorVersion)
{
if (WM_ZOOM_XYZ == uMessage)
{
structCoords.dbDesiredX = dbX;
structCoords.dbDesiredY = dbY;
structCoords.dbDesiredZ = dbZ;
}
bVersionOk = TRUE;
}
else
{
MessageBox.Show(
"Unrecognized shared memory: " +
structCoords.uMajorVersion.ToString() + "." + structCoords.uMinorVersion.ToString());
}
if (IntPtr.Zero != hMem)
{
CloseHandle(hMem);
}
UnmapViewOfFile(pvHead);
IntPtr HWND_BROADCAST = (IntPtr)0xffff;
if (bVersionOk == TRUE)
{
PostMessage(HWND_BROADCAST, uMessage, 0, 0);
return 1;
}
else
return 0;
}
I think your intention was to put the changed structCoords back to the mapped file. When we use Marshal.PtrToStructure() we receive a copy of the content of the unmanaged memory. The changes of the received object will not reflect in the unmanaged memory. When we are done with the data, we should put the changes back to the memory using Marshal.StructureToPtr.
Here is what I think it should be:
if (1 == structCoords.uMajorVersion)
{
if (WM_ZOOM_XYZ == uMessage)
{
structCoords.dbDesiredX = dbX;
structCoords.dbDesiredY = dbY;
structCoords.dbDesiredZ = dbZ;
}
bVersionOk = TRUE;
Marshal.StructureToPtr(structCoords , pvHead, false); // <-- this is what you (I) forgot!
}
I would like to add a string resource to an executable file programmatically. Just for example purposes, let's say I am trying to add a string named "String SO" which holds the value of "stringVal"
If this helps anyone - if I were to do this through VS.net I could just right click on my Project => Resources => Add New String Resource etc..
I am using the following Win32 API's:
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr BeginUpdateResource(string pFileName,
[MarshalAs(UnmanagedType.Bool)]bool bDeleteExistingResources);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool UpdateResource(IntPtr hUpdate, uint lpType, uint lpName, ushort wLanguage, byte[] lpData, uint cbData);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool EndUpdateResource(IntPtr hUpdate, bool fDiscard);
So, I have found a couple of pages online but none of them seem to help me in what I am trying to do. If any of you are able to find anything I would be very grateful.
Otherwise, I would greatly appreciate any snippets that may help.
Thank you,
Evan
There is a very helpful library for many resource-tasks at github.
Many classes and function do wrap those window-api-calls around UpdateResource(...), etc.
Hope that helps.
I'm injecting an application byte[] as a resource to execute it on runtime. Here's my piece of code, hope it helps:
class AddResource
{
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool UpdateResource(IntPtr hUpdate, string lpType, string lpName, ushort wLanguage, IntPtr lpData, uint cbData);
[DllImport("kernel32.dll", SetLastError = true)]
static extern IntPtr BeginUpdateResource(string pFileName,
[MarshalAs(UnmanagedType.Bool)]bool bDeleteExistingResources);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool EndUpdateResource(IntPtr hUpdate, bool fDiscard);
private static IntPtr ToPtr(object data)
{
GCHandle h = GCHandle.Alloc(data, GCHandleType.Pinned);
IntPtr ptr;
try
{
ptr = h.AddrOfPinnedObject();
}
finally
{
h.Free();
}
return ptr;
}
public static bool InjectResource(string filename, byte[] bytes, string resourceName)
{
try
{
IntPtr handle = BeginUpdateResource(filename, false);
byte[] file1 = bytes;
IntPtr fileptr = ToPtr(file1);
bool res = UpdateResource(handle, resourceName,
//"RT_RCDATA",
"0", 0, fileptr, Convert.ToUInt32(file1.Length));
EndUpdateResource(handle, false);
}
catch
{
return false;
}
return true;
}
public static void CopyStream(Stream input, Stream output,long sz)
{
// Insert null checking here for production
byte[] buffer = new byte[sz];
int bytesRead;
while ((bytesRead = input.Read(buffer, 0, buffer.Length)) > 0)
{
output.Write(buffer, 0, bytesRead);
}
}
}
Here is how I use it:
using (Stream input = Assembly.GetExecutingAssembly().GetManifestResourceStream("AppLicensing.Resources.SAppStarter.exe"))
using (Stream output = File.Create(outputFilePath))
{
long sz = input.Length;
AddResource.CopyStream(input, output, sz);
}
//inject crypted bytes
AddResource.InjectResource(outputFilePath, Encryptor.cryptedbytes, "RT_RCDATA");
And here is how I extract the resource (notice the "RT_RCDATA" -> that s the name of the resource):
class ReadResource
{
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr GetModuleHandle(string lpModuleName);
[DllImport("kernel32.dll", SetLastError = true)]
static extern IntPtr FindResource(IntPtr hModule, string lpName, string lpType);
[DllImport("Kernel32.dll", EntryPoint = "SizeofResource", SetLastError = true)]
private static extern uint SizeofResource(IntPtr hModule, IntPtr hResource);
[DllImport("Kernel32.dll", EntryPoint = "LoadResource", SetLastError = true)]
private static extern IntPtr LoadResource(IntPtr hModule, IntPtr hResource);
public static byte[] GetFromResource(String resourceName)
{
try
{
IntPtr hModule = GetModuleHandle(System.Diagnostics.Process.GetCurrentProcess().MainModule.ModuleName);
IntPtr loc = FindResource(hModule, "0", resourceName);
uint size = SizeofResource(hModule, loc);
IntPtr x = LoadResource(hModule, loc);
byte[] bPtr = new byte[size];
Marshal.Copy(x, bPtr, 0, (int)(size));
return bPtr;
}
catch (Exception e)
{
System.Windows.Forms.MessageBox.Show(e.ToString());
System.Environment.Exit(0);
return null;
}
}
}
byte[] encryptedData = ReadResource.GetFromResource("RT_RCDATA");
The code gets a bit messy... hope this helps.
Although the author is dealing with his own issue right now, the SO question UpdateResource function fails has code snippet for using these calls.
The code from Samson work with String lpType, that mean you can't actually add RT_RCDATA resource either reading from it, it's only create and read lpType named "RT_RCDATA" only. If you want it to read real RT data you'll need to modify lpType from string to uint and this is RT API table:
private const uint RT_CURSOR = 0x00000001;
private const uint RT_BITMAP = 0x00000002;
private const uint RT_ICON = 0x00000003;
private const uint RT_MENU = 0x00000004;
private const uint RT_DIALOG = 0x00000005;
private const uint RT_STRING = 0x00000006;
private const uint RT_FONTDIR = 0x00000007;
private const uint RT_FONT = 0x00000008;
private const uint RT_ACCELERATOR = 0x00000009;
private const uint RT_RCDATA = 0x0000000a;
private const uint RT_MESSAGETABLE = 0x0000000b;