I'm trying to enable the SeDebugPrivilege for my process by using the AdjustTokenPrivileges function, it succeeds but no privileges are enabled and the last error code is ERROR_NOT_ALL_ASSIGNED.
I read the documentation of the function and looked for examples both here and on other sites to understand if I was doing something wrong but I couldn't find any problems.
I checked if my user account has this privilege and it does (the Administrator group does have it and the account is part of the group).
The application is running elevated.
This is the method that I use to enable the privilege:
public static bool EnableRequiredPrivilegesForCurrentProcess()
{
IntPtr TokenHandle = GetCurrentProcessTokenForWriting();
Win32Structures.LUID_AND_ATTRIBUTES[] PrivilegesLUIDs = new Win32Structures.LUID_AND_ATTRIBUTES[1];
if (Win32SecurityFunctions.LookupPrivilegeValue(null, Win32Constants.SE_DEBUG_NAME, out Win32Structures.LUID DebugPrivilegeLUID))
{
Win32Structures.LUID_AND_ATTRIBUTES Attribute = new()
{
Luid = DebugPrivilegeLUID,
Attributes = (uint)Win32Enumerations.PrivilegeLUIDAttributes.SE_PRIVILEGE_ENABLED //0x00000002
};
PrivilegesLUIDs[0] = Attribute;
}
else
{
return false;
}
int StructureSize = Marshal.SizeOf(typeof(Win32Structures.LUID_AND_ATTRIBUTES));
IntPtr LuidAttributesArrayPointer = Marshal.AllocHGlobal(StructureSize * PrivilegesLUIDs.Length);
Marshal.StructureToPtr(PrivilegesLUIDs[0], LuidAttributesArrayPointer, false);
Win32Structures.TOKEN_PRIVILEGES NewPrivileges = new()
{
PrivilegeCount = 1,
Privileges = LuidAttributesArrayPointer
};
if (Win32TokenFunctions.AdjustTokenPrivileges(TokenHandle, false, ref NewPrivileges, 0, IntPtr.Zero, out _))
{
Marshal.DestroyStructure(LuidAttributesArrayPointer, typeof(Win32Structures.LUID_AND_ATTRIBUTES));
Marshal.FreeHGlobal(LuidAttributesArrayPointer);
return true;
}
else
{
Win32OtherFunctions.CloseHandle(TokenHandle);
Marshal.DestroyStructure(LuidAttributesArrayPointer, typeof(Win32Structures.LUID_AND_ATTRIBUTES));
Marshal.FreeHGlobal(LuidAttributesArrayPointer);
return false;
}
}
This is the GetCurrentProcessTokenForWriting() method referenced in the first line:
private static IntPtr GetCurrentProcessTokenForWriting()
{
if (Win32TokenFunctions.OpenProcessToken(Win32OtherFunctions.GetCurrentProcess(), Win32Enumerations.TokenAccessRights.TOKEN_WRITE, out IntPtr TokenHandle))
{
return TokenHandle;
}
else
{
return IntPtr.Zero;
}
}
My application gathers a wide array of info from all processes in the system by calling various functions which require different access rights on the process handle, I require the SeDebugPrivilege to be able to open an handle to a process with PROCESS_ALL_ACCESS access right (if possible).
Related
I just discovered that the reason my code for detecting and changing key values wasn't working, was because it WAS working - just on a different user. Because my winform app will need elevated access for some operations, I have the following in my manifest per many guides saying this is necessary:
<requestedExecutionLevel level="highestAvailable" uiAccess="false" />
Meanwhile, I have code for detecting and changing a value in the registry as follows:
using (var hklm = RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Registry64))
using (var F1key = hklm.OpenSubKey(#"SOFTWARE\Classes\TypeLib\{8cec5860-07a1-11d9-b15e-000d56bfe6ee}\1.0\0\win64"))
{
// EGADS! It's active!
if (F1key != null)
{
fckF1Status.Text = "F1 Help is on. Turning off";
F1key.SetValue("", "", RegistryValueKind.String);
}
else
{
fckF1Status.Text = "F1 Help is off. Turning on";
F1key.SetValue("", "c:\windows\helppane.exe", RegistryValueKind.String);
}
}
The problem is that the changes only show up when I'm looking at regedit as an admin and it appears to be loading it into the "current user" branch of the admin and not the logged in user. How can I make sure registry changes to Registry.CurrentUser are for the logged in user and not the admin/elevated account?
The answer was that HKCU is just an alias and you need to work with Registry.Users instead. To do that, you have to determine who the current user actually is which was done like so (from How to get current windows username from windows service in multiuser environment using .NET):
private string loggedInUser;
private SecurityIdentifier loggedInSID;
private string loggedInSIDStr;
[DllImport("Wtsapi32.dll")]
private static extern bool WTSQuerySessionInformation(IntPtr hServer, int sessionId, WtsInfoClass wtsInfoClass, out IntPtr ppBuffer, out int pBytesReturned);
[DllImport("Wtsapi32.dll")]
private static extern void WTSFreeMemory(IntPtr pointer);
private enum WtsInfoClass
{
WTSUserName = 5,
WTSDomainName = 7,
}
private static string GetUsername(int sessionId, bool prependDomain = true)
{
IntPtr buffer;
int strLen;
string username = "SYSTEM";
if (WTSQuerySessionInformation(IntPtr.Zero, sessionId, WtsInfoClass.WTSUserName, out buffer, out strLen) && strLen > 1)
{
username = Marshal.PtrToStringAnsi(buffer);
WTSFreeMemory(buffer);
if (prependDomain)
{
if (WTSQuerySessionInformation(IntPtr.Zero, sessionId, WtsInfoClass.WTSDomainName, out buffer, out strLen) && strLen > 1)
{
username = Marshal.PtrToStringAnsi(buffer) + "\\" + username;
WTSFreeMemory(buffer);
}
}
}
return username;
}
private void YOUR_CONSTRUCTOR(object sender, EventArgs e)
{
loggedInUser = GetUsername(Process.GetCurrentProcess().SessionId);
NTAccount f = new NTAccount(loggedInUser);
loggedInSID = (SecurityIdentifier)f.Translate(typeof(SecurityIdentifier));
loggedInSIDStr = loggedInSID.ToString();
}
The only modificaations I made from the linked answer was to to set some variables in my class and assign them in the constructor. Later, when I wanted to set the values for the user, I used this:
using (var hklm = RegistryKey.OpenBaseKey(RegistryHive.Users, RegistryView.Registry64))
{
using (hklm.CreateSubKey(loggedInSIDStr + #"\SOFTWARE\Classes\Typelib\{8cec5860-07a1-11d9-b15e-000d56bfe6ee}\1.0\0\win32")) { };
using (hklm.CreateSubKey(loggedInSIDStr + #"\SOFTWARE\Classes\Typelib\{8cec5860-07a1-11d9-b15e-000d56bfe6ee}\1.0\0\win64")) { };
}
This seems to do the job. You still won't see the changes in HKCU if you opened Regedit as admin because it will point to the admin's users branch of the registry, but it will show up in the proper user's branch.
Based on the MSDN documentation of File.Exists, the File.Exists method should return false on any error, including the caller not having access to read the file.
I would expect it to return false both when the file is set to FullControl denied to the user and FullControl denied to the user to the directory the file lives in.
What I'm seeing is when the user has access to the directory, but not the file, File.Exists returns true; however, if the user has no access to the directory, File.Exists returns false.
I wrote a small program that demonstrates what I'm talking about:
using System;
using System.DirectoryServices;
using System.IO;
using System.Runtime.InteropServices;
using System.Security.AccessControl;
using System.Security.Principal;
namespace ConsoleApplication1
{
internal class Program
{
private const string DirName = "TestDir";
private const string FileName = "File.txt";
private const string Password = "Password1";
private const string UserName = "PermissionTestUser";
private static WindowsImpersonationContext Identity = null;
private static IntPtr LogonToken = IntPtr.Zero;
public enum LogonProvider
{
LOGON32_PROVIDER_DEFAULT = 0,
LOGON32_PROVIDER_WINNT35 = 1,
LOGON32_PROVIDER_WINNT40 = 2,
LOGON32_PROVIDER_WINNT50 = 3
};
public enum LogonType
{
LOGON32_LOGON_INTERACTIVE = 2,
LOGON32_LOGON_NETWORK = 3,
LOGON32_LOGON_BATCH = 4,
LOGON32_LOGON_SERVICE = 5,
LOGON32_LOGON_UNLOCK = 7,
LOGON32_LOGON_NETWORK_CLEARTEXT = 8, // Win2K or higher
LOGON32_LOGON_NEW_CREDENTIALS = 9 // Win2K or higher
};
public static void Main(string[] args)
{
string filePath = Path.Combine(DirName, FileName);
try
{
CreateUser();
CreateDir();
CreateFile(filePath);
// grant user full control to the dir
SetAccess(DirName, AccessControlType.Allow);
// deny user full control to the file
SetAccess(filePath, AccessControlType.Deny);
// impersonate user
Impersonate();
Console.WriteLine("File.Exists (with dir permissions): {0}", File.Exists(filePath));
UndoImpersonate();
// deny access to dir
SetAccess(DirName, AccessControlType.Deny);
// impersonate user
Impersonate();
Console.WriteLine("File.Exists (without dir permissions): {0}", File.Exists(filePath));
UndoImpersonate();
}
finally
{
UndoImpersonate();
DeleteDir();
DeleteUser();
}
}
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
private static extern bool CloseHandle(IntPtr handle);
private static void CreateDir()
{
Directory.CreateDirectory(DirName);
}
private static void CreateFile(string path)
{
File.Create(path).Dispose();
}
private static void CreateUser()
{
DirectoryEntry ad = new DirectoryEntry("WinNT://" + Environment.MachineName + ",computer");
DirectoryEntry newUser = ad.Children.Add(UserName, "user");
newUser.Invoke("SetPassword", new object[] { Password });
newUser.Invoke("Put", new object[] { "Description", "Test user" });
newUser.CommitChanges();
}
private static void DeleteDir()
{
Directory.Delete(DirName, true);
}
private static void DeleteUser()
{
DirectoryEntry ad = new DirectoryEntry("WinNT://" + Environment.MachineName + ",computer");
DirectoryEntries users = ad.Children;
DirectoryEntry user = users.Find(UserName, "user");
if (user != null)
{
users.Remove(user);
}
}
private static void Impersonate()
{
if (LogonUser(UserName, ".", Password, (int)LogonType.LOGON32_LOGON_INTERACTIVE, (int)LogonProvider.LOGON32_PROVIDER_DEFAULT, ref LogonToken))
{
Identity = WindowsIdentity.Impersonate(LogonToken);
return;
}
}
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern bool LogonUser(string lpszUserName,
string lpszDomain,
string lpszPassword,
int dwLogonType,
int dwLogonProvider,
ref IntPtr phToken);
private static void SetAccess(string path, AccessControlType type)
{
FileSecurity fs = File.GetAccessControl(path);
FileSystemAccessRule far = new FileSystemAccessRule(UserName, FileSystemRights.FullControl, type);
fs.AddAccessRule(far);
File.SetAccessControl(path, fs);
}
private static void UndoImpersonate()
{
if (Identity != null)
{
Identity.Undo();
Identity = null;
}
if (LogonToken != IntPtr.Zero)
{
CloseHandle(LogonToken);
LogonToken = IntPtr.Zero;
}
}
}
}
The result of running this program is:
File.Exists (with dir permissions): True
File.Exists (without dir permissions): False
Can anyone explain why they differ? In both instances, the user doesn't have read access to the file.
That is the default behavior of the File.Exist. According to MSDN:
File.Exist
Return Value Type: System.Boolean
true if the caller has
the required permissions and path contains the name of an existing
file; otherwise, false. This method also returns false if path is
null, an invalid path, or a zero-length string. If the caller does not
have sufficient permissions to read the specified file, no exception
is thrown and the method returns false regardless of the existence of
path.
And additionally
The Exists method should not be used for path validation, this method
merely checks if the file specified in path exists. Passing an invalid
path to Exists returns false.
In other words, the required permission here, is the required permission to know the existence of the file (as the method name implies, File.Exist). And this means that as long as a user has access to the directory, it can know if the file exists or not.
Whether the user has file access or not doesn't affect the user's knowledge of the existence of the file, given the directory permission. But without directory permission, a user cannot know the existence of the file, and thus File.Exist returns false
Edit (after feedback from comments):
And probably the rather confusing part would be the last sentence:
If the caller does not
have sufficient permissions to read the specified file, no exception
is thrown and the method returns false regardless of the existence of
path.
The sufficient permissions to read the specified file is depending on the read-access of the parent directory rather than read-access of the specified file. (Additional comment by Mr. Rob). The word "sufficient" may give some hint about the behavior that it will only depend on read-access to the parent directory is needed, not the read-access to the specified file.
But I admit that the explanation and the choice of word may sound rather counter-intuitive as people may intuitively interpret "sufficient permissions to read the specified file" as the read-access to the specified file rather than to the parent directory.
If the file does exist but File.Exists(filePath) returns false, that means the application has the read permission to neither directory nor file, which can be proved.
if you really want to test if a file exists or not, you can invoke File.GetAccessControl(filePath). If the call returns without exception, it means the file does exist.
My user is an Administrator (I see it in the configuration panel), the below code throws a Win32Exception in which it says Access Denied, how can I change this (Win7 32 bits) ?
static Guid VideoGuid = new Guid("4d36e968-e325-11ce-bfc1-08002be10318");
[SecurityPermission(SecurityAction.Demand, UnmanagedCode = true)]
static void Main(string[] args)
{
SafeDeviceHandle handle = null;
try
{
handle = NativeMethods.SetupDiGetClassDevs(ref VideoGuid, IntPtr.Zero, IntPtr.Zero, NativeMethods.DIGCF.PRESENT);
var data = new NativeMethods.SP_DEVINFO_DATA().Initialize();
var param = new NativeMethods.SP_PROPCHANGE_PARAMS().Initialize();
param.ClassInstallHeader.InstallFunction = 0x12;
param.StateChange = NativeMethods.DICS.ENABLE; // 0x01
param.Scope = NativeMethods.DICS_GLOBAL.GLOBAL; // 0x01
param.HwProfile = 0;
RunWin32Method(() => NativeMethods.SetupDiEnumDeviceInfo(handle, 0u, out data));
RunWin32Method(() => NativeMethods.SetupDiSetClassInstallParams(handle, ref data, ref param, (UInt32)Marshal.SizeOf(param)));
RunWin32Method(() => NativeMethods.SetupDiChangeState(handle, ref data));
}
catch
{
var w = new Win32Exception(Marshal.GetLastWin32Error());
}
finally
{
if (handle != null && (!handle.IsInvalid))
handle.Close();
}
}
static void RunWin32Method(Func<bool> f)
{
if (!f())
{
Debug.WriteLine(new Win32Exception(Marshal.GetLastWin32Error()).Message);
}
}
If you want more code, just ask :-)
Thanks
Recapping the comment trail, a user in the Administrator group doesn't have admin rights on Vista/Server 2008 and later unless the process runs elevated. A manifest is required to get Windows to display the UAC elevation prompt.
This cannot work for programs that are started at login by the Run registry key or the Startup folder. Windows refuses to display the elevation prompt because the user cannot accurately guess exactly what program asked for the elevation. Code-signing the program with a certificate may fix this since that permits Windows to verify and display the program owner, never actually tried that.
Workarounds for such programs are activating it as a service or a scheduled task. Neither of which requires the manifest. The theory behind this seeming oddity is that it already requires elevation to get a service or scheduled task installed.
I am in need of an example, that can let me pass a parameter
e.g. executing delete.exe /killme.txt
So it will use the "MoveFile" to delete killme.txt after reboot.
Although please not the MS precompiled version, as it has an annoying disclaimer, every time its run on a different computer.
You'll need the P/Invoke declarations for MoveFileEx:
[Flags]
internal enum MoveFileFlags
{
None = 0,
ReplaceExisting = 1,
CopyAllowed = 2,
DelayUntilReboot = 4,
WriteThrough = 8,
CreateHardlink = 16,
FailIfNotTrackable = 32,
}
internal static class NativeMethods
{
[DllImport("kernel32.dll", SetLastError=true, CharSet=CharSet.Unicode)]
public static extern bool MoveFileEx(
string lpExistingFileName,
string lpNewFileName,
MoveFileFlags dwFlags);
}
And some example code:
if (!NativeMethods.MoveFileEx("a.txt", null, MoveFileFlags.DelayUntilReboot))
{
Console.Error.WriteLine("Unable to schedule 'a.txt' for deletion");
}
Because you want to perform this after reboot as a requirement, you could use the Windows Task Scheduler API. You can invoke this in C# by adding a reference to the COM library TaskScheduler 1.1 Type Library. Below is a full code example on running Notepad.exe at logon.
Also, here is another resource: http://bartdesmet.net/blogs/bart/archive/2008/02/23/calling-the-task-scheduler-in-windows-vista-and-windows-server-2008-from-managed-code.aspx
You could call the system command DEL from Windows Command line, potentially with this code.
namespace TaskSchedulerExample {
using System;
using TaskScheduler;
class Program {
static void Main(string[] args) {
var scheduler = new TaskSchedulerClass();
scheduler.Connect(null, null, null, null);
ITaskDefinition task = scheduler.NewTask(0);
task.RegistrationInfo.Author = "DCOM Productions";
task.RegistrationInfo.Description = "Demo";
ILogonTrigger trigger = (ILogonTrigger)task.Triggers.Create(_TASK_TRIGGER_TYPE2.TASK_TRIGGER_LOGON);
trigger.Id = "Logon Demo";
IExecAction action = (IExecAction)task.Actions.Create(_TASK_ACTION_TYPE.TASK_ACTION_EXEC);
action.Id = "Delete";
action.Path = "c:\\delete.exe"; // Or similar path
action.WorkingDirectory = "c:\\"; // Working path
action.Arguments = "c:\\killme.txt"; // Path to your file
ITaskFolder root = scheduler.GetFolder("\\");
IRegisteredTask regTask = root.RegisterTaskDefinition("Demo", task, (int)_TASK_CREATION.TASK_CREATE_OR_UPDATE, null, null, _TASK_LOGON_TYPE.TASK_LOGON_INTERACTIVE_TOKEN, "");
//Force run task
//IRunningTask runTask = regTask.Run(null);
}
}
}
This gives you some flexibility. You could run your own delete.exe, or you could potentially invoke the Windows Command Line to execute the DEL command.
I have the following snippet of code here that im trying to build to automatically change the proxy settings:
public class ProxyManager
{
public static bool UnsetProxy()
{
return SetProxy(null);
}
public static bool SetProxy(string Ip,int Port)
{
return SetProxy(Ip + ":" + Port.ToString());
}
public static bool SetProxy(string ProxyAddress)
{
RegistryKey registry = Registry.CurrentUser.OpenSubKey("Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings", true);
if (ProxyAddress == null)
{
registry.SetValue("ProxyEnable", 0);
}
else
{
registry.SetValue("ProxyEnable", 1);
registry.SetValue("ProxyServer", ProxyAddress.ToString());
}
//Force the update!
registry.Clase();
InternetSetOption(IntPtr.Zero, INTERNET_OPTION_SETTINGS_CHANGED, IntPtr.Zero, 0);
InternetSetOption(IntPtr.Zero, INTERNET_OPTION_REFRESH, IntPtr.Zero, 0);
return true;
}
[DllImport("wininet.dll")]
public static extern bool InternetSetOption(IntPtr hInternet, int dwOption, IntPtr lpBuffer, int dwBufferLength);
public const int INTERNET_OPTION_SETTINGS_CHANGED = 39;
public const int INTERNET_OPTION_REFRESH = 37;
}
But for some reason the proxy settings are not being set, I know the method is being executed correctly as I insert an event into the Event Manger after the method is called and that is visible.
For some reason though the proxy settings are not, I'me calling the function like so:
EventManager.WriteEntry("Proxy Settings Enabled");
ProxyManager.SetProxy("10.222.62.65:8080");
My application is a windows service and is running under the authority of the Local System Account which has full privileges.
I suspect that it might be a combination of the fact that you're using the code Registry.CurrentUser and that it's running under the Local System Account.
The combination of those two snippets of your question makes me think that you might be changing the settings for the wrong user account? I'd suggest trying to run the service under your account and see if that makes any difference (assuming that this is possible due to UAC etc).
i wrote a similar program for disabling network adapters and changing proxy. It is at tognet.codeplex.com. I have experienced that // Force the update code somehow does not wan to refresh proxy settings on a windows 7 box. If i restart IE and look at the proxy settings again only then it shows the correct state of the proxy.
The reason is that you are changing the registry branch of CURRENT_USER, so there are actually two different branches - for your own user, and for Local System. And when you are running as Windows Service, you are changing the other branch. So actually you set the values, bot for a totally defferent user.
So what you need - is to get SID of your user, and then save it somewhere, so your service could use it, and access the correct branch (the one that is owned by your user). The code below tested on Windows 10.
public static RegistryKey? GetCurrentUserKey()
{
var sidString = GetSidFromLocalMachine();
if (string.IsNullOrWhiteSpace(sidString))
{
sidString = WindowsIdentity.GetCurrent().User?.ToString();
}
if (string.IsNullOrWhiteSpace(sidString))
return null;
RegistryKey resultKey = Registry.Users.OpenSubKey(sidString + "\\", true);
return resultKey;
}
public static string GetSidFromLocalMachine()
{
var settingsKey = Registry.LocalMachine.OpenSubKey(regKeyInternetSettings, true);
if (settingsKey != null)
return settingsKey.GetValue(regSid).ToString();
return string.Empty;
}
public static bool SaveSidToLocalMachine(string sid)
{
if (string.IsNullOrWhiteSpace(sid))
return false;
var settingsKey = Registry.LocalMachine.OpenSubKey(regKeyInternetSettings, true);
if (settingsKey == null)
return false;
settingsKey.SetValue("SID", sid);
settingsKey.Close();
return true;
}
You need to call SaveSidToLocalMachine before running the service, or set it manually. Then any time you need to load any registry key from your service, just call
var key = GetCurrentUserKey()?.OpenSubKey(regKeyInternetSettings, true);
key.SetValue("ProxyEnable", 1);
key.Close();
And don't forget to refresh:
InternetSetOption(IntPtr.Zero, INTERNET_OPTION_SETTINGS_CHANGED, IntPtr.Zero, 0);
InternetSetOption(IntPtr.Zero, INTERNET_OPTION_REFRESH, IntPtr.Zero, 0);