Get path of process without Admin privileges in C# - c#

I am trying to get the path of a running process without admin privileges. There is lots of tutorials online, but i can't get them to work. This is the basic premise that works great when running as administrator:
// main
Process pname = Process.GetProcessesByName(processName)[0];
string path = GetExecutablePath(pname);
// api
private static string GetExecutablePath(Process process) {
//If running on Vista or later use the new function
if (Environment.OSVersion.Version.Major >= 6) {
return GetExecutablePathAboveVista(process.Id);
}
return process.MainModule.FileName;
}
private static string GetExecutablePathAboveVista(int ProcessId) {
var buffer = new StringBuilder(1024);
IntPtr hprocess = OpenProcess(ProcessAccessFlags.QueryLimitedInformation,
false, ProcessId);
if (hprocess != IntPtr.Zero) {
try {
int size = buffer.Capacity;
if (QueryFullProcessImageName(hprocess, 0, buffer, out size)) {
return buffer.ToString();
}
} finally {
CloseHandle(hprocess);
}
}
throw new Win32Exception(Marshal.GetLastWin32Error()); //always throws error
}
[DllImport("kernel32.dll")]
private static extern bool QueryFullProcessImageName(IntPtr hprocess, int dwFlags,
StringBuilder lpExeName, out int size);
[DllImport("kernel32.dll")]
private static extern IntPtr OpenProcess(ProcessAccessFlags dwDesiredAccess,
bool bInheritHandle, int dwProcessId);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool CloseHandle(IntPtr hHandle);
[Flags]
public enum ProcessAccessFlags : uint {
All = 0x001F0FFF,
Terminate = 0x00000001,
CreateThread = 0x00000002,
VirtualMemoryOperation = 0x00000008,
VirtualMemoryRead = 0x00000010,
VirtualMemoryWrite = 0x00000020,
DuplicateHandle = 0x00000040,
CreateProcess = 0x000000080,
SetQuota = 0x00000100,
SetInformation = 0x00000200,
QueryInformation = 0x00000400,
QueryLimitedInformation = 0x1000,
Synchronize = 0x00100000
}
Unfortunately, the line
throw new Win32Exception(Marshal.GetLastWin32Error());
always executes with an error message
System.ComponentModel.Win32Exception: 'Not all privileges or groups referenced are assigned to the caller'
Is it possible to find the path of an exe without running as administrator? What solution do you recommend?
Update:
I think admin priviligies are needed because the program that I am inspecting has been run with admin priviliges. So the question could be rephrased to: how to find the executable path of a running process that is started as administrator?
In broader context.. I've been trying to determine if process A, that I installed, is already running on the machine. Process A requirs admin priviliges and it is usually starter as a service. There are many processes on the machines, that are named A. To find out, if this is my process, I inspected the path to exe file. Is there another way to determine if the process is indeed the one you are looking for? What else can be checked beside path to executable?

You can't. You must run as admin to get the process's path.
You don't need all that excess code you can just use this, as long as you run as admin:
string path = Process.GetProcessesByName("processfilename")[0].MainModule.FileName;

Related

How do I queue processes on C#? [duplicate]

How do I suspend a whole process (like the Process Explorer does when I click Suspend) in C#.
I'm starting the Process with Process.Start, and on a certain event, I want to suspend the process to be able to do some investigation on a "snapshot" of it.
Here's my suggestion:
[Flags]
public enum ThreadAccess : int
{
TERMINATE = (0x0001),
SUSPEND_RESUME = (0x0002),
GET_CONTEXT = (0x0008),
SET_CONTEXT = (0x0010),
SET_INFORMATION = (0x0020),
QUERY_INFORMATION = (0x0040),
SET_THREAD_TOKEN = (0x0080),
IMPERSONATE = (0x0100),
DIRECT_IMPERSONATION = (0x0200)
}
[DllImport("kernel32.dll")]
static extern IntPtr OpenThread(ThreadAccess dwDesiredAccess, bool bInheritHandle, uint dwThreadId);
[DllImport("kernel32.dll")]
static extern uint SuspendThread(IntPtr hThread);
[DllImport("kernel32.dll")]
static extern int ResumeThread(IntPtr hThread);
[DllImport("kernel32", CharSet = CharSet.Auto,SetLastError = true)]
static extern bool CloseHandle(IntPtr handle);
private static void SuspendProcess(int pid)
{
var process = Process.GetProcessById(pid); // throws exception if process does not exist
foreach (ProcessThread pT in process.Threads)
{
IntPtr pOpenThread = OpenThread(ThreadAccess.SUSPEND_RESUME, false, (uint)pT.Id);
if (pOpenThread == IntPtr.Zero)
{
continue;
}
SuspendThread(pOpenThread);
CloseHandle(pOpenThread);
}
}
public static void ResumeProcess(int pid)
{
var process = Process.GetProcessById(pid);
if (process.ProcessName == string.Empty)
return;
foreach (ProcessThread pT in process.Threads)
{
IntPtr pOpenThread = OpenThread(ThreadAccess.SUSPEND_RESUME, false, (uint)pT.Id);
if (pOpenThread == IntPtr.Zero)
{
continue;
}
var suspendCount = 0;
do
{
suspendCount = ResumeThread(pOpenThread);
} while (suspendCount > 0);
CloseHandle(pOpenThread);
}
}
Thanks to Magnus
After including the Flags, I modified the code a bit to be an extension method in my project. I could now use
var process = Process.GetProcessById(param.PId);
process.Suspend();
Here is the code for those who might be interested.
public static class ProcessExtension
{
[DllImport("kernel32.dll")]
static extern IntPtr OpenThread(ThreadAccess dwDesiredAccess, bool bInheritHandle, uint dwThreadId);
[DllImport("kernel32.dll")]
static extern uint SuspendThread(IntPtr hThread);
[DllImport("kernel32.dll")]
static extern int ResumeThread(IntPtr hThread);
public static void Suspend(this Process process)
{
foreach (ProcessThread thread in process.Threads)
{
var pOpenThread = OpenThread(ThreadAccess.SUSPEND_RESUME, false, (uint)thread.Id);
if (pOpenThread == IntPtr.Zero)
{
break;
}
SuspendThread(pOpenThread);
}
}
public static void Resume(this Process process)
{
foreach (ProcessThread thread in process.Threads)
{
var pOpenThread = OpenThread(ThreadAccess.SUSPEND_RESUME, false, (uint)thread.Id);
if (pOpenThread == IntPtr.Zero)
{
break;
}
ResumeThread(pOpenThread);
}
}
}
I have a utility done which I use to generally suspend/kill/list a process. Full source is on Git
So really, what the other answer's are showing is suspending thread's in the process, there is no way to really suspend the process (i.e. in one call)....
A bit of a different solution would be to actually debug the target process which you are starting, see Mike Stall's blog for some advice how to implement this from a managed context.
If you implement a debugger, you will be able to scan memory or what other snap-shotting you would like.
However, I would like to point out, that technically, there is now way to really do this. Even if you do debugbreak a target debuggee process, another process on your system may inject a thread and will be given some ability to execute code regardless of the state of the target process (even let's say if it's hit a breakpoint due to an access violation), if you have all thread's suspended up to a super high suspend count, are currently at a break point in the main process thread and any other such presumed-frozen status, it is still possible for the system to inject another thread into that process and execute some instructions. You could also go through the trouble of modifying or replacing all of the entry point's the kernel usually calls and so on, but you've now entered the viscous arm's race of MALWARE ;)...
In any case, using the managed interfaces for debugging seems' a fair amount easier than p/invoke'ng a lot of native API call's which will do a poor job of emulating what you probably really want to be doing... using debug api's ;)
See this CodeProject article for the win32 basics : http://www.codeproject.com/KB/threads/pausep.aspx. This sample code makes use of the ToolHelp32 library from the SDK, so I would recommend turning this sample code into an unmanaged C++/CLI library with a simple interface like "SuspendProcess(uint processID).
Process.Start will return you a Process object, from which you can get the process id, and then pass this to your new library based on the above.
Dave
[DllImport("ntdll.dll", PreserveSig = false)]
public static extern void NtSuspendProcess(IntPtr processHandle);
static IntPtr handle;
string p = "";
foreach (Process item in Process.GetProcesses())
{
if (item.ProcessName == "GammaVPN")
{
p = item.ProcessName;
handle = item.Handle;
NtSuspendProcess(handle);
}
}
Console.WriteLine(p);
Console.WriteLine("done");

Starting process and storing handle to window

Would someone mind helping me out with a problem I've been stuck on for a bit? I'm using C# and trying to start a couple processes, and later move those windows to separate monitors.
So far this was the main idea:
Process p1 = Process.Start(#"1.pptx");
Process p2 = Process.Start(#"2.pptx");
SetWindowPos(p1.MainWindowHandle, -1,
0,
0,
100,
100,
SWP_SHOWWINDOW);
SetWindowPos(p2.MainWindowHandle, -1,
200,
200,
100,
100,
SWP_SHOWWINDOW);
But after trying a bunch of different things, I haven't been able to get it to work. Could anyone give me some pointers?
As a side note which is confusing me, if I print those to process IDs (p1, p2), and then run this code:
Process[] processlist = Process.GetProcesses();
foreach (Process process in processlist)
{
if (!String.IsNullOrEmpty(process.MainWindowTitle))
{
Console.WriteLine("Process: {0} ID: {1} Window title: {2}", process.ProcessName, process.Id, process.MainWindowTitle);
}
}
those process IDs don't exist. I know there must be something simple I'm missing...?
UPDATE: The reason for the problem above is that for some reason the MainWindowTitle didn't have a value, so it wasn't printing the pid.
When you use Process.Start to open a document this is handled by the shell. The shell looks in the file association registry and takes whatever steps are needed to open the document.
This may involve creating a new process but equally may not. Office applications will typically reuse already open processes to open new documents. That's what is happening here.
And when this does happen, when no new process is started, the shell returns 0 for the new process handle. That's reflected back to the .net Process object. It explains why you have no main window handle.
So fundamentally your basic approach is flawed. Using Process.Start will not yield window handles for these documents. You'll have to find another way to locate these windows. For instance EnumWindows or a CBT hook. Or perhaps COM automation is the right solution.
As an aside it seems that you did not check for errors when you called SetWindowPos. That would have helped you work this out more quickly. Always check return values when calling Win32 functions.
For those who are still looking for an answer, this is what I did to get it working.
First, use EnumWindows to get a handle to each open window before starting any new process. Then start your process, then check all windows again making sure they're visible and have window text. If you have only 1 new process, chances are that's your new window. If not, I've tried it 3 times before failing. So far, the code has worked great.
Here is the code for helper functions/Win32 API calls.
public delegate bool EnumedWindow(IntPtr handleWindow, ArrayList handles);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool EnumWindows(EnumedWindow lpEnumFunc, ArrayList lParam);
public static ArrayList GetWindows()
{
ArrayList windowHandles = new ArrayList();
EnumedWindow callBackPtr = GetWindowHandle;
EnumWindows(callBackPtr, windowHandles);
return windowHandles;
}
private static bool GetWindowHandle(IntPtr windowHandle, ArrayList windowHandles)
{
windowHandles.Add(windowHandle);
return true;
}
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool IsWindowVisible(IntPtr hWnd);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
const int SWP_SHOWWINDOW = 0x0040;
[DllImport("user32.dll", EntryPoint = "SetWindowPos", SetLastError = true)]
public static extern Boolean SetWindowPos(IntPtr hWnd, int hWndInsertAfter, int x, int Y, int cx, int cy, int wFlags);
My main logic then went something like this (adjust as necessary):
List<IntPtr> alreadyOpenWindows = new List<IntPtr>();
foreach (IntPtr ip in GetWindows())
{
alreadyOpenWindows.Add(ip);
}
Process.Start("your command here");
System.Threading.Thread.Sleep(1000);
foreach (IntPtr ip in GetWindows())
{
// To consider it a new window, it must be visible, it must not have been open previously, and it must have window text length > 0
if (IsWindowVisible(ip) && alreadyOpenWindows.Contains(ip) == false)
{
StringBuilder windowText = new StringBuilder();
windowText.Length = 256;
GetWindowText(ip, windowText, windowText.Length);
if (windowText.Length > 0)
{
numNewWindows++;
handle = ip;
// break if your confident there will only be one new window opened
}
}
}
// Check numNewWindows if you'd like
if (handle != IntPtr.Zero)
{
SetWindowPos(handle, -1,
this.GetScreen().WorkingArea.X,
this.GetScreen().WorkingArea.Y,
this.GetScreen().WorkingArea.Width,
this.GetScreen().WorkingArea.Height,
SWP_SHOWWINDOW);
}

Injecting Python Code into Process

I want to Inject Python Code into a process and It seems to be crashing my process when it injects. I do not get any errors on my own program but the target process stops working. The called unmanaged APIs have not given me any errors and seem to have carried out their execution properly.
[DllImport("kernel32")]
public static extern IntPtr CreateRemoteThread(IntPtr hProcess,IntPtr lpThreadAttributes,uint dwStackSize, IntPtr lpStartAddress,IntPtr lpParameter,uint dwCreationFlags, out uint lpThreadId);
[Flags]
enum ProcessAccessFlags : uint
{
All = 0x001F0FFF,
Terminate = 0x00000001,
CreateThread = 0x00000002,
VMOperation = 0x00000008,
VMRead = 0x00000010,
VMWrite = 0x00000020,
DupHandle = 0x00000040,
SetInformation = 0x00000200,
QueryInformation = 0x00000400,
Synchronize = 0x00100000
}
[DllImport("kernel32.dll")]
static extern IntPtr OpenProcess(ProcessAccessFlags dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, int dwProcessId);
[Flags]
public enum AllocationType
{
Commit = 0x1000,
Reserve = 0x2000,
Decommit = 0x4000,
Release = 0x8000,
Reset = 0x80000,
Physical = 0x400000,
TopDown = 0x100000,
WriteWatch = 0x200000,
LargePages = 0x20000000,
VIRTUAL_MEM = (0x1000 | 0x2000)
}
[Flags]
public enum MemoryProtection
{
Execute = 0x10,
ExecuteRead = 0x20,
ExecuteReadWrite = 0x40,
ExecuteWriteCopy = 0x80,
NoAccess = 0x01,
ReadOnly = 0x02,
ReadWrite = 0x04,
WriteCopy = 0x08,
GuardModifierflag = 0x100,
NoCacheModifierflag = 0x200,
WriteCombineModifierflag = 0x400,
PAGE_EXECUTE_READWRITE = 0x00000040
}
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize, AllocationType flAllocationType, MemoryProtection flProtect);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, uint nSize, out UIntPtr lpNumberOfBytesWritten);
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
static extern bool VirtualFreeEx(IntPtr hProcess, IntPtr lpAddress, UIntPtr dwSize, uint dwFreeType);
[DllImport("kernel32", SetLastError = true, ExactSpelling = true)]
internal static extern Int32 WaitForSingleObject( IntPtr handle,Int32 milliseconds);
[DllImport("kernel32.dll")]
public static extern Int32 CloseHandle(IntPtr hObject);
private void InjectCode(string shellcode = "print('Hello, World!')")
{
foreach (Process proc in Process.GetProcesses())
{
if (proc.ProcessName == "Toontown")
{
int shellcode_length = shellcode.Length;
IntPtr h_process = OpenProcess(ProcessAccessFlags.All, false, (int)proc.Id);
IntPtr shellcode_address = (IntPtr)VirtualAllocEx(h_process, (IntPtr)0, (uint)shellcode_length, AllocationType.VIRTUAL_MEM, MemoryProtection.PAGE_EXECUTE_READWRITE);
byte[] bytes = new byte[shellcode.Length * sizeof(char)];
Buffer.BlockCopy(shellcode.ToCharArray(), 0, bytes, 0, bytes.Length);
UIntPtr bytesout;
uint t_id;
bool Written = WriteProcessMemory(h_process, shellcode_address, bytes, (uint)shellcode_length, out bytesout);
IntPtr hThread = (IntPtr)CreateRemoteThread(h_process, (IntPtr)null, 0, (IntPtr)shellcode_length, (IntPtr)shellcode_address, 0, out t_id);
int Result = WaitForSingleObject(hThread, 10 * 1000);
if (Result == 0x00000080L || Result == 0x00000102L || Result == 0xFFFFFFFF)
{
if (hThread != null)
{
CloseHandle(hThread);
}
}
Thread.Sleep(1000);
VirtualFreeEx(h_process, shellcode_address, (UIntPtr)0, 0x8000);
if (hThread != null)
{
CloseHandle(hThread);
}
}
}
}
As you may see, I have saved the returned values of the unmanaged API into variables which I used to see if it was working or not, it seemed to be doing fine but it crashes the target process, logs haven't recorded any errors related to it.
Can managed programs inject into unmanaged processes? Am I casting wrong variable types? Is the shellcode translated into byte array incorrectly? Please let me know, thanks.
EDIT: It crashes at CreateRemoteThread
CreateRemoteThread creates a native thread in another process, the start address it receives must point to valid machine code or the thread will crash the process.
The scenario you describe is different, you want to instruct the Python interpreter of another process to execute some code. This can be done, but it is different and considerably more difficult.
Inject a native library into the other process which does two things:
setup the Python interpreter
setup some means of interprocess communication (IPC)
Use the IPC to send the Python code you want to execute to the other process and the code in the library you injected then executes that code using the Python interpreter.
You can find an example of how to inject a DLL into another process in this Codeproject article.
It looks like you are trying to run some arbitrary Python code from .net. Now you are trying to invoke the Python interpreter to actually do this.
The cons of doing this are:-
It is complicated as you found out.
Interprocess communication makes it even harder, as you move bytes across the boundary
Then you need to parse the information that you get from each side into something that makes sense (probably using some kind of XML)
Finally with all the above overhead its slow
Now one way around this is to invoke the python program directly within .net. Now I have never done so in my life AND I have never seen a python in my life (except maybe the type that hisses). Take a look at http://msdn.microsoft.com/en-us/library/ee461504.aspx, unfortunately it looks like they are storing the python in a file and invoking it there. However I am sure you can invoke code stored as a string.
The main disadvantage of using the DLR implementation of python is that you are relying on a third party to get the Python -> CLR translation correct. But I think IronPython is a microsoft sponsored open source project.
For more information see: http://ironpython.codeplex.com/

Process.MainModule --> "Access is denied" [duplicate]

This question already has an answer here:
Access Denied while using System.Diagnostics.Process
(1 answer)
Closed 2 years ago.
I want to handle this differently,
ie. determine if I have access or not.
Is it possible to see if you have access to the main module or not?
foreach (Process p in Process.GetProcesses())
{
try
{
//This throws error for some processes.
if (p.MainModule.FileName.ToLower().EndsWith(ExeName, StringComparison.CurrentCultureIgnoreCase))
{
//Do some stuff
}
}
catch (Exception)
{
//Acess denied
}
}
[Flags]
private enum ProcessAccessFlags : uint
{
QueryLimitedInformation = 0x00001000
}
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool QueryFullProcessImageName(
[In] IntPtr hProcess,
[In] int dwFlags,
[Out] StringBuilder lpExeName,
ref int lpdwSize);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr OpenProcess(
ProcessAccessFlags processAccess,
bool bInheritHandle,
int processId);
String GetProcessFilename(Process p)
{
int capacity = 2000;
StringBuilder builder = new StringBuilder(capacity);
IntPtr ptr = OpenProcess(ProcessAccessFlags.QueryLimitedInformation, false, p.Id);
if (!QueryFullProcessImageName(ptr, 0, builder, ref capacity))
{
return String.Empty;
}
return builder.ToString();
}
Use pinvoke with ProcessAccessFlags.QueryLimitedInformation. This will allow you to grab the filename of the process without having special admin privileges and works across x32 and x64 processes.
I see two possible causes of the exception:
It may be that your process is x86 and the process being queried is x64 or vice versa.
Every process has a so called ACL (Access control list) that describes who can interact with it, the processes you are having problems with have for security reasons an empty ACL so even as administrator you cannot mess with them. For example, there's a handfull of processes (audiodg, System, and Idle from the top of my head) that throw an exception due to the access rights.
Just use a try/catch to your loop to deal with those processes.

Impersonation and CurrentUser Registry Access

Environment: Windows XP SP3, C#, .Net 4.0
Problem:
I'm attempting to add access to an impersonated users registry hive in an impersonation class and I'm running into issues based on the type of user being impersonated (or more accurately the limitation seems to be on the impersonating user).
I was originally following an impersonation example from CodeProject which showed a call to LoadUserProfile() taking place after impersonation was started using a duplicate token generated througha call to DuplcateToken() from the original token obtained from LogonUser(). I was not able to get this example working in my environment impersonating a limited user from an administrator account (From the screen shots included in the example it appears as though it was done on a windows Vista\7 system and no details were given about the account types involved).
The call to LoadUserProfile() was throwing an error of "Access Denied". Looking at userenv.log showed the line "LoadUserProfile: failed to enable the restore privilege. error c0000022". The LoadUserProfile documentation on MSDN shows that the calling process must posses the SE_RESTORE_NAME and SE_BACKUP_NAME privileges which by default only members of the Administrators and Backup Operators groups have. (As a side note when I attempted to add these two privileges later on to the Users group i still received Access Denied but the userenv.log showed "DropClientContext: Client [number] does not have enough permission. error 5" which I couldn't find any information on)
Given that the user I was impersonating did not have these privileges I moved the call to LoadUserProfile() up to before starting the impersonation and this time it loaded without a problem and I was able to read and write to it in this test. Thinking that I had discovered my answer I created a conditional check for the type of account so that LoadUserProfile() would be called before impersonation if the current user was a member of Administrators or wait until after impersonation if the member was not a member of Administrators (in the later instance I would be relying on the impersonated user having these privileges). Unfortunately I was wrong; I had not discovered my answer. When I tested the call with the role reversed (User > Administrator) The call to LoadUserProfile() still failed again with the Access Denied error and userenv.log showed the same "LoadUserProfile: failed to enable the restore privilege. error c0000061" but with a different error number this time.
Thinking that the privileges may not be enabled by default on the tokens returned from LogonUser() and\or DuplicateToken() I added two calls to AdjustTokenPrivilege() on the current users token (taking place after impersonation) obtained from WindowsIdentity.GetCurrent(TokenAccessLevels.AdjustPrivileges | TokenAccessLevels.Query).Token.
TokenAccessLevels.AdjustPrivileges and TokenAccessLevels.Query were specified because the documentation for AdjustTokenPrivilege on MSDN specifies that they are needed on the token being adjusted (I also tried obtaining the token through a call to OpenProcessToken() using a handle retrieved from System.Diagnostics.Process.GetCurrentProcess().Handle but that failed when called from the User both inside and outside of impersonation with GetCurrentProcess() being the function that threw access denied)
AdjustTokenPrivilege() returned successfully when used with the WindowsIdentity...Token but LoadUserProfile() still resulted in Access Denied (restore privilege).
At this point I was not convinced that AdjustTokenPrivilege() was doing it's job so I set out to determine what privileges were available and what state they were in for a particular token with GetTokenInformation() which resulted in it's own little bag of fun. After learning some new things I was able to call GetTokenInformation() and print out a list of privileges and their current status but the results were somewhat inconclusive since both Restore and Backup showed an attribute of 0 before and after calling AdjustTokenPrivilege() both as the administrator and while impersonating the administrator (Strangely three other privileges changed from 2 to 1 on the token when calling AdjustTokenPrivilege() but not the ones actually being adjusted which remained at a value of 0)
I removed the call to DuplicateToken() and replaced all of the places it was being used with the token returned from LogonUser() to see if that would help though in testing the privileges on the tokens the LogonUser() and DuplicateToken() tokens were identical. When I initially wrote the impersonation class I had been using the primary token in my call to WindowsImpersonationContext.Impersonate() without any problems and figured it was worth a try.
In the code example I've provided below I am able to impersonate and access the registry of a User when run as an Administrator but not the other way around. Any help would be greatly appreciated.
Pre-Post Edit:
I've also tried using the RegOpenCurrentUser() API in place of LoadUserProfile() and had success with Administrator > self and Administrator > User impersonation but when impersonating an Administrator from either another Administrator account or a User RegOpenCurrentUser() returns a pointer to HKEY_USERS\S-1-5-18 (what ever that is) instead of the actual account hive. I'm guessing because it is not actually loaded which brings me back to square one with needing to use LoadUserProfile()
From RegOpenCurrentUser documentation (MSDN):
RegOpenCurrentUser uses the thread's token to access the appropriate key, or the default if the profile is not loaded.
Code Snippet:
// Private variables used by class
private IntPtr tokenHandle;
private PROFILEINFO pInfo;
private WindowsImpersonationContext thisUser;
private string sDomain = string.Empty;
private string sUsername = string.Empty;
private string sPassword = string.Empty;
private bool bDisposed = false;
private RegistryKey rCurrentUser = null;
private SafeRegistryHandle safeHandle = null;
//Constants used for privilege adjustment
private const string SE_RESTORE_NAME = "SeRestorePrivilege";
private const string SE_BACKUP_NAME = "SeBackupPrivilege";
private const UInt32 SE_PRIVILEGE_ENABLED_BY_DEFAULT = 0x00000001;
private const UInt32 SE_PRIVILEGE_ENABLED = 0x00000002;
private const UInt32 SE_PRIVILEGE_REMOVED = 0x00000004;
private const UInt32 SE_PRIVILEGE_USED_FOR_ACCESS = 0x80000000;
[StructLayout(LayoutKind.Sequential)]
protected struct PROFILEINFO {
public int dwSize;
public int dwFlags;
[MarshalAs(UnmanagedType.LPTStr)]
public String lpUserName;
[MarshalAs(UnmanagedType.LPTStr)]
public String lpProfilePath;
[MarshalAs(UnmanagedType.LPTStr)]
public String lpDefaultPath;
[MarshalAs(UnmanagedType.LPTStr)]
public String lpServerName;
[MarshalAs(UnmanagedType.LPTStr)]
public String lpPolicyPath;
public IntPtr hProfile;
}
protected struct TOKEN_PRIVILEGES {
public UInt32 PrivilegeCount;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]
public LUID_AND_ATTRIBUTES[] Privileges;
}
[StructLayout(LayoutKind.Sequential)]
protected struct LUID_AND_ATTRIBUTES {
public LUID Luid;
public UInt32 Attributes;
}
[StructLayout(LayoutKind.Sequential)]
protected struct LUID {
public uint LowPart;
public int HighPart;
}
// Private API calls used by class
[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
protected static extern bool LogonUser(string lpszUsername, string lpszDomain, string lpszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken);
[DllImport("userenv.dll", SetLastError = true, CharSet = CharSet.Auto)]
protected static extern bool LoadUserProfile(IntPtr hToken, ref PROFILEINFO lpProfileInfo);
[DllImport("userenv.dll", SetLastError = true, CharSet = CharSet.Auto)]
protected static extern bool UnloadUserProfile(IntPtr hToken, IntPtr hProfile);
[DllImport("kernel32.dll", SetLastError = true)][return: MarshalAs(UnmanagedType.Bool)]
protected static extern bool CloseHandle(IntPtr hObject);
[DllImport("advapi32.dll", SetLastError = true)][return: MarshalAs(UnmanagedType.Bool)]
protected static extern bool AdjustTokenPrivileges(IntPtr TokenHandle, [MarshalAs(UnmanagedType.Bool)]bool DisableAllPrivileges, ref TOKEN_PRIVILEGES NewState, UInt32 Zero, IntPtr Null1, IntPtr Null2);
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)][return: MarshalAs(UnmanagedType.Bool)]
protected static extern bool LookupPrivilegeValue(string lpSystemName, string lpName, ref LUID lpLuid);
[PermissionSetAttribute(SecurityAction.Demand, Name = "FullTrust")]
public void Start() {
tokenHandle = IntPtr.Zero; // set the pointer to nothing
if (!LogonUser(sUsername, sDomain, sPassword, 2, 0, ref tokenHandle)) {
throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
} // end if !LogonUser returned false
try { //All of this is for loading the registry and is not required for impersonation to start
LUID LuidRestore = new LUID();
LUID LuidBackup = new LUID();
if(LookupPrivilegeValue(null, SE_RESTORE_NAME, ref LuidRestore) && LookupPrivilegeValue(null, SE_BACKUP_NAME, ref LuidBackup)) {
//Create the TokenPrivileges array to pass to AdjustTokenPrivileges
LUID_AND_ATTRIBUTES[] LuidAndAttributes = new LUID_AND_ATTRIBUTES[2];
LuidAndAttributes[0].Luid = LuidRestore;
LuidAndAttributes[0].Attributes = SE_PRIVILEGE_ENABLED;
LuidAndAttributes[1].Luid = LuidBackup;
LuidAndAttributes[1].Attributes = SE_PRIVILEGE_ENABLED;
TOKEN_PRIVILEGES TokenPrivileges = new TOKEN_PRIVILEGES();
TokenPrivileges.PrivilegeCount = 2;
TokenPrivileges.Privileges = LuidAndAttributes;
IntPtr procHandle = WindowsIdentity.GetCurrent(TokenAccessLevels.AdjustPrivileges | TokenAccessLevels.Query).Token;
if(AdjustTokenPrivileges(procHandle, false, ref TokenPrivileges, 0, IntPtr.Zero, IntPtr.Zero)) {
pInfo = new PROFILEINFO();
pInfo.dwSize = Marshal.SizeOf(pInfo);
pInfo.lpUserName = sUsername;
pInfo.dwFlags = 1;
LoadUserProfile(tokenHandle, ref pInfo); //this is not required to take place
if(pInfo.hProfile != IntPtr.Zero) {
safeHandle = new SafeRegistryHandle(pInfo.hProfile, true);
rCurrentUser = RegistryKey.FromHandle(safeHandle);
}//end if pInfo.hProfile
}//end if AdjustTokenPrivileges
}//end if LookupPrivilegeValue 1 & 2
}catch{
//We don't really care that this didn't work but we don't want to throw any errors at this point as it would stop impersonation
}//end try
WindowsIdentity thisId = new WindowsIdentity(tokenHandle);
thisUser = thisId.Impersonate();
} // end function Start
From the LoadUserProfile docs:
Starting with Windows XP Service Pack 2 (SP2) and Windows Server 2003, the caller must be an administrator or the LocalSystem account. It is not sufficient for the caller to merely impersonate the administrator or LocalSystem account.
If your process starts as a regular user you're out of luck. You could possibly launch a new process (under the admin credentials) to load the profile.
I found that the logon type set in the call to LogonUser() can be a factor. Even when running as an administrator I couldn't get past the error unless I switched from LOGON32_LOGON_INTERACTIVE to LOGON32_LOGON_BATCH. You would need to be sure the "Log on as a batch job" group policy doesn't interfere though.

Categories